]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
third_party: import ngtcp2 v1.13.0 from https://github.com/ngtcp2/ngtcp2.git
authorStefan Metzmacher <metze@samba.org>
Fri, 2 May 2025 12:32:11 +0000 (14:32 +0200)
committerStefan Metzmacher <metze@samba.org>
Thu, 17 Jul 2025 08:59:37 +0000 (08:59 +0000)
This imports commit 7dd482f949f145632c482d01af2184954b471795.

It will be used to implement a quic_ko_wrapper, but also
used as userspace client too.

Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
143 files changed:
buildtools/wafsamba/samba_third_party.py
third_party/ngtcp2/crypto/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/crypto/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/crypto/boringssl/.gitignore [new file with mode: 0644]
third_party/ngtcp2/crypto/boringssl/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/crypto/boringssl/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/crypto/boringssl/boringssl.c [new file with mode: 0644]
third_party/ngtcp2/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in [new file with mode: 0644]
third_party/ngtcp2/crypto/gnutls/.gitignore [new file with mode: 0644]
third_party/ngtcp2/crypto/gnutls/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/crypto/gnutls/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/crypto/gnutls/gnutls.c [new file with mode: 0644]
third_party/ngtcp2/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_boringssl.h [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_gnutls.h [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_ossl.h [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_picotls.h [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_quictls.h [new file with mode: 0644]
third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_wolfssl.h [new file with mode: 0644]
third_party/ngtcp2/crypto/ossl/.gitignore [new file with mode: 0644]
third_party/ngtcp2/crypto/ossl/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/crypto/ossl/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/crypto/ossl/libngtcp2_crypto_ossl.pc.in [new file with mode: 0644]
third_party/ngtcp2/crypto/ossl/ossl.c [new file with mode: 0644]
third_party/ngtcp2/crypto/picotls/.gitignore [new file with mode: 0644]
third_party/ngtcp2/crypto/picotls/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/crypto/picotls/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/crypto/picotls/libngtcp2_crypto_picotls.pc.in [new file with mode: 0644]
third_party/ngtcp2/crypto/picotls/picotls.c [new file with mode: 0644]
third_party/ngtcp2/crypto/quictls/.gitignore [new file with mode: 0644]
third_party/ngtcp2/crypto/quictls/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/crypto/quictls/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_quictls.pc.in [new file with mode: 0644]
third_party/ngtcp2/crypto/quictls/quictls.c [new file with mode: 0644]
third_party/ngtcp2/crypto/shared.c [new file with mode: 0644]
third_party/ngtcp2/crypto/shared.h [new file with mode: 0644]
third_party/ngtcp2/crypto/wolfssl/.gitignore [new file with mode: 0644]
third_party/ngtcp2/crypto/wolfssl/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/crypto/wolfssl/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in [new file with mode: 0644]
third_party/ngtcp2/crypto/wolfssl/wolfssl.c [new file with mode: 0644]
third_party/ngtcp2/lib/.gitignore [new file with mode: 0644]
third_party/ngtcp2/lib/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/lib/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/lib/config.cmake.in [new file with mode: 0644]
third_party/ngtcp2/lib/includes/CMakeLists.txt [new file with mode: 0644]
third_party/ngtcp2/lib/includes/Makefile.am [new file with mode: 0644]
third_party/ngtcp2/lib/includes/ngtcp2/ngtcp2.h [new file with mode: 0644]
third_party/ngtcp2/lib/includes/ngtcp2/version.h.in [new file with mode: 0644]
third_party/ngtcp2/lib/libngtcp2.pc.in [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_acktr.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_acktr.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_addr.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_addr.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_balloc.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_balloc.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_bbr.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_bbr.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_buf.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_buf.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_cc.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_cc.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_cid.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_cid.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_conn.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_conn.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_conn_stat.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_conv.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_conv.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_crypto.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_crypto.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_dcidtr.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_dcidtr.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_err.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_err.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_frame_chain.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_frame_chain.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_gaptr.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_gaptr.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_idtr.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_idtr.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_ksl.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_ksl.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_log.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_log.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_macro.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_map.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_map.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_mem.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_mem.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_net.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_objalloc.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_objalloc.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_opl.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_opl.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_path.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_path.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pkt.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pkt.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pktns_id.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pmtud.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pmtud.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_ppe.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_ppe.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pq.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pq.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pv.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_pv.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_qlog.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_qlog.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_range.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_range.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_rcvry.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_ringbuf.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_ringbuf.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_rob.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_rob.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_rst.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_rst.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_rtb.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_rtb.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_settings.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_settings.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_str.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_str.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_strm.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_strm.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_transport_params.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_transport_params.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_tstamp.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_unreachable.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_unreachable.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_vec.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_vec.h [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_version.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_window_filter.c [new file with mode: 0644]
third_party/ngtcp2/lib/ngtcp2_window_filter.h [new file with mode: 0644]
third_party/ngtcp2/update.sh [new file with mode: 0755]
third_party/ngtcp2/wscript [new file with mode: 0644]
third_party/wscript

index ed1dd2f06c06c9b17335f8e4e1ecbcf38a862aa9..b6c5ad6096474d74a97fc64905c43cf0ce19bc35 100644 (file)
@@ -51,3 +51,15 @@ Build.BuildContext.CHECK_PAM_WRAPPER = CHECK_PAM_WRAPPER
 def CHECK_LIBQUIC(conf):
     return conf.CHECK_BUNDLED_SYSTEM_PKG('libquic', minversion='1.1')
 Build.BuildContext.CHECK_LIBQUIC = CHECK_LIBQUIC
+
+@conf
+def CHECK_LIBNGTCP2(conf):
+    minversion = '1.12.0'
+    if not conf.CHECK_BUNDLED_SYSTEM_PKG('libngtcp2_crypto_gnutls',
+                                         minversion=minversion):
+        return False
+    if not conf.CHECK_BUNDLED_SYSTEM_PKG('libngtcp2',
+                                         minversion=minversion):
+        return False
+    return True
+Build.BuildContext.CHECK_LIBNGTCP2 = CHECK_LIBNGTCP2
diff --git a/third_party/ngtcp2/crypto/CMakeLists.txt b/third_party/ngtcp2/crypto/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c947837
--- /dev/null
@@ -0,0 +1,62 @@
+# ngtcp2
+
+# Copyright (c) 2019 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+if(HAVE_CRYPTO)
+  add_subdirectory(includes)
+endif()
+
+if(HAVE_QUICTLS)
+  add_subdirectory(quictls)
+elseif(ENABLE_OPENSSL)
+  message(WARNING "libngtcp2_crypto_quictls library is disabled due to lack of good quictls")
+endif()
+
+if(HAVE_GNUTLS)
+  add_subdirectory(gnutls)
+elseif(ENABLE_GNUTLS)
+  message(WARNING "libngtcp2_crypto_gnutls library is disabled due to lack of good GnuTLS")
+endif()
+
+if(HAVE_BORINGSSL)
+  add_subdirectory(boringssl)
+elseif(ENABLE_BORINGSSL)
+  message(WARNING "libngtcp2_crypto_boringssl library is disabled due to lack of good BoringSSL")
+endif()
+
+if(HAVE_PICOTLS)
+  add_subdirectory(picotls)
+elseif(ENABLE_PICOTLS)
+  message(WARNING "libngtcp2_crypto_picotls library is disabled due to lack of good Picotls")
+endif()
+
+if(HAVE_WOLFSSL)
+  add_subdirectory(wolfssl)
+elseif(ENABLE_WOLFSSL)
+  message(WARNING "libngtcp2_crypto_wolfssl library is disabled due to lack of good wolfSSL")
+endif()
+
+if(HAVE_OSSL)
+  add_subdirectory(ossl)
+elseif(ENABLE_OPENSSL)
+  message(WARNING "libngtcp2_crypto_ossl library is disabled due to lack of good ossl")
+endif()
diff --git a/third_party/ngtcp2/crypto/Makefile.am b/third_party/ngtcp2/crypto/Makefile.am
new file mode 100644 (file)
index 0000000..8d2f760
--- /dev/null
@@ -0,0 +1,53 @@
+# ngtcp2
+
+# Copyright (c) 2019 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+SUBDIRS =
+
+if HAVE_CRYPTO
+SUBDIRS += includes
+endif
+
+if HAVE_QUICTLS
+SUBDIRS += quictls
+endif
+
+if HAVE_GNUTLS
+SUBDIRS += gnutls
+endif
+
+if HAVE_BORINGSSL
+SUBDIRS += boringssl
+endif
+
+if HAVE_PICOTLS
+SUBDIRS += picotls
+endif
+
+if HAVE_WOLFSSL
+SUBDIRS += wolfssl
+endif
+
+if HAVE_OSSL
+SUBDIRS += ossl
+endif
+
+EXTRA_DIST = CMakeLists.txt
diff --git a/third_party/ngtcp2/crypto/boringssl/.gitignore b/third_party/ngtcp2/crypto/boringssl/.gitignore
new file mode 100644 (file)
index 0000000..3d57277
--- /dev/null
@@ -0,0 +1,2 @@
+/libngtcp2_crypto_boringssl.pc
+/libngtcp2_crypto_boringssl.a
diff --git a/third_party/ngtcp2/crypto/boringssl/CMakeLists.txt b/third_party/ngtcp2/crypto/boringssl/CMakeLists.txt
new file mode 100644 (file)
index 0000000..60d2d41
--- /dev/null
@@ -0,0 +1,64 @@
+# ngtcp2
+
+# Copyright (c) 2020 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+add_definitions(-DBUILDING_NGTCP2)
+
+set(ngtcp2_crypto_boringssl_SOURCES
+  boringssl.c
+  ../shared.c
+)
+
+set(ngtcp2_crypto_boringssl_INCLUDE_DIRS
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto"
+  "${BORINGSSL_INCLUDE_DIRS}"
+)
+
+foreach(name libngtcp2_crypto_boringssl.pc)
+  configure_file("${name}.in" "${name}" @ONLY)
+endforeach()
+
+if(ENABLE_STATIC_LIB)
+  # Public static library
+  add_library(ngtcp2_crypto_boringssl_static STATIC ${ngtcp2_crypto_boringssl_SOURCES})
+  set_target_properties(ngtcp2_crypto_boringssl_static PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    ARCHIVE_OUTPUT_NAME ngtcp2_crypto_boringssl${STATIC_LIB_SUFFIX}
+    C_VISIBILITY_PRESET hidden
+  )
+  target_compile_definitions(ngtcp2_crypto_boringssl_static PUBLIC
+    "-DNGTCP2_STATICLIB")
+  target_include_directories(ngtcp2_crypto_boringssl_static PUBLIC
+    ${ngtcp2_crypto_boringssl_INCLUDE_DIRS})
+
+  install(TARGETS ngtcp2_crypto_boringssl_static
+    DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+endif()
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_boringssl.pc"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/third_party/ngtcp2/crypto/boringssl/Makefile.am b/third_party/ngtcp2/crypto/boringssl/Makefile.am
new file mode 100644 (file)
index 0000000..7743233
--- /dev/null
@@ -0,0 +1,39 @@
+# ngtcp2
+
+# Copyright (c) 2020 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = CMakeLists.txt
+
+AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG)
+AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \
+       -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \
+       -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \
+       -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \
+       @BORINGSSL_CFLAGS@
+AM_LDFLAGS = ${LIBTOOL_LDFLAGS}
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libngtcp2_crypto_boringssl.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LIBRARIES = libngtcp2_crypto_boringssl.a
+
+libngtcp2_crypto_boringssl_a_SOURCES = boringssl.c ../shared.c ../shared.h
diff --git a/third_party/ngtcp2/crypto/boringssl/boringssl.c b/third_party/ngtcp2/crypto/boringssl/boringssl.c
new file mode 100644 (file)
index 0000000..283063f
--- /dev/null
@@ -0,0 +1,666 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2020 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <assert.h>
+#include <string.h>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_boringssl.h>
+
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/aes.h>
+#include <openssl/chacha.h>
+#include <openssl/rand.h>
+
+#include "shared.h"
+
+typedef enum ngtcp2_crypto_boringssl_cipher_type {
+  NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128,
+  NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256,
+  NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20,
+} ngtcp2_crypto_boringssl_cipher_type;
+
+typedef struct ngtcp2_crypto_boringssl_cipher {
+  ngtcp2_crypto_boringssl_cipher_type type;
+} ngtcp2_crypto_boringssl_cipher;
+
+static ngtcp2_crypto_boringssl_cipher crypto_cipher_aes_128 = {
+  NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128,
+};
+
+static ngtcp2_crypto_boringssl_cipher crypto_cipher_aes_256 = {
+  NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256,
+};
+
+static ngtcp2_crypto_boringssl_cipher crypto_cipher_chacha20 = {
+  NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20,
+};
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)EVP_aead_aes_128_gcm());
+}
+
+ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) {
+  md->native_handle = (void *)EVP_sha256();
+  return md;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) {
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)EVP_aead_aes_128_gcm());
+  ctx->md.native_handle = (void *)EVP_sha256();
+  ctx->hp.native_handle = (void *)&crypto_cipher_aes_128;
+  ctx->max_encryption = 0;
+  ctx->max_decryption_failure = 0;
+  return ctx;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead,
+                                            void *aead_native_handle) {
+  aead->native_handle = aead_native_handle;
+  aead->max_overhead = EVP_AEAD_max_overhead(aead->native_handle);
+  return aead;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)EVP_aead_aes_128_gcm());
+}
+
+static const EVP_AEAD *crypto_cipher_id_get_aead(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_CK_AES_128_GCM_SHA256:
+    return EVP_aead_aes_128_gcm();
+  case TLS1_CK_AES_256_GCM_SHA384:
+    return EVP_aead_aes_256_gcm();
+  case TLS1_CK_CHACHA20_POLY1305_SHA256:
+    return EVP_aead_chacha20_poly1305();
+  default:
+    return NULL;
+  }
+}
+
+static uint64_t crypto_cipher_id_get_aead_max_encryption(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_CK_AES_128_GCM_SHA256:
+  case TLS1_CK_AES_256_GCM_SHA384:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM;
+  case TLS1_CK_CHACHA20_POLY1305_SHA256:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305;
+  default:
+    return 0;
+  }
+}
+
+static uint64_t
+crypto_cipher_id_get_aead_max_decryption_failure(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_CK_AES_128_GCM_SHA256:
+  case TLS1_CK_AES_256_GCM_SHA384:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM;
+  case TLS1_CK_CHACHA20_POLY1305_SHA256:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305;
+  default:
+    return 0;
+  }
+}
+
+static const ngtcp2_crypto_boringssl_cipher *
+crypto_cipher_id_get_hp(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_CK_AES_128_GCM_SHA256:
+    return &crypto_cipher_aes_128;
+  case TLS1_CK_AES_256_GCM_SHA384:
+    return &crypto_cipher_aes_256;
+  case TLS1_CK_CHACHA20_POLY1305_SHA256:
+    return &crypto_cipher_chacha20;
+  default:
+    return NULL;
+  }
+}
+
+static const EVP_MD *crypto_cipher_id_get_md(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_CK_AES_128_GCM_SHA256:
+  case TLS1_CK_CHACHA20_POLY1305_SHA256:
+    return EVP_sha256();
+  case TLS1_CK_AES_256_GCM_SHA384:
+    return EVP_sha384();
+  default:
+    return NULL;
+  }
+}
+
+static int supported_cipher_id(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_CK_AES_128_GCM_SHA256:
+  case TLS1_CK_AES_256_GCM_SHA384:
+  case TLS1_CK_CHACHA20_POLY1305_SHA256:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+static ngtcp2_crypto_ctx *crypto_ctx_cipher_id(ngtcp2_crypto_ctx *ctx,
+                                               uint32_t cipher_id) {
+  ngtcp2_crypto_aead_init(&ctx->aead,
+                          (void *)crypto_cipher_id_get_aead(cipher_id));
+  ctx->md.native_handle = (void *)crypto_cipher_id_get_md(cipher_id);
+  ctx->hp.native_handle = (void *)crypto_cipher_id_get_hp(cipher_id);
+  ctx->max_encryption = crypto_cipher_id_get_aead_max_encryption(cipher_id);
+  ctx->max_decryption_failure =
+    crypto_cipher_id_get_aead_max_decryption_failure(cipher_id);
+
+  return ctx;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx,
+                                         void *tls_native_handle) {
+  SSL *ssl = tls_native_handle;
+  const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+  uint32_t cipher_id;
+
+  if (cipher == NULL) {
+    return NULL;
+  }
+
+  cipher_id = SSL_CIPHER_get_id(cipher);
+
+  if (!supported_cipher_id(cipher_id)) {
+    return NULL;
+  }
+
+  return crypto_ctx_cipher_id(ctx, cipher_id);
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx,
+                                               void *tls_native_handle) {
+  return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle);
+}
+
+static size_t crypto_md_hashlen(const EVP_MD *md) {
+  return (size_t)EVP_MD_size(md);
+}
+
+size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) {
+  return crypto_md_hashlen(md->native_handle);
+}
+
+static size_t crypto_aead_keylen(const EVP_AEAD *aead) {
+  return (size_t)EVP_AEAD_key_length(aead);
+}
+
+size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_keylen(aead->native_handle);
+}
+
+static size_t crypto_aead_noncelen(const EVP_AEAD *aead) {
+  return (size_t)EVP_AEAD_nonce_length(aead);
+}
+
+size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_noncelen(aead->native_handle);
+}
+
+int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const EVP_AEAD *cipher = aead->native_handle;
+  size_t keylen = crypto_aead_keylen(cipher);
+  EVP_AEAD_CTX *actx;
+
+  (void)noncelen;
+
+  actx = EVP_AEAD_CTX_new(cipher, key, keylen, EVP_AEAD_DEFAULT_TAG_LENGTH);
+  if (actx == NULL) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  return ngtcp2_crypto_aead_ctx_encrypt_init(aead_ctx, aead, key, noncelen);
+}
+
+void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (aead_ctx->native_handle) {
+    EVP_AEAD_CTX_free(aead_ctx->native_handle);
+  }
+}
+
+typedef struct ngtcp2_crypto_boringssl_cipher_ctx {
+  ngtcp2_crypto_boringssl_cipher_type type;
+  union {
+    /* aes_key is an encryption key when type is either
+       NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128 or
+       NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256. */
+    AES_KEY aes_key;
+    /* key contains an encryption key when type ==
+       NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20. */
+    uint8_t key[32];
+  };
+} ngtcp2_crypto_boringssl_cipher_ctx;
+
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key) {
+  ngtcp2_crypto_boringssl_cipher *hp_cipher = cipher->native_handle;
+  ngtcp2_crypto_boringssl_cipher_ctx *ctx;
+  int rv;
+  (void)rv;
+
+  ctx = malloc(sizeof(*ctx));
+  if (ctx == NULL) {
+    return -1;
+  }
+
+  ctx->type = hp_cipher->type;
+  cipher_ctx->native_handle = ctx;
+
+  switch (hp_cipher->type) {
+  case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128:
+    rv = AES_set_encrypt_key(key, 128, &ctx->aes_key);
+    assert(0 == rv);
+    return 0;
+  case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256:
+    rv = AES_set_encrypt_key(key, 256, &ctx->aes_key);
+    assert(0 == rv);
+    return 0;
+  case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20:
+    memcpy(ctx->key, key, sizeof(ctx->key));
+    return 0;
+  default:
+    assert(0);
+    abort();
+  };
+}
+
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (!cipher_ctx->native_handle) {
+    return;
+  }
+
+  free(cipher_ctx->native_handle);
+}
+
+int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
+                               const uint8_t *secret, size_t secretlen,
+                               const uint8_t *salt, size_t saltlen) {
+  const EVP_MD *prf = md->native_handle;
+  size_t destlen = (size_t)EVP_MD_size(prf);
+
+  if (HKDF_extract(dest, &destlen, prf, secret, secretlen, salt, saltlen) !=
+      1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen,
+                              const ngtcp2_crypto_md *md, const uint8_t *secret,
+                              size_t secretlen, const uint8_t *info,
+                              size_t infolen) {
+  const EVP_MD *prf = md->native_handle;
+
+  if (HKDF_expand(dest, destlen, prf, secret, secretlen, info, infolen) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen,
+                       const ngtcp2_crypto_md *md, const uint8_t *secret,
+                       size_t secretlen, const uint8_t *salt, size_t saltlen,
+                       const uint8_t *info, size_t infolen) {
+  const EVP_MD *prf = md->native_handle;
+
+  if (HKDF(dest, destlen, prf, secret, secretlen, salt, saltlen, info,
+           infolen) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *plaintext, size_t plaintextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  const EVP_AEAD *cipher = aead->native_handle;
+  EVP_AEAD_CTX *actx = aead_ctx->native_handle;
+  size_t max_outlen = plaintextlen + EVP_AEAD_max_overhead(cipher);
+  size_t outlen;
+
+  if (EVP_AEAD_CTX_seal(actx, dest, &outlen, max_outlen, nonce, noncelen,
+                        plaintext, plaintextlen, aad, aadlen) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *ciphertext, size_t ciphertextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  const EVP_AEAD *cipher = aead->native_handle;
+  EVP_AEAD_CTX *actx = aead_ctx->native_handle;
+  size_t max_overhead = EVP_AEAD_max_overhead(cipher);
+  size_t max_outlen;
+  size_t outlen;
+
+  if (ciphertextlen < max_overhead) {
+    return -1;
+  }
+
+  max_outlen = ciphertextlen - max_overhead;
+
+  if (EVP_AEAD_CTX_open(actx, dest, &outlen, max_outlen, nonce, noncelen,
+                        ciphertext, ciphertextlen, aad, aadlen) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                          const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                          const uint8_t *sample) {
+  static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
+  ngtcp2_crypto_boringssl_cipher_ctx *ctx = hp_ctx->native_handle;
+  uint32_t counter;
+
+  (void)hp;
+
+  switch (ctx->type) {
+  case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128:
+  case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256:
+    AES_ecb_encrypt(sample, dest, &ctx->aes_key, 1);
+    return 0;
+  case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20:
+#ifdef WORDS_BIGENDIAN
+    counter = (uint32_t)sample[0] + (uint32_t)(sample[1] << 8) +
+              (uint32_t)(sample[2] << 16) + (uint32_t)(sample[3] << 24);
+#else  /* !defined(WORDS_BIGENDIAN) */
+    memcpy(&counter, sample, sizeof(counter));
+#endif /* !defined(WORDS_BIGENDIAN) */
+    CRYPTO_chacha_20(dest, PLAINTEXT, sizeof(PLAINTEXT) - 1, ctx->key,
+                     sample + sizeof(counter), counter);
+    return 0;
+  default:
+    assert(0);
+    abort();
+  }
+}
+
+int ngtcp2_crypto_read_write_crypto_data(
+  ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level,
+  const uint8_t *data, size_t datalen) {
+  SSL *ssl = ngtcp2_conn_get_tls_native_handle(conn);
+  int rv;
+  int err;
+
+  if (SSL_provide_quic_data(
+        ssl,
+        ngtcp2_crypto_boringssl_from_ngtcp2_encryption_level(encryption_level),
+        data, datalen) != 1) {
+    return -1;
+  }
+
+  if (!ngtcp2_conn_get_handshake_completed(conn)) {
+  retry:
+    rv = SSL_do_handshake(ssl);
+    if (rv <= 0) {
+      err = SSL_get_error(ssl, rv);
+      switch (err) {
+      case SSL_ERROR_WANT_READ:
+      case SSL_ERROR_WANT_WRITE:
+        return 0;
+      case SSL_ERROR_SSL:
+        return -1;
+      case SSL_ERROR_EARLY_DATA_REJECTED:
+        assert(!ngtcp2_conn_is_server(conn));
+
+        SSL_reset_early_data_reject(ssl);
+
+        rv = ngtcp2_conn_tls_early_data_rejected(conn);
+        if (rv != 0) {
+          return -1;
+        }
+
+        goto retry;
+      default:
+        return -1;
+      }
+    }
+
+    if (SSL_in_early_data(ssl)) {
+      return 0;
+    }
+
+    ngtcp2_conn_tls_handshake_completed(conn);
+  }
+
+  rv = SSL_process_quic_post_handshake(ssl);
+  if (rv != 1) {
+    err = SSL_get_error(ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+    case SSL_ERROR_WANT_WRITE:
+      return 0;
+    case SSL_ERROR_SSL:
+    case SSL_ERROR_ZERO_RETURN:
+      return -1;
+    default:
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) {
+  SSL *ssl = tls;
+  const uint8_t *tp;
+  size_t tplen;
+  int rv;
+
+  SSL_get_peer_quic_transport_params(ssl, &tp, &tplen);
+
+  rv = ngtcp2_conn_decode_and_set_remote_transport_params(conn, tp, tplen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf,
+                                             size_t len) {
+  if (SSL_set_quic_transport_params(tls, buf, len) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+ngtcp2_encryption_level ngtcp2_crypto_boringssl_from_ssl_encryption_level(
+  enum ssl_encryption_level_t ssl_level) {
+  switch (ssl_level) {
+  case ssl_encryption_initial:
+    return NGTCP2_ENCRYPTION_LEVEL_INITIAL;
+  case ssl_encryption_early_data:
+    return NGTCP2_ENCRYPTION_LEVEL_0RTT;
+  case ssl_encryption_handshake:
+    return NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE;
+  case ssl_encryption_application:
+    return NGTCP2_ENCRYPTION_LEVEL_1RTT;
+  default:
+    assert(0);
+    abort();
+  }
+}
+
+enum ssl_encryption_level_t
+ngtcp2_crypto_boringssl_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level) {
+  switch (encryption_level) {
+  case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
+    return ssl_encryption_initial;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    return ssl_encryption_handshake;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    return ssl_encryption_application;
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    return ssl_encryption_early_data;
+  default:
+    assert(0);
+    abort();
+  }
+}
+
+int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data,
+                                             void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  if (RAND_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_random(uint8_t *data, size_t datalen) {
+  if (RAND_bytes(data, datalen) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int set_read_secret(SSL *ssl, enum ssl_encryption_level_t bssl_level,
+                           const SSL_CIPHER *cipher, const uint8_t *secret,
+                           size_t secretlen) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_boringssl_from_ssl_encryption_level(bssl_level);
+  (void)cipher;
+
+  if (ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level,
+                                              secret, secretlen) != 0) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int set_write_secret(SSL *ssl, enum ssl_encryption_level_t bssl_level,
+                            const SSL_CIPHER *cipher, const uint8_t *secret,
+                            size_t secretlen) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_boringssl_from_ssl_encryption_level(bssl_level);
+  (void)cipher;
+
+  if (ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level,
+                                              secret, secretlen) != 0) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int add_handshake_data(SSL *ssl, enum ssl_encryption_level_t bssl_level,
+                              const uint8_t *data, size_t datalen) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_boringssl_from_ssl_encryption_level(bssl_level);
+  int rv;
+
+  rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int flush_flight(SSL *ssl) {
+  (void)ssl;
+  return 1;
+}
+
+static int send_alert(SSL *ssl, enum ssl_encryption_level_t bssl_level,
+                      uint8_t alert) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  (void)bssl_level;
+
+  ngtcp2_conn_set_tls_alert(conn, alert);
+
+  return 1;
+}
+
+static SSL_QUIC_METHOD quic_method = {
+  set_read_secret, set_write_secret, add_handshake_data,
+  flush_flight,    send_alert,
+};
+
+static void crypto_boringssl_configure_context(SSL_CTX *ssl_ctx) {
+  SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
+  SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
+  SSL_CTX_set_quic_method(ssl_ctx, &quic_method);
+}
+
+int ngtcp2_crypto_boringssl_configure_server_context(SSL_CTX *ssl_ctx) {
+  crypto_boringssl_configure_context(ssl_ctx);
+
+  return 0;
+}
+
+int ngtcp2_crypto_boringssl_configure_client_context(SSL_CTX *ssl_ctx) {
+  crypto_boringssl_configure_context(ssl_ctx);
+
+  return 0;
+}
diff --git a/third_party/ngtcp2/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in b/third_party/ngtcp2/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in
new file mode 100644 (file)
index 0000000..737970a
--- /dev/null
@@ -0,0 +1,33 @@
+# ngtcp2
+
+# Copyright (c) 2020 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libngtcp2_crypto_boringssl
+Description: ngtcp2 BoringSSL crypto library
+URL: https://github.com/ngtcp2/ngtcp2
+Version: @VERSION@
+Libs: -L${libdir} -lngtcp2_crypto_boringssl
+Cflags: -I${includedir}
diff --git a/third_party/ngtcp2/crypto/gnutls/.gitignore b/third_party/ngtcp2/crypto/gnutls/.gitignore
new file mode 100644 (file)
index 0000000..b11bf3d
--- /dev/null
@@ -0,0 +1 @@
+/libngtcp2_crypto_gnutls.pc
diff --git a/third_party/ngtcp2/crypto/gnutls/CMakeLists.txt b/third_party/ngtcp2/crypto/gnutls/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d09ccf4
--- /dev/null
@@ -0,0 +1,86 @@
+# ngtcp2
+
+# Copyright (c) 2020 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+add_definitions(-DBUILDING_NGTCP2)
+
+set(ngtcp2_crypto_gnutls_SOURCES
+  gnutls.c
+  ../shared.c
+)
+
+set(ngtcp2_crypto_gnutls_INCLUDE_DIRS
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto"
+  "${GNUTLS_INCLUDE_DIRS}"
+)
+
+foreach(name libngtcp2_crypto_gnutls.pc)
+  configure_file("${name}.in" "${name}" @ONLY)
+endforeach()
+
+# Public shared library
+if(ENABLE_SHARED_LIB)
+  add_library(ngtcp2_crypto_gnutls SHARED ${ngtcp2_crypto_gnutls_SOURCES})
+  set_target_properties(ngtcp2_crypto_gnutls PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${CRYPTO_GNUTLS_LT_VERSION}
+    SOVERSION ${CRYPTO_GNUTLS_LT_SOVERSION}
+    C_VISIBILITY_PRESET hidden
+    POSITION_INDEPENDENT_CODE ON
+  )
+  target_include_directories(ngtcp2_crypto_gnutls PUBLIC
+    ${ngtcp2_crypto_gnutls_INCLUDE_DIRS})
+  target_link_libraries(ngtcp2_crypto_gnutls ngtcp2 ${GNUTLS_LIBRARIES})
+
+  install(TARGETS ngtcp2_crypto_gnutls
+    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+if(ENABLE_STATIC_LIB)
+  # Public static library
+  add_library(ngtcp2_crypto_gnutls_static STATIC ${ngtcp2_crypto_gnutls_SOURCES})
+  set_target_properties(ngtcp2_crypto_gnutls_static PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${CRYPTO_GNUTLS_LT_VERSION}
+    SOVERSION ${CRYPTO_GNUTLS_LT_SOVERSION}
+    ARCHIVE_OUTPUT_NAME ngtcp2_crypto_gnutls${STATIC_LIB_SUFFIX}
+    C_VISIBILITY_PRESET hidden
+  )
+  target_compile_definitions(ngtcp2_crypto_gnutls_static PUBLIC
+    "-DNGTCP2_STATICLIB")
+  target_include_directories(ngtcp2_crypto_gnutls_static PUBLIC
+    ${ngtcp2_crypto_gnutls_INCLUDE_DIRS})
+
+  install(TARGETS ngtcp2_crypto_gnutls_static
+    DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+endif()
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_gnutls.pc"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/third_party/ngtcp2/crypto/gnutls/Makefile.am b/third_party/ngtcp2/crypto/gnutls/Makefile.am
new file mode 100644 (file)
index 0000000..b4ed6c6
--- /dev/null
@@ -0,0 +1,43 @@
+# ngtcp2
+
+# Copyright (c) 2020 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = CMakeLists.txt
+
+AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG)
+AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \
+       -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \
+       -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \
+       -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \
+       @GNUTLS_CFLAGS@
+AM_LDFLAGS = ${LIBTOOL_LDFLAGS}
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libngtcp2_crypto_gnutls.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LTLIBRARIES = libngtcp2_crypto_gnutls.la
+
+libngtcp2_crypto_gnutls_la_SOURCES = gnutls.c ../shared.c ../shared.h
+libngtcp2_crypto_gnutls_la_LDFLAGS = -no-undefined \
+       -version-info $(CRYPTO_GNUTLS_LT_CURRENT):$(CRYPTO_GNUTLS_LT_REVISION):$(CRYPTO_GNUTLS_LT_AGE)
+libngtcp2_crypto_gnutls_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \
+       @GNUTLS_LIBS@
diff --git a/third_party/ngtcp2/crypto/gnutls/gnutls.c b/third_party/ngtcp2/crypto/gnutls/gnutls.c
new file mode 100644 (file)
index 0000000..4e6990f
--- /dev/null
@@ -0,0 +1,674 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2020 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <assert.h>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include <string.h>
+
+#include "shared.h"
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)GNUTLS_CIPHER_AES_128_GCM);
+}
+
+ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) {
+  md->native_handle = (void *)GNUTLS_DIG_SHA256;
+  return md;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) {
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)GNUTLS_CIPHER_AES_128_GCM);
+  ctx->md.native_handle = (void *)GNUTLS_DIG_SHA256;
+  ctx->hp.native_handle = (void *)GNUTLS_CIPHER_AES_128_CBC;
+  ctx->max_encryption = 0;
+  ctx->max_decryption_failure = 0;
+  return ctx;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead,
+                                            void *aead_native_handle) {
+  aead->native_handle = aead_native_handle;
+  aead->max_overhead = gnutls_cipher_get_tag_size(
+    (gnutls_cipher_algorithm_t)(intptr_t)aead_native_handle);
+  return aead;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)GNUTLS_CIPHER_AES_128_GCM);
+}
+
+static gnutls_cipher_algorithm_t
+crypto_get_hp(gnutls_cipher_algorithm_t cipher) {
+  switch (cipher) {
+  case GNUTLS_CIPHER_AES_128_GCM:
+  case GNUTLS_CIPHER_AES_128_CCM:
+    return GNUTLS_CIPHER_AES_128_CBC;
+  case GNUTLS_CIPHER_AES_256_GCM:
+  case GNUTLS_CIPHER_AES_256_CCM:
+    return GNUTLS_CIPHER_AES_256_CBC;
+  case GNUTLS_CIPHER_CHACHA20_POLY1305:
+    return GNUTLS_CIPHER_CHACHA20_32;
+  default:
+    return GNUTLS_CIPHER_UNKNOWN;
+  }
+}
+
+static uint64_t
+crypto_get_aead_max_encryption(gnutls_cipher_algorithm_t cipher) {
+  switch (cipher) {
+  case GNUTLS_CIPHER_AES_128_GCM:
+  case GNUTLS_CIPHER_AES_256_GCM:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM;
+  case GNUTLS_CIPHER_CHACHA20_POLY1305:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305;
+  case GNUTLS_CIPHER_AES_128_CCM:
+  case GNUTLS_CIPHER_AES_256_CCM:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM;
+  default:
+    return 0;
+  }
+}
+
+static uint64_t
+crypto_get_aead_max_decryption_failure(gnutls_cipher_algorithm_t cipher) {
+  switch (cipher) {
+  case GNUTLS_CIPHER_AES_128_GCM:
+  case GNUTLS_CIPHER_AES_256_GCM:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM;
+  case GNUTLS_CIPHER_CHACHA20_POLY1305:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305;
+  case GNUTLS_CIPHER_AES_128_CCM:
+  case GNUTLS_CIPHER_AES_256_CCM:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM;
+  default:
+    return 0;
+  }
+}
+
+static int supported_cipher(gnutls_cipher_algorithm_t cipher) {
+  switch (cipher) {
+  case GNUTLS_CIPHER_AES_128_GCM:
+  case GNUTLS_CIPHER_AES_256_GCM:
+  case GNUTLS_CIPHER_CHACHA20_POLY1305:
+  case GNUTLS_CIPHER_AES_128_CCM:
+  case GNUTLS_CIPHER_AES_256_CCM:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx,
+                                         void *tls_native_handle) {
+  gnutls_session_t session = tls_native_handle;
+  gnutls_cipher_algorithm_t cipher;
+  gnutls_digest_algorithm_t hash;
+  gnutls_cipher_algorithm_t hp_cipher;
+
+  cipher = gnutls_cipher_get(session);
+  if (cipher == GNUTLS_CIPHER_UNKNOWN || cipher == GNUTLS_CIPHER_NULL) {
+    return NULL;
+  }
+
+  if (!supported_cipher(cipher)) {
+    return NULL;
+  }
+
+  hash = gnutls_prf_hash_get(session);
+  if (hash == GNUTLS_DIG_UNKNOWN || hash == GNUTLS_DIG_NULL) {
+    return NULL;
+  }
+
+  hp_cipher = crypto_get_hp(cipher);
+  if (hp_cipher == GNUTLS_CIPHER_UNKNOWN || hp_cipher == GNUTLS_CIPHER_NULL) {
+    return NULL;
+  }
+
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)cipher);
+  ctx->md.native_handle = (void *)hash;
+  ctx->hp.native_handle = (void *)hp_cipher;
+  ctx->max_encryption = crypto_get_aead_max_encryption(cipher);
+  ctx->max_decryption_failure = crypto_get_aead_max_decryption_failure(cipher);
+
+  return ctx;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx,
+                                               void *tls_native_handle) {
+  gnutls_session_t session = tls_native_handle;
+  gnutls_cipher_algorithm_t cipher;
+  gnutls_digest_algorithm_t hash;
+  gnutls_cipher_algorithm_t hp_cipher;
+
+  cipher = gnutls_early_cipher_get(session);
+  if (cipher == GNUTLS_CIPHER_UNKNOWN || cipher == GNUTLS_CIPHER_NULL) {
+    return NULL;
+  }
+
+  if (!supported_cipher(cipher)) {
+    return NULL;
+  }
+
+  hash = gnutls_early_prf_hash_get(session);
+  if (hash == GNUTLS_DIG_UNKNOWN || hash == GNUTLS_DIG_NULL) {
+    return NULL;
+  }
+
+  hp_cipher = crypto_get_hp(cipher);
+  if (hp_cipher == GNUTLS_CIPHER_UNKNOWN || hp_cipher == GNUTLS_CIPHER_NULL) {
+    return NULL;
+  }
+
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)cipher);
+  ctx->md.native_handle = (void *)hash;
+  ctx->hp.native_handle = (void *)hp_cipher;
+  ctx->max_encryption = crypto_get_aead_max_encryption(cipher);
+  ctx->max_decryption_failure = crypto_get_aead_max_decryption_failure(cipher);
+
+  return ctx;
+}
+
+size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) {
+  return gnutls_hash_get_len(
+    (gnutls_digest_algorithm_t)(intptr_t)md->native_handle);
+}
+
+size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) {
+  return gnutls_cipher_get_key_size(
+    (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle);
+}
+
+size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) {
+  return gnutls_cipher_get_iv_size(
+    (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle);
+}
+
+int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  gnutls_cipher_algorithm_t cipher =
+    (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle;
+  gnutls_aead_cipher_hd_t hd;
+  gnutls_datum_t _key;
+
+  (void)noncelen;
+
+  _key.data = (void *)key;
+  _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead);
+
+  if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = hd;
+
+  return 0;
+}
+
+int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  gnutls_cipher_algorithm_t cipher =
+    (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle;
+  gnutls_aead_cipher_hd_t hd;
+  gnutls_datum_t _key;
+
+  (void)noncelen;
+
+  _key.data = (void *)key;
+  _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead);
+
+  if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = hd;
+
+  return 0;
+}
+
+void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (aead_ctx->native_handle) {
+    gnutls_aead_cipher_deinit(aead_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key) {
+  gnutls_cipher_algorithm_t _cipher =
+    (gnutls_cipher_algorithm_t)(intptr_t)cipher->native_handle;
+  gnutls_cipher_hd_t hd;
+  gnutls_datum_t _key;
+
+  _key.data = (void *)key;
+  _key.size = (unsigned int)gnutls_cipher_get_key_size(_cipher);
+
+  if (gnutls_cipher_init(&hd, _cipher, &_key, NULL) != 0) {
+    return -1;
+  }
+
+  cipher_ctx->native_handle = hd;
+
+  return 0;
+}
+
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (cipher_ctx->native_handle) {
+    gnutls_cipher_deinit(cipher_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
+                               const uint8_t *secret, size_t secretlen,
+                               const uint8_t *salt, size_t saltlen) {
+  gnutls_mac_algorithm_t prf =
+    (gnutls_mac_algorithm_t)(intptr_t)md->native_handle;
+  gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen};
+  gnutls_datum_t _salt = {(void *)salt, (unsigned int)saltlen};
+
+  if (gnutls_hkdf_extract(prf, &_secret, &_salt, dest) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen,
+                              const ngtcp2_crypto_md *md, const uint8_t *secret,
+                              size_t secretlen, const uint8_t *info,
+                              size_t infolen) {
+  gnutls_mac_algorithm_t prf =
+    (gnutls_mac_algorithm_t)(intptr_t)md->native_handle;
+  gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen};
+  gnutls_datum_t _info = {(void *)info, (unsigned int)infolen};
+
+  if (gnutls_hkdf_expand(prf, &_secret, &_info, dest, destlen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen,
+                       const ngtcp2_crypto_md *md, const uint8_t *secret,
+                       size_t secretlen, const uint8_t *salt, size_t saltlen,
+                       const uint8_t *info, size_t infolen) {
+  gnutls_mac_algorithm_t prf =
+    (gnutls_mac_algorithm_t)(intptr_t)md->native_handle;
+  size_t keylen = ngtcp2_crypto_md_hashlen(md);
+  uint8_t key[64];
+  gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen};
+  gnutls_datum_t _key = {(void *)key, (unsigned int)keylen};
+  gnutls_datum_t _salt = {(void *)salt, (unsigned int)saltlen};
+  gnutls_datum_t _info = {(void *)info, (unsigned int)infolen};
+
+  assert(keylen <= sizeof(key));
+
+  if (gnutls_hkdf_extract(prf, &_secret, &_salt, key) != 0) {
+    return -1;
+  }
+
+  if (gnutls_hkdf_expand(prf, &_key, &_info, dest, destlen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *plaintext, size_t plaintextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  gnutls_cipher_algorithm_t cipher =
+    (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle;
+  gnutls_aead_cipher_hd_t hd = aead_ctx->native_handle;
+  size_t taglen = gnutls_cipher_get_tag_size(cipher);
+  size_t ciphertextlen = plaintextlen + taglen;
+
+  if (gnutls_aead_cipher_encrypt(hd, nonce, noncelen, aad, aadlen, taglen,
+                                 plaintext, plaintextlen, dest,
+                                 &ciphertextlen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *ciphertext, size_t ciphertextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  gnutls_cipher_algorithm_t cipher =
+    (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle;
+  gnutls_aead_cipher_hd_t hd = aead_ctx->native_handle;
+  size_t taglen = gnutls_cipher_get_tag_size(cipher);
+  size_t plaintextlen;
+
+  if (taglen > ciphertextlen) {
+    return -1;
+  }
+
+  plaintextlen = ciphertextlen - taglen;
+
+  if (gnutls_aead_cipher_decrypt(hd, nonce, noncelen, aad, aadlen, taglen,
+                                 ciphertext, ciphertextlen, dest,
+                                 &plaintextlen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                          const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                          const uint8_t *sample) {
+  gnutls_cipher_algorithm_t cipher =
+    (gnutls_cipher_algorithm_t)(intptr_t)hp->native_handle;
+  gnutls_cipher_hd_t hd = hp_ctx->native_handle;
+
+  switch (cipher) {
+  case GNUTLS_CIPHER_AES_128_CBC:
+  case GNUTLS_CIPHER_AES_256_CBC: {
+    uint8_t iv[16];
+    uint8_t buf[16];
+
+    /* Emulate one block AES-ECB by invalidating the effect of IV */
+    memset(iv, 0, sizeof(iv));
+
+    gnutls_cipher_set_iv(hd, iv, sizeof(iv));
+
+    if (gnutls_cipher_encrypt2(hd, sample, 16, buf, sizeof(buf)) != 0) {
+      return -1;
+    }
+
+    memcpy(dest, buf, 5);
+  } break;
+
+  case GNUTLS_CIPHER_CHACHA20_32: {
+    static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
+    uint8_t buf[5 + 16];
+    size_t buflen = sizeof(buf);
+
+    gnutls_cipher_set_iv(hd, (void *)sample, 16);
+
+    if (gnutls_cipher_encrypt2(hd, PLAINTEXT, sizeof(PLAINTEXT) - 1, buf,
+                               buflen) != 0) {
+      return -1;
+    }
+
+    memcpy(dest, buf, 5);
+  } break;
+  default:
+    assert(0);
+  }
+
+  return 0;
+}
+
+ngtcp2_encryption_level
+ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(
+  gnutls_record_encryption_level_t gtls_level) {
+  switch (gtls_level) {
+  case GNUTLS_ENCRYPTION_LEVEL_INITIAL:
+    return NGTCP2_ENCRYPTION_LEVEL_INITIAL;
+  case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE:
+    return NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE;
+  case GNUTLS_ENCRYPTION_LEVEL_APPLICATION:
+    return NGTCP2_ENCRYPTION_LEVEL_1RTT;
+  case GNUTLS_ENCRYPTION_LEVEL_EARLY:
+    return NGTCP2_ENCRYPTION_LEVEL_0RTT;
+  default:
+    assert(0);
+    abort();
+  }
+}
+
+gnutls_record_encryption_level_t
+ngtcp2_crypto_gnutls_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level) {
+  switch (encryption_level) {
+  case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
+    return GNUTLS_ENCRYPTION_LEVEL_INITIAL;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    return GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    return GNUTLS_ENCRYPTION_LEVEL_APPLICATION;
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    return GNUTLS_ENCRYPTION_LEVEL_EARLY;
+  default:
+    assert(0);
+    abort();
+  }
+}
+
+int ngtcp2_crypto_read_write_crypto_data(
+  ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level,
+  const uint8_t *data, size_t datalen) {
+  gnutls_session_t session = ngtcp2_conn_get_tls_native_handle(conn);
+  int rv;
+
+  if (datalen > 0) {
+    rv = gnutls_handshake_write(
+      session,
+      ngtcp2_crypto_gnutls_from_ngtcp2_encryption_level(encryption_level), data,
+      datalen);
+    if (rv != 0) {
+      if (!gnutls_error_is_fatal(rv)) {
+        return 0;
+      }
+      gnutls_alert_send_appropriate(session, rv);
+      return -1;
+    }
+  }
+
+  if (!ngtcp2_conn_get_handshake_completed(conn)) {
+    rv = gnutls_handshake(session);
+    if (rv < 0) {
+      if (!gnutls_error_is_fatal(rv)) {
+        return 0;
+      }
+      gnutls_alert_send_appropriate(session, rv);
+      return -1;
+    }
+
+    ngtcp2_conn_tls_handshake_completed(conn);
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) {
+  (void)conn;
+  (void)tls;
+  /* Nothing to do; GnuTLS applications are supposed to register the
+     quic_transport_parameters extension with
+     gnutls_session_ext_register. */
+  return 0;
+}
+
+int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf,
+                                             size_t len) {
+  (void)tls;
+  (void)buf;
+  (void)len;
+  /* Nothing to do; GnuTLS applications are supposed to register the
+     quic_transport_parameters extension with
+     gnutls_session_ext_register. */
+  return 0;
+}
+
+int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data,
+                                             void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  if (gnutls_rnd(GNUTLS_RND_RANDOM, data, NGTCP2_PATH_CHALLENGE_DATALEN) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_random(uint8_t *data, size_t datalen) {
+  if (gnutls_rnd(GNUTLS_RND_RANDOM, data, datalen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int secret_func(gnutls_session_t session,
+                       gnutls_record_encryption_level_t gtls_level,
+                       const void *rx_secret, const void *tx_secret,
+                       size_t secretlen) {
+  ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level);
+
+  if (rx_secret &&
+      ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level,
+                                              rx_secret, secretlen) != 0) {
+    return -1;
+  }
+
+  if (tx_secret &&
+      ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level,
+                                              tx_secret, secretlen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int read_func(gnutls_session_t session,
+                     gnutls_record_encryption_level_t gtls_level,
+                     gnutls_handshake_description_t htype, const void *data,
+                     size_t datalen) {
+  ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level);
+  int rv;
+
+  if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) {
+    return 0;
+  }
+
+  rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+static int alert_read_func(gnutls_session_t session,
+                           gnutls_record_encryption_level_t gtls_level,
+                           gnutls_alert_level_t alert_level,
+                           gnutls_alert_description_t alert_desc) {
+  ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  (void)gtls_level;
+  (void)alert_level;
+
+  ngtcp2_conn_set_tls_alert(conn, (uint8_t)alert_desc);
+
+  return 0;
+}
+
+static int tp_recv_func(gnutls_session_t session, const uint8_t *data,
+                        size_t datalen) {
+  ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  int rv;
+
+  rv = ngtcp2_conn_decode_and_set_remote_transport_params(conn, data, datalen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+static int tp_send_func(gnutls_session_t session, gnutls_buffer_t extdata) {
+  ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  uint8_t buf[256];
+  ngtcp2_ssize nwrite;
+  int rv;
+
+  nwrite = ngtcp2_conn_encode_local_transport_params(conn, buf, sizeof(buf));
+  if (nwrite < 0) {
+    return -1;
+  }
+
+  rv = gnutls_buffer_append_data(extdata, buf, (size_t)nwrite);
+  if (rv != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int crypto_gnutls_configure_session(gnutls_session_t session) {
+  int rv;
+
+  gnutls_handshake_set_secret_function(session, secret_func);
+  gnutls_handshake_set_read_function(session, read_func);
+  gnutls_alert_set_read_function(session, alert_read_func);
+
+  rv = gnutls_session_ext_register(
+    session, "QUIC Transport Parameters",
+    NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1, GNUTLS_EXT_TLS, tp_recv_func,
+    tp_send_func, NULL, NULL, NULL,
+    GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE);
+  if (rv != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_gnutls_configure_server_session(gnutls_session_t session) {
+  return crypto_gnutls_configure_session(session);
+}
+
+int ngtcp2_crypto_gnutls_configure_client_session(gnutls_session_t session) {
+  return crypto_gnutls_configure_session(session);
+}
diff --git a/third_party/ngtcp2/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in b/third_party/ngtcp2/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in
new file mode 100644 (file)
index 0000000..890e89d
--- /dev/null
@@ -0,0 +1,33 @@
+# ngtcp2
+
+# Copyright (c) 2020 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libngtcp2_crypto_gnutls
+Description: ngtcp2 GnuTLS crypto library
+URL: https://github.com/ngtcp2/ngtcp2
+Version: @VERSION@
+Libs: -L${libdir} -lngtcp2_crypto_gnutls
+Cflags: -I${includedir}
diff --git a/third_party/ngtcp2/crypto/includes/CMakeLists.txt b/third_party/ngtcp2/crypto/includes/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8415158
--- /dev/null
@@ -0,0 +1,62 @@
+# ngtcp2
+
+# Copyright (c) 2019 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+install(FILES
+    ngtcp2/ngtcp2_crypto.h
+  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2")
+
+if(HAVE_QUICTLS)
+  install(FILES
+      ngtcp2/ngtcp2_crypto_quictls.h
+      DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2")
+endif()
+
+if(HAVE_GNUTLS)
+  install(FILES
+      ngtcp2/ngtcp2_crypto_gnutls.h
+      DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2")
+endif()
+
+if(HAVE_BORINGSSL)
+  install(FILES
+      ngtcp2/ngtcp2_crypto_boringssl.h
+      DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2")
+endif()
+
+if(HAVE_PICOTLS)
+  install(FILES
+      ngtcp2/ngtcp2_crypto_picotls.h
+      DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2")
+endif()
+
+if(HAVE_WOLFSSL)
+  install(FILES
+      ngtcp2/ngtcp2_crypto_wolfssl.h
+      DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2")
+endif()
+
+if(HAVE_OSSL)
+  install(FILES
+      ngtcp2/ngtcp2_crypto_ossl.h
+      DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2")
+endif()
diff --git a/third_party/ngtcp2/crypto/includes/Makefile.am b/third_party/ngtcp2/crypto/includes/Makefile.am
new file mode 100644 (file)
index 0000000..4aebf42
--- /dev/null
@@ -0,0 +1,49 @@
+# ngtcp2
+
+# Copyright (c) 2019 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = CMakeLists.txt
+
+nobase_include_HEADERS = ngtcp2/ngtcp2_crypto.h
+
+if HAVE_QUICTLS
+nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_quictls.h
+endif
+
+if HAVE_GNUTLS
+nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_gnutls.h
+endif
+
+if HAVE_BORINGSSL
+nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_boringssl.h
+endif
+
+if HAVE_PICOTLS
+nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_picotls.h
+endif
+
+if HAVE_WOLFSSL
+nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_wolfssl.h
+endif
+
+if HAVE_OSSL
+nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_ossl.h
+endif
diff --git a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto.h
new file mode 100644 (file)
index 0000000..003ec6b
--- /dev/null
@@ -0,0 +1,963 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_H
+#define NGTCP2_CRYPTO_H
+
+#include <ngtcp2/ngtcp2.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+#ifdef WIN32
+#  ifndef WIN32_LEAN_AND_MEAN
+#    define WIN32_LEAN_AND_MEAN
+#  endif /* !defined(WIN32_LEAN_AND_MEAN) */
+#  include <ws2tcpip.h>
+#endif /* defined(WIN32) */
+
+/**
+ * @macrosection
+ *
+ * ngtcp2 crypto library error codes
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_ERR_INTERNAL` indicates an internal error.
+ */
+#define NGTCP2_CRYPTO_ERR_INTERNAL -201
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN` indicates that a token
+ * is unreadable because it is not correctly formatted; or verifying
+ * the integrity protection failed.
+ */
+#define NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN -202
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_ERR_VERIFY_TOKEN` indicates that a token does
+ * not probe the client address; or the token validity has expired; or
+ * it contains invalid Connection ID.
+ */
+#define NGTCP2_CRYPTO_ERR_VERIFY_TOKEN -203
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_ERR_NOMEM` indicates out of memory.
+ */
+#define NGTCP2_CRYPTO_ERR_NOMEM -501
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ctx_tls` initializes |ctx| by extracting negotiated
+ * ciphers and message digests from native TLS session
+ * |tls_native_handle|.  This is used for encrypting/decrypting
+ * Handshake and 1-RTT packets.  If it is unable to obtain necessary
+ * data from |tls_native_handle|, this function returns NULL.
+ *
+ * If libngtcp2_crypto_quictls is linked, |tls_native_handle| must be
+ * a pointer to SSL object.
+ */
+NGTCP2_EXTERN ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx,
+                                                       void *tls_native_handle);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ctx_tls_early` initializes |ctx| by extracting early
+ * ciphers and message digests from native TLS session
+ * |tls_native_handle|.  This is used for encrypting/decrypting 0-RTT
+ * packets.  If it is unable to obtain necessary data from
+ * |tls_native_handle|, this function returns NULL.
+ *
+ * If libngtcp2_crypto_quictls is linked, |tls_native_handle| must be
+ * a pointer to SSL object.
+ */
+NGTCP2_EXTERN ngtcp2_crypto_ctx *
+ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, void *tls_native_handle);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_md_init` initializes |md| with the provided
+ * |md_native_handle| which is an underlying message digest object.
+ *
+ * If libngtcp2_crypto_quictls is linked, |md_native_handle| must be a
+ * pointer to EVP_MD.
+ *
+ * If libngtcp2_crypto_gnutls is linked, |md_native_handle| must be
+ * gnutls_mac_algorithm_t casted to ``void *``.
+ *
+ * If libngtcp2_crypto_boringssl is linked, |md_native_handle| must be
+ * a pointer to EVP_MD.
+ */
+NGTCP2_EXTERN ngtcp2_crypto_md *ngtcp2_crypto_md_init(ngtcp2_crypto_md *md,
+                                                      void *md_native_handle);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_md_hashlen` returns the length of |md| output.
+ */
+NGTCP2_EXTERN size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_keylen` returns the length of key for |aead|.
+ */
+NGTCP2_EXTERN size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_noncelen` returns the length of nonce for
+ * |aead|.
+ */
+NGTCP2_EXTERN size_t
+ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_hkdf_extract` performs HKDF extract operation.
+ *
+ * The length of output is `ngtcp2_crypto_md_hashlen(md)
+ * <ngtcp2_crypto_md_hashlen>`.  The output is stored in the buffer
+ * pointed by |dest|.  The caller is responsible to specify the buffer
+ * that has enough capacity to store the output.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
+                           const uint8_t *secret, size_t secretlen,
+                           const uint8_t *salt, size_t saltlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_hkdf_expand` performs HKDF expand operation.  The
+ * result is |destlen| bytes long, and is stored in the buffer pointed
+ * by |dest|.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_hkdf_expand(
+  uint8_t *dest, size_t destlen, const ngtcp2_crypto_md *md,
+  const uint8_t *secret, size_t secretlen, const uint8_t *info, size_t infolen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_hkdf` performs HKDF operation.  The result is
+ * |destlen| bytes long, and is stored in the buffer pointed by
+ * |dest|.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen,
+                                     const ngtcp2_crypto_md *md,
+                                     const uint8_t *secret, size_t secretlen,
+                                     const uint8_t *salt, size_t saltlen,
+                                     const uint8_t *info, size_t infolen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_packet_protection_ivlen` returns the length of IV
+ * used to encrypt QUIC packet.
+ */
+NGTCP2_EXTERN size_t
+ngtcp2_crypto_packet_protection_ivlen(const ngtcp2_crypto_aead *aead);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_encrypt` encrypts |plaintext| of length
+ * |plaintextlen| and writes the ciphertext into the buffer pointed by
+ * |dest|.  The length of ciphertext is |plaintextlen| +
+ * :member:`aead->max_overhead <ngtcp2_crypto_aead.max_overhead>`
+ * bytes long.  |dest| must have enough capacity to store the
+ * ciphertext.  |dest| and |plaintext| may point to the same buffer.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_encrypt(uint8_t *dest,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const uint8_t *plaintext,
+                                        size_t plaintextlen,
+                                        const uint8_t *nonce, size_t noncelen,
+                                        const uint8_t *aad, size_t aadlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_encrypt_cb` is a wrapper function around
+ * `ngtcp2_crypto_encrypt`.  It can be directly passed to
+ * :member:`ngtcp2_callbacks.encrypt` field.
+ *
+ * This function returns 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                         const ngtcp2_crypto_aead_ctx *aead_ctx,
+                         const uint8_t *plaintext, size_t plaintextlen,
+                         const uint8_t *nonce, size_t noncelen,
+                         const uint8_t *aad, size_t aadlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_decrypt` decrypts |ciphertext| of length
+ * |ciphertextlen| and writes the plaintext into the buffer pointed by
+ * |dest|.  The length of plaintext is |ciphertextlen| -
+ * :member:`aead->max_overhead <ngtcp2_crypto_aead.max_overhead>`
+ * bytes long.  |dest| must have enough capacity to store the
+ * plaintext.  |dest| and |ciphertext| may point to the same buffer.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_decrypt(uint8_t *dest,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const uint8_t *ciphertext,
+                                        size_t ciphertextlen,
+                                        const uint8_t *nonce, size_t noncelen,
+                                        const uint8_t *aad, size_t aadlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_decrypt_cb` is a wrapper function around
+ * `ngtcp2_crypto_decrypt`.  It can be directly passed to
+ * :member:`ngtcp2_callbacks.decrypt` field.
+ *
+ * This function returns 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_TLS_DECRYPT`.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                         const ngtcp2_crypto_aead_ctx *aead_ctx,
+                         const uint8_t *ciphertext, size_t ciphertextlen,
+                         const uint8_t *nonce, size_t noncelen,
+                         const uint8_t *aad, size_t aadlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_hp_mask` generates a mask which is used in packet
+ * header encryption.  The mask is written to the buffer pointed by
+ * |dest|.  The sample is passed as |sample| which is
+ * :macro:`NGTCP2_HP_SAMPLELEN` bytes long.  The length of mask must
+ * be at least :macro:`NGTCP2_HP_MASKLEN`.  The library only uses the
+ * first :macro:`NGTCP2_HP_MASKLEN` bytes of the produced mask.  The
+ * buffer pointed by |dest| must have at least
+ * :macro:`NGTCP2_HP_SAMPLELEN` bytes available.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_hp_mask(uint8_t *dest,
+                                        const ngtcp2_crypto_cipher *hp,
+                                        const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                                        const uint8_t *sample);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_hp_mask_cb` is a wrapper function around
+ * `ngtcp2_crypto_hp_mask`.  It can be directly passed to
+ * :member:`ngtcp2_callbacks.hp_mask` field.
+ *
+ * This function returns 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                         const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                         const uint8_t *sample);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_derive_and_install_rx_key` derives the decryption
+ * keying materials from |secret|, and installs them to |conn|.
+ *
+ * If |key| is not NULL, the derived packet protection key is written
+ * to the buffer pointed by |key|.  If |iv| is not NULL, the derived
+ * packet protection IV is written to the buffer pointed by |iv|.  If
+ * |hp| is not NULL, the derived header protection key is written to
+ * the buffer pointed by |hp|.
+ *
+ * |secretlen| specifies the length of |secret|.
+ *
+ * The length of packet protection key and header protection key is
+ * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`,
+ * and the length of packet protection IV is
+ * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead)
+ * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by
+ * `ngtcp2_crypto_ctx_tls` (or `ngtcp2_crypto_ctx_tls_early` if
+ * |level| ==
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`).
+ *
+ * In the first call of this function, it calls
+ * `ngtcp2_conn_set_crypto_ctx` (or `ngtcp2_conn_set_early_crypto_ctx`
+ * if |level| ==
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to
+ * set negotiated AEAD and message digest algorithm.  After the
+ * successful call of this function, application can use
+ * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_0rtt_crypto_ctx`
+ * if |level| ==
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to
+ * get :type:`ngtcp2_crypto_ctx`.
+ *
+ * If |conn| is initialized as client, and |level| is
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_1RTT`, this
+ * function retrieves a remote QUIC transport parameters extension
+ * from an object obtained by `ngtcp2_conn_get_tls_native_handle`, and
+ * sets it to |conn| by calling
+ * `ngtcp2_conn_decode_and_set_remote_transport_params`.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_rx_key(
+  ngtcp2_conn *conn, uint8_t *key, uint8_t *iv, uint8_t *hp,
+  ngtcp2_encryption_level level, const uint8_t *secret, size_t secretlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_derive_and_install_tx_key` derives the encryption
+ * keying materials from |secret|, and installs new keys to |conn|.
+ *
+ * If |key| is not NULL, the derived packet protection key is written
+ * to the buffer pointed by |key|.  If |iv| is not NULL, the derived
+ * packet protection IV is written to the buffer pointed by |iv|.  If
+ * |hp| is not NULL, the derived header protection key is written to
+ * the buffer pointed by |hp|.
+ *
+ * |secretlen| specifies the length of |secret|.
+ *
+ * The length of packet protection key and header protection key is
+ * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`,
+ * and the length of packet protection IV is
+ * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead)
+ * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by
+ * `ngtcp2_crypto_ctx_tls` (or `ngtcp2_crypto_ctx_tls_early` if
+ * |level| ==
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`).
+ *
+ * In the first call of this function, it calls
+ * `ngtcp2_conn_set_crypto_ctx` (or `ngtcp2_conn_set_early_crypto_ctx`
+ * if |level| ==
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to
+ * set negotiated AEAD and message digest algorithm.  After the
+ * successful call of this function, application can use
+ * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_0rtt_crypto_ctx`
+ * if |level| ==
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`) to
+ * get :type:`ngtcp2_crypto_ctx`.
+ *
+ * If |conn| is initialized as server, and |level| is
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_1RTT`, this
+ * function retrieves a remote QUIC transport parameters extension
+ * from an object obtained by `ngtcp2_conn_get_tls_native_handle`, and
+ * sets it to |conn| by calling
+ * `ngtcp2_conn_decode_and_set_remote_transport_params`.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_tx_key(
+  ngtcp2_conn *conn, uint8_t *key, uint8_t *iv, uint8_t *hp,
+  ngtcp2_encryption_level level, const uint8_t *secret, size_t secretlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_update_key` updates traffic keying materials.
+ *
+ * The new decryption traffic secret is written to the buffer pointed
+ * by |rx_secret|.  The length of secret is |secretlen| bytes, and
+ * |rx_secret| must point to the buffer which has enough capacity.
+ *
+ * The new encryption traffic secret is written to the buffer pointed
+ * by |tx_secret|.  The length of secret is |secretlen| bytes, and
+ * |tx_secret| must point to the buffer which has enough capacity.
+ *
+ * The derived decryption packet protection key is written to the
+ * buffer pointed by |rx_key|.  The derived decryption packet
+ * protection IV is written to the buffer pointed by |rx_iv|.
+ * |rx_aead_ctx| is initialized with the derived key and IV.
+ *
+ * The derived encryption packet protection key is written to the
+ * buffer pointed by |tx_key|.  The derived encryption packet
+ * protection IV is written to the buffer pointed by |tx_iv|.
+ * |tx_aead_ctx| is initialized with the derived key and IV.
+ *
+ * |current_rx_secret| and |current_tx_secret| are the current
+ * decryption and encryption traffic secrets respectively.  They share
+ * the same length with |rx_secret| and |tx_secret|.
+ *
+ * The length of packet protection key and header protection key is
+ * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`,
+ * and the length of packet protection IV is
+ * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead)
+ * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by
+ * `ngtcp2_crypto_ctx_tls`.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_update_key(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_key, uint8_t *rx_iv,
+  ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_key, uint8_t *tx_iv,
+  const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+  size_t secretlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_update_key_cb` is a wrapper function around
+ * `ngtcp2_crypto_update_key`.  It can be directly passed to
+ * :member:`ngtcp2_callbacks.update_key` field.
+ *
+ * This function returns 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_update_key_cb(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+  ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+  const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+  size_t secretlen, void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_client_initial_cb` installs initial secrets and
+ * encryption keys, and sets QUIC transport parameters.
+ *
+ * This function can be directly passed to
+ * :member:`ngtcp2_callbacks.client_initial` field.  It is only used
+ * by client.
+ *
+ * This function returns 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_client_initial_cb(ngtcp2_conn *conn,
+                                                  void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_recv_retry_cb` re-installs initial secrets in
+ * response to incoming Retry packet.
+ *
+ * This function can be directly passed to
+ * :member:`ngtcp2_callbacks.recv_retry` field.  It is only used by
+ * client.
+ *
+ * This function returns 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_recv_retry_cb(ngtcp2_conn *conn,
+                                              const ngtcp2_pkt_hd *hd,
+                                              void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_recv_client_initial_cb` installs initial secrets in
+ * response to an incoming Initial packet from client, and sets QUIC
+ * transport parameters.
+ *
+ * This function can be directly passed to
+ * :member:`ngtcp2_callbacks.recv_client_initial` field.  It is only
+ * used by server.
+ *
+ * This function returns 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_recv_client_initial_cb(ngtcp2_conn *conn,
+                                                       const ngtcp2_cid *dcid,
+                                                       void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_read_write_crypto_data` reads CRYPTO data |data| of
+ * length |datalen| in an encryption level |encryption_level|, and may
+ * feed outgoing CRYPTO data to |conn|.  This function can drive
+ * handshake.  This function can be also used after handshake
+ * completes.  It is allowed to call this function with |datalen| ==
+ * 0.  In this case, no additional read operation is done.
+ *
+ * This function returns 0 if it succeeds, or a negative error code.
+ * The generic error code is -1 if a specific error code is not
+ * suitable.  The error codes less than -10000 are specific to
+ * underlying TLS implementation.  For quictls, the error codes are
+ * defined in *ngtcp2_crypto_quictls.h*.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn,
+                                     ngtcp2_encryption_level encryption_level,
+                                     const uint8_t *data, size_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_recv_crypto_data_cb` is a wrapper function around
+ * `ngtcp2_crypto_read_write_crypto_data`.  It can be directly passed
+ * to :member:`ngtcp2_callbacks.recv_crypto_data` field.
+ *
+ * If this function is used, the TLS implementation specific error
+ * codes described in `ngtcp2_crypto_read_write_crypto_data` are
+ * treated as if it returns -1.  Do not use this function if an
+ * application wishes to use the TLS implementation specific error
+ * codes.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_recv_crypto_data_cb(
+  ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level, uint64_t offset,
+  const uint8_t *data, size_t datalen, void *user_data);
+
+/**
+ * @function
+ *
+ *  `ngtcp2_crypto_generate_stateless_reset_token` generates a
+ *  stateless reset token using HKDF extraction using the given |cid|
+ *  and |secret| as input.  The token will be written to the buffer
+ *  pointed by |token|, and it must have a capacity of at least
+ *  :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` bytes.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_generate_stateless_reset_token(
+  uint8_t *token, const uint8_t *secret, size_t secretlen,
+  const ngtcp2_cid *cid);
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_TOKEN_RAND_DATALEN` is the length of random
+ * data added to a token generated by
+ * `ngtcp2_crypto_generate_retry_token` or
+ * `ngtcp2_crypto_generate_regular_token`.
+ */
+#define NGTCP2_CRYPTO_TOKEN_RAND_DATALEN 16
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY` is the magic byte for
+ * Retry token generated by `ngtcp2_crypto_generate_retry_token`.
+ */
+#define NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY 0xb6
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2` is the magic byte for
+ * Retry token generated by `ngtcp2_crypto_generate_retry_token2`.
+ */
+#define NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2 0xb7
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR` is the magic byte for a
+ * token generated by `ngtcp2_crypto_generate_regular_token`.
+ */
+#define NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR 0x36
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN` is the maximum length of
+ * a token generated by `ngtcp2_crypto_generate_retry_token`.
+ */
+#define NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN                                       \
+  (/* magic = */ 1 + /* cid len = */ 1 + NGTCP2_MAX_CIDLEN +                   \
+   sizeof(ngtcp2_tstamp) + /* aead tag = */ 16 +                               \
+   NGTCP2_CRYPTO_TOKEN_RAND_DATALEN)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN2` is the maximum length of
+ * a token generated by `ngtcp2_crypto_generate_retry_token2`.
+ */
+#define NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN2                                      \
+  (/* magic = */ 1 + sizeof(ngtcp2_sockaddr_union) + /* cid len = */ 1 +       \
+   NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp) + /* aead tag = */ 16 +           \
+   NGTCP2_CRYPTO_TOKEN_RAND_DATALEN)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN` is the maximum length
+ *  of a token generated by `ngtcp2_crypto_generate_regular_token`.
+ */
+#define NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN                                     \
+  (/* magic = */ 1 + sizeof(ngtcp2_tstamp) + /* aead tag = */ 16 +             \
+   NGTCP2_CRYPTO_TOKEN_RAND_DATALEN)
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_generate_retry_token` generates a token in the
+ * buffer pointed by |token| that is sent with Retry packet.  The
+ * buffer pointed by |token| must have at least
+ * :macro:`NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN` bytes long.  The
+ * successfully generated token starts with
+ * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY`.  |secret| of length
+ * |secretlen| is a keying material to generate keys to encrypt the
+ * token.  |version| is QUIC version.  |remote_addr| of length
+ * |remote_addrlen| is an address of client.  |retry_scid| is a Source
+ * Connection ID chosen by server, and set in Retry packet.  |odcid|
+ * is a Destination Connection ID in Initial packet sent by client.
+ * |ts| is the timestamp when the token is generated.
+ *
+ * See also `ngtcp2_crypto_generate_retry_token2`.
+ *
+ * This function returns the length of generated token if it succeeds,
+ * or -1.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_generate_retry_token(
+  uint8_t *token, const uint8_t *secret, size_t secretlen, uint32_t version,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  const ngtcp2_cid *retry_scid, const ngtcp2_cid *odcid, ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_verify_retry_token` verifies Retry token stored in
+ * the buffer pointed by |token| of length |tokenlen|.  |secret| of
+ * length |secretlen| is a keying material to generate keys to decrypt
+ * the token.  |version| is QUIC version of the Initial packet that
+ * contains this token.  |remote_addr| of length |remote_addrlen| is
+ * an address of client.  |dcid| is a Destination Connection ID in
+ * Initial packet sent by client.  |timeout| is the period during
+ * which the token is valid.  |ts| is the current timestamp.  When
+ * validation succeeds, the extracted Destination Connection ID (which
+ * is the Destination Connection ID in Initial packet sent by client
+ * that triggered Retry packet) is stored in the buffer pointed by
+ * |odcid|.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_verify_retry_token(
+  ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen,
+  const uint8_t *secret, size_t secretlen, uint32_t version,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  const ngtcp2_cid *dcid, ngtcp2_duration timeout, ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_generate_retry_token2` generates a token in the
+ * buffer pointed by |token| that is sent with Retry packet.  The
+ * buffer pointed by |token| must have at least
+ * :macro:`NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN2` bytes long.  The
+ * successfully generated token starts with
+ * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2`.  |secret| of length
+ * |secretlen| is a keying material to generate keys to encrypt the
+ * token.  |version| is QUIC version.  |remote_addr| of length
+ * |remote_addrlen| is an address of client.  |retry_scid| is a Source
+ * Connection ID chosen by server, and set in Retry packet.  |odcid|
+ * is a Destination Connection ID in Initial packet sent by client.
+ * |ts| is the timestamp when the token is generated.
+ *
+ * Use this function instead of `ngtcp2_crypto_generate_retry_token`
+ * if more detailed error handling is required when verifying the
+ * token.  `ngtcp2_crypto_verify_retry_token2` must be used to verify
+ * the token.
+ *
+ * This function returns the length of generated token if it succeeds,
+ * or -1.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_generate_retry_token2(
+  uint8_t *token, const uint8_t *secret, size_t secretlen, uint32_t version,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  const ngtcp2_cid *retry_scid, const ngtcp2_cid *odcid, ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_verify_retry_token2` verifies Retry token stored in
+ * the buffer pointed by |token| of length |tokenlen|.  |secret| of
+ * length |secretlen| is a keying material to generate keys to decrypt
+ * the token.  |version| is QUIC version of the Initial packet that
+ * contains this token.  |remote_addr| of length |remote_addrlen| is
+ * an address of client.  |dcid| is a Destination Connection ID in
+ * Initial packet sent by client.  |timeout| is the period during
+ * which the token is valid.  |ts| is the current timestamp.  When
+ * validation succeeds, the extracted Destination Connection ID (which
+ * is the Destination Connection ID in Initial packet sent by client
+ * that triggered Retry packet) is stored in the buffer pointed by
+ * |odcid|.
+ *
+ * The token must be generated by
+ * `ngtcp2_crypto_generate_retry_token2`.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN`
+ *     A token is badly formatted; or verifying the integrity
+ *     protection failed.
+ * :macro:`NGTCP2_CRYPTO_ERR_VERIFY_TOKEN`
+ *     A token does not probe the client address; or the token
+ *     validity has expired; or it contains invalid Connection ID.
+ * :macro:`NGTCP2_CRYPTO_ERR_INTERNAL`
+ *     Internal error occurred.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_verify_retry_token2(
+  ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen,
+  const uint8_t *secret, size_t secretlen, uint32_t version,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  const ngtcp2_cid *dcid, ngtcp2_duration timeout, ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_generate_regular_token` generates a token in the
+ * buffer pointed by |token| that is sent with NEW_TOKEN frame.  The
+ * buffer pointed by |token| must have at least
+ * :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN` bytes long.  The
+ * successfully generated token starts with
+ * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR`.  |secret| of length
+ * |secretlen| is a keying material to generate keys to encrypt the
+ * token.  |remote_addr| of length |remote_addrlen| is an address of
+ * client.  |ts| is the timestamp when the token is generated.
+ *
+ * This function returns the length of generated token if it succeeds,
+ * or -1.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_generate_regular_token(
+  uint8_t *token, const uint8_t *secret, size_t secretlen,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_verify_regular_token` verifies a regular token
+ * stored in the buffer pointed by |token| of length |tokenlen|.
+ * |secret| of length |secretlen| is a keying material to generate
+ * keys to decrypt the token.  |remote_addr| of length
+ * |remote_addrlen| is an address of client.  |timeout| is the period
+ * during which the token is valid.  |ts| is the current timestamp.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_verify_regular_token(
+  const uint8_t *token, size_t tokenlen, const uint8_t *secret,
+  size_t secretlen, const ngtcp2_sockaddr *remote_addr,
+  ngtcp2_socklen remote_addrlen, ngtcp2_duration timeout, ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_write_connection_close` writes Initial packet
+ * containing CONNECTION_CLOSE with the given |error_code| and the
+ * optional |reason| of length |reasonlen| to the buffer pointed by
+ * |dest| of length |destlen|.  This function is designed for server
+ * to close connection without committing the state when validating
+ * Retry token fails.  This function must not be used by client.  The
+ * |dcid| must be the Source Connection ID in Initial packet from
+ * client.  The |scid| must be the Destination Connection ID in
+ * Initial packet from client.  |scid| is used to derive initial
+ * keying materials.
+ *
+ * This function wraps around `ngtcp2_pkt_write_connection_close` for
+ * easier use.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_write_connection_close(
+  uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid,
+  const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason,
+  size_t reasonlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_write_retry` writes Retry packet to the buffer
+ * pointed by |dest| of length |destlen|.  |dcid| is the Connection ID
+ * which appeared in a packet as a Source Connection ID sent by
+ * client.  |scid| is a server chosen Source Connection ID.  |odcid|
+ * specifies Original Destination Connection ID which appeared in a
+ * packet as a Destination Connection ID sent by client.  |token|
+ * specifies Retry Token, and |tokenlen| specifies its length.
+ *
+ * This function wraps around `ngtcp2_pkt_write_retry` for easier use.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_write_retry(
+  uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid,
+  const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token,
+  size_t tokenlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_ctx_encrypt_init` initializes |aead_ctx| with
+ * new AEAD cipher context object for encryption which is constructed
+ * to use |key| as encryption key.  |aead| specifies AEAD cipher to
+ * use.  |noncelen| is the length of nonce.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                    const ngtcp2_crypto_aead *aead,
+                                    const uint8_t *key, size_t noncelen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_ctx_decrypt_init` initializes |aead_ctx| with
+ * new AEAD cipher context object for decryption which is constructed
+ * to use |key| as decryption key.  |aead| specifies AEAD cipher to
+ * use.  |noncelen| is the length of nonce.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                    const ngtcp2_crypto_aead *aead,
+                                    const uint8_t *key, size_t noncelen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_ctx_free` frees up resources used by
+ * |aead_ctx|.  This function does not free the memory pointed by
+ * |aead_ctx| itself.
+ */
+NGTCP2_EXTERN void
+ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_delete_crypto_aead_ctx_cb` deletes the given
+ * |aead_ctx|.
+ *
+ * This function can be directly passed to
+ * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` field.
+ */
+NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_aead_ctx_cb(
+  ngtcp2_conn *conn, ngtcp2_crypto_aead_ctx *aead_ctx, void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_delete_crypto_cipher_ctx_cb` deletes the given
+ * |cipher_ctx|.
+ *
+ * This function can be directly passed to
+ * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` field.
+ */
+NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_cipher_ctx_cb(
+  ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_get_path_challenge_data_cb` writes unpredictable
+ * sequence of :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` bytes to |data|
+ * which is sent with PATH_CHALLENGE frame.
+ *
+ * This function can be directly passed to
+ * :member:`ngtcp2_callbacks.get_path_challenge_data` field.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn,
+                                                           uint8_t *data,
+                                                           void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_version_negotiation_cb` installs Initial keys for
+ * |version| which is negotiated or being negotiated.  |client_dcid|
+ * is the destination connection ID in first Initial packet from
+ * client.
+ *
+ * This function can be directly passed to
+ * :member:`ngtcp2_callbacks.version_negotiation` field.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_version_negotiation_cb(ngtcp2_conn *conn, uint32_t version,
+                                     const ngtcp2_cid *client_dcid,
+                                     void *user_data);
+
+typedef struct ngtcp2_crypto_conn_ref ngtcp2_crypto_conn_ref;
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_crypto_get_conn` is a callback function to get a
+ * pointer to :type:`ngtcp2_conn` from |conn_ref|.  The implementation
+ * must return non-NULL :type:`ngtcp2_conn` object.
+ */
+typedef ngtcp2_conn *(*ngtcp2_crypto_get_conn)(
+  ngtcp2_crypto_conn_ref *conn_ref);
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_conn_ref` is a structure to get a pointer to
+ * :type:`ngtcp2_conn`.  It is meant to be set to TLS native handle as
+ * an application specific data (e.g. SSL_set_app_data in quictls).
+ */
+typedef struct ngtcp2_crypto_conn_ref {
+  /**
+   * :member:`get_conn` is a callback function to get a pointer to
+   * :type:`ngtcp2_conn` object.
+   */
+  ngtcp2_crypto_get_conn get_conn;
+  /**
+   * :member:`user_data` is a pointer to arbitrary user data.
+   */
+  void *user_data;
+} ngtcp2_crypto_conn_ref;
+
+#ifdef __cplusplus
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(NGTCP2_CRYPTO_H) */
diff --git a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_boringssl.h b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_boringssl.h
new file mode 100644 (file)
index 0000000..89b26f0
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2020 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_BORINGSSL_H
+#define NGTCP2_CRYPTO_BORINGSSL_H
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <openssl/ssl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_boringssl_from_ssl_encryption_level` translates
+ * |ssl_level| to :type:`ngtcp2_encryption_level`.  This function is
+ * only available for BoringSSL backend.
+ */
+NGTCP2_EXTERN ngtcp2_encryption_level
+ngtcp2_crypto_boringssl_from_ssl_encryption_level(
+  enum ssl_encryption_level_t ssl_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_boringssl_from_ngtcp2_encryption_level` translates
+ * |encryption_level| to ssl_encryption_level_t.  This function is
+ * only available for BoringSSL backend.
+ */
+NGTCP2_EXTERN enum ssl_encryption_level_t
+ngtcp2_crypto_boringssl_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_boringssl_configure_server_context` configures
+ * |ssl_ctx| for server side QUIC connection.  It performs the
+ * following modifications:
+ *
+ * - Set minimum and maximum TLS version to TLSv1.3.
+ * - Set SSL_QUIC_METHOD by calling SSL_CTX_set_quic_method.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * SSL object by calling SSL_set_app_data, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_boringssl_configure_server_context(SSL_CTX *ssl_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_boringssl_configure_client_context` configures
+ * |ssl_ctx| for client side QUIC connection.  It performs the
+ * following modifications:
+ *
+ * - Set minimum and maximum TLS version to TLSv1.3.
+ * - Set SSL_QUIC_METHOD by calling SSL_CTX_set_quic_method.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * SSL object by calling SSL_set_app_data, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_boringssl_configure_client_context(SSL_CTX *ssl_ctx);
+
+#ifdef __cplusplus
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(NGTCP2_CRYPTO_BORINGSSL_H) */
diff --git a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_gnutls.h b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_gnutls.h
new file mode 100644 (file)
index 0000000..9b30d70
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2020 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_GNUTLS_H
+#define NGTCP2_CRYPTO_GNUTLS_H
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <gnutls/gnutls.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level`
+ * translates |gtls_level| to :type:`ngtcp2_encryption_level`.  This
+ * function is only available for GnuTLS backend.
+ */
+NGTCP2_EXTERN ngtcp2_encryption_level
+ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(
+  gnutls_record_encryption_level_t gtls_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_gnutls_from_ngtcp2_encryption_level` translates
+ * |encryption_level| to gnutls_record_encryption_level_t.  This
+ * function is only available for GnuTLS backend.
+ */
+NGTCP2_EXTERN gnutls_record_encryption_level_t
+ngtcp2_crypto_gnutls_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_gnutls_configure_server_session` configures
+ * |session| for server side QUIC connection.  It performs the
+ * following modifications:
+ *
+ * - Set gnutls_handshake_set_secret_function.
+ * - Set gnutls_handshake_set_read_function.
+ * - Set gnutls_alert_set_read_function.
+ * - Register a TLS extension handler for QUIC Transport Parameters.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * gnutls_session_t object by calling gnutls_session_set_ptr, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_gnutls_configure_server_session(gnutls_session_t session);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_gnutls_configure_client_session` configures
+ * |session| for client side QUIC connection.  It performs the
+ * following modifications:
+ *
+ * - Set gnutls_handshake_set_secret_function.
+ * - Set gnutls_handshake_set_read_function.
+ * - Set gnutls_alert_set_read_function.
+ * - Register a TLS extension handler for QUIC Transport Parameters.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * gnutls_session_t object by calling gnutls_session_set_ptr, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_gnutls_configure_client_session(gnutls_session_t session);
+
+#ifdef __cplusplus
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(NGTCP2_CRYPTO_GNUTLS_H) */
diff --git a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_ossl.h b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_ossl.h
new file mode 100644 (file)
index 0000000..417ec01
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2025 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_OSSL_H
+#define NGTCP2_CRYPTO_OSSL_H
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <openssl/ssl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * @macrosection
+ *
+ * ossl specific error codes
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_OSSL_ERR_TLS_WANT_X509_LOOKUP` is the error
+ * code which indicates that TLS handshake routine is interrupted by
+ * X509 certificate lookup.  See :macro:`SSL_ERROR_WANT_X509_LOOKUP`
+ * error description from `SSL_do_handshake`.
+ */
+#define NGTCP2_CRYPTO_OSSL_ERR_TLS_WANT_X509_LOOKUP -10001
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_OSSL_ERR_TLS_WANT_CLIENT_HELLO_CB` is the
+ * error code which indicates that TLS handshake routine is
+ * interrupted by client hello callback.  See
+ * :macro:`SSL_ERROR_WANT_CLIENT_HELLO_CB` error description from
+ * `SSL_do_handshake`.
+ */
+#define NGTCP2_CRYPTO_OSSL_ERR_TLS_WANT_CLIENT_HELLO_CB -10002
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_from_ossl_encryption_level` translates
+ * |ossl_level| to :type:`ngtcp2_encryption_level`.  This function is
+ * only available for ossl backend.
+ */
+NGTCP2_EXTERN ngtcp2_encryption_level
+ngtcp2_crypto_ossl_from_ossl_encryption_level(uint32_t ossl_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_from_ngtcp2_encryption_level` translates
+ * |encryption_level| to OpenSSL encryption level.  This function is
+ * only available for ossl backend.
+ */
+NGTCP2_EXTERN uint32_t ngtcp2_crypto_ossl_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level);
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_ossl_ctx` contains per-connection state, and
+ * must be set to `ngtcp2_conn_set_tls_native_handle`.
+ */
+typedef struct ngtcp2_crypto_ossl_ctx ngtcp2_crypto_ossl_ctx;
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_ctx_new` creates new
+ * :type:`ngtcp2_crypto_ossl_ctx` object, and sets it to |*pctx| if it
+ * succeeds.
+ *
+ * |ssl| is set to |*pctx|.  It may be NULL, and in that case, call
+ * `ngtcp2_crypto_ossl_ctx_set_ssl` later to set ``SSL`` object.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGTCP2_CRYPTO_ERR_NOMEM`
+ *     Out of memory
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_ossl_ctx_new(ngtcp2_crypto_ossl_ctx **pctx,
+                                             SSL *ssl);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_ctx_del` frees resources allocated for |ctx|.
+ * It also frees memory pointed by |ctx|.
+ */
+NGTCP2_EXTERN void ngtcp2_crypto_ossl_ctx_del(ngtcp2_crypto_ossl_ctx *ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_ctx_set_ssl` sets |ssl| to |ctx|.  This
+ * function must be called after ``SSL`` object is created.
+ */
+NGTCP2_EXTERN void ngtcp2_crypto_ossl_ctx_set_ssl(ngtcp2_crypto_ossl_ctx *ctx,
+                                                  SSL *ssl);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_ctx_get_ssl` returns ``SSL`` object set to
+ * |ctx|.  If the object has not been set, this function returns NULL.
+ */
+NGTCP2_EXTERN SSL *ngtcp2_crypto_ossl_ctx_get_ssl(ngtcp2_crypto_ossl_ctx *ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_init` initializes libngtcp2_crypto_ossl
+ * library.  This initialization is optional.  It is highly
+ * recommended to call this function before any use of
+ * libngtcp2_crypto library API to workaround the performance
+ * regression.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_ossl_init(void);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_configure_server_session` configures |ssl| for
+ * server side QUIC connection.  It performs the following
+ * modifications:
+ *
+ * - Register callbacks via ``SSL_set_quic_tls_cbs``
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * SSL object by calling SSL_set_app_data, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * Application must call ``SSL_set_app_data(ssl, NULL)`` before
+ * calling ``SSL_free(ssl)`` if you cannot make `ngtcp2_conn` object
+ * alive until ``SSL_free`` is called.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_ossl_configure_server_session(SSL *ssl);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ossl_configure_client_session` configures |ssl| for
+ * client side QUIC connection.  It performs the following
+ * modifications:
+ *
+ * - Register callbacks via ``SSL_set_quic_tls_cbs``
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * SSL object by calling SSL_set_app_data, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * Application must call ``SSL_set_app_data(ssl, NULL)`` before
+ * calling ``SSL_free(ssl)`` if you cannot make `ngtcp2_conn` object
+ * alive until ``SSL_free`` is called.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_ossl_configure_client_session(SSL *ssl);
+
+#ifdef __cplusplus
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(NGTCP2_CRYPTO_OSSL_H) */
diff --git a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_picotls.h b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_picotls.h
new file mode 100644 (file)
index 0000000..87ea4ba
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_PICOTLS_H
+#define NGTCP2_CRYPTO_PICOTLS_H
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <picotls.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_picotls_ctx` contains per-connection state of
+ * Picotls object, and must be set to
+ * `ngtcp2_conn_set_tls_native_handle`.
+ */
+typedef struct ngtcp2_crypto_picotls_ctx {
+  /**
+   * :member:`ptls` is a pointer to ptls_t object.
+   */
+  ptls_t *ptls;
+  /**
+   * :member:`handshake_properties` is a set of configurations used
+   * during this particular TLS handshake.
+   */
+  ptls_handshake_properties_t handshake_properties;
+} ngtcp2_crypto_picotls_ctx;
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_ctx_init` initializes the object pointed by
+ * |cptls|.  |cptls| must not be NULL.
+ */
+NGTCP2_EXTERN void
+ngtcp2_crypto_picotls_ctx_init(ngtcp2_crypto_picotls_ctx *cptls);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_from_epoch` translates |epoch| to
+ * :type:`ngtcp2_encryption_level`.  This function is only available
+ * for Picotls backend.
+ */
+NGTCP2_EXTERN ngtcp2_encryption_level
+ngtcp2_crypto_picotls_from_epoch(size_t epoch);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_from_ngtcp2_encryption_level` translates
+ * |encryption_level| to epoch.  This function is only available for
+ * Picotls backend.
+ */
+NGTCP2_EXTERN size_t ngtcp2_crypto_picotls_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_configure_server_context` configures |ctx|
+ * for server side QUIC connection.  It performs the following
+ * modifications:
+ *
+ * - Set max_early_data_size to UINT32_MAX.
+ * - Set omit_end_of_early_data to 1.
+ * - Set update_traffic_key callback.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * ptls_t object by assigning the pointer using ptls_get_data_ptr, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_picotls_configure_server_context(ptls_context_t *ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_configure_client_context` configures |ctx|
+ * for client side QUIC connection.  It performs the following
+ * modifications:
+ *
+ * - Set omit_end_of_early_data to 1.
+ * - Set update_traffic_key callback.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * ptls_t object by assigning the pointer using ptls_get_data_ptr, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_picotls_configure_client_context(ptls_context_t *ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_configure_server_session` configures |cptls|
+ * for server side QUIC connection.  It performs the following
+ * modifications:
+ *
+ * - Set handshake_properties.collect_extension to
+ *   `ngtcp2_crypto_picotls_collect_extension`.
+ * - Set handshake_properties.collected_extensions to
+ *   `ngtcp2_crypto_picotls_collected_extensions`.
+ *
+ * The callbacks set by this function only handle QUIC Transport
+ * Parameters TLS extension.  If an application needs to handle the
+ * other TLS extensions, set its own callbacks and call
+ * `ngtcp2_crypto_picotls_collect_extension` and
+ * `ngtcp2_crypto_picotls_collected_extensions` form them.
+ *
+ * During the QUIC handshake, the first element of
+ * handshake_properties.additional_extensions is assigned to send QUIC
+ * Transport Parameter TLS extension.  Therefore, an application must
+ * allocate at least 2 elements for
+ * handshake_properties.additional_extensions.
+ *
+ * Call `ngtcp2_crypto_picotls_deconfigure_session` to free up the
+ * resources.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * ptls_t object by assigning the pointer using ptls_get_data_ptr, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_picotls_configure_server_session(
+  ngtcp2_crypto_picotls_ctx *cptls);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_configure_client_session` configures |cptls|
+ * for client side QUIC connection.  It performs the following
+ * modifications:
+ *
+ * - Set handshake_properties.max_early_data_size to a pointer to
+ *   uint32_t, which is allocated dynamically by this function.
+ * - Set handshake_properties.collect_extension to
+ *   `ngtcp2_crypto_picotls_collect_extension`.
+ * - Set handshake_properties.collected_extensions to
+ *   `ngtcp2_crypto_picotls_collected_extensions`.
+ * - Set handshake_properties.additional_extensions[0].data to the
+ *   dynamically allocated buffer which contains QUIC Transport
+ *   Parameters TLS extension.  An application must allocate at least
+ *   2 elements for handshake_properties.additional_extensions.
+ *
+ * The callbacks set by this function only handle QUIC Transport
+ * Parameters TLS extension.  If an application needs to handle the
+ * other TLS extensions, set its own callbacks and call
+ * `ngtcp2_crypto_picotls_collect_extension` and
+ * `ngtcp2_crypto_picotls_collected_extensions` form them.
+ *
+ * Call `ngtcp2_crypto_picotls_deconfigure_session` to free up the
+ * resources.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * ptls_t object by assigning the pointer using ptls_get_data_ptr, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_picotls_configure_client_session(ngtcp2_crypto_picotls_ctx *cptls,
+                                               ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_deconfigure_session` frees the resources
+ * allocated for |cptls| during QUIC connection.  It frees the
+ * following data using :manpage:`free(3)`:
+ *
+ * - handshake_properties.max_early_data_size
+ * - handshake_properties.additional_extensions[0].data.base
+ *
+ * If |cptls| is NULL, this function does nothing.
+ */
+NGTCP2_EXTERN void
+ngtcp2_crypto_picotls_deconfigure_session(ngtcp2_crypto_picotls_ctx *cptls);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_collect_extension` is a callback function
+ * which only returns nonzero if |type| ==
+ * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1`.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_picotls_collect_extension(
+  ptls_t *ptls, struct st_ptls_handshake_properties_t *properties,
+  uint16_t type);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_picotls_collected_extensions` is a callback function
+ * which only handles the extension of type
+ * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1`.  The other
+ * extensions are ignored.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_picotls_collected_extensions(
+  ptls_t *ptls, struct st_ptls_handshake_properties_t *properties,
+  ptls_raw_extension_t *extensions);
+
+#ifdef __cplusplus
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(NGTCP2_CRYPTO_PICOTLS_H) */
diff --git a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_quictls.h b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_quictls.h
new file mode 100644 (file)
index 0000000..22e3eda
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_QUICTLS_H
+#define NGTCP2_CRYPTO_QUICTLS_H
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <openssl/ssl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * @macrosection
+ *
+ * quictls specific error codes
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_X509_LOOKUP` is the
+ * error code which indicates that TLS handshake routine is
+ * interrupted by X509 certificate lookup.  See
+ * :macro:`SSL_ERROR_WANT_X509_LOOKUP` error description from
+ * `SSL_do_handshake`.
+ */
+#define NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_X509_LOOKUP -10001
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_CLIENT_HELLO_CB` is the
+ * error code which indicates that TLS handshake routine is
+ * interrupted by client hello callback.  See
+ * :macro:`SSL_ERROR_WANT_CLIENT_HELLO_CB` error description from
+ * `SSL_do_handshake`.
+ */
+#define NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_CLIENT_HELLO_CB -10002
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_quictls_from_ossl_encryption_level` translates
+ * |ossl_level| to :type:`ngtcp2_encryption_level`.  This function is
+ * only available for quictls backend.
+ */
+NGTCP2_EXTERN ngtcp2_encryption_level
+ngtcp2_crypto_quictls_from_ossl_encryption_level(
+  OSSL_ENCRYPTION_LEVEL ossl_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_quictls_from_ngtcp2_encryption_level` translates
+ * |encryption_level| to OSSL_ENCRYPTION_LEVEL.  This function is only
+ * available for quictls backend.
+ */
+NGTCP2_EXTERN OSSL_ENCRYPTION_LEVEL
+ngtcp2_crypto_quictls_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_quictls_configure_server_context` configures
+ * |ssl_ctx| for server side QUIC connection.  It performs the
+ * following modifications:
+ *
+ * - Set minimum and maximum TLS version to TLSv1.3.
+ * - Set SSL_QUIC_METHOD by calling SSL_CTX_set_quic_method.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * SSL object by calling SSL_set_app_data, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_quictls_configure_server_context(SSL_CTX *ssl_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_quictls_configure_client_context` configures
+ * |ssl_ctx| for client side QUIC connection.  It performs the
+ * following modifications:
+ *
+ * - Set minimum and maximum TLS version to TLSv1.3.
+ * - Set SSL_QUIC_METHOD by calling SSL_CTX_set_quic_method.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * SSL object by calling SSL_set_app_data, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_quictls_configure_client_context(SSL_CTX *ssl_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_quictls_init` initializes libngtcp2_crypto_quictls
+ * library.  This initialization is optional.  For quictls >= 3.0, it
+ * is highly recommended to call this function before any use of
+ * libngtcp2_crypto library API to workaround the performance
+ * regression.  Note that calling this function does not solve all
+ * performance issues introduced in 3.x.  For quictls 1.1.1, this
+ * function does nothing, and always succeeds.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int ngtcp2_crypto_quictls_init(void);
+
+#ifdef __cplusplus
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(NGTCP2_CRYPTO_QUICTLS_H) */
diff --git a/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_wolfssl.h b/third_party/ngtcp2/crypto/includes/ngtcp2/ngtcp2_crypto_wolfssl.h
new file mode 100644 (file)
index 0000000..e95056d
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_WOLFSSL_H
+#define NGTCP2_CRYPTO_WOLFSSL_H
+
+#include <ngtcp2/ngtcp2.h>
+
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level` translates
+ * |wolfssl_level| to :type:`ngtcp2_encryption_level`.  This function
+ * is only available for wolfSSL backend.
+ */
+NGTCP2_EXTERN ngtcp2_encryption_level
+ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level(
+  WOLFSSL_ENCRYPTION_LEVEL wolfssl_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_wolfssl_from_ngtcp2_encryption_level` translates
+ * |encryption_level| to WOLFSSL_ENCRYPTION_LEVEL.  This function is
+ * only available for wolfSSL backend.
+ */
+NGTCP2_EXTERN WOLFSSL_ENCRYPTION_LEVEL
+ngtcp2_crypto_wolfssl_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_wolfssl_configure_server_context` configures
+ * |ssl_ctx| for server side QUIC connection.  It performs the
+ * following modifications:
+ *
+ * - Set minimum and maximum TLS version to TLSv1.3.
+ * - Set WOLFSSL_QUIC_METHOD by calling wolfSSL_CTX_set_quic_method.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * WOLFSSL object by calling wolfSSL_set_app_data, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_wolfssl_configure_server_context(WOLFSSL_CTX *ssl_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_wolfssl_configure_client_context` configures
+ * |ssl_ctx| for client side QUIC connection.  It performs the
+ * following modifications:
+ *
+ * - Set minimum and maximum TLS version to TLSv1.3.
+ * - Set WOLFSSL_QUIC_METHOD by calling wolfSSL_CTX_set_quic_method.
+ *
+ * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to
+ * SSL object by calling wolfSSL_set_app_data, and
+ * :type:`ngtcp2_crypto_conn_ref` object must have
+ * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get
+ * :type:`ngtcp2_conn`.
+ *
+ * It returns 0 if it succeeds, or -1.
+ */
+NGTCP2_EXTERN int
+ngtcp2_crypto_wolfssl_configure_client_context(WOLFSSL_CTX *ssl_ctx);
+
+#ifdef __cplusplus
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(NGTCP2_CRYPTO_WOLFSSL_H) */
diff --git a/third_party/ngtcp2/crypto/ossl/.gitignore b/third_party/ngtcp2/crypto/ossl/.gitignore
new file mode 100644 (file)
index 0000000..774bc00
--- /dev/null
@@ -0,0 +1 @@
+/libngtcp2_crypto_ossl.pc
diff --git a/third_party/ngtcp2/crypto/ossl/CMakeLists.txt b/third_party/ngtcp2/crypto/ossl/CMakeLists.txt
new file mode 100644 (file)
index 0000000..da2ef2d
--- /dev/null
@@ -0,0 +1,86 @@
+# ngtcp2
+
+# Copyright (c) 2025 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+add_definitions(-DBUILDING_NGTCP2)
+
+set(ngtcp2_crypto_ossl_SOURCES
+  ossl.c
+  ../shared.c
+)
+
+set(ngtcp2_crypto_ossl_INCLUDE_DIRS
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto"
+  "${OPENSSL_INCLUDE_DIRS}"
+)
+
+foreach(name libngtcp2_crypto_ossl.pc)
+  configure_file("${name}.in" "${name}" @ONLY)
+endforeach()
+
+# Public shared library
+if(ENABLE_SHARED_LIB)
+  add_library(ngtcp2_crypto_ossl SHARED ${ngtcp2_crypto_ossl_SOURCES})
+  set_target_properties(ngtcp2_crypto_ossl PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${CRYPTO_OSSL_LT_VERSION}
+    SOVERSION ${CRYPTO_OSSL_LT_SOVERSION}
+    C_VISIBILITY_PRESET hidden
+    POSITION_INDEPENDENT_CODE ON
+  )
+  target_include_directories(ngtcp2_crypto_ossl PUBLIC
+    ${ngtcp2_crypto_ossl_INCLUDE_DIRS})
+  target_link_libraries(ngtcp2_crypto_ossl ngtcp2 ${OPENSSL_LIBRARIES})
+
+  install(TARGETS ngtcp2_crypto_ossl
+    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+if(ENABLE_STATIC_LIB)
+  # Public static library
+  add_library(ngtcp2_crypto_ossl_static STATIC ${ngtcp2_crypto_ossl_SOURCES})
+  set_target_properties(ngtcp2_crypto_ossl_static PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${CRYPTO_OSSL_LT_VERSION}
+    SOVERSION ${CRYPTO_OSSL_LT_SOVERSION}
+    ARCHIVE_OUTPUT_NAME ngtcp2_crypto_ossl${STATIC_LIB_SUFFIX}
+    C_VISIBILITY_PRESET hidden
+  )
+  target_compile_definitions(ngtcp2_crypto_ossl_static PUBLIC
+    "-DNGTCP2_STATICLIB")
+  target_include_directories(ngtcp2_crypto_ossl_static PUBLIC
+    ${ngtcp2_crypto_ossl_INCLUDE_DIRS})
+
+  install(TARGETS ngtcp2_crypto_ossl_static
+    DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+endif()
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_ossl.pc"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/third_party/ngtcp2/crypto/ossl/Makefile.am b/third_party/ngtcp2/crypto/ossl/Makefile.am
new file mode 100644 (file)
index 0000000..d58e5ab
--- /dev/null
@@ -0,0 +1,43 @@
+# ngtcp2
+
+# Copyright (c) 2025 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = CMakeLists.txt
+
+AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG)
+AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \
+       -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \
+       -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \
+       -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \
+       @OPENSSL_CFLAGS@
+AM_LDFLAGS = ${LIBTOOL_LDFLAGS}
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libngtcp2_crypto_ossl.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LTLIBRARIES = libngtcp2_crypto_ossl.la
+
+libngtcp2_crypto_ossl_la_SOURCES = ossl.c ../shared.c ../shared.h
+libngtcp2_crypto_ossl_la_LDFLAGS = -no-undefined \
+       -version-info $(CRYPTO_OSSL_LT_CURRENT):$(CRYPTO_OSSL_LT_REVISION):$(CRYPTO_OSSL_LT_AGE)
+libngtcp2_crypto_ossl_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \
+       @OPENSSL_LIBS@
diff --git a/third_party/ngtcp2/crypto/ossl/libngtcp2_crypto_ossl.pc.in b/third_party/ngtcp2/crypto/ossl/libngtcp2_crypto_ossl.pc.in
new file mode 100644 (file)
index 0000000..1397b4b
--- /dev/null
@@ -0,0 +1,33 @@
+# ngtcp2
+
+# Copyright (c) 2025 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libngtcp2_crypto_ossl
+Description: ngtcp2 ossl crypto library
+URL: https://github.com/ngtcp2/ngtcp2
+Version: @VERSION@
+Libs: -L${libdir} -lngtcp2_crypto_ossl
+Cflags: -I${includedir}
diff --git a/third_party/ngtcp2/crypto/ossl/ossl.c b/third_party/ngtcp2/crypto/ossl/ossl.c
new file mode 100644 (file)
index 0000000..061d6ac
--- /dev/null
@@ -0,0 +1,1191 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2025 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <assert.h>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_ossl.h>
+
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+#include <openssl/kdf.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#include <openssl/core_names.h>
+
+#include "ngtcp2_macro.h"
+#include "shared.h"
+
+static int crypto_initialized;
+static EVP_CIPHER *crypto_aes_128_gcm;
+static EVP_CIPHER *crypto_aes_256_gcm;
+static EVP_CIPHER *crypto_chacha20_poly1305;
+static EVP_CIPHER *crypto_aes_128_ccm;
+static EVP_CIPHER *crypto_aes_128_ctr;
+static EVP_CIPHER *crypto_aes_256_ctr;
+static EVP_CIPHER *crypto_chacha20;
+static EVP_MD *crypto_sha256;
+static EVP_MD *crypto_sha384;
+static EVP_KDF *crypto_hkdf;
+
+int ngtcp2_crypto_ossl_init(void) {
+  crypto_aes_128_gcm = EVP_CIPHER_fetch(NULL, "AES-128-GCM", NULL);
+  if (crypto_aes_128_gcm == NULL) {
+    return -1;
+  }
+
+  crypto_aes_256_gcm = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL);
+  if (crypto_aes_256_gcm == NULL) {
+    return -1;
+  }
+
+  crypto_chacha20_poly1305 = EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL);
+  if (crypto_chacha20_poly1305 == NULL) {
+    return -1;
+  }
+
+  crypto_aes_128_ccm = EVP_CIPHER_fetch(NULL, "AES-128-CCM", NULL);
+  if (crypto_aes_128_ccm == NULL) {
+    return -1;
+  }
+
+  crypto_aes_128_ctr = EVP_CIPHER_fetch(NULL, "AES-128-CTR", NULL);
+  if (crypto_aes_128_ctr == NULL) {
+    return -1;
+  }
+
+  crypto_aes_256_ctr = EVP_CIPHER_fetch(NULL, "AES-256-CTR", NULL);
+  if (crypto_aes_256_ctr == NULL) {
+    return -1;
+  }
+
+  crypto_chacha20 = EVP_CIPHER_fetch(NULL, "ChaCha20", NULL);
+  if (crypto_chacha20 == NULL) {
+    return -1;
+  }
+
+  crypto_sha256 = EVP_MD_fetch(NULL, "sha256", NULL);
+  if (crypto_sha256 == NULL) {
+    return -1;
+  }
+
+  crypto_sha384 = EVP_MD_fetch(NULL, "sha384", NULL);
+  if (crypto_sha384 == NULL) {
+    return -1;
+  }
+
+  crypto_hkdf = EVP_KDF_fetch(NULL, "hkdf", NULL);
+  if (crypto_hkdf == NULL) {
+    return -1;
+  }
+
+  crypto_initialized = 1;
+
+  return 0;
+}
+
+static const EVP_CIPHER *crypto_aead_aes_128_gcm(void) {
+  if (crypto_aes_128_gcm) {
+    return crypto_aes_128_gcm;
+  }
+
+  return EVP_aes_128_gcm();
+}
+
+static const EVP_CIPHER *crypto_aead_aes_256_gcm(void) {
+  if (crypto_aes_256_gcm) {
+    return crypto_aes_256_gcm;
+  }
+
+  return EVP_aes_256_gcm();
+}
+
+static const EVP_CIPHER *crypto_aead_chacha20_poly1305(void) {
+  if (crypto_chacha20_poly1305) {
+    return crypto_chacha20_poly1305;
+  }
+
+  return EVP_chacha20_poly1305();
+}
+
+static const EVP_CIPHER *crypto_aead_aes_128_ccm(void) {
+  if (crypto_aes_128_ccm) {
+    return crypto_aes_128_ccm;
+  }
+
+  return EVP_aes_128_ccm();
+}
+
+static const EVP_CIPHER *crypto_cipher_aes_128_ctr(void) {
+  if (crypto_aes_128_ctr) {
+    return crypto_aes_128_ctr;
+  }
+
+  return EVP_aes_128_ctr();
+}
+
+static const EVP_CIPHER *crypto_cipher_aes_256_ctr(void) {
+  if (crypto_aes_256_ctr) {
+    return crypto_aes_256_ctr;
+  }
+
+  return EVP_aes_256_ctr();
+}
+
+static const EVP_CIPHER *crypto_cipher_chacha20(void) {
+  if (crypto_chacha20) {
+    return crypto_chacha20;
+  }
+
+  return EVP_chacha20();
+}
+
+static const EVP_MD *crypto_md_sha256(void) {
+  if (crypto_sha256) {
+    return crypto_sha256;
+  }
+
+  return EVP_sha256();
+}
+
+static const EVP_MD *crypto_md_sha384(void) {
+  if (crypto_sha384) {
+    return crypto_sha384;
+  }
+
+  return EVP_sha384();
+}
+
+static EVP_KDF *crypto_kdf_hkdf(void) {
+  if (crypto_hkdf) {
+    return crypto_hkdf;
+  }
+
+  return EVP_KDF_fetch(NULL, "hkdf", NULL);
+}
+
+static size_t crypto_aead_max_overhead(const EVP_CIPHER *aead) {
+  switch (EVP_CIPHER_nid(aead)) {
+  case NID_aes_128_gcm:
+  case NID_aes_256_gcm:
+    return EVP_GCM_TLS_TAG_LEN;
+  case NID_chacha20_poly1305:
+    return EVP_CHACHAPOLY_TLS_TAG_LEN;
+  case NID_aes_128_ccm:
+    return EVP_CCM_TLS_TAG_LEN;
+  default:
+    assert(0);
+    abort(); /* if NDEBUG is set */
+  }
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)crypto_aead_aes_128_gcm());
+}
+
+ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) {
+  md->native_handle = (void *)crypto_md_sha256();
+  return md;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) {
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)crypto_aead_aes_128_gcm());
+  ctx->md.native_handle = (void *)crypto_md_sha256();
+  ctx->hp.native_handle = (void *)crypto_cipher_aes_128_ctr();
+  ctx->max_encryption = 0;
+  ctx->max_decryption_failure = 0;
+  return ctx;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead,
+                                            void *aead_native_handle) {
+  aead->native_handle = aead_native_handle;
+  aead->max_overhead = crypto_aead_max_overhead(aead_native_handle);
+  return aead;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)crypto_aead_aes_128_gcm());
+}
+
+static const EVP_CIPHER *crypto_cipher_id_get_aead(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+    return crypto_aead_aes_128_gcm();
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return crypto_aead_aes_256_gcm();
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+    return crypto_aead_chacha20_poly1305();
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return crypto_aead_aes_128_ccm();
+  default:
+    return NULL;
+  }
+}
+
+static uint64_t crypto_cipher_id_get_aead_max_encryption(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM;
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305;
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM;
+  default:
+    return 0;
+  }
+}
+
+static uint64_t
+crypto_cipher_id_get_aead_max_decryption_failure(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM;
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305;
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM;
+  default:
+    return 0;
+  }
+}
+
+static const EVP_CIPHER *crypto_cipher_id_get_hp(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return crypto_cipher_aes_128_ctr();
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return crypto_cipher_aes_256_ctr();
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+    return crypto_cipher_chacha20();
+  default:
+    return NULL;
+  }
+}
+
+static const EVP_MD *crypto_cipher_id_get_md(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return crypto_md_sha256();
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return crypto_md_sha384();
+  default:
+    return NULL;
+  }
+}
+
+static int supported_cipher_id(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+typedef struct crypto_buf crypto_buf;
+
+typedef struct crypto_buf {
+  uint8_t data[4096];
+  uint8_t *pos;
+  uint8_t *last;
+
+  crypto_buf *next;
+} crypto_buf;
+
+static crypto_buf *crypto_buf_new(void) {
+  crypto_buf *cbuf = malloc(sizeof(crypto_buf));
+
+  if (cbuf == NULL) {
+    return NULL;
+  }
+
+  cbuf->pos = cbuf->last = cbuf->data;
+  cbuf->next = NULL;
+
+  return cbuf;
+}
+
+static void crypto_buf_del(crypto_buf *cbuf) { free(cbuf); }
+
+static size_t crypto_buf_left(const crypto_buf *cbuf) {
+  return (size_t)(cbuf->data + sizeof(cbuf->data) - cbuf->last);
+}
+
+static size_t crypto_buf_len(const crypto_buf *cbuf) {
+  return (size_t)(cbuf->last - cbuf->pos);
+}
+
+static size_t crypto_buf_eof(const crypto_buf *cbuf) {
+  return cbuf->pos == cbuf->data + sizeof(cbuf->data);
+}
+
+static void crypto_buf_write(crypto_buf *cbuf, const uint8_t *data,
+                             size_t datalen) {
+  assert(crypto_buf_left(cbuf) >= datalen);
+
+  memcpy(cbuf->last, data, datalen);
+  cbuf->last += datalen;
+}
+
+struct ngtcp2_crypto_ossl_ctx {
+  SSL *ssl;
+  ngtcp2_encryption_level tx_level;
+  crypto_buf *crypto_head, *crypto_read, *crypto_write;
+  size_t crypto_head_released;
+  uint8_t *remote_params;
+};
+
+int ngtcp2_crypto_ossl_ctx_new(ngtcp2_crypto_ossl_ctx **possl_ctx, SSL *ssl) {
+  ngtcp2_crypto_ossl_ctx *ossl_ctx = malloc(sizeof(**possl_ctx));
+
+  if (ossl_ctx == NULL) {
+    return NGTCP2_CRYPTO_ERR_NOMEM;
+  }
+
+  ossl_ctx->ssl = ssl;
+  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;
+  ossl_ctx->remote_params = NULL;
+
+  *possl_ctx = ossl_ctx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_ossl_ctx_del(ngtcp2_crypto_ossl_ctx *ossl_ctx) {
+  crypto_buf *cbuf, *next;
+
+  if (!ossl_ctx) {
+    return;
+  }
+
+  for (cbuf = ossl_ctx->crypto_head; cbuf;) {
+    next = cbuf->next;
+    crypto_buf_del(cbuf);
+    cbuf = next;
+  }
+
+  free(ossl_ctx->remote_params);
+  free(ossl_ctx);
+}
+
+void ngtcp2_crypto_ossl_ctx_set_ssl(ngtcp2_crypto_ossl_ctx *ossl_ctx,
+                                    SSL *ssl) {
+  ossl_ctx->ssl = ssl;
+}
+
+SSL *ngtcp2_crypto_ossl_ctx_get_ssl(ngtcp2_crypto_ossl_ctx *ossl_ctx) {
+  return ossl_ctx->ssl;
+}
+
+static int crypto_ossl_ctx_write_crypto_data(ngtcp2_crypto_ossl_ctx *ossl_ctx,
+                                             const uint8_t *data,
+                                             size_t datalen) {
+  crypto_buf *cbuf;
+  const uint8_t *end;
+  size_t n, left;
+
+  if (datalen == 0) {
+    return 0;
+  }
+
+  if (ossl_ctx->crypto_write == NULL) {
+    ossl_ctx->crypto_head = ossl_ctx->crypto_read = ossl_ctx->crypto_write =
+      crypto_buf_new();
+    if (ossl_ctx->crypto_head == NULL) {
+      return NGTCP2_CRYPTO_ERR_NOMEM;
+    }
+  }
+
+  for (end = data + datalen; data != end;) {
+    left = crypto_buf_left(ossl_ctx->crypto_write);
+    if (left == 0) {
+      cbuf = crypto_buf_new();
+      if (cbuf == NULL) {
+        return NGTCP2_CRYPTO_ERR_NOMEM;
+      }
+
+      ossl_ctx->crypto_write->next = cbuf;
+      ossl_ctx->crypto_write = cbuf;
+
+      left = crypto_buf_left(ossl_ctx->crypto_write);
+    }
+
+    n = ngtcp2_min_size((size_t)(end - data), left);
+    crypto_buf_write(ossl_ctx->crypto_write, data, n);
+    data += n;
+  }
+
+  return 0;
+}
+
+static void crypto_ossl_ctx_read_crypto_data(ngtcp2_crypto_ossl_ctx *ossl_ctx,
+                                             const uint8_t **pbuf,
+                                             size_t *pbytes_read) {
+  size_t n;
+
+  if (ossl_ctx->crypto_read == NULL) {
+    *pbuf = NULL;
+    *pbytes_read = 0;
+    return;
+  }
+
+  n = crypto_buf_len(ossl_ctx->crypto_read);
+
+  *pbuf = ossl_ctx->crypto_read->pos;
+  *pbytes_read = n;
+
+  ossl_ctx->crypto_read->pos += n;
+
+  if (crypto_buf_eof(ossl_ctx->crypto_read) &&
+      ossl_ctx->crypto_read != ossl_ctx->crypto_write) {
+    ossl_ctx->crypto_read = ossl_ctx->crypto_read->next;
+  }
+}
+
+static void
+crypto_ossl_ctx_release_crypto_data(ngtcp2_crypto_ossl_ctx *ossl_ctx,
+                                    size_t released) {
+  crypto_buf *cbuf = NULL;
+
+  if (released == 0) {
+    return;
+  }
+
+  assert(ossl_ctx->crypto_head);
+
+  ossl_ctx->crypto_head_released += released;
+
+  for (; ossl_ctx->crypto_head_released >= sizeof(cbuf->data);) {
+    assert(ossl_ctx->crypto_head);
+
+    cbuf = ossl_ctx->crypto_head;
+    ossl_ctx->crypto_head = cbuf->next;
+
+    crypto_buf_del(cbuf);
+    ossl_ctx->crypto_head_released -= sizeof(cbuf->data);
+  }
+
+  if (cbuf == ossl_ctx->crypto_read) {
+    ossl_ctx->crypto_read = ossl_ctx->crypto_head;
+
+    if (cbuf == ossl_ctx->crypto_write) {
+      assert(ossl_ctx->crypto_head == NULL);
+
+      ossl_ctx->crypto_write = NULL;
+    }
+  }
+}
+
+static ngtcp2_crypto_ctx *crypto_ctx_cipher_id(ngtcp2_crypto_ctx *ctx,
+                                               uint32_t cipher_id) {
+  ngtcp2_crypto_aead_init(&ctx->aead,
+                          (void *)crypto_cipher_id_get_aead(cipher_id));
+  ctx->md.native_handle = (void *)crypto_cipher_id_get_md(cipher_id);
+  ctx->hp.native_handle = (void *)crypto_cipher_id_get_hp(cipher_id);
+  ctx->max_encryption = crypto_cipher_id_get_aead_max_encryption(cipher_id);
+  ctx->max_decryption_failure =
+    crypto_cipher_id_get_aead_max_decryption_failure(cipher_id);
+
+  return ctx;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx,
+                                         void *tls_native_handle) {
+  ngtcp2_crypto_ossl_ctx *ossl_ctx = tls_native_handle;
+  SSL *ssl = ossl_ctx->ssl;
+  const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+  uint32_t cipher_id;
+
+  if (cipher == NULL) {
+    return NULL;
+  }
+
+  cipher_id = (uint32_t)SSL_CIPHER_get_id(cipher);
+
+  if (!supported_cipher_id(cipher_id)) {
+    return NULL;
+  }
+
+  return crypto_ctx_cipher_id(ctx, cipher_id);
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx,
+                                               void *tls_native_handle) {
+  return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle);
+}
+
+static size_t crypto_md_hashlen(const EVP_MD *md) {
+  return (size_t)EVP_MD_size(md);
+}
+
+size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) {
+  return crypto_md_hashlen(md->native_handle);
+}
+
+static size_t crypto_aead_keylen(const EVP_CIPHER *aead) {
+  return (size_t)EVP_CIPHER_key_length(aead);
+}
+
+size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_keylen(aead->native_handle);
+}
+
+static size_t crypto_aead_noncelen(const EVP_CIPHER *aead) {
+  return (size_t)EVP_CIPHER_iv_length(aead);
+}
+
+size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_noncelen(aead->native_handle);
+}
+
+int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  int cipher_nid = EVP_CIPHER_nid(cipher);
+  EVP_CIPHER_CTX *actx;
+  size_t taglen = crypto_aead_max_overhead(cipher);
+  OSSL_PARAM params[3];
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+  params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_IVLEN, &noncelen);
+
+  if (cipher_nid == NID_aes_128_ccm) {
+    params[1] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+                                                  NULL, taglen);
+    params[2] = OSSL_PARAM_construct_end();
+  } else {
+    params[1] = OSSL_PARAM_construct_end();
+  }
+
+  if (!EVP_EncryptInit_ex(actx, cipher, NULL, NULL, NULL) ||
+      !EVP_CIPHER_CTX_set_params(actx, params) ||
+      !EVP_EncryptInit_ex(actx, NULL, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  int cipher_nid = EVP_CIPHER_nid(cipher);
+  EVP_CIPHER_CTX *actx;
+  size_t taglen = crypto_aead_max_overhead(cipher);
+  OSSL_PARAM params[3];
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+  params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_IVLEN, &noncelen);
+
+  if (cipher_nid == NID_aes_128_ccm) {
+    params[1] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+                                                  NULL, taglen);
+    params[2] = OSSL_PARAM_construct_end();
+  } else {
+    params[1] = OSSL_PARAM_construct_end();
+  }
+
+  if (!EVP_DecryptInit_ex(actx, cipher, NULL, NULL, NULL) ||
+      !EVP_CIPHER_CTX_set_params(actx, params) ||
+      !EVP_DecryptInit_ex(actx, NULL, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (aead_ctx->native_handle) {
+    EVP_CIPHER_CTX_free(aead_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key) {
+  EVP_CIPHER_CTX *actx;
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+  if (!EVP_EncryptInit_ex(actx, cipher->native_handle, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  cipher_ctx->native_handle = actx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (cipher_ctx->native_handle) {
+    EVP_CIPHER_CTX_free(cipher_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
+                               const uint8_t *secret, size_t secretlen,
+                               const uint8_t *salt, size_t saltlen) {
+  const EVP_MD *prf = md->native_handle;
+  EVP_KDF *kdf = crypto_kdf_hkdf();
+  EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
+  int mode = EVP_KDF_HKDF_MODE_EXTRACT_ONLY;
+  OSSL_PARAM params[] = {
+    OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode),
+    OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+                                     (char *)EVP_MD_get0_name(prf), 0),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret,
+                                      secretlen),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt,
+                                      saltlen),
+    OSSL_PARAM_construct_end(),
+  };
+  int rv = 0;
+
+  if (!crypto_initialized) {
+    EVP_KDF_free(kdf);
+  }
+
+  if (EVP_KDF_derive(kctx, dest, (size_t)EVP_MD_size(prf), params) <= 0) {
+    rv = -1;
+  }
+
+  EVP_KDF_CTX_free(kctx);
+
+  return rv;
+}
+
+int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen,
+                              const ngtcp2_crypto_md *md, const uint8_t *secret,
+                              size_t secretlen, const uint8_t *info,
+                              size_t infolen) {
+  const EVP_MD *prf = md->native_handle;
+  EVP_KDF *kdf = crypto_kdf_hkdf();
+  EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
+  int mode = EVP_KDF_HKDF_MODE_EXPAND_ONLY;
+  OSSL_PARAM params[] = {
+    OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode),
+    OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+                                     (char *)EVP_MD_get0_name(prf), 0),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret,
+                                      secretlen),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (void *)info,
+                                      infolen),
+    OSSL_PARAM_construct_end(),
+  };
+  int rv = 0;
+
+  if (!crypto_initialized) {
+    EVP_KDF_free(kdf);
+  }
+
+  if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) {
+    rv = -1;
+  }
+
+  EVP_KDF_CTX_free(kctx);
+
+  return rv;
+}
+
+int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen,
+                       const ngtcp2_crypto_md *md, const uint8_t *secret,
+                       size_t secretlen, const uint8_t *salt, size_t saltlen,
+                       const uint8_t *info, size_t infolen) {
+  const EVP_MD *prf = md->native_handle;
+  EVP_KDF *kdf = crypto_kdf_hkdf();
+  EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
+  OSSL_PARAM params[] = {
+    OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+                                     (char *)EVP_MD_get0_name(prf), 0),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret,
+                                      secretlen),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt,
+                                      saltlen),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (void *)info,
+                                      infolen),
+    OSSL_PARAM_construct_end(),
+  };
+  int rv = 0;
+
+  if (!crypto_initialized) {
+    EVP_KDF_free(kdf);
+  }
+
+  if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) {
+    rv = -1;
+  }
+
+  EVP_KDF_CTX_free(kctx);
+
+  return rv;
+}
+
+int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *plaintext, size_t plaintextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  size_t taglen = crypto_aead_max_overhead(cipher);
+  int cipher_nid = EVP_CIPHER_nid(cipher);
+  EVP_CIPHER_CTX *actx = aead_ctx->native_handle;
+  int len;
+  OSSL_PARAM params[] = {
+    OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+                                      dest + plaintextlen, taglen),
+    OSSL_PARAM_construct_end(),
+  };
+
+  (void)noncelen;
+
+  if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, nonce) ||
+      (cipher_nid == NID_aes_128_ccm &&
+       !EVP_EncryptUpdate(actx, NULL, &len, NULL, (int)plaintextlen)) ||
+      !EVP_EncryptUpdate(actx, NULL, &len, aad, (int)aadlen) ||
+      !EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) ||
+      !EVP_EncryptFinal_ex(actx, dest + len, &len) ||
+      !EVP_CIPHER_CTX_get_params(actx, params)) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *ciphertext, size_t ciphertextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  size_t taglen = crypto_aead_max_overhead(cipher);
+  int cipher_nid = EVP_CIPHER_nid(cipher);
+  EVP_CIPHER_CTX *actx = aead_ctx->native_handle;
+  int len;
+  const uint8_t *tag;
+  OSSL_PARAM params[2];
+
+  (void)noncelen;
+
+  if (taglen > ciphertextlen) {
+    return -1;
+  }
+
+  ciphertextlen -= taglen;
+  tag = ciphertext + ciphertextlen;
+
+  params[0] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+                                                (void *)tag, taglen);
+  params[1] = OSSL_PARAM_construct_end();
+
+  if (!EVP_DecryptInit_ex(actx, NULL, NULL, NULL, nonce) ||
+      !EVP_CIPHER_CTX_set_params(actx, params) ||
+      (cipher_nid == NID_aes_128_ccm &&
+       !EVP_DecryptUpdate(actx, NULL, &len, NULL, (int)ciphertextlen)) ||
+      !EVP_DecryptUpdate(actx, NULL, &len, aad, (int)aadlen) ||
+      !EVP_DecryptUpdate(actx, dest, &len, ciphertext, (int)ciphertextlen) ||
+      (cipher_nid != NID_aes_128_ccm &&
+       !EVP_DecryptFinal_ex(actx, dest + ciphertextlen, &len))) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                          const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                          const uint8_t *sample) {
+  static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
+  EVP_CIPHER_CTX *actx = hp_ctx->native_handle;
+  int len;
+
+  (void)hp;
+
+  if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) ||
+      !EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, sizeof(PLAINTEXT) - 1) ||
+      !EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len)) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_read_write_crypto_data(
+  ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level,
+  const uint8_t *data, size_t datalen) {
+  ngtcp2_crypto_ossl_ctx *ossl_ctx = ngtcp2_conn_get_tls_native_handle(conn);
+  SSL *ssl = ossl_ctx->ssl;
+  int rv;
+  int err;
+  (void)encryption_level;
+
+  if (crypto_ossl_ctx_write_crypto_data(ossl_ctx, data, datalen) != 0) {
+    return -1;
+  }
+
+  if (!ngtcp2_conn_get_handshake_completed(conn)) {
+    rv = SSL_do_handshake(ssl);
+    if (rv <= 0) {
+      err = SSL_get_error(ssl, rv);
+      switch (err) {
+      case SSL_ERROR_WANT_READ:
+      case SSL_ERROR_WANT_WRITE:
+        return 0;
+      case SSL_ERROR_WANT_CLIENT_HELLO_CB:
+        return NGTCP2_CRYPTO_OSSL_ERR_TLS_WANT_CLIENT_HELLO_CB;
+      case SSL_ERROR_WANT_X509_LOOKUP:
+        return NGTCP2_CRYPTO_OSSL_ERR_TLS_WANT_X509_LOOKUP;
+      case SSL_ERROR_SSL:
+        return -1;
+      default:
+        return -1;
+      }
+    }
+
+    ngtcp2_conn_tls_handshake_completed(conn);
+  }
+
+  rv = SSL_read(ssl, NULL, 0);
+  if (rv != 1) {
+    err = SSL_get_error(ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+    case SSL_ERROR_WANT_WRITE:
+      return 0;
+    case SSL_ERROR_SSL:
+    case SSL_ERROR_ZERO_RETURN:
+      return -1;
+    default:
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) {
+  (void)conn;
+  (void)tls;
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf,
+                                             size_t len) {
+  ngtcp2_crypto_ossl_ctx *ossl_ctx = tls;
+
+  if (len) {
+    assert(!ossl_ctx->remote_params);
+
+    ossl_ctx->remote_params = malloc(len);
+    if (!ossl_ctx->remote_params) {
+      return -1;
+    }
+
+    memcpy(ossl_ctx->remote_params, buf, len);
+  }
+
+  if (SSL_set_quic_tls_transport_params(ossl_ctx->ssl, ossl_ctx->remote_params,
+                                        len) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+ngtcp2_encryption_level
+ngtcp2_crypto_ossl_from_ossl_encryption_level(uint32_t ossl_level) {
+  switch (ossl_level) {
+  case OSSL_RECORD_PROTECTION_LEVEL_NONE:
+    return NGTCP2_ENCRYPTION_LEVEL_INITIAL;
+  case OSSL_RECORD_PROTECTION_LEVEL_EARLY:
+    return NGTCP2_ENCRYPTION_LEVEL_0RTT;
+  case OSSL_RECORD_PROTECTION_LEVEL_HANDSHAKE:
+    return NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE;
+  case OSSL_RECORD_PROTECTION_LEVEL_APPLICATION:
+    return NGTCP2_ENCRYPTION_LEVEL_1RTT;
+  default:
+    assert(0);
+    abort(); /* if NDEBUG is set */
+  }
+}
+
+uint32_t ngtcp2_crypto_ossl_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level) {
+  switch (encryption_level) {
+  case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
+    return OSSL_RECORD_PROTECTION_LEVEL_NONE;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    return OSSL_RECORD_PROTECTION_LEVEL_HANDSHAKE;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    return OSSL_RECORD_PROTECTION_LEVEL_APPLICATION;
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    return OSSL_RECORD_PROTECTION_LEVEL_EARLY;
+  default:
+    assert(0);
+    abort(); /* if NDEBUG is set */
+  }
+}
+
+int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data,
+                                             void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  if (RAND_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_random(uint8_t *data, size_t datalen) {
+  if (RAND_bytes(data, (int)datalen) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int ossl_yield_secret(SSL *ssl, uint32_t ossl_level, int direction,
+                             const unsigned char *secret, size_t secretlen,
+                             void *arg) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn;
+  ngtcp2_crypto_ossl_ctx *ossl_ctx;
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_ossl_from_ossl_encryption_level(ossl_level);
+  (void)arg;
+
+  if (!conn_ref) {
+    return 1;
+  }
+
+  conn = conn_ref->get_conn(conn_ref);
+  ossl_ctx = ngtcp2_conn_get_tls_native_handle(conn);
+
+  if (direction) {
+    if (ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level,
+                                                secret, secretlen) != 0) {
+      return 0;
+    }
+
+    ossl_ctx->tx_level = level;
+
+    return 1;
+  }
+
+  if (ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level,
+                                              secret, secretlen) != 0) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int ossl_crypto_send(SSL *ssl, const unsigned char *buf, size_t buflen,
+                            size_t *consumed, void *arg) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn;
+  ngtcp2_crypto_ossl_ctx *ossl_ctx;
+  int rv;
+  (void)arg;
+
+  if (!conn_ref) {
+    return 1;
+  }
+
+  conn = conn_ref->get_conn(conn_ref);
+  ossl_ctx = ngtcp2_conn_get_tls_native_handle(conn);
+
+  rv = ngtcp2_conn_submit_crypto_data(conn, ossl_ctx->tx_level, buf, buflen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return 0;
+  }
+
+  *consumed = buflen;
+
+  return 1;
+}
+
+static int ossl_crypto_recv_rcd(SSL *ssl, const unsigned char **buf,
+                                size_t *bytes_read, void *arg) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn;
+  ngtcp2_crypto_ossl_ctx *ossl_ctx;
+  (void)arg;
+
+  if (!conn_ref) {
+    *buf = NULL;
+    *bytes_read = 0;
+    return 1;
+  }
+
+  conn = conn_ref->get_conn(conn_ref);
+  ossl_ctx = ngtcp2_conn_get_tls_native_handle(conn);
+
+  crypto_ossl_ctx_read_crypto_data(ossl_ctx, buf, bytes_read);
+
+  return 1;
+}
+
+static int ossl_crypto_release_rcd(SSL *ssl, size_t released, void *arg) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn;
+  ngtcp2_crypto_ossl_ctx *ossl_ctx;
+  (void)arg;
+
+  /* It is sometimes a bit hard or tedious to keep ngtcp2_conn alive
+     until SSL_free is called.  Instead, we require application to
+     call SSL_set_app_data(ssl, NULL) before SSL_free(ssl) so that
+     ngtcp2_conn is never used in this function. */
+  if (!conn_ref) {
+    return 1;
+  }
+
+  conn = conn_ref->get_conn(conn_ref);
+  ossl_ctx = ngtcp2_conn_get_tls_native_handle(conn);
+
+  crypto_ossl_ctx_release_crypto_data(ossl_ctx, released);
+
+  return 1;
+}
+
+static int ossl_got_transport_params(SSL *ssl, const unsigned char *params,
+                                     size_t paramslen, void *arg) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn;
+  int rv;
+  (void)arg;
+
+  if (!conn_ref) {
+    return 1;
+  }
+
+  conn = conn_ref->get_conn(conn_ref);
+
+  rv =
+    ngtcp2_conn_decode_and_set_remote_transport_params(conn, params, paramslen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int ossl_alert(SSL *ssl, uint8_t alert_code, void *arg) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn;
+  (void)arg;
+
+  if (!conn_ref) {
+    return 1;
+  }
+
+  conn = conn_ref->get_conn(conn_ref);
+
+  ngtcp2_conn_set_tls_alert(conn, alert_code);
+
+  return 1;
+}
+
+static const OSSL_DISPATCH qtdis[] = {
+  {
+    OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_SEND,
+    (void (*)(void))ossl_crypto_send,
+  },
+  {
+    OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RECV_RCD,
+    (void (*)(void))ossl_crypto_recv_rcd,
+  },
+  {
+    OSSL_FUNC_SSL_QUIC_TLS_CRYPTO_RELEASE_RCD,
+    (void (*)(void))ossl_crypto_release_rcd,
+  },
+  {
+    OSSL_FUNC_SSL_QUIC_TLS_YIELD_SECRET,
+    (void (*)(void))ossl_yield_secret,
+  },
+  {
+    OSSL_FUNC_SSL_QUIC_TLS_GOT_TRANSPORT_PARAMS,
+    (void (*)(void))ossl_got_transport_params,
+  },
+  {
+    OSSL_FUNC_SSL_QUIC_TLS_ALERT,
+    (void (*)(void))ossl_alert,
+  },
+  OSSL_DISPATCH_END,
+};
+
+static int crypto_ossl_configure_session(SSL *ssl) {
+  if (!SSL_set_quic_tls_cbs(ssl, qtdis, NULL)) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_ossl_configure_server_session(SSL *ssl) {
+  return crypto_ossl_configure_session(ssl);
+}
+
+int ngtcp2_crypto_ossl_configure_client_session(SSL *ssl) {
+  return crypto_ossl_configure_session(ssl);
+}
diff --git a/third_party/ngtcp2/crypto/picotls/.gitignore b/third_party/ngtcp2/crypto/picotls/.gitignore
new file mode 100644 (file)
index 0000000..3df25b1
--- /dev/null
@@ -0,0 +1,2 @@
+/libngtcp2_crypto_picotls.pc
+/libngtcp2_crypto_picotls.a
diff --git a/third_party/ngtcp2/crypto/picotls/CMakeLists.txt b/third_party/ngtcp2/crypto/picotls/CMakeLists.txt
new file mode 100644 (file)
index 0000000..e24f1bd
--- /dev/null
@@ -0,0 +1,65 @@
+# ngtcp2
+
+# Copyright (c) 2022 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+add_definitions(-DBUILDING_NGTCP2)
+
+set(ngtcp2_crypto_picotls_SOURCES
+  picotls.c
+  ../shared.c
+)
+
+set(ngtcp2_crypto_picotls_INCLUDE_DIRS
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto"
+  "${PICOTLS_INCLUDE_DIRS}"
+  "${VANILLA_OPENSSL_INCLUDE_DIRS}"
+)
+
+foreach(name libngtcp2_crypto_picotls.pc)
+  configure_file("${name}.in" "${name}" @ONLY)
+endforeach()
+
+if(ENABLE_STATIC_LIB)
+  # Public static library
+  add_library(ngtcp2_crypto_picotls_static STATIC ${ngtcp2_crypto_picotls_SOURCES})
+  set_target_properties(ngtcp2_crypto_picotls_static PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    ARCHIVE_OUTPUT_NAME ngtcp2_crypto_picotls${STATIC_LIB_SUFFIX}
+    C_VISIBILITY_PRESET hidden
+  )
+  target_compile_definitions(ngtcp2_crypto_picotls_static PUBLIC
+    "-DNGTCP2_STATICLIB")
+  target_include_directories(ngtcp2_crypto_picotls_static PUBLIC
+    ${ngtcp2_crypto_picotls_INCLUDE_DIRS})
+
+  install(TARGETS ngtcp2_crypto_picotls_static
+    DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+endif()
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_picotls.pc"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/third_party/ngtcp2/crypto/picotls/Makefile.am b/third_party/ngtcp2/crypto/picotls/Makefile.am
new file mode 100644 (file)
index 0000000..b2ed766
--- /dev/null
@@ -0,0 +1,39 @@
+# ngtcp2
+
+# Copyright (c) 2022 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = CMakeLists.txt
+
+AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG)
+AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \
+       -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \
+       -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \
+       -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \
+       @PICOTLS_CFLAGS@ @VANILLA_OPENSSL_CFLAGS@
+AM_LDFLAGS = ${LIBTOOL_LDFLAGS}
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libngtcp2_crypto_picotls.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LIBRARIES = libngtcp2_crypto_picotls.a
+
+libngtcp2_crypto_picotls_a_SOURCES = picotls.c ../shared.c ../shared.h
diff --git a/third_party/ngtcp2/crypto/picotls/libngtcp2_crypto_picotls.pc.in b/third_party/ngtcp2/crypto/picotls/libngtcp2_crypto_picotls.pc.in
new file mode 100644 (file)
index 0000000..1cb24f1
--- /dev/null
@@ -0,0 +1,33 @@
+# ngtcp2
+
+# Copyright (c) 2022 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libngtcp2_crypto_picotls
+Description: ngtcp2 Picotls crypto library
+URL: https://github.com/ngtcp2/ngtcp2
+Version: @VERSION@
+Libs: -L${libdir} -lngtcp2_crypto_picotls
+Cflags: -I${includedir}
diff --git a/third_party/ngtcp2/crypto/picotls/picotls.c b/third_party/ngtcp2/crypto/picotls/picotls.c
new file mode 100644 (file)
index 0000000..98ebf9e
--- /dev/null
@@ -0,0 +1,714 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <assert.h>
+#include <string.h>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_picotls.h>
+
+#include <picotls.h>
+#include <picotls/openssl.h>
+
+#include "shared.h"
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)&ptls_openssl_aes128gcm);
+}
+
+ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) {
+  md->native_handle = (void *)&ptls_openssl_sha256;
+  return md;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) {
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)&ptls_openssl_aes128gcm);
+  ctx->md.native_handle = (void *)&ptls_openssl_sha256;
+  ctx->hp.native_handle = (void *)&ptls_openssl_aes128ctr;
+  ctx->max_encryption = 0;
+  ctx->max_decryption_failure = 0;
+  return ctx;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead,
+                                            void *aead_native_handle) {
+  ptls_aead_algorithm_t *alg = aead_native_handle;
+
+  aead->native_handle = aead_native_handle;
+  aead->max_overhead = alg->tag_size;
+  return aead;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)&ptls_openssl_aes128gcm);
+}
+
+static uint64_t
+crypto_cipher_suite_get_aead_max_encryption(ptls_cipher_suite_t *cs) {
+  if (cs->aead == &ptls_openssl_aes128gcm ||
+      cs->aead == &ptls_openssl_aes256gcm) {
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM;
+  }
+
+#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
+  if (cs->aead == &ptls_openssl_chacha20poly1305) {
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305;
+  }
+#endif /* defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) */
+
+  return 0;
+}
+
+static uint64_t
+crypto_cipher_suite_get_aead_max_decryption_failure(ptls_cipher_suite_t *cs) {
+  if (cs->aead == &ptls_openssl_aes128gcm ||
+      cs->aead == &ptls_openssl_aes256gcm) {
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM;
+  }
+
+#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
+  if (cs->aead == &ptls_openssl_chacha20poly1305) {
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305;
+  }
+#endif /* defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) */
+
+  return 0;
+}
+
+static const ptls_cipher_algorithm_t *
+crypto_cipher_suite_get_hp(ptls_cipher_suite_t *cs) {
+  if (cs->aead == &ptls_openssl_aes128gcm) {
+    return &ptls_openssl_aes128ctr;
+  }
+
+  if (cs->aead == &ptls_openssl_aes256gcm) {
+    return &ptls_openssl_aes256ctr;
+  }
+
+#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
+  if (cs->aead == &ptls_openssl_chacha20poly1305) {
+    return &ptls_openssl_chacha20;
+  }
+#endif /* defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) */
+
+  return NULL;
+}
+
+static int supported_cipher_suite(ptls_cipher_suite_t *cs) {
+  return cs->aead == &ptls_openssl_aes128gcm ||
+         cs->aead == &ptls_openssl_aes256gcm
+#ifdef PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
+         || cs->aead == &ptls_openssl_chacha20poly1305
+#endif /* defined(PTLS_OPENSSL_HAVE_CHACHA20_POLY1305) */
+    ;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx,
+                                         void *tls_native_handle) {
+  ngtcp2_crypto_picotls_ctx *cptls = tls_native_handle;
+  ptls_cipher_suite_t *cs = ptls_get_cipher(cptls->ptls);
+
+  if (cs == NULL) {
+    return NULL;
+  }
+
+  if (!supported_cipher_suite(cs)) {
+    return NULL;
+  }
+
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)cs->aead);
+  ctx->md.native_handle = (void *)cs->hash;
+  ctx->hp.native_handle = (void *)crypto_cipher_suite_get_hp(cs);
+  ctx->max_encryption = crypto_cipher_suite_get_aead_max_encryption(cs);
+  ctx->max_decryption_failure =
+    crypto_cipher_suite_get_aead_max_decryption_failure(cs);
+  return ctx;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx,
+                                               void *tls_native_handle) {
+  return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle);
+}
+
+static size_t crypto_md_hashlen(const ptls_hash_algorithm_t *md) {
+  return md->digest_size;
+}
+
+size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) {
+  return crypto_md_hashlen(md->native_handle);
+}
+
+static size_t crypto_aead_keylen(const ptls_aead_algorithm_t *aead) {
+  return aead->key_size;
+}
+
+size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_keylen(aead->native_handle);
+}
+
+static size_t crypto_aead_noncelen(const ptls_aead_algorithm_t *aead) {
+  return aead->iv_size;
+}
+
+size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_noncelen(aead->native_handle);
+}
+
+int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const ptls_aead_algorithm_t *cipher = aead->native_handle;
+  size_t keylen = crypto_aead_keylen(cipher);
+  ptls_aead_context_t *actx;
+  static const uint8_t iv[PTLS_MAX_IV_SIZE] = {0};
+
+  (void)noncelen;
+  (void)keylen;
+
+  actx = ptls_aead_new_direct(cipher, /* is_enc = */ 1, key, iv);
+  if (actx == NULL) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const ptls_aead_algorithm_t *cipher = aead->native_handle;
+  size_t keylen = crypto_aead_keylen(cipher);
+  ptls_aead_context_t *actx;
+  const uint8_t iv[PTLS_MAX_IV_SIZE] = {0};
+
+  (void)noncelen;
+  (void)keylen;
+
+  actx = ptls_aead_new_direct(cipher, /* is_enc = */ 0, key, iv);
+  if (actx == NULL) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (aead_ctx->native_handle) {
+    ptls_aead_free(aead_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key) {
+  ptls_cipher_context_t *actx;
+
+  actx = ptls_cipher_new(cipher->native_handle, /* is_enc = */ 1, key);
+  if (actx == NULL) {
+    return -1;
+  }
+
+  cipher_ctx->native_handle = actx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (cipher_ctx->native_handle) {
+    ptls_cipher_free(cipher_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
+                               const uint8_t *secret, size_t secretlen,
+                               const uint8_t *salt, size_t saltlen) {
+  ptls_iovec_t saltv, ikm;
+
+  saltv = ptls_iovec_init(salt, saltlen);
+  ikm = ptls_iovec_init(secret, secretlen);
+
+  if (ptls_hkdf_extract(md->native_handle, dest, saltv, ikm) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen,
+                              const ngtcp2_crypto_md *md, const uint8_t *secret,
+                              size_t secretlen, const uint8_t *info,
+                              size_t infolen) {
+  ptls_iovec_t prk, infov;
+
+  prk = ptls_iovec_init(secret, secretlen);
+  infov = ptls_iovec_init(info, infolen);
+
+  if (ptls_hkdf_expand(md->native_handle, dest, destlen, prk, infov) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen,
+                       const ngtcp2_crypto_md *md, const uint8_t *secret,
+                       size_t secretlen, const uint8_t *salt, size_t saltlen,
+                       const uint8_t *info, size_t infolen) {
+  ptls_iovec_t saltv, ikm, prk, infov;
+  uint8_t prkbuf[PTLS_MAX_DIGEST_SIZE];
+  ptls_hash_algorithm_t *algo = md->native_handle;
+
+  saltv = ptls_iovec_init(salt, saltlen);
+  ikm = ptls_iovec_init(secret, secretlen);
+
+  if (ptls_hkdf_extract(algo, prkbuf, saltv, ikm) != 0) {
+    return -1;
+  }
+
+  prk = ptls_iovec_init(prkbuf, algo->digest_size);
+  infov = ptls_iovec_init(info, infolen);
+
+  if (ptls_hkdf_expand(algo, dest, destlen, prk, infov) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *plaintext, size_t plaintextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  ptls_aead_context_t *actx = aead_ctx->native_handle;
+
+  (void)aead;
+
+  ptls_aead_xor_iv(actx, nonce, noncelen);
+
+  ptls_aead_encrypt(actx, dest, plaintext, plaintextlen, 0, aad, aadlen);
+
+  /* zero-out static iv once again */
+  ptls_aead_xor_iv(actx, nonce, noncelen);
+
+  return 0;
+}
+
+int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *ciphertext, size_t ciphertextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  ptls_aead_context_t *actx = aead_ctx->native_handle;
+  size_t nwrite;
+
+  (void)aead;
+
+  ptls_aead_xor_iv(actx, nonce, noncelen);
+
+  nwrite =
+    ptls_aead_decrypt(actx, dest, ciphertext, ciphertextlen, 0, aad, aadlen);
+
+  /* zero-out static iv once again */
+  ptls_aead_xor_iv(actx, nonce, noncelen);
+
+  if (nwrite == SIZE_MAX) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                          const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                          const uint8_t *sample) {
+  ptls_cipher_context_t *actx = hp_ctx->native_handle;
+  static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
+
+  (void)hp;
+
+  ptls_cipher_init(actx, sample);
+  ptls_cipher_encrypt(actx, dest, PLAINTEXT, sizeof(PLAINTEXT) - 1);
+
+  return 0;
+}
+
+int ngtcp2_crypto_read_write_crypto_data(
+  ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level,
+  const uint8_t *data, size_t datalen) {
+  ngtcp2_crypto_picotls_ctx *cptls = ngtcp2_conn_get_tls_native_handle(conn);
+  ptls_buffer_t sendbuf;
+  size_t epoch_offsets[5] = {0};
+  size_t epoch =
+    ngtcp2_crypto_picotls_from_ngtcp2_encryption_level(encryption_level);
+  size_t epoch_datalen;
+  size_t i;
+  int rv;
+
+  ptls_buffer_init(&sendbuf, (void *)"", 0);
+
+  assert(epoch == ptls_get_read_epoch(cptls->ptls));
+
+  rv = ptls_handle_message(cptls->ptls, &sendbuf, epoch_offsets, epoch, data,
+                           datalen, &cptls->handshake_properties);
+  if (rv != 0 && rv != PTLS_ERROR_IN_PROGRESS) {
+    if (PTLS_ERROR_GET_CLASS(rv) == PTLS_ERROR_CLASS_SELF_ALERT) {
+      ngtcp2_conn_set_tls_alert(conn, (uint8_t)PTLS_ERROR_TO_ALERT(rv));
+    }
+
+    rv = -1;
+    goto fin;
+  }
+
+  if (!ngtcp2_conn_is_server(conn) &&
+      cptls->handshake_properties.client.early_data_acceptance ==
+        PTLS_EARLY_DATA_REJECTED) {
+    rv = ngtcp2_conn_tls_early_data_rejected(conn);
+    if (rv != 0) {
+      rv = -1;
+      goto fin;
+    }
+  }
+
+  for (i = 0; i < 4; ++i) {
+    epoch_datalen = epoch_offsets[i + 1] - epoch_offsets[i];
+    if (epoch_datalen == 0) {
+      continue;
+    }
+
+    assert(i != 1);
+
+    if (ngtcp2_conn_submit_crypto_data(
+          conn, ngtcp2_crypto_picotls_from_epoch(i),
+          sendbuf.base + epoch_offsets[i], epoch_datalen) != 0) {
+      rv = -1;
+      goto fin;
+    }
+  }
+
+  if (rv == 0) {
+    ngtcp2_conn_tls_handshake_completed(conn);
+  }
+
+  rv = 0;
+
+fin:
+  ptls_buffer_dispose(&sendbuf);
+
+  return rv;
+}
+
+int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) {
+  (void)conn;
+  (void)tls;
+
+  /* The remote transport parameters will be set via picotls
+     collected_extensions callback */
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf,
+                                             size_t len) {
+  (void)tls;
+  (void)buf;
+  (void)len;
+
+  /* The local transport parameters will be set in an external
+     call. */
+
+  return 0;
+}
+
+ngtcp2_encryption_level ngtcp2_crypto_picotls_from_epoch(size_t epoch) {
+  switch (epoch) {
+  case 0:
+    return NGTCP2_ENCRYPTION_LEVEL_INITIAL;
+  case 1:
+    return NGTCP2_ENCRYPTION_LEVEL_0RTT;
+  case 2:
+    return NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE;
+  case 3:
+    return NGTCP2_ENCRYPTION_LEVEL_1RTT;
+  default:
+    assert(0);
+    abort();
+  }
+}
+
+size_t ngtcp2_crypto_picotls_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level) {
+  switch (encryption_level) {
+  case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
+    return 0;
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    return 1;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    return 2;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    return 3;
+  default:
+    assert(0);
+    abort();
+  }
+}
+
+int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data,
+                                             void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  ptls_openssl_random_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN);
+
+  return 0;
+}
+
+int ngtcp2_crypto_random(uint8_t *data, size_t datalen) {
+  ptls_openssl_random_bytes(data, datalen);
+
+  return 0;
+}
+
+void ngtcp2_crypto_picotls_ctx_init(ngtcp2_crypto_picotls_ctx *cptls) {
+  cptls->ptls = NULL;
+  memset(&cptls->handshake_properties, 0, sizeof(cptls->handshake_properties));
+}
+
+static int set_additional_extensions(ptls_handshake_properties_t *hsprops,
+                                     ngtcp2_conn *conn) {
+  const size_t buflen = 256;
+  uint8_t *buf;
+  ngtcp2_ssize nwrite;
+  ptls_raw_extension_t *exts = hsprops->additional_extensions;
+
+  assert(exts);
+
+  buf = malloc(buflen);
+  if (buf == NULL) {
+    return -1;
+  }
+
+  nwrite = ngtcp2_conn_encode_local_transport_params(conn, buf, buflen);
+  if (nwrite < 0) {
+    goto fail;
+  }
+
+  exts[0].type = NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1;
+  exts[0].data.base = buf;
+  exts[0].data.len = (size_t)nwrite;
+
+  return 0;
+
+fail:
+  free(buf);
+
+  return -1;
+}
+
+int ngtcp2_crypto_picotls_collect_extension(
+  ptls_t *ptls, struct st_ptls_handshake_properties_t *properties,
+  uint16_t type) {
+  (void)ptls;
+  (void)properties;
+
+  return type == NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1;
+}
+
+int ngtcp2_crypto_picotls_collected_extensions(
+  ptls_t *ptls, struct st_ptls_handshake_properties_t *properties,
+  ptls_raw_extension_t *extensions) {
+  ngtcp2_crypto_conn_ref *conn_ref;
+  ngtcp2_conn *conn;
+  int rv;
+
+  (void)properties;
+
+  for (; extensions->type != UINT16_MAX; ++extensions) {
+    if (extensions->type != NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1) {
+      continue;
+    }
+
+    conn_ref = *ptls_get_data_ptr(ptls);
+    conn = conn_ref->get_conn(conn_ref);
+
+    rv = ngtcp2_conn_decode_and_set_remote_transport_params(
+      conn, extensions->data.base, extensions->data.len);
+    if (rv != 0) {
+      ngtcp2_conn_set_tls_error(conn, rv);
+      return -1;
+    }
+
+    return 0;
+  }
+
+  return 0;
+}
+
+static int update_traffic_key_server_cb(ptls_update_traffic_key_t *self,
+                                        ptls_t *ptls, int is_enc, size_t epoch,
+                                        const void *secret) {
+  ngtcp2_crypto_conn_ref *conn_ref = *ptls_get_data_ptr(ptls);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level = ngtcp2_crypto_picotls_from_epoch(epoch);
+  ptls_cipher_suite_t *cipher = ptls_get_cipher(ptls);
+  size_t secretlen = cipher->hash->digest_size;
+  ngtcp2_crypto_picotls_ctx *cptls;
+
+  (void)self;
+
+  if (is_enc) {
+    if (ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level,
+                                                secret, secretlen) != 0) {
+      return -1;
+    }
+
+    if (level == NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE) {
+      /* libngtcp2 allows an application to change QUIC transport
+       * parameters before installing Handshake tx key.  We need to
+       * wait for the key to get the correct local transport
+       * parameters from ngtcp2_conn.
+       */
+      cptls = ngtcp2_conn_get_tls_native_handle(conn);
+
+      if (set_additional_extensions(&cptls->handshake_properties, conn) != 0) {
+        return -1;
+      }
+    }
+
+    return 0;
+  }
+
+  if (ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level,
+                                              secret, secretlen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static ptls_update_traffic_key_t update_traffic_key_server = {
+  update_traffic_key_server_cb,
+};
+
+static int update_traffic_key_cb(ptls_update_traffic_key_t *self, ptls_t *ptls,
+                                 int is_enc, size_t epoch, const void *secret) {
+  ngtcp2_crypto_conn_ref *conn_ref = *ptls_get_data_ptr(ptls);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level = ngtcp2_crypto_picotls_from_epoch(epoch);
+  ptls_cipher_suite_t *cipher = ptls_get_cipher(ptls);
+  size_t secretlen = cipher->hash->digest_size;
+
+  (void)self;
+
+  if (is_enc) {
+    if (ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level,
+                                                secret, secretlen) != 0) {
+      return -1;
+    }
+
+    return 0;
+  }
+
+  if (ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level,
+                                              secret, secretlen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static ptls_update_traffic_key_t update_traffic_key = {update_traffic_key_cb};
+
+int ngtcp2_crypto_picotls_configure_server_context(ptls_context_t *ctx) {
+  ctx->max_early_data_size = UINT32_MAX;
+  ctx->omit_end_of_early_data = 1;
+  ctx->update_traffic_key = &update_traffic_key_server;
+
+  return 0;
+}
+
+int ngtcp2_crypto_picotls_configure_client_context(ptls_context_t *ctx) {
+  ctx->omit_end_of_early_data = 1;
+  ctx->update_traffic_key = &update_traffic_key;
+
+  return 0;
+}
+
+int ngtcp2_crypto_picotls_configure_server_session(
+  ngtcp2_crypto_picotls_ctx *cptls) {
+  ptls_handshake_properties_t *hsprops = &cptls->handshake_properties;
+
+  hsprops->collect_extension = ngtcp2_crypto_picotls_collect_extension;
+  hsprops->collected_extensions = ngtcp2_crypto_picotls_collected_extensions;
+
+  return 0;
+}
+
+int ngtcp2_crypto_picotls_configure_client_session(
+  ngtcp2_crypto_picotls_ctx *cptls, ngtcp2_conn *conn) {
+  ptls_handshake_properties_t *hsprops = &cptls->handshake_properties;
+
+  hsprops->client.max_early_data_size = calloc(1, sizeof(size_t));
+  if (hsprops->client.max_early_data_size == NULL) {
+    return -1;
+  }
+
+  if (set_additional_extensions(hsprops, conn) != 0) {
+    free(hsprops->client.max_early_data_size);
+    hsprops->client.max_early_data_size = NULL;
+    return -1;
+  }
+
+  hsprops->collect_extension = ngtcp2_crypto_picotls_collect_extension;
+  hsprops->collected_extensions = ngtcp2_crypto_picotls_collected_extensions;
+
+  return 0;
+}
+
+void ngtcp2_crypto_picotls_deconfigure_session(
+  ngtcp2_crypto_picotls_ctx *cptls) {
+  ptls_handshake_properties_t *hsprops;
+  ptls_raw_extension_t *exts;
+
+  if (cptls == NULL) {
+    return;
+  }
+
+  hsprops = &cptls->handshake_properties;
+
+  free(hsprops->client.max_early_data_size);
+
+  exts = hsprops->additional_extensions;
+  if (exts) {
+    free(hsprops->additional_extensions[0].data.base);
+  }
+}
diff --git a/third_party/ngtcp2/crypto/quictls/.gitignore b/third_party/ngtcp2/crypto/quictls/.gitignore
new file mode 100644 (file)
index 0000000..6f4bcf8
--- /dev/null
@@ -0,0 +1 @@
+/libngtcp2_crypto_quictls.pc
diff --git a/third_party/ngtcp2/crypto/quictls/CMakeLists.txt b/third_party/ngtcp2/crypto/quictls/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c1296b2
--- /dev/null
@@ -0,0 +1,86 @@
+# ngtcp2
+
+# Copyright (c) 2019 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+add_definitions(-DBUILDING_NGTCP2)
+
+set(ngtcp2_crypto_quictls_SOURCES
+  quictls.c
+  ../shared.c
+)
+
+set(ngtcp2_crypto_quictls_INCLUDE_DIRS
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto"
+  "${OPENSSL_INCLUDE_DIRS}"
+)
+
+foreach(name libngtcp2_crypto_quictls.pc)
+  configure_file("${name}.in" "${name}" @ONLY)
+endforeach()
+
+# Public shared library
+if(ENABLE_SHARED_LIB)
+  add_library(ngtcp2_crypto_quictls SHARED ${ngtcp2_crypto_quictls_SOURCES})
+  set_target_properties(ngtcp2_crypto_quictls PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${CRYPTO_QUICTLS_LT_VERSION}
+    SOVERSION ${CRYPTO_QUICTLS_LT_SOVERSION}
+    C_VISIBILITY_PRESET hidden
+    POSITION_INDEPENDENT_CODE ON
+  )
+  target_include_directories(ngtcp2_crypto_quictls PUBLIC
+    ${ngtcp2_crypto_quictls_INCLUDE_DIRS})
+  target_link_libraries(ngtcp2_crypto_quictls ngtcp2 ${OPENSSL_LIBRARIES})
+
+  install(TARGETS ngtcp2_crypto_quictls
+    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+if(ENABLE_STATIC_LIB)
+  # Public static library
+  add_library(ngtcp2_crypto_quictls_static STATIC ${ngtcp2_crypto_quictls_SOURCES})
+  set_target_properties(ngtcp2_crypto_quictls_static PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${CRYPTO_QUICTLS_LT_VERSION}
+    SOVERSION ${CRYPTO_QUICTLS_LT_SOVERSION}
+    ARCHIVE_OUTPUT_NAME ngtcp2_crypto_quictls${STATIC_LIB_SUFFIX}
+    C_VISIBILITY_PRESET hidden
+  )
+  target_compile_definitions(ngtcp2_crypto_quictls_static PUBLIC
+    "-DNGTCP2_STATICLIB")
+  target_include_directories(ngtcp2_crypto_quictls_static PUBLIC
+    ${ngtcp2_crypto_quictls_INCLUDE_DIRS})
+
+  install(TARGETS ngtcp2_crypto_quictls_static
+    DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+endif()
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_quictls.pc"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/third_party/ngtcp2/crypto/quictls/Makefile.am b/third_party/ngtcp2/crypto/quictls/Makefile.am
new file mode 100644 (file)
index 0000000..d15dcbb
--- /dev/null
@@ -0,0 +1,43 @@
+# ngtcp2
+
+# Copyright (c) 2019 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = CMakeLists.txt
+
+AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG)
+AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \
+       -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \
+       -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \
+       -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \
+       @OPENSSL_CFLAGS@
+AM_LDFLAGS = ${LIBTOOL_LDFLAGS}
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libngtcp2_crypto_quictls.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LTLIBRARIES = libngtcp2_crypto_quictls.la
+
+libngtcp2_crypto_quictls_la_SOURCES = quictls.c ../shared.c ../shared.h
+libngtcp2_crypto_quictls_la_LDFLAGS = -no-undefined \
+       -version-info $(CRYPTO_QUICTLS_LT_CURRENT):$(CRYPTO_QUICTLS_LT_REVISION):$(CRYPTO_QUICTLS_LT_AGE)
+libngtcp2_crypto_quictls_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \
+       @OPENSSL_LIBS@
diff --git a/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_quictls.pc.in b/third_party/ngtcp2/crypto/quictls/libngtcp2_crypto_quictls.pc.in
new file mode 100644 (file)
index 0000000..991d2a7
--- /dev/null
@@ -0,0 +1,33 @@
+# ngtcp2
+
+# Copyright (c) 2019 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libngtcp2_crypto_quictls
+Description: ngtcp2 quictls crypto library
+URL: https://github.com/ngtcp2/ngtcp2
+Version: @VERSION@
+Libs: -L${libdir} -lngtcp2_crypto_quictls
+Cflags: -I${includedir}
diff --git a/third_party/ngtcp2/crypto/quictls/quictls.c b/third_party/ngtcp2/crypto/quictls/quictls.c
new file mode 100644 (file)
index 0000000..592e5a8
--- /dev/null
@@ -0,0 +1,1013 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <assert.h>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_quictls.h>
+
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+#include <openssl/kdf.h>
+#include <openssl/rand.h>
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#  include <openssl/core_names.h>
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
+
+#include "shared.h"
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+static int crypto_initialized;
+static EVP_CIPHER *crypto_aes_128_gcm;
+static EVP_CIPHER *crypto_aes_256_gcm;
+static EVP_CIPHER *crypto_chacha20_poly1305;
+static EVP_CIPHER *crypto_aes_128_ccm;
+static EVP_CIPHER *crypto_aes_128_ctr;
+static EVP_CIPHER *crypto_aes_256_ctr;
+static EVP_CIPHER *crypto_chacha20;
+static EVP_MD *crypto_sha256;
+static EVP_MD *crypto_sha384;
+static EVP_KDF *crypto_hkdf;
+
+int ngtcp2_crypto_quictls_init(void) {
+  crypto_aes_128_gcm = EVP_CIPHER_fetch(NULL, "AES-128-GCM", NULL);
+  if (crypto_aes_128_gcm == NULL) {
+    return -1;
+  }
+
+  crypto_aes_256_gcm = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL);
+  if (crypto_aes_256_gcm == NULL) {
+    return -1;
+  }
+
+  crypto_chacha20_poly1305 = EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL);
+  if (crypto_chacha20_poly1305 == NULL) {
+    return -1;
+  }
+
+  crypto_aes_128_ccm = EVP_CIPHER_fetch(NULL, "AES-128-CCM", NULL);
+  if (crypto_aes_128_ccm == NULL) {
+    return -1;
+  }
+
+  crypto_aes_128_ctr = EVP_CIPHER_fetch(NULL, "AES-128-CTR", NULL);
+  if (crypto_aes_128_ctr == NULL) {
+    return -1;
+  }
+
+  crypto_aes_256_ctr = EVP_CIPHER_fetch(NULL, "AES-256-CTR", NULL);
+  if (crypto_aes_256_ctr == NULL) {
+    return -1;
+  }
+
+  crypto_chacha20 = EVP_CIPHER_fetch(NULL, "ChaCha20", NULL);
+  if (crypto_chacha20 == NULL) {
+    return -1;
+  }
+
+  crypto_sha256 = EVP_MD_fetch(NULL, "sha256", NULL);
+  if (crypto_sha256 == NULL) {
+    return -1;
+  }
+
+  crypto_sha384 = EVP_MD_fetch(NULL, "sha384", NULL);
+  if (crypto_sha384 == NULL) {
+    return -1;
+  }
+
+  crypto_hkdf = EVP_KDF_fetch(NULL, "hkdf", NULL);
+  if (crypto_hkdf == NULL) {
+    return -1;
+  }
+
+  crypto_initialized = 1;
+
+  return 0;
+}
+
+static const EVP_CIPHER *crypto_aead_aes_128_gcm(void) {
+  if (crypto_aes_128_gcm) {
+    return crypto_aes_128_gcm;
+  }
+
+  return EVP_aes_128_gcm();
+}
+
+static const EVP_CIPHER *crypto_aead_aes_256_gcm(void) {
+  if (crypto_aes_256_gcm) {
+    return crypto_aes_256_gcm;
+  }
+
+  return EVP_aes_256_gcm();
+}
+
+static const EVP_CIPHER *crypto_aead_chacha20_poly1305(void) {
+  if (crypto_chacha20_poly1305) {
+    return crypto_chacha20_poly1305;
+  }
+
+  return EVP_chacha20_poly1305();
+}
+
+static const EVP_CIPHER *crypto_aead_aes_128_ccm(void) {
+  if (crypto_aes_128_ccm) {
+    return crypto_aes_128_ccm;
+  }
+
+  return EVP_aes_128_ccm();
+}
+
+static const EVP_CIPHER *crypto_cipher_aes_128_ctr(void) {
+  if (crypto_aes_128_ctr) {
+    return crypto_aes_128_ctr;
+  }
+
+  return EVP_aes_128_ctr();
+}
+
+static const EVP_CIPHER *crypto_cipher_aes_256_ctr(void) {
+  if (crypto_aes_256_ctr) {
+    return crypto_aes_256_ctr;
+  }
+
+  return EVP_aes_256_ctr();
+}
+
+static const EVP_CIPHER *crypto_cipher_chacha20(void) {
+  if (crypto_chacha20) {
+    return crypto_chacha20;
+  }
+
+  return EVP_chacha20();
+}
+
+static const EVP_MD *crypto_md_sha256(void) {
+  if (crypto_sha256) {
+    return crypto_sha256;
+  }
+
+  return EVP_sha256();
+}
+
+static const EVP_MD *crypto_md_sha384(void) {
+  if (crypto_sha384) {
+    return crypto_sha384;
+  }
+
+  return EVP_sha384();
+}
+
+static EVP_KDF *crypto_kdf_hkdf(void) {
+  if (crypto_hkdf) {
+    return crypto_hkdf;
+  }
+
+  return EVP_KDF_fetch(NULL, "hkdf", NULL);
+}
+#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+#  define crypto_aead_aes_128_gcm EVP_aes_128_gcm
+#  define crypto_aead_aes_256_gcm EVP_aes_256_gcm
+#  define crypto_aead_chacha20_poly1305 EVP_chacha20_poly1305
+#  define crypto_aead_aes_128_ccm EVP_aes_128_ccm
+#  define crypto_cipher_aes_128_ctr EVP_aes_128_ctr
+#  define crypto_cipher_aes_256_ctr EVP_aes_256_ctr
+#  define crypto_cipher_chacha20 EVP_chacha20
+#  define crypto_md_sha256 EVP_sha256
+#  define crypto_md_sha384 EVP_sha384
+
+int ngtcp2_crypto_quictls_init(void) { return 0; }
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+
+static size_t crypto_aead_max_overhead(const EVP_CIPHER *aead) {
+  switch (EVP_CIPHER_nid(aead)) {
+  case NID_aes_128_gcm:
+  case NID_aes_256_gcm:
+    return EVP_GCM_TLS_TAG_LEN;
+  case NID_chacha20_poly1305:
+    return EVP_CHACHAPOLY_TLS_TAG_LEN;
+  case NID_aes_128_ccm:
+    return EVP_CCM_TLS_TAG_LEN;
+  default:
+    assert(0);
+    abort(); /* if NDEBUG is set */
+  }
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)crypto_aead_aes_128_gcm());
+}
+
+ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) {
+  md->native_handle = (void *)crypto_md_sha256();
+  return md;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) {
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)crypto_aead_aes_128_gcm());
+  ctx->md.native_handle = (void *)crypto_md_sha256();
+  ctx->hp.native_handle = (void *)crypto_cipher_aes_128_ctr();
+  ctx->max_encryption = 0;
+  ctx->max_decryption_failure = 0;
+  return ctx;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead,
+                                            void *aead_native_handle) {
+  aead->native_handle = aead_native_handle;
+  aead->max_overhead = crypto_aead_max_overhead(aead_native_handle);
+  return aead;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)crypto_aead_aes_128_gcm());
+}
+
+static const EVP_CIPHER *crypto_cipher_id_get_aead(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+    return crypto_aead_aes_128_gcm();
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return crypto_aead_aes_256_gcm();
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+    return crypto_aead_chacha20_poly1305();
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return crypto_aead_aes_128_ccm();
+  default:
+    return NULL;
+  }
+}
+
+static uint64_t crypto_cipher_id_get_aead_max_encryption(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM;
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305;
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM;
+  default:
+    return 0;
+  }
+}
+
+static uint64_t
+crypto_cipher_id_get_aead_max_decryption_failure(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM;
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305;
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM;
+  default:
+    return 0;
+  }
+}
+
+static const EVP_CIPHER *crypto_cipher_id_get_hp(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return crypto_cipher_aes_128_ctr();
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return crypto_cipher_aes_256_ctr();
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+    return crypto_cipher_chacha20();
+  default:
+    return NULL;
+  }
+}
+
+static const EVP_MD *crypto_cipher_id_get_md(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return crypto_md_sha256();
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+    return crypto_md_sha384();
+  default:
+    return NULL;
+  }
+}
+
+static int supported_cipher_id(uint32_t cipher_id) {
+  switch (cipher_id) {
+  case TLS1_3_CK_AES_128_GCM_SHA256:
+  case TLS1_3_CK_AES_256_GCM_SHA384:
+  case TLS1_3_CK_CHACHA20_POLY1305_SHA256:
+  case TLS1_3_CK_AES_128_CCM_SHA256:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+static ngtcp2_crypto_ctx *crypto_ctx_cipher_id(ngtcp2_crypto_ctx *ctx,
+                                               uint32_t cipher_id) {
+  ngtcp2_crypto_aead_init(&ctx->aead,
+                          (void *)crypto_cipher_id_get_aead(cipher_id));
+  ctx->md.native_handle = (void *)crypto_cipher_id_get_md(cipher_id);
+  ctx->hp.native_handle = (void *)crypto_cipher_id_get_hp(cipher_id);
+  ctx->max_encryption = crypto_cipher_id_get_aead_max_encryption(cipher_id);
+  ctx->max_decryption_failure =
+    crypto_cipher_id_get_aead_max_decryption_failure(cipher_id);
+
+  return ctx;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx,
+                                         void *tls_native_handle) {
+  SSL *ssl = tls_native_handle;
+  const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
+  uint32_t cipher_id;
+
+  if (cipher == NULL) {
+    return NULL;
+  }
+
+  cipher_id = (uint32_t)SSL_CIPHER_get_id(cipher);
+
+  if (!supported_cipher_id(cipher_id)) {
+    return NULL;
+  }
+
+  return crypto_ctx_cipher_id(ctx, cipher_id);
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx,
+                                               void *tls_native_handle) {
+  return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle);
+}
+
+static size_t crypto_md_hashlen(const EVP_MD *md) {
+  return (size_t)EVP_MD_size(md);
+}
+
+size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) {
+  return crypto_md_hashlen(md->native_handle);
+}
+
+static size_t crypto_aead_keylen(const EVP_CIPHER *aead) {
+  return (size_t)EVP_CIPHER_key_length(aead);
+}
+
+size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_keylen(aead->native_handle);
+}
+
+static size_t crypto_aead_noncelen(const EVP_CIPHER *aead) {
+  return (size_t)EVP_CIPHER_iv_length(aead);
+}
+
+size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_noncelen(aead->native_handle);
+}
+
+int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  int cipher_nid = EVP_CIPHER_nid(cipher);
+  EVP_CIPHER_CTX *actx;
+  size_t taglen = crypto_aead_max_overhead(cipher);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  OSSL_PARAM params[3];
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_IVLEN, &noncelen);
+
+  if (cipher_nid == NID_aes_128_ccm) {
+    params[1] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+                                                  NULL, taglen);
+    params[2] = OSSL_PARAM_construct_end();
+  } else {
+    params[1] = OSSL_PARAM_construct_end();
+  }
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
+
+  if (!EVP_EncryptInit_ex(actx, cipher, NULL, NULL, NULL) ||
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+      !EVP_CIPHER_CTX_set_params(actx, params) ||
+#else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen,
+                           NULL) ||
+      (cipher_nid == NID_aes_128_ccm &&
+       !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen, NULL)) ||
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+      !EVP_EncryptInit_ex(actx, NULL, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  int cipher_nid = EVP_CIPHER_nid(cipher);
+  EVP_CIPHER_CTX *actx;
+  size_t taglen = crypto_aead_max_overhead(cipher);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  OSSL_PARAM params[3];
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_IVLEN, &noncelen);
+
+  if (cipher_nid == NID_aes_128_ccm) {
+    params[1] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+                                                  NULL, taglen);
+    params[2] = OSSL_PARAM_construct_end();
+  } else {
+    params[1] = OSSL_PARAM_construct_end();
+  }
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
+
+  if (!EVP_DecryptInit_ex(actx, cipher, NULL, NULL, NULL) ||
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+      !EVP_CIPHER_CTX_set_params(actx, params) ||
+#else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen,
+                           NULL) ||
+      (cipher_nid == NID_aes_128_ccm &&
+       !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen, NULL)) ||
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+      !EVP_DecryptInit_ex(actx, NULL, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (aead_ctx->native_handle) {
+    EVP_CIPHER_CTX_free(aead_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key) {
+  EVP_CIPHER_CTX *actx;
+
+  actx = EVP_CIPHER_CTX_new();
+  if (actx == NULL) {
+    return -1;
+  }
+
+  if (!EVP_EncryptInit_ex(actx, cipher->native_handle, NULL, key, NULL)) {
+    EVP_CIPHER_CTX_free(actx);
+    return -1;
+  }
+
+  cipher_ctx->native_handle = actx;
+
+  return 0;
+}
+
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (cipher_ctx->native_handle) {
+    EVP_CIPHER_CTX_free(cipher_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
+                               const uint8_t *secret, size_t secretlen,
+                               const uint8_t *salt, size_t saltlen) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  const EVP_MD *prf = md->native_handle;
+  EVP_KDF *kdf = crypto_kdf_hkdf();
+  EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
+  int mode = EVP_KDF_HKDF_MODE_EXTRACT_ONLY;
+  OSSL_PARAM params[] = {
+    OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode),
+    OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+                                     (char *)EVP_MD_get0_name(prf), 0),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret,
+                                      secretlen),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt,
+                                      saltlen),
+    OSSL_PARAM_construct_end(),
+  };
+  int rv = 0;
+
+  if (!crypto_initialized) {
+    EVP_KDF_free(kdf);
+  }
+
+  if (EVP_KDF_derive(kctx, dest, (size_t)EVP_MD_size(prf), params) <= 0) {
+    rv = -1;
+  }
+
+  EVP_KDF_CTX_free(kctx);
+
+  return rv;
+#else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+  const EVP_MD *prf = md->native_handle;
+  int rv = 0;
+  EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+  size_t destlen = (size_t)EVP_MD_size(prf);
+
+  if (pctx == NULL) {
+    return -1;
+  }
+
+  if (EVP_PKEY_derive_init(pctx) != 1 ||
+      EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1 ||
+      EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 ||
+      EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1 ||
+      EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1 ||
+      EVP_PKEY_derive(pctx, dest, &destlen) != 1) {
+    rv = -1;
+  }
+
+  EVP_PKEY_CTX_free(pctx);
+
+  return rv;
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+}
+
+int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen,
+                              const ngtcp2_crypto_md *md, const uint8_t *secret,
+                              size_t secretlen, const uint8_t *info,
+                              size_t infolen) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  const EVP_MD *prf = md->native_handle;
+  EVP_KDF *kdf = crypto_kdf_hkdf();
+  EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
+  int mode = EVP_KDF_HKDF_MODE_EXPAND_ONLY;
+  OSSL_PARAM params[] = {
+    OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode),
+    OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+                                     (char *)EVP_MD_get0_name(prf), 0),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret,
+                                      secretlen),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (void *)info,
+                                      infolen),
+    OSSL_PARAM_construct_end(),
+  };
+  int rv = 0;
+
+  if (!crypto_initialized) {
+    EVP_KDF_free(kdf);
+  }
+
+  if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) {
+    rv = -1;
+  }
+
+  EVP_KDF_CTX_free(kctx);
+
+  return rv;
+#else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+  const EVP_MD *prf = md->native_handle;
+  int rv = 0;
+  EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+  if (pctx == NULL) {
+    return -1;
+  }
+
+  if (EVP_PKEY_derive_init(pctx) != 1 ||
+      EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1 ||
+      EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 ||
+      EVP_PKEY_CTX_set1_hkdf_salt(pctx, (const unsigned char *)"", 0) != 1 ||
+      EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1 ||
+      EVP_PKEY_CTX_add1_hkdf_info(pctx, info, (int)infolen) != 1 ||
+      EVP_PKEY_derive(pctx, dest, &destlen) != 1) {
+    rv = -1;
+  }
+
+  EVP_PKEY_CTX_free(pctx);
+
+  return rv;
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+}
+
+int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen,
+                       const ngtcp2_crypto_md *md, const uint8_t *secret,
+                       size_t secretlen, const uint8_t *salt, size_t saltlen,
+                       const uint8_t *info, size_t infolen) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  const EVP_MD *prf = md->native_handle;
+  EVP_KDF *kdf = crypto_kdf_hkdf();
+  EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
+  OSSL_PARAM params[] = {
+    OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+                                     (char *)EVP_MD_get0_name(prf), 0),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret,
+                                      secretlen),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt,
+                                      saltlen),
+    OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (void *)info,
+                                      infolen),
+    OSSL_PARAM_construct_end(),
+  };
+  int rv = 0;
+
+  if (!crypto_initialized) {
+    EVP_KDF_free(kdf);
+  }
+
+  if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) {
+    rv = -1;
+  }
+
+  EVP_KDF_CTX_free(kctx);
+
+  return rv;
+#else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+  const EVP_MD *prf = md->native_handle;
+  int rv = 0;
+  EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+  if (pctx == NULL) {
+    return -1;
+  }
+
+  if (EVP_PKEY_derive_init(pctx) != 1 ||
+      EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) !=
+        1 ||
+      EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 ||
+      EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1 ||
+      EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1 ||
+      EVP_PKEY_CTX_add1_hkdf_info(pctx, info, (int)infolen) != 1 ||
+      EVP_PKEY_derive(pctx, dest, &destlen) != 1) {
+    rv = -1;
+  }
+
+  EVP_PKEY_CTX_free(pctx);
+
+  return rv;
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+}
+
+int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *plaintext, size_t plaintextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  size_t taglen = crypto_aead_max_overhead(cipher);
+  int cipher_nid = EVP_CIPHER_nid(cipher);
+  EVP_CIPHER_CTX *actx = aead_ctx->native_handle;
+  int len;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  OSSL_PARAM params[] = {
+    OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+                                      dest + plaintextlen, taglen),
+    OSSL_PARAM_construct_end(),
+  };
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
+
+  (void)noncelen;
+
+  if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, nonce) ||
+      (cipher_nid == NID_aes_128_ccm &&
+       !EVP_EncryptUpdate(actx, NULL, &len, NULL, (int)plaintextlen)) ||
+      !EVP_EncryptUpdate(actx, NULL, &len, aad, (int)aadlen) ||
+      !EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) ||
+      !EVP_EncryptFinal_ex(actx, dest + len, &len) ||
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+      !EVP_CIPHER_CTX_get_params(actx, params)
+#else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG, (int)taglen,
+                           dest + plaintextlen)
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+  ) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *ciphertext, size_t ciphertextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  const EVP_CIPHER *cipher = aead->native_handle;
+  size_t taglen = crypto_aead_max_overhead(cipher);
+  int cipher_nid = EVP_CIPHER_nid(cipher);
+  EVP_CIPHER_CTX *actx = aead_ctx->native_handle;
+  int len;
+  const uint8_t *tag;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  OSSL_PARAM params[2];
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
+
+  (void)noncelen;
+
+  if (taglen > ciphertextlen) {
+    return -1;
+  }
+
+  ciphertextlen -= taglen;
+  tag = ciphertext + ciphertextlen;
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+  params[0] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+                                                (void *)tag, taglen);
+  params[1] = OSSL_PARAM_construct_end();
+#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */
+
+  if (!EVP_DecryptInit_ex(actx, NULL, NULL, NULL, nonce) ||
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+      !EVP_CIPHER_CTX_set_params(actx, params) ||
+#else  /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+      !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen,
+                           (uint8_t *)tag) ||
+#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */
+      (cipher_nid == NID_aes_128_ccm &&
+       !EVP_DecryptUpdate(actx, NULL, &len, NULL, (int)ciphertextlen)) ||
+      !EVP_DecryptUpdate(actx, NULL, &len, aad, (int)aadlen) ||
+      !EVP_DecryptUpdate(actx, dest, &len, ciphertext, (int)ciphertextlen) ||
+      (cipher_nid != NID_aes_128_ccm &&
+       !EVP_DecryptFinal_ex(actx, dest + ciphertextlen, &len))) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                          const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                          const uint8_t *sample) {
+  static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
+  EVP_CIPHER_CTX *actx = hp_ctx->native_handle;
+  int len;
+
+  (void)hp;
+
+  if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) ||
+      !EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, sizeof(PLAINTEXT) - 1) ||
+      !EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len)) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_read_write_crypto_data(
+  ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level,
+  const uint8_t *data, size_t datalen) {
+  SSL *ssl = ngtcp2_conn_get_tls_native_handle(conn);
+  int rv;
+  int err;
+
+  if (SSL_provide_quic_data(
+        ssl,
+        ngtcp2_crypto_quictls_from_ngtcp2_encryption_level(encryption_level),
+        data, datalen) != 1) {
+    return -1;
+  }
+
+  if (!ngtcp2_conn_get_handshake_completed(conn)) {
+    rv = SSL_do_handshake(ssl);
+    if (rv <= 0) {
+      err = SSL_get_error(ssl, rv);
+      switch (err) {
+      case SSL_ERROR_WANT_READ:
+      case SSL_ERROR_WANT_WRITE:
+        return 0;
+      case SSL_ERROR_WANT_CLIENT_HELLO_CB:
+        return NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_CLIENT_HELLO_CB;
+      case SSL_ERROR_WANT_X509_LOOKUP:
+        return NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_X509_LOOKUP;
+      case SSL_ERROR_SSL:
+        return -1;
+      default:
+        return -1;
+      }
+    }
+
+    ngtcp2_conn_tls_handshake_completed(conn);
+  }
+
+  rv = SSL_process_quic_post_handshake(ssl);
+  if (rv != 1) {
+    err = SSL_get_error(ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+    case SSL_ERROR_WANT_WRITE:
+      return 0;
+    case SSL_ERROR_SSL:
+    case SSL_ERROR_ZERO_RETURN:
+      return -1;
+    default:
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) {
+  SSL *ssl = tls;
+  const uint8_t *tp;
+  size_t tplen;
+  int rv;
+
+  SSL_get_peer_quic_transport_params(ssl, &tp, &tplen);
+
+  rv = ngtcp2_conn_decode_and_set_remote_transport_params(conn, tp, tplen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf,
+                                             size_t len) {
+  if (SSL_set_quic_transport_params(tls, buf, len) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+ngtcp2_encryption_level ngtcp2_crypto_quictls_from_ossl_encryption_level(
+  OSSL_ENCRYPTION_LEVEL ossl_level) {
+  switch (ossl_level) {
+  case ssl_encryption_initial:
+    return NGTCP2_ENCRYPTION_LEVEL_INITIAL;
+  case ssl_encryption_early_data:
+    return NGTCP2_ENCRYPTION_LEVEL_0RTT;
+  case ssl_encryption_handshake:
+    return NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE;
+  case ssl_encryption_application:
+    return NGTCP2_ENCRYPTION_LEVEL_1RTT;
+  default:
+    assert(0);
+    abort(); /* if NDEBUG is set */
+  }
+}
+
+OSSL_ENCRYPTION_LEVEL
+ngtcp2_crypto_quictls_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level) {
+  switch (encryption_level) {
+  case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
+    return ssl_encryption_initial;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    return ssl_encryption_handshake;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    return ssl_encryption_application;
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    return ssl_encryption_early_data;
+  default:
+    assert(0);
+    abort(); /* if NDEBUG is set */
+  }
+}
+
+int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data,
+                                             void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  if (RAND_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_random(uint8_t *data, size_t datalen) {
+  if (RAND_bytes(data, (int)datalen) != 1) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
+                                  const uint8_t *rx_secret,
+                                  const uint8_t *tx_secret, size_t secretlen) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_quictls_from_ossl_encryption_level(ossl_level);
+
+  if (rx_secret &&
+      ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level,
+                                              rx_secret, secretlen) != 0) {
+    return 0;
+  }
+
+  if (tx_secret &&
+      ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level,
+                                              tx_secret, secretlen) != 0) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level,
+                              const uint8_t *data, size_t datalen) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_quictls_from_ossl_encryption_level(ossl_level);
+  int rv;
+
+  rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int flush_flight(SSL *ssl) {
+  (void)ssl;
+  return 1;
+}
+
+static int send_alert(SSL *ssl, OSSL_ENCRYPTION_LEVEL level, uint8_t alert) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  (void)level;
+
+  ngtcp2_conn_set_tls_alert(conn, alert);
+
+  return 1;
+}
+
+static SSL_QUIC_METHOD quic_method = {
+  set_encryption_secrets,
+  add_handshake_data,
+  flush_flight,
+  send_alert,
+#ifdef LIBRESSL_VERSION_NUMBER
+  NULL,
+  NULL,
+#endif /* defined(LIBRESSL_VERSION_NUMBER) */
+};
+
+static void crypto_quictls_configure_context(SSL_CTX *ssl_ctx) {
+  SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
+  SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
+  SSL_CTX_set_quic_method(ssl_ctx, &quic_method);
+}
+
+int ngtcp2_crypto_quictls_configure_server_context(SSL_CTX *ssl_ctx) {
+  crypto_quictls_configure_context(ssl_ctx);
+
+  return 0;
+}
+
+int ngtcp2_crypto_quictls_configure_client_context(SSL_CTX *ssl_ctx) {
+  crypto_quictls_configure_context(ssl_ctx);
+
+  return 0;
+}
diff --git a/third_party/ngtcp2/crypto/shared.c b/third_party/ngtcp2/crypto/shared.c
new file mode 100644 (file)
index 0000000..98cd4de
--- /dev/null
@@ -0,0 +1,1650 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shared.h"
+
+#ifdef WIN32
+#  include <winsock2.h>
+#  include <ws2tcpip.h>
+#elif defined(HAVE_NETINET_IN_H)
+#  include <netinet/in.h>
+#endif /* defined(HAVE_NETINET_IN_H) */
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_macro.h"
+#include "ngtcp2_net.h"
+
+ngtcp2_crypto_md *ngtcp2_crypto_md_init(ngtcp2_crypto_md *md,
+                                        void *md_native_handle) {
+  md->native_handle = md_native_handle;
+  return md;
+}
+
+int ngtcp2_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen,
+                                    const ngtcp2_crypto_md *md,
+                                    const uint8_t *secret, size_t secretlen,
+                                    const uint8_t *label, size_t labellen) {
+  static const uint8_t LABEL[] = "tls13 ";
+  uint8_t info[256];
+  uint8_t *p = info;
+
+  *p++ = (uint8_t)(destlen / 256);
+  *p++ = (uint8_t)(destlen % 256);
+  *p++ = (uint8_t)(sizeof(LABEL) - 1 + labellen);
+  memcpy(p, LABEL, sizeof(LABEL) - 1);
+  p += sizeof(LABEL) - 1;
+  memcpy(p, label, labellen);
+  p += labellen;
+  *p++ = 0;
+
+  return ngtcp2_crypto_hkdf_expand(dest, destlen, md, secret, secretlen, info,
+                                   (size_t)(p - info));
+}
+
+int ngtcp2_crypto_derive_initial_secrets(uint8_t *rx_secret, uint8_t *tx_secret,
+                                         uint8_t *initial_secret,
+                                         uint32_t version,
+                                         const ngtcp2_cid *client_dcid,
+                                         ngtcp2_crypto_side side) {
+  static const uint8_t CLABEL[] = "client in";
+  static const uint8_t SLABEL[] = "server in";
+  uint8_t initial_secret_buf[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t *client_secret;
+  uint8_t *server_secret;
+  ngtcp2_crypto_ctx ctx;
+  const uint8_t *salt;
+  size_t saltlen;
+
+  if (!initial_secret) {
+    initial_secret = initial_secret_buf;
+  }
+
+  ngtcp2_crypto_ctx_initial(&ctx);
+
+  switch (version) {
+  case NGTCP2_PROTO_VER_V1:
+  default:
+    salt = (const uint8_t *)NGTCP2_INITIAL_SALT_V1;
+    saltlen = sizeof(NGTCP2_INITIAL_SALT_V1) - 1;
+    break;
+  case NGTCP2_PROTO_VER_V2:
+    salt = (const uint8_t *)NGTCP2_INITIAL_SALT_V2;
+    saltlen = sizeof(NGTCP2_INITIAL_SALT_V2) - 1;
+    break;
+  }
+
+  if (ngtcp2_crypto_hkdf_extract(initial_secret, &ctx.md, client_dcid->data,
+                                 client_dcid->datalen, salt, saltlen) != 0) {
+    return -1;
+  }
+
+  if (side == NGTCP2_CRYPTO_SIDE_SERVER) {
+    client_secret = rx_secret;
+    server_secret = tx_secret;
+  } else {
+    client_secret = tx_secret;
+    server_secret = rx_secret;
+  }
+
+  if (ngtcp2_crypto_hkdf_expand_label(
+        client_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, &ctx.md, initial_secret,
+        NGTCP2_CRYPTO_INITIAL_SECRETLEN, CLABEL, sizeof(CLABEL) - 1) != 0 ||
+      ngtcp2_crypto_hkdf_expand_label(
+        server_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, &ctx.md, initial_secret,
+        NGTCP2_CRYPTO_INITIAL_SECRETLEN, SLABEL, sizeof(SLABEL) - 1) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+size_t ngtcp2_crypto_packet_protection_ivlen(const ngtcp2_crypto_aead *aead) {
+  size_t noncelen = ngtcp2_crypto_aead_noncelen(aead);
+  return ngtcp2_max_size(8, noncelen);
+}
+
+int ngtcp2_crypto_derive_packet_protection_key(
+  uint8_t *key, uint8_t *iv, uint8_t *hp_key, uint32_t version,
+  const ngtcp2_crypto_aead *aead, const ngtcp2_crypto_md *md,
+  const uint8_t *secret, size_t secretlen) {
+  static const uint8_t KEY_LABEL_V1[] = "quic key";
+  static const uint8_t IV_LABEL_V1[] = "quic iv";
+  static const uint8_t HP_KEY_LABEL_V1[] = "quic hp";
+  static const uint8_t KEY_LABEL_V2[] = "quicv2 key";
+  static const uint8_t IV_LABEL_V2[] = "quicv2 iv";
+  static const uint8_t HP_KEY_LABEL_V2[] = "quicv2 hp";
+  size_t keylen = ngtcp2_crypto_aead_keylen(aead);
+  size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
+  const uint8_t *key_label;
+  size_t key_labellen;
+  const uint8_t *iv_label;
+  size_t iv_labellen;
+  const uint8_t *hp_key_label;
+  size_t hp_key_labellen;
+
+  switch (version) {
+  case NGTCP2_PROTO_VER_V2:
+    key_label = KEY_LABEL_V2;
+    key_labellen = sizeof(KEY_LABEL_V2) - 1;
+    iv_label = IV_LABEL_V2;
+    iv_labellen = sizeof(IV_LABEL_V2) - 1;
+    hp_key_label = HP_KEY_LABEL_V2;
+    hp_key_labellen = sizeof(HP_KEY_LABEL_V2) - 1;
+    break;
+  default:
+    key_label = KEY_LABEL_V1;
+    key_labellen = sizeof(KEY_LABEL_V1) - 1;
+    iv_label = IV_LABEL_V1;
+    iv_labellen = sizeof(IV_LABEL_V1) - 1;
+    hp_key_label = HP_KEY_LABEL_V1;
+    hp_key_labellen = sizeof(HP_KEY_LABEL_V1) - 1;
+  }
+
+  if (ngtcp2_crypto_hkdf_expand_label(key, keylen, md, secret, secretlen,
+                                      key_label, key_labellen) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_hkdf_expand_label(iv, ivlen, md, secret, secretlen,
+                                      iv_label, iv_labellen) != 0) {
+    return -1;
+  }
+
+  if (hp_key != NULL &&
+      ngtcp2_crypto_hkdf_expand_label(hp_key, keylen, md, secret, secretlen,
+                                      hp_key_label, hp_key_labellen) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_update_traffic_secret(uint8_t *dest, uint32_t version,
+                                        const ngtcp2_crypto_md *md,
+                                        const uint8_t *secret,
+                                        size_t secretlen) {
+  static const uint8_t LABEL[] = "quic ku";
+  static const uint8_t LABEL_V2[] = "quicv2 ku";
+  const uint8_t *label;
+  size_t labellen;
+
+  switch (version) {
+  case NGTCP2_PROTO_VER_V2:
+    label = LABEL_V2;
+    labellen = sizeof(LABEL_V2) - 1;
+    break;
+  default:
+    label = LABEL;
+    labellen = sizeof(LABEL) - 1;
+  }
+
+  if (ngtcp2_crypto_hkdf_expand_label(dest, secretlen, md, secret, secretlen,
+                                      label, labellen) != 0) {
+    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,
+                                            const uint8_t *secret,
+                                            size_t secretlen) {
+  const ngtcp2_crypto_ctx *ctx;
+  const ngtcp2_crypto_aead *aead;
+  const ngtcp2_crypto_md *md;
+  const ngtcp2_crypto_cipher *hp;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
+  void *tls = ngtcp2_conn_get_tls_native_handle(conn);
+  uint8_t keybuf[64], ivbuf[64], hp_keybuf[64];
+  size_t ivlen;
+  int rv;
+  ngtcp2_crypto_ctx cctx;
+  uint32_t version;
+
+  if (level == NGTCP2_ENCRYPTION_LEVEL_0RTT && !ngtcp2_conn_is_server(conn)) {
+    return 0;
+  }
+
+  if (!key) {
+    key = keybuf;
+  }
+  if (!iv) {
+    iv = ivbuf;
+  }
+  if (!hp_key) {
+    hp_key = hp_keybuf;
+  }
+
+  switch (level) {
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    if (ngtcp2_crypto_ctx_tls_early(&cctx, tls) == NULL) {
+      return -1;
+    }
+
+    ngtcp2_conn_set_0rtt_crypto_ctx(conn, &cctx);
+    ctx = ngtcp2_conn_get_0rtt_crypto_ctx(conn);
+    version = ngtcp2_conn_get_client_chosen_version(conn);
+    break;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    if (ngtcp2_conn_is_server(conn) &&
+        !ngtcp2_conn_get_negotiated_version(conn)) {
+      rv = ngtcp2_crypto_set_remote_transport_params(conn, tls);
+      if (rv != 0) {
+        return -1;
+      }
+    }
+    /* fall through */
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    ctx = ngtcp2_conn_get_crypto_ctx(conn);
+    version = ngtcp2_conn_get_negotiated_version(conn);
+
+    if (!ctx->aead.native_handle) {
+      if (ngtcp2_crypto_ctx_tls(&cctx, tls) == NULL) {
+        return -1;
+      }
+
+      ngtcp2_conn_set_crypto_ctx(conn, &cctx);
+      ctx = ngtcp2_conn_get_crypto_ctx(conn);
+    }
+    break;
+  default:
+    return -1;
+  }
+
+  aead = &ctx->aead;
+  md = &ctx->md;
+  hp = &ctx->hp;
+  ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
+
+  if (ngtcp2_crypto_derive_packet_protection_key(key, iv, hp_key, version, aead,
+                                                 md, secret, secretlen) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, aead, key, ivlen) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, hp, hp_key) != 0) {
+    goto fail;
+  }
+
+  switch (level) {
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    rv = ngtcp2_conn_install_0rtt_key(conn, &aead_ctx, iv, ivlen, &hp_ctx);
+    if (rv != 0) {
+      goto fail;
+    }
+    break;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    rv =
+      ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, iv, ivlen, &hp_ctx);
+    if (rv != 0) {
+      goto fail;
+    }
+    break;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    if (!ngtcp2_conn_is_server(conn)) {
+      rv = ngtcp2_crypto_set_remote_transport_params(conn, tls);
+      if (rv != 0) {
+        goto fail;
+      }
+    }
+
+    rv = ngtcp2_conn_install_rx_key(conn, secret, secretlen, &aead_ctx, iv,
+                                    ivlen, &hp_ctx);
+    if (rv != 0) {
+      goto fail;
+    }
+
+    break;
+  default:
+    goto fail;
+  }
+
+  return 0;
+
+fail:
+  ngtcp2_crypto_cipher_ctx_free(&hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  return -1;
+}
+
+/*
+ * crypto_set_local_transport_params gets local QUIC transport
+ * parameters from |conn| and sets it to |tls|.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+static int crypto_set_local_transport_params(ngtcp2_conn *conn, void *tls) {
+  ngtcp2_ssize nwrite;
+  uint8_t buf[256];
+
+  nwrite = ngtcp2_conn_encode_local_transport_params(conn, buf, sizeof(buf));
+  if (nwrite < 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_set_local_transport_params(tls, buf, (size_t)nwrite) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key,
+                                            uint8_t *iv, uint8_t *hp_key,
+                                            ngtcp2_encryption_level level,
+                                            const uint8_t *secret,
+                                            size_t secretlen) {
+  const ngtcp2_crypto_ctx *ctx;
+  const ngtcp2_crypto_aead *aead;
+  const ngtcp2_crypto_md *md;
+  const ngtcp2_crypto_cipher *hp;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
+  void *tls = ngtcp2_conn_get_tls_native_handle(conn);
+  uint8_t keybuf[64], ivbuf[64], hp_keybuf[64];
+  size_t ivlen;
+  int rv;
+  ngtcp2_crypto_ctx cctx;
+  uint32_t version;
+
+  if (level == NGTCP2_ENCRYPTION_LEVEL_0RTT && ngtcp2_conn_is_server(conn)) {
+    return 0;
+  }
+
+  if (!key) {
+    key = keybuf;
+  }
+  if (!iv) {
+    iv = ivbuf;
+  }
+  if (!hp_key) {
+    hp_key = hp_keybuf;
+  }
+
+  switch (level) {
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    if (ngtcp2_crypto_ctx_tls_early(&cctx, tls) == NULL) {
+      return -1;
+    }
+
+    ngtcp2_conn_set_0rtt_crypto_ctx(conn, &cctx);
+    ctx = ngtcp2_conn_get_0rtt_crypto_ctx(conn);
+    version = ngtcp2_conn_get_client_chosen_version(conn);
+    break;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    if (ngtcp2_conn_is_server(conn) &&
+        !ngtcp2_conn_get_negotiated_version(conn)) {
+      rv = ngtcp2_crypto_set_remote_transport_params(conn, tls);
+      if (rv != 0) {
+        return -1;
+      }
+    }
+    /* fall through */
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    ctx = ngtcp2_conn_get_crypto_ctx(conn);
+    version = ngtcp2_conn_get_negotiated_version(conn);
+
+    if (!ctx->aead.native_handle) {
+      if (ngtcp2_crypto_ctx_tls(&cctx, tls) == NULL) {
+        return -1;
+      }
+
+      ngtcp2_conn_set_crypto_ctx(conn, &cctx);
+      ctx = ngtcp2_conn_get_crypto_ctx(conn);
+    }
+    break;
+  default:
+    return -1;
+  }
+
+  aead = &ctx->aead;
+  md = &ctx->md;
+  hp = &ctx->hp;
+  ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
+
+  if (ngtcp2_crypto_derive_packet_protection_key(key, iv, hp_key, version, aead,
+                                                 md, secret, secretlen) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, aead, key, ivlen) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, hp, hp_key) != 0) {
+    goto fail;
+  }
+
+  switch (level) {
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    rv = ngtcp2_conn_install_0rtt_key(conn, &aead_ctx, iv, ivlen, &hp_ctx);
+    if (rv != 0) {
+      goto fail;
+    }
+    break;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    rv =
+      ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, iv, ivlen, &hp_ctx);
+    if (rv != 0) {
+      goto fail;
+    }
+
+    if (ngtcp2_conn_is_server(conn) &&
+        crypto_set_local_transport_params(conn, tls) != 0) {
+      goto fail;
+    }
+
+    break;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    rv = ngtcp2_conn_install_tx_key(conn, secret, secretlen, &aead_ctx, iv,
+                                    ivlen, &hp_ctx);
+    if (rv != 0) {
+      goto fail;
+    }
+
+    break;
+  default:
+    goto fail;
+  }
+
+  return 0;
+
+fail:
+  ngtcp2_crypto_cipher_ctx_free(&hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  return -1;
+}
+
+int ngtcp2_crypto_derive_and_install_initial_key(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, uint8_t *rx_hp_key,
+  uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp_key, uint32_t version,
+  const ngtcp2_cid *client_dcid) {
+  uint8_t rx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t tx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t initial_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t rx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  uint8_t rx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN];
+  uint8_t rx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  uint8_t tx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  uint8_t tx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN];
+  uint8_t tx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  ngtcp2_crypto_ctx ctx;
+  ngtcp2_crypto_aead retry_aead;
+  ngtcp2_crypto_aead_ctx rx_aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx rx_hp_ctx = {0};
+  ngtcp2_crypto_aead_ctx tx_aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx tx_hp_ctx = {0};
+  ngtcp2_crypto_aead_ctx retry_aead_ctx = {0};
+  int rv;
+  int server = ngtcp2_conn_is_server(conn);
+  const uint8_t *retry_key;
+  size_t retry_noncelen;
+
+  ngtcp2_crypto_ctx_initial(&ctx);
+
+  if (!rx_secret) {
+    rx_secret = rx_secretbuf;
+  }
+  if (!tx_secret) {
+    tx_secret = tx_secretbuf;
+  }
+  if (!initial_secret) {
+    initial_secret = initial_secretbuf;
+  }
+
+  if (!rx_key) {
+    rx_key = rx_keybuf;
+  }
+  if (!rx_iv) {
+    rx_iv = rx_ivbuf;
+  }
+  if (!rx_hp_key) {
+    rx_hp_key = rx_hp_keybuf;
+  }
+  if (!tx_key) {
+    tx_key = tx_keybuf;
+  }
+  if (!tx_iv) {
+    tx_iv = tx_ivbuf;
+  }
+  if (!tx_hp_key) {
+    tx_hp_key = tx_hp_keybuf;
+  }
+
+  ngtcp2_conn_set_initial_crypto_ctx(conn, &ctx);
+
+  if (ngtcp2_crypto_derive_initial_secrets(
+        rx_secret, tx_secret, initial_secret, version, client_dcid,
+        server ? NGTCP2_CRYPTO_SIDE_SERVER : NGTCP2_CRYPTO_SIDE_CLIENT) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_derive_packet_protection_key(
+        rx_key, rx_iv, rx_hp_key, version, &ctx.aead, &ctx.md, rx_secret,
+        NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_derive_packet_protection_key(
+        tx_key, tx_iv, tx_hp_key, version, &ctx.aead, &ctx.md, tx_secret,
+        NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&rx_aead_ctx, &ctx.aead, rx_key,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&rx_hp_ctx, &ctx.hp, rx_hp_key) !=
+      0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&tx_aead_ctx, &ctx.aead, tx_key,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&tx_hp_ctx, &ctx.hp, tx_hp_key) !=
+      0) {
+    goto fail;
+  }
+
+  if (!server && !ngtcp2_conn_after_retry(conn)) {
+    ngtcp2_crypto_aead_retry(&retry_aead);
+
+    switch (version) {
+    case NGTCP2_PROTO_VER_V1:
+    default:
+      retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_V1;
+      retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1;
+      break;
+    case NGTCP2_PROTO_VER_V2:
+      retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_V2;
+      retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1;
+      break;
+    }
+
+    if (ngtcp2_crypto_aead_ctx_encrypt_init(&retry_aead_ctx, &retry_aead,
+                                            retry_key, retry_noncelen) != 0) {
+      goto fail;
+    }
+  }
+
+  rv = ngtcp2_conn_install_initial_key(conn, &rx_aead_ctx, rx_iv, &rx_hp_ctx,
+                                       &tx_aead_ctx, tx_iv, &tx_hp_ctx,
+                                       NGTCP2_CRYPTO_INITIAL_IVLEN);
+  if (rv != 0) {
+    goto fail;
+  }
+
+  if (retry_aead_ctx.native_handle) {
+    ngtcp2_conn_set_retry_aead(conn, &retry_aead, &retry_aead_ctx);
+  }
+
+  return 0;
+
+fail:
+  ngtcp2_crypto_aead_ctx_free(&retry_aead_ctx);
+  ngtcp2_crypto_cipher_ctx_free(&tx_hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&tx_aead_ctx);
+  ngtcp2_crypto_cipher_ctx_free(&rx_hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&rx_aead_ctx);
+
+  return -1;
+}
+
+int ngtcp2_crypto_derive_and_install_vneg_initial_key(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, uint8_t *rx_hp_key,
+  uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp_key, uint32_t version,
+  const ngtcp2_cid *client_dcid) {
+  uint8_t rx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t tx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t initial_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t rx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  uint8_t rx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN];
+  uint8_t rx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  uint8_t tx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  uint8_t tx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN];
+  uint8_t tx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  const ngtcp2_crypto_ctx *ctx = ngtcp2_conn_get_initial_crypto_ctx(conn);
+  ngtcp2_crypto_aead_ctx rx_aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx rx_hp_ctx = {0};
+  ngtcp2_crypto_aead_ctx tx_aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx tx_hp_ctx = {0};
+  int rv;
+  int server = ngtcp2_conn_is_server(conn);
+
+  if (!rx_secret) {
+    rx_secret = rx_secretbuf;
+  }
+  if (!tx_secret) {
+    tx_secret = tx_secretbuf;
+  }
+  if (!initial_secret) {
+    initial_secret = initial_secretbuf;
+  }
+
+  if (!rx_key) {
+    rx_key = rx_keybuf;
+  }
+  if (!rx_iv) {
+    rx_iv = rx_ivbuf;
+  }
+  if (!rx_hp_key) {
+    rx_hp_key = rx_hp_keybuf;
+  }
+  if (!tx_key) {
+    tx_key = tx_keybuf;
+  }
+  if (!tx_iv) {
+    tx_iv = tx_ivbuf;
+  }
+  if (!tx_hp_key) {
+    tx_hp_key = tx_hp_keybuf;
+  }
+
+  if (ngtcp2_crypto_derive_initial_secrets(
+        rx_secret, tx_secret, initial_secret, version, client_dcid,
+        server ? NGTCP2_CRYPTO_SIDE_SERVER : NGTCP2_CRYPTO_SIDE_CLIENT) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_derive_packet_protection_key(
+        rx_key, rx_iv, rx_hp_key, version, &ctx->aead, &ctx->md, rx_secret,
+        NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_derive_packet_protection_key(
+        tx_key, tx_iv, tx_hp_key, version, &ctx->aead, &ctx->md, tx_secret,
+        NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&rx_aead_ctx, &ctx->aead, rx_key,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&rx_hp_ctx, &ctx->hp, rx_hp_key) !=
+      0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&tx_aead_ctx, &ctx->aead, tx_key,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
+    goto fail;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&tx_hp_ctx, &ctx->hp, tx_hp_key) !=
+      0) {
+    goto fail;
+  }
+
+  rv = ngtcp2_conn_install_vneg_initial_key(
+    conn, version, &rx_aead_ctx, rx_iv, &rx_hp_ctx, &tx_aead_ctx, tx_iv,
+    &tx_hp_ctx, NGTCP2_CRYPTO_INITIAL_IVLEN);
+  if (rv != 0) {
+    goto fail;
+  }
+
+  return 0;
+
+fail:
+  ngtcp2_crypto_cipher_ctx_free(&tx_hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&tx_aead_ctx);
+  ngtcp2_crypto_cipher_ctx_free(&rx_hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&rx_aead_ctx);
+
+  return -1;
+}
+
+int ngtcp2_crypto_update_key(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_key, uint8_t *rx_iv,
+  ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_key, uint8_t *tx_iv,
+  const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+  size_t secretlen) {
+  const ngtcp2_crypto_ctx *ctx = ngtcp2_conn_get_crypto_ctx(conn);
+  const ngtcp2_crypto_aead *aead = &ctx->aead;
+  const ngtcp2_crypto_md *md = &ctx->md;
+  size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
+  uint32_t version = ngtcp2_conn_get_negotiated_version(conn);
+
+  if (ngtcp2_crypto_update_traffic_secret(rx_secret, version, md,
+                                          current_rx_secret, secretlen) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_derive_packet_protection_key(
+        rx_key, rx_iv, NULL, version, aead, md, rx_secret, secretlen) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_update_traffic_secret(tx_secret, version, md,
+                                          current_tx_secret, secretlen) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_derive_packet_protection_key(
+        tx_key, tx_iv, NULL, version, aead, md, tx_secret, secretlen) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(rx_aead_ctx, aead, rx_key, ivlen) !=
+      0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(tx_aead_ctx, aead, tx_key, ivlen) !=
+      0) {
+    ngtcp2_crypto_aead_ctx_free(rx_aead_ctx);
+    rx_aead_ctx->native_handle = NULL;
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                             const ngtcp2_crypto_aead_ctx *aead_ctx,
+                             const uint8_t *plaintext, size_t plaintextlen,
+                             const uint8_t *nonce, size_t noncelen,
+                             const uint8_t *aad, size_t aadlen) {
+  if (ngtcp2_crypto_encrypt(dest, aead, aead_ctx, plaintext, plaintextlen,
+                            nonce, noncelen, aad, aadlen) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                             const ngtcp2_crypto_aead_ctx *aead_ctx,
+                             const uint8_t *ciphertext, size_t ciphertextlen,
+                             const uint8_t *nonce, size_t noncelen,
+                             const uint8_t *aad, size_t aadlen) {
+  if (ngtcp2_crypto_decrypt(dest, aead, aead_ctx, ciphertext, ciphertextlen,
+                            nonce, noncelen, aad, aadlen) != 0) {
+    return NGTCP2_ERR_DECRYPT;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                             const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                             const uint8_t *sample) {
+  if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_update_key_cb(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+  ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+  const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+  size_t secretlen, void *user_data) {
+  uint8_t rx_key[64];
+  uint8_t tx_key[64];
+  (void)conn;
+  (void)user_data;
+
+  if (ngtcp2_crypto_update_key(
+        conn, rx_secret, tx_secret, rx_aead_ctx, rx_key, rx_iv, tx_aead_ctx,
+        tx_key, tx_iv, current_rx_secret, current_tx_secret, secretlen) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_generate_stateless_reset_token(uint8_t *token,
+                                                 const uint8_t *secret,
+                                                 size_t secretlen,
+                                                 const ngtcp2_cid *cid) {
+  static const uint8_t info[] = "stateless_reset";
+  ngtcp2_crypto_md md;
+
+  if (ngtcp2_crypto_hkdf(token, NGTCP2_STATELESS_RESET_TOKENLEN,
+                         ngtcp2_crypto_md_sha256(&md), secret, secretlen,
+                         cid->data, cid->datalen, info,
+                         sizeof(info) - 1) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int crypto_derive_token_key(uint8_t *key, size_t keylen, uint8_t *iv,
+                                   size_t ivlen, const ngtcp2_crypto_md *md,
+                                   const uint8_t *secret, size_t secretlen,
+                                   const uint8_t *salt, size_t saltlen,
+                                   const uint8_t *info_prefix,
+                                   size_t info_prefixlen) {
+  static const uint8_t key_info_suffix[] = " key";
+  static const uint8_t iv_info_suffix[] = " iv";
+  uint8_t intsecret[32];
+  uint8_t info[32];
+  uint8_t *p;
+
+  assert(ngtcp2_crypto_md_hashlen(md) == sizeof(intsecret));
+  assert(info_prefixlen + sizeof(key_info_suffix) - 1 <= sizeof(info));
+  assert(info_prefixlen + sizeof(iv_info_suffix) - 1 <= sizeof(info));
+
+  if (ngtcp2_crypto_hkdf_extract(intsecret, md, secret, secretlen, salt,
+                                 saltlen) != 0) {
+    return -1;
+  }
+
+  memcpy(info, info_prefix, info_prefixlen);
+  p = info + info_prefixlen;
+
+  memcpy(p, key_info_suffix, sizeof(key_info_suffix) - 1);
+  p += sizeof(key_info_suffix) - 1;
+
+  if (ngtcp2_crypto_hkdf_expand(key, keylen, md, intsecret, sizeof(intsecret),
+                                info, (size_t)(p - info)) != 0) {
+    return -1;
+  }
+
+  p = info + info_prefixlen;
+
+  memcpy(p, iv_info_suffix, sizeof(iv_info_suffix) - 1);
+  p += sizeof(iv_info_suffix) - 1;
+
+  if (ngtcp2_crypto_hkdf_expand(iv, ivlen, md, intsecret, sizeof(intsecret),
+                                info, (size_t)(p - info)) != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static size_t crypto_generate_retry_token_aad(uint8_t *dest, uint32_t version,
+                                              const ngtcp2_sockaddr *sa,
+                                              ngtcp2_socklen salen,
+                                              const ngtcp2_cid *retry_scid) {
+  uint8_t *p = dest;
+
+  version = ngtcp2_htonl(version);
+  memcpy(p, &version, sizeof(version));
+  p += sizeof(version);
+  memcpy(p, sa, (size_t)salen);
+  p += salen;
+  memcpy(p, retry_scid->data, retry_scid->datalen);
+  p += retry_scid->datalen;
+
+  return (size_t)(p - dest);
+}
+
+static const uint8_t retry_token_info_prefix[] = "retry_token";
+
+ngtcp2_ssize ngtcp2_crypto_generate_retry_token(
+  uint8_t *token, const uint8_t *secret, size_t secretlen, uint32_t version,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  const ngtcp2_cid *retry_scid, const ngtcp2_cid *odcid, ngtcp2_tstamp ts) {
+  uint8_t
+    plaintext[/* cid len = */ 1 + NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)];
+  uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN];
+  uint8_t key[16];
+  uint8_t iv[12];
+  size_t keylen;
+  size_t ivlen;
+  ngtcp2_crypto_aead aead;
+  ngtcp2_crypto_md md;
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  size_t plaintextlen;
+  uint8_t
+    aad[sizeof(version) + sizeof(ngtcp2_sockaddr_union) + NGTCP2_MAX_CIDLEN];
+  size_t aadlen;
+  uint8_t *p = plaintext;
+  ngtcp2_tstamp ts_be = ngtcp2_htonl64(ts);
+  int rv;
+
+  assert((size_t)remote_addrlen <= sizeof(ngtcp2_sockaddr_union));
+
+  memset(plaintext, 0, sizeof(plaintext));
+
+  *p++ = (uint8_t)odcid->datalen;
+  memcpy(p, odcid->data, odcid->datalen);
+  p += NGTCP2_MAX_CIDLEN;
+  memcpy(p, &ts_be, sizeof(ts_be));
+  p += sizeof(ts_be);
+
+  plaintextlen = (size_t)(p - plaintext);
+
+  if (ngtcp2_crypto_random(rand_data, sizeof(rand_data)) != 0) {
+    return -1;
+  }
+
+  ngtcp2_crypto_aead_aes_128_gcm(&aead);
+  ngtcp2_crypto_md_sha256(&md);
+
+  keylen = ngtcp2_crypto_aead_keylen(&aead);
+  ivlen = ngtcp2_crypto_aead_noncelen(&aead);
+
+  assert(sizeof(key) == keylen);
+  assert(sizeof(iv) == ivlen);
+
+  if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen,
+                              rand_data, sizeof(rand_data),
+                              retry_token_info_prefix,
+                              sizeof(retry_token_info_prefix) - 1) != 0) {
+    return -1;
+  }
+
+  aadlen = crypto_generate_retry_token_aad(aad, version, remote_addr,
+                                           remote_addrlen, retry_scid);
+
+  p = token;
+  *p++ = NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY;
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, ivlen) != 0) {
+    return -1;
+  }
+
+  rv = ngtcp2_crypto_encrypt(p, &aead, &aead_ctx, plaintext, plaintextlen, iv,
+                             ivlen, aad, aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  p += plaintextlen + aead.max_overhead;
+  memcpy(p, rand_data, sizeof(rand_data));
+  p += sizeof(rand_data);
+
+  return p - token;
+}
+
+int ngtcp2_crypto_verify_retry_token(
+  ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen,
+  const uint8_t *secret, size_t secretlen, uint32_t version,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  const ngtcp2_cid *dcid, ngtcp2_duration timeout, ngtcp2_tstamp ts) {
+  uint8_t
+    plaintext[/* cid len = */ 1 + NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)];
+  uint8_t key[16];
+  uint8_t iv[12];
+  size_t keylen;
+  size_t ivlen;
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  ngtcp2_crypto_aead aead;
+  ngtcp2_crypto_md md;
+  uint8_t
+    aad[sizeof(version) + sizeof(ngtcp2_sockaddr_union) + NGTCP2_MAX_CIDLEN];
+  size_t aadlen;
+  const uint8_t *rand_data;
+  const uint8_t *ciphertext;
+  size_t ciphertextlen;
+  size_t cil;
+  int rv;
+  ngtcp2_tstamp gen_ts;
+
+  assert((size_t)remote_addrlen <= sizeof(ngtcp2_sockaddr_union));
+
+  if (tokenlen != NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN ||
+      token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY) {
+    return -1;
+  }
+
+  rand_data = token + tokenlen - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN;
+  ciphertext = token + 1;
+  ciphertextlen = tokenlen - 1 - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN;
+
+  ngtcp2_crypto_aead_aes_128_gcm(&aead);
+  ngtcp2_crypto_md_sha256(&md);
+
+  keylen = ngtcp2_crypto_aead_keylen(&aead);
+  ivlen = ngtcp2_crypto_aead_noncelen(&aead);
+
+  assert(sizeof(key) == keylen);
+  assert(sizeof(iv) == ivlen);
+
+  if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen,
+                              rand_data, NGTCP2_CRYPTO_TOKEN_RAND_DATALEN,
+                              retry_token_info_prefix,
+                              sizeof(retry_token_info_prefix) - 1) != 0) {
+    return -1;
+  }
+
+  aadlen = crypto_generate_retry_token_aad(aad, version, remote_addr,
+                                           remote_addrlen, dcid);
+
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key, ivlen) != 0) {
+    return -1;
+  }
+
+  rv = ngtcp2_crypto_decrypt(plaintext, &aead, &aead_ctx, ciphertext,
+                             ciphertextlen, iv, ivlen, aad, aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  cil = plaintext[0];
+
+  if (cil != 0 && (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN)) {
+    return -1;
+  }
+
+  memcpy(&gen_ts, plaintext + /* cid len = */ 1 + NGTCP2_MAX_CIDLEN,
+         sizeof(gen_ts));
+
+  gen_ts = ngtcp2_ntohl64(gen_ts);
+  if (gen_ts + timeout <= ts) {
+    return -1;
+  }
+
+  ngtcp2_cid_init(odcid, plaintext + /* cid len = */ 1, cil);
+
+  return 0;
+}
+
+static size_t crypto_generate_retry_token_aad2(uint8_t *dest, uint32_t version,
+                                               const ngtcp2_cid *retry_scid) {
+  uint8_t *p = dest;
+
+  version = ngtcp2_htonl(version);
+  memcpy(p, &version, sizeof(version));
+  p += sizeof(version);
+  memcpy(p, retry_scid->data, retry_scid->datalen);
+  p += retry_scid->datalen;
+
+  return (size_t)(p - dest);
+}
+
+static const uint8_t retry_token_info_prefix2[] = "retry_token2";
+
+ngtcp2_ssize ngtcp2_crypto_generate_retry_token2(
+  uint8_t *token, const uint8_t *secret, size_t secretlen, uint32_t version,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  const ngtcp2_cid *retry_scid, const ngtcp2_cid *odcid, ngtcp2_tstamp ts) {
+  uint8_t plaintext[sizeof(ngtcp2_sockaddr_union) + /* cid len = */ 1 +
+                    NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)];
+  uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN];
+  uint8_t key[16];
+  uint8_t iv[12];
+  size_t keylen;
+  size_t ivlen;
+  ngtcp2_crypto_aead aead;
+  ngtcp2_crypto_md md;
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  uint8_t aad[sizeof(version) + NGTCP2_MAX_CIDLEN];
+  size_t aadlen;
+  uint8_t *p = plaintext;
+  ngtcp2_tstamp ts_be = ngtcp2_htonl64(ts);
+  int rv;
+
+  assert((size_t)remote_addrlen <= sizeof(ngtcp2_sockaddr_union));
+
+  memset(plaintext, 0, sizeof(plaintext));
+
+  memcpy(p, remote_addr, (size_t)remote_addrlen);
+  p += sizeof(ngtcp2_sockaddr_union);
+  *p++ = (uint8_t)odcid->datalen;
+  memcpy(p, odcid->data, odcid->datalen);
+  p += NGTCP2_MAX_CIDLEN;
+  memcpy(p, &ts_be, sizeof(ts_be));
+
+  assert((size_t)(p + sizeof(ts_be) - plaintext) == sizeof(plaintext));
+
+  if (ngtcp2_crypto_random(rand_data, sizeof(rand_data)) != 0) {
+    return -1;
+  }
+
+  ngtcp2_crypto_aead_aes_128_gcm(&aead);
+  ngtcp2_crypto_md_sha256(&md);
+
+  keylen = ngtcp2_crypto_aead_keylen(&aead);
+  ivlen = ngtcp2_crypto_aead_noncelen(&aead);
+
+  assert(sizeof(key) == keylen);
+  assert(sizeof(iv) == ivlen);
+
+  if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen,
+                              rand_data, sizeof(rand_data),
+                              retry_token_info_prefix2,
+                              sizeof(retry_token_info_prefix2) - 1) != 0) {
+    return -1;
+  }
+
+  aadlen = crypto_generate_retry_token_aad2(aad, version, retry_scid);
+
+  p = token;
+  *p++ = NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2;
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, ivlen) != 0) {
+    return -1;
+  }
+
+  rv = ngtcp2_crypto_encrypt(p, &aead, &aead_ctx, plaintext, sizeof(plaintext),
+                             iv, ivlen, aad, aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  p += sizeof(plaintext) + aead.max_overhead;
+  memcpy(p, rand_data, sizeof(rand_data));
+  p += sizeof(rand_data);
+
+  return p - token;
+}
+
+int ngtcp2_crypto_verify_retry_token2(
+  ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen,
+  const uint8_t *secret, size_t secretlen, uint32_t version,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  const ngtcp2_cid *dcid, ngtcp2_duration timeout, ngtcp2_tstamp ts) {
+  uint8_t plaintext[sizeof(ngtcp2_sockaddr_union) + /* cid len = */ 1 +
+                    NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)];
+  uint8_t key[16];
+  uint8_t iv[12];
+  size_t keylen;
+  size_t ivlen;
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  ngtcp2_crypto_aead aead;
+  ngtcp2_crypto_md md;
+  uint8_t aad[sizeof(version) + NGTCP2_MAX_CIDLEN];
+  size_t aadlen;
+  const uint8_t *rand_data;
+  const uint8_t *ciphertext;
+  size_t ciphertextlen;
+  size_t cil;
+  int rv;
+  ngtcp2_tstamp gen_ts;
+  ngtcp2_sockaddr_union addr;
+  size_t addrlen;
+  uint8_t *p;
+
+  assert((size_t)remote_addrlen <= sizeof(ngtcp2_sockaddr_union));
+
+  if (tokenlen != NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN2 ||
+      token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY2) {
+    return NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN;
+  }
+
+  rand_data = token + tokenlen - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN;
+  ciphertext = token + 1;
+  ciphertextlen = (size_t)(rand_data - ciphertext);
+
+  ngtcp2_crypto_aead_aes_128_gcm(&aead);
+  ngtcp2_crypto_md_sha256(&md);
+
+  keylen = ngtcp2_crypto_aead_keylen(&aead);
+  ivlen = ngtcp2_crypto_aead_noncelen(&aead);
+
+  assert(sizeof(key) == keylen);
+  assert(sizeof(iv) == ivlen);
+
+  if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen,
+                              rand_data, NGTCP2_CRYPTO_TOKEN_RAND_DATALEN,
+                              retry_token_info_prefix2,
+                              sizeof(retry_token_info_prefix2) - 1) != 0) {
+    return NGTCP2_CRYPTO_ERR_INTERNAL;
+  }
+
+  aadlen = crypto_generate_retry_token_aad2(aad, version, dcid);
+
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key, ivlen) != 0) {
+    return NGTCP2_CRYPTO_ERR_INTERNAL;
+  }
+
+  rv = ngtcp2_crypto_decrypt(plaintext, &aead, &aead_ctx, ciphertext,
+                             ciphertextlen, iv, ivlen, aad, aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
+    return NGTCP2_CRYPTO_ERR_UNREADABLE_TOKEN;
+  }
+
+  p = plaintext;
+
+  memcpy(&addr, p, sizeof(addr));
+
+  switch (addr.sa.sa_family) {
+  case NGTCP2_AF_INET:
+    addrlen = sizeof(ngtcp2_sockaddr_in);
+    break;
+  case NGTCP2_AF_INET6:
+    addrlen = sizeof(ngtcp2_sockaddr_in6);
+    break;
+  default:
+    return NGTCP2_CRYPTO_ERR_VERIFY_TOKEN;
+  }
+
+  if (addrlen != (size_t)remote_addrlen ||
+      memcmp(&addr, remote_addr, addrlen) != 0) {
+    return NGTCP2_CRYPTO_ERR_VERIFY_TOKEN;
+  }
+
+  p += sizeof(addr);
+  cil = *p++;
+
+  if (cil != 0 && (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN)) {
+    return NGTCP2_CRYPTO_ERR_VERIFY_TOKEN;
+  }
+
+  memcpy(&gen_ts, p + NGTCP2_MAX_CIDLEN, sizeof(gen_ts));
+
+  gen_ts = ngtcp2_ntohl64(gen_ts);
+  if (gen_ts + timeout <= ts) {
+    return NGTCP2_CRYPTO_ERR_VERIFY_TOKEN;
+  }
+
+  ngtcp2_cid_init(odcid, p, cil);
+
+  return 0;
+}
+
+static size_t crypto_generate_regular_token_aad(uint8_t *dest,
+                                                const ngtcp2_sockaddr *sa) {
+  const uint8_t *addr;
+  size_t addrlen;
+
+  switch (sa->sa_family) {
+  case NGTCP2_AF_INET:
+    addr = (const uint8_t *)&((const ngtcp2_sockaddr_in *)(void *)sa)->sin_addr;
+    addrlen = sizeof(((const ngtcp2_sockaddr_in *)(void *)sa)->sin_addr);
+    break;
+  case NGTCP2_AF_INET6:
+    addr =
+      (const uint8_t *)&((const ngtcp2_sockaddr_in6 *)(void *)sa)->sin6_addr;
+    addrlen = sizeof(((const ngtcp2_sockaddr_in6 *)(void *)sa)->sin6_addr);
+    break;
+  default:
+    assert(0);
+    abort();
+  }
+
+  memcpy(dest, addr, addrlen);
+
+  return addrlen;
+}
+
+static const uint8_t regular_token_info_prefix[] = "regular_token";
+
+ngtcp2_ssize ngtcp2_crypto_generate_regular_token(
+  uint8_t *token, const uint8_t *secret, size_t secretlen,
+  const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen,
+  ngtcp2_tstamp ts) {
+  uint8_t plaintext[sizeof(ngtcp2_tstamp)];
+  uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN];
+  uint8_t key[16];
+  uint8_t iv[12];
+  size_t keylen;
+  size_t ivlen;
+  ngtcp2_crypto_aead aead;
+  ngtcp2_crypto_md md;
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  size_t plaintextlen;
+  uint8_t aad[sizeof(ngtcp2_sockaddr_in6)];
+  size_t aadlen;
+  uint8_t *p = plaintext;
+  ngtcp2_tstamp ts_be = ngtcp2_htonl64(ts);
+  int rv;
+  (void)remote_addrlen;
+
+  memcpy(p, &ts_be, sizeof(ts_be));
+  p += sizeof(ts_be);
+
+  plaintextlen = (size_t)(p - plaintext);
+
+  if (ngtcp2_crypto_random(rand_data, sizeof(rand_data)) != 0) {
+    return -1;
+  }
+
+  ngtcp2_crypto_aead_aes_128_gcm(&aead);
+  ngtcp2_crypto_md_sha256(&md);
+
+  keylen = ngtcp2_crypto_aead_keylen(&aead);
+  ivlen = ngtcp2_crypto_aead_noncelen(&aead);
+
+  assert(sizeof(key) == keylen);
+  assert(sizeof(iv) == ivlen);
+
+  if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen,
+                              rand_data, sizeof(rand_data),
+                              regular_token_info_prefix,
+                              sizeof(regular_token_info_prefix) - 1) != 0) {
+    return -1;
+  }
+
+  aadlen = crypto_generate_regular_token_aad(aad, remote_addr);
+
+  p = token;
+  *p++ = NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR;
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, ivlen) != 0) {
+    return -1;
+  }
+
+  rv = ngtcp2_crypto_encrypt(p, &aead, &aead_ctx, plaintext, plaintextlen, iv,
+                             ivlen, aad, aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  p += plaintextlen + aead.max_overhead;
+  memcpy(p, rand_data, sizeof(rand_data));
+  p += sizeof(rand_data);
+
+  return p - token;
+}
+
+int ngtcp2_crypto_verify_regular_token(const uint8_t *token, size_t tokenlen,
+                                       const uint8_t *secret, size_t secretlen,
+                                       const ngtcp2_sockaddr *remote_addr,
+                                       ngtcp2_socklen remote_addrlen,
+                                       ngtcp2_duration timeout,
+                                       ngtcp2_tstamp ts) {
+  uint8_t plaintext[sizeof(ngtcp2_tstamp)];
+  uint8_t key[16];
+  uint8_t iv[12];
+  size_t keylen;
+  size_t ivlen;
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  ngtcp2_crypto_aead aead;
+  ngtcp2_crypto_md md;
+  uint8_t aad[sizeof(ngtcp2_sockaddr_in6)];
+  size_t aadlen;
+  const uint8_t *rand_data;
+  const uint8_t *ciphertext;
+  size_t ciphertextlen;
+  int rv;
+  ngtcp2_tstamp gen_ts;
+  (void)remote_addrlen;
+
+  if (tokenlen != NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN ||
+      token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR) {
+    return -1;
+  }
+
+  rand_data = token + tokenlen - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN;
+  ciphertext = token + 1;
+  ciphertextlen = tokenlen - 1 - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN;
+
+  ngtcp2_crypto_aead_aes_128_gcm(&aead);
+  ngtcp2_crypto_md_sha256(&md);
+
+  keylen = ngtcp2_crypto_aead_keylen(&aead);
+  ivlen = ngtcp2_crypto_aead_noncelen(&aead);
+
+  assert(sizeof(key) == keylen);
+  assert(sizeof(iv) == ivlen);
+
+  if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen,
+                              rand_data, NGTCP2_CRYPTO_TOKEN_RAND_DATALEN,
+                              regular_token_info_prefix,
+                              sizeof(regular_token_info_prefix) - 1) != 0) {
+    return -1;
+  }
+
+  aadlen = crypto_generate_regular_token_aad(aad, remote_addr);
+
+  if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key, ivlen) != 0) {
+    return -1;
+  }
+
+  rv = ngtcp2_crypto_decrypt(plaintext, &aead, &aead_ctx, ciphertext,
+                             ciphertextlen, iv, ivlen, aad, aadlen);
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  memcpy(&gen_ts, plaintext, sizeof(gen_ts));
+
+  gen_ts = ngtcp2_ntohl64(gen_ts);
+  if (gen_ts + timeout <= ts) {
+    return -1;
+  }
+
+  return 0;
+}
+
+ngtcp2_ssize ngtcp2_crypto_write_connection_close(
+  uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid,
+  const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason,
+  size_t reasonlen) {
+  uint8_t rx_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t tx_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t initial_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN];
+  uint8_t tx_key[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  uint8_t tx_iv[NGTCP2_CRYPTO_INITIAL_IVLEN];
+  uint8_t tx_hp_key[NGTCP2_CRYPTO_INITIAL_KEYLEN];
+  ngtcp2_crypto_ctx ctx;
+  ngtcp2_ssize spktlen;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  ngtcp2_crypto_cipher_ctx hp_ctx = {0};
+
+  ngtcp2_crypto_ctx_initial(&ctx);
+
+  if (ngtcp2_crypto_derive_initial_secrets(rx_secret, tx_secret, initial_secret,
+                                           version, scid,
+                                           NGTCP2_CRYPTO_SIDE_SERVER) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_derive_packet_protection_key(
+        tx_key, tx_iv, tx_hp_key, version, &ctx.aead, &ctx.md, tx_secret,
+        NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) {
+    return -1;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &ctx.aead, tx_key,
+                                          NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) {
+    spktlen = -1;
+    goto end;
+  }
+
+  if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, &ctx.hp, tx_hp_key) != 0) {
+    spktlen = -1;
+    goto end;
+  }
+
+  spktlen = ngtcp2_pkt_write_connection_close(
+    dest, destlen, version, dcid, scid, error_code, reason, reasonlen,
+    ngtcp2_crypto_encrypt_cb, &ctx.aead, &aead_ctx, tx_iv,
+    ngtcp2_crypto_hp_mask_cb, &ctx.hp, &hp_ctx);
+  if (spktlen < 0) {
+    spktlen = -1;
+  }
+
+end:
+  ngtcp2_crypto_cipher_ctx_free(&hp_ctx);
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  return spktlen;
+}
+
+ngtcp2_ssize ngtcp2_crypto_write_retry(uint8_t *dest, size_t destlen,
+                                       uint32_t version, const ngtcp2_cid *dcid,
+                                       const ngtcp2_cid *scid,
+                                       const ngtcp2_cid *odcid,
+                                       const uint8_t *token, size_t tokenlen) {
+  ngtcp2_crypto_aead aead;
+  ngtcp2_ssize spktlen;
+  ngtcp2_crypto_aead_ctx aead_ctx = {0};
+  const uint8_t *key;
+  size_t noncelen;
+
+  ngtcp2_crypto_aead_retry(&aead);
+
+  switch (version) {
+  case NGTCP2_PROTO_VER_V1:
+  default:
+    key = (const uint8_t *)NGTCP2_RETRY_KEY_V1;
+    noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1;
+    break;
+  case NGTCP2_PROTO_VER_V2:
+    key = (const uint8_t *)NGTCP2_RETRY_KEY_V2;
+    noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1;
+    break;
+  }
+
+  if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, noncelen) !=
+      0) {
+    return -1;
+  }
+
+  spktlen = ngtcp2_pkt_write_retry(dest, destlen, version, dcid, scid, odcid,
+                                   token, tokenlen, ngtcp2_crypto_encrypt_cb,
+                                   &aead, &aead_ctx);
+  if (spktlen < 0) {
+    spktlen = -1;
+  }
+
+  ngtcp2_crypto_aead_ctx_free(&aead_ctx);
+
+  return spktlen;
+}
+
+int ngtcp2_crypto_client_initial_cb(ngtcp2_conn *conn, void *user_data) {
+  const ngtcp2_cid *dcid = ngtcp2_conn_get_dcid(conn);
+  void *tls = ngtcp2_conn_get_tls_native_handle(conn);
+  (void)user_data;
+
+  if (ngtcp2_crypto_derive_and_install_initial_key(
+        conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+        ngtcp2_conn_get_client_chosen_version(conn), dcid) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  if (crypto_set_local_transport_params(conn, tls) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  if (ngtcp2_crypto_read_write_crypto_data(
+        conn, NGTCP2_ENCRYPTION_LEVEL_INITIAL, NULL, 0) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_recv_retry_cb(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
+                                void *user_data) {
+  (void)user_data;
+
+  if (ngtcp2_crypto_derive_and_install_initial_key(
+        conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+        ngtcp2_conn_get_client_chosen_version(conn), &hd->scid) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_recv_client_initial_cb(ngtcp2_conn *conn,
+                                         const ngtcp2_cid *dcid,
+                                         void *user_data) {
+  (void)user_data;
+
+  if (ngtcp2_crypto_derive_and_install_initial_key(
+        conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+        ngtcp2_conn_get_client_chosen_version(conn), dcid) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_version_negotiation_cb(ngtcp2_conn *conn, uint32_t version,
+                                         const ngtcp2_cid *client_dcid,
+                                         void *user_data) {
+  (void)user_data;
+
+  if (ngtcp2_crypto_derive_and_install_vneg_initial_key(
+        conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, version,
+        client_dcid) != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+void ngtcp2_crypto_delete_crypto_aead_ctx_cb(ngtcp2_conn *conn,
+                                             ngtcp2_crypto_aead_ctx *aead_ctx,
+                                             void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  ngtcp2_crypto_aead_ctx_free(aead_ctx);
+}
+
+void ngtcp2_crypto_delete_crypto_cipher_ctx_cb(
+  ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  ngtcp2_crypto_cipher_ctx_free(cipher_ctx);
+}
+
+int ngtcp2_crypto_recv_crypto_data_cb(ngtcp2_conn *conn,
+                                      ngtcp2_encryption_level encryption_level,
+                                      uint64_t offset, const uint8_t *data,
+                                      size_t datalen, void *user_data) {
+  int rv;
+  (void)offset;
+  (void)user_data;
+
+  if (ngtcp2_crypto_read_write_crypto_data(conn, encryption_level, data,
+                                           datalen) != 0) {
+    rv = ngtcp2_conn_get_tls_error(conn);
+    if (rv) {
+      return rv;
+    }
+    return NGTCP2_ERR_CRYPTO;
+  }
+
+  return 0;
+}
diff --git a/third_party/ngtcp2/crypto/shared.h b/third_party/ngtcp2/crypto/shared.h
new file mode 100644 (file)
index 0000000..34158d3
--- /dev/null
@@ -0,0 +1,397 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHARED_H
+#define SHARED_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_INITIAL_SALT_V1` is a salt value which is used to
+ * derive initial secret.  It is used for QUIC v1.
+ */
+#define NGTCP2_INITIAL_SALT_V1                                                 \
+  "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb"   \
+  "\x7f\x0a"
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_INITIAL_SALT_V2` is a salt value which is used to
+ * derive initial secret.  It is used for QUIC v2.
+ */
+#define NGTCP2_INITIAL_SALT_V2                                                 \
+  "\x0d\xed\xe3\xde\xf7\x00\xa6\xdb\x81\x93\x81\xbe\x6e\x26\x9d\xcb\xf9\xbd"   \
+  "\x2e\xd9"
+
+/* Maximum key usage (encryption) limits */
+#define NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM (1ULL << 23)
+#define NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305 (1ULL << 62)
+#define NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM (2965820ULL)
+
+/* Maximum authentication failure (decryption) limits during the
+   lifetime of a connection. */
+#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM (1ULL << 52)
+#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305 (1ULL << 36)
+#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM (2965820ULL)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_INITIAL_SECRETLEN` is the length of secret
+ * for Initial packets.
+ */
+#define NGTCP2_CRYPTO_INITIAL_SECRETLEN 32
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_INITIAL_KEYLEN` is the length of key for
+ * Initial packets.
+ */
+#define NGTCP2_CRYPTO_INITIAL_KEYLEN 16
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_INITIAL_IVLEN` is the length of IV for
+ * Initial packets.
+ */
+#define NGTCP2_CRYPTO_INITIAL_IVLEN 12
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_ctx_initial` initializes |ctx| for Initial packet
+ * encryption and decryption.
+ */
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_init` initializes |aead| with the provided
+ * |aead_native_handle| which is an underlying AEAD object.
+ *
+ * If libngtcp2_crypto_quictls is linked, |aead_native_handle| must be
+ * a pointer to EVP_CIPHER.
+ *
+ * If libngtcp2_crypto_gnutls is linked, |aead_native_handle| must be
+ * gnutls_cipher_algorithm_t casted to ``void *``.
+ *
+ * If libngtcp2_crypto_boringssl is linked, |aead_native_handle| must
+ * be a pointer to EVP_AEAD.
+ */
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead,
+                                            void *aead_native_handle);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_aead_retry` initializes |aead| with the AEAD cipher
+ * AEAD_AES_128_GCM for Retry packet integrity protection.
+ */
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead);
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_crypto_side` indicates which side the application
+ * implements; client or server.
+ */
+typedef enum ngtcp2_crypto_side {
+  /**
+   * :enum:`NGTCP2_CRYPTO_SIDE_CLIENT` indicates that the application
+   * is client.
+   */
+  NGTCP2_CRYPTO_SIDE_CLIENT,
+  /**
+   * :enum:`NGTCP2_CRYPTO_SIDE_SERVER` indicates that the application
+   * is server.
+   */
+  NGTCP2_CRYPTO_SIDE_SERVER
+} ngtcp2_crypto_side;
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_derive_initial_secrets` derives initial secrets.
+ * |rx_secret| and |tx_secret| must point to the buffer of at least 32
+ * bytes capacity.  rx for read and tx for write.  This function
+ * writes rx and tx secrets into |rx_secret| and |tx_secret|
+ * respectively.  The length of secret is 32 bytes long.
+ * |client_dcid| is the destination connection ID in first Initial
+ * packet of client.  If |initial_secret| is not NULL, the initial
+ * secret is written to it.  It must point to the buffer which has at
+ * least 32 bytes capacity.  The initial secret is 32 bytes long.
+ * |side| specifies the side of application.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_derive_initial_secrets(uint8_t *rx_secret, uint8_t *tx_secret,
+                                         uint8_t *initial_secret,
+                                         uint32_t version,
+                                         const ngtcp2_cid *client_dcid,
+                                         ngtcp2_crypto_side side);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_derive_packet_protection_key` derives packet
+ * protection key.  This function writes packet protection key into
+ * the buffer pointed by |key|.  The length of derived key is
+ * `ngtcp2_crypto_aead_keylen(aead) <ngtcp2_crypto_aead_keylen>`
+ * bytes.  |key| must have enough capacity to store the key.  This
+ * function writes packet protection IV into |iv|.  The length of
+ * derived IV is `ngtcp2_crypto_packet_protection_ivlen(aead)
+ * <ngtcp2_crypto_packet_protection_ivlen>` bytes.  |iv| must have
+ * enough capacity to store the IV.
+ *
+ * If |hp| is not NULL, this function also derives packet header
+ * protection key and writes the key into the buffer pointed by |hp|.
+ * The length of derived key is `ngtcp2_crypto_aead_keylen(aead)
+ * <ngtcp2_crypto_aead_keylen>` bytes.  |hp|, if not NULL, must have
+ * enough capacity to store the key.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_derive_packet_protection_key(uint8_t *key, uint8_t *iv,
+                                               uint8_t *hp, uint32_t version,
+                                               const ngtcp2_crypto_aead *aead,
+                                               const ngtcp2_crypto_md *md,
+                                               const uint8_t *secret,
+                                               size_t secretlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_update_traffic_secret` derives the next generation
+ * of the traffic secret.  |secret| specifies the current secret and
+ * its length is given in |secretlen|.  The length of new key is the
+ * same as the current key.  This function writes new key into the
+ * buffer pointed by |dest|.  |dest| must have the enough capacity to
+ * store the new key.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_update_traffic_secret(uint8_t *dest, uint32_t version,
+                                        const ngtcp2_crypto_md *md,
+                                        const uint8_t *secret,
+                                        size_t secretlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_set_local_transport_params` sets QUIC transport
+ * parameter, which is encoded in wire format and stored in the buffer
+ * pointed by |buf| of length |len|, to the native handle |tls|.
+ *
+ * |tls| points to a implementation dependent TLS session object.  If
+ * libngtcp2_crypto_quictls is linked, |tls| must be a pointer to SSL
+ * object.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf,
+                                             size_t len);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_set_remote_transport_params` retrieves a remote QUIC
+ * transport parameters from |tls| and sets it to |conn| using
+ * `ngtcp2_conn_set_remote_transport_params`.
+ *
+ * |tls| points to a implementation dependent TLS session object.  If
+ * libngtcp2_crypto_quictls is linked, |tls| must be a pointer to SSL
+ * object.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_derive_and_install_initial_key` derives initial
+ * keying materials and installs keys to |conn|.
+ *
+ * If |rx_secret| is not NULL, the secret for decryption is written to
+ * the buffer pointed by |rx_secret|.  The length of secret is 32
+ * bytes, and |rx_secret| must point to the buffer which has enough
+ * capacity.
+ *
+ * If |tx_secret| is not NULL, the secret for encryption is written to
+ * the buffer pointed by |tx_secret|.  The length of secret is 32
+ * bytes, and |tx_secret| must point to the buffer which has enough
+ * capacity.
+ *
+ * If |initial_secret| is not NULL, the initial secret is written to
+ * the buffer pointed by |initial_secret|.  The length of secret is 32
+ * bytes, and |initial_secret| must point to the buffer which has
+ * enough capacity.
+ *
+ * |client_dcid| is the destination connection ID in first Initial
+ * packet of client.
+ *
+ * If |rx_key| is not NULL, the derived packet protection key for
+ * decryption is written to the buffer pointed by |rx_key|.  If
+ * |rx_iv| is not NULL, the derived packet protection IV for
+ * decryption is written to the buffer pointed by |rx_iv|.  If |rx_hp|
+ * is not NULL, the derived header protection key for decryption is
+ * written to the buffer pointed by |rx_hp|.
+ *
+ * If |tx_key| is not NULL, the derived packet protection key for
+ * encryption is written to the buffer pointed by |tx_key|.  If
+ * |tx_iv| is not NULL, the derived packet protection IV for
+ * encryption is written to the buffer pointed by |tx_iv|.  If |tx_hp|
+ * is not NULL, the derived header protection key for encryption is
+ * written to the buffer pointed by |tx_hp|.
+ *
+ * The length of packet protection key and header protection key is 16
+ * bytes long.  The length of packet protection IV is 12 bytes long.
+ *
+ * This function calls `ngtcp2_conn_set_initial_crypto_ctx` to set
+ * initial AEAD and message digest algorithm.  After the successful
+ * call of this function, application can use
+ * `ngtcp2_conn_get_initial_crypto_ctx` to get the object.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_derive_and_install_initial_key(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, uint8_t *rx_hp,
+  uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp, uint32_t version,
+  const ngtcp2_cid *client_dcid);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_derive_and_install_vneg_initial_key` derives initial
+ * keying materials and installs keys to |conn|.  This function is
+ * dedicated to install keys for |version| which is negotiated, or
+ * being negotiated.
+ *
+ * If |rx_secret| is not NULL, the secret for decryption is written to
+ * the buffer pointed by |rx_secret|.  The length of secret is 32
+ * bytes, and |rx_secret| must point to the buffer which has enough
+ * capacity.
+ *
+ * If |tx_secret| is not NULL, the secret for encryption is written to
+ * the buffer pointed by |tx_secret|.  The length of secret is 32
+ * bytes, and |tx_secret| must point to the buffer which has enough
+ * capacity.
+ *
+ * If |initial_secret| is not NULL, the initial secret is written to
+ * the buffer pointed by |initial_secret|.  The length of secret is 32
+ * bytes, and |initial_secret| must point to the buffer which has
+ * enough capacity.
+ *
+ * |client_dcid| is the destination connection ID in first Initial
+ * packet of client.
+ *
+ * If |rx_key| is not NULL, the derived packet protection key for
+ * decryption is written to the buffer pointed by |rx_key|.  If
+ * |rx_iv| is not NULL, the derived packet protection IV for
+ * decryption is written to the buffer pointed by |rx_iv|.  If |rx_hp|
+ * is not NULL, the derived header protection key for decryption is
+ * written to the buffer pointed by |rx_hp|.
+ *
+ * If |tx_key| is not NULL, the derived packet protection key for
+ * encryption is written to the buffer pointed by |tx_key|.  If
+ * |tx_iv| is not NULL, the derived packet protection IV for
+ * encryption is written to the buffer pointed by |tx_iv|.  If |tx_hp|
+ * is not NULL, the derived header protection key for encryption is
+ * written to the buffer pointed by |tx_hp|.
+ *
+ * The length of packet protection key and header protection key is 16
+ * bytes long.  The length of packet protection IV is 12 bytes long.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_derive_and_install_vneg_initial_key(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, uint8_t *rx_hp,
+  uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp, uint32_t version,
+  const ngtcp2_cid *client_dcid);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_cipher_ctx_encrypt_init` initializes |cipher_ctx|
+ * with new cipher context object for encryption which is constructed
+ * to use |key| as encryption key.  |cipher| specifies cipher to use.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_cipher_ctx_free` frees up resources used by
+ * |cipher_ctx|.  This function does not free the memory pointed by
+ * |cipher_ctx| itself.
+ */
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx);
+
+/*
+ * `ngtcp2_crypto_md_sha256` initializes |md| with SHA256 message
+ * digest algorithm and returns |md|.
+ */
+ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md);
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead);
+
+/*
+ * `ngtcp2_crypto_random` writes cryptographically-secure random
+ * |datalen| bytes into the buffer pointed by |data|.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_random(uint8_t *data, size_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_crypto_hkdf_expand_label` performs HKDF expand label.  The
+ * result is |destlen| bytes long, and is stored to the buffer pointed
+ * by |dest|.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int ngtcp2_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen,
+                                    const ngtcp2_crypto_md *md,
+                                    const uint8_t *secret, size_t secretlen,
+                                    const uint8_t *label, size_t labellen);
+
+#endif /* !defined(SHARED_H) */
diff --git a/third_party/ngtcp2/crypto/wolfssl/.gitignore b/third_party/ngtcp2/crypto/wolfssl/.gitignore
new file mode 100644 (file)
index 0000000..936b2be
--- /dev/null
@@ -0,0 +1 @@
+/libngtcp2_crypto_wolfssl.pc
diff --git a/third_party/ngtcp2/crypto/wolfssl/CMakeLists.txt b/third_party/ngtcp2/crypto/wolfssl/CMakeLists.txt
new file mode 100644 (file)
index 0000000..20cc489
--- /dev/null
@@ -0,0 +1,84 @@
+# ngtcp2
+
+# Copyright (c) 2022 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+add_definitions(-DBUILDING_NGTCP2)
+
+set(ngtcp2_crypto_wolfssl_SOURCES
+  wolfssl.c
+  ../shared.c
+)
+
+set(ngtcp2_crypto_wolfssl_INCLUDE_DIRS
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../lib"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes"
+  "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto"
+  "${CMAKE_CURRENT_BINARY_DIR}/../../crypto"
+  "${WOLFSSL_INCLUDE_DIRS}"
+)
+
+foreach(name libngtcp2_crypto_wolfssl.pc)
+  configure_file("${name}.in" "${name}" @ONLY)
+endforeach()
+
+# Public shared library
+if(ENABLE_SHARED_LIB)
+  add_library(ngtcp2_crypto_wolfssl SHARED ${ngtcp2_crypto_wolfssl_SOURCES})
+  set_target_properties(ngtcp2_crypto_wolfssl PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${CRYPTO_WOLFSSL_LT_VERSION}
+    SOVERSION ${CRYPTO_WOLFSSL_LT_SOVERSION}
+    C_VISIBILITY_PRESET hidden
+    POSITION_INDEPENDENT_CODE ON
+  )
+  target_include_directories(ngtcp2_crypto_wolfssl PUBLIC
+    ${ngtcp2_crypto_wolfssl_INCLUDE_DIRS})
+  target_link_libraries(ngtcp2_crypto_wolfssl ngtcp2 ${WOLFSSL_LIBRARIES})
+
+  install(TARGETS ngtcp2_crypto_wolfssl
+    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+if(ENABLE_STATIC_LIB)
+  # Public static library
+  add_library(ngtcp2_crypto_wolfssl_static STATIC ${ngtcp2_crypto_wolfssl_SOURCES})
+  set_target_properties(ngtcp2_crypto_wolfssl_static PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    ARCHIVE_OUTPUT_NAME ngtcp2_crypto_wolfssl${STATIC_LIB_SUFFIX}
+    C_VISIBILITY_PRESET hidden
+  )
+  target_compile_definitions(ngtcp2_crypto_wolfssl_static PUBLIC
+    "-DNGTCP2_STATICLIB")
+  target_include_directories(ngtcp2_crypto_wolfssl_static PUBLIC
+    ${ngtcp2_crypto_wolfssl_INCLUDE_DIRS})
+
+  install(TARGETS ngtcp2_crypto_wolfssl_static
+    DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+endif()
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_wolfssl.pc"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/third_party/ngtcp2/crypto/wolfssl/Makefile.am b/third_party/ngtcp2/crypto/wolfssl/Makefile.am
new file mode 100644 (file)
index 0000000..b9d5f8c
--- /dev/null
@@ -0,0 +1,43 @@
+# ngtcp2
+
+# Copyright (c) 2022 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = CMakeLists.txt
+
+AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG)
+AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \
+       -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \
+       -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \
+       -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \
+       @WOLFSSL_CFLAGS@
+AM_LDFLAGS = ${LIBTOOL_LDFLAGS}
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libngtcp2_crypto_wolfssl.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LTLIBRARIES = libngtcp2_crypto_wolfssl.la
+
+libngtcp2_crypto_wolfssl_la_SOURCES = wolfssl.c ../shared.c ../shared.h
+libngtcp2_crypto_wolfssl_la_LDFLAGS = -no-undefined \
+       -version-info $(CRYPTO_WOLFSSL_LT_CURRENT):$(CRYPTO_WOLFSSL_LT_REVISION):$(CRYPTO_WOLFSSL_LT_AGE)
+libngtcp2_crypto_wolfssl_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \
+       @WOLFSSL_LIBS@
diff --git a/third_party/ngtcp2/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in b/third_party/ngtcp2/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in
new file mode 100644 (file)
index 0000000..720c784
--- /dev/null
@@ -0,0 +1,33 @@
+# ngtcp2
+
+# Copyright (c) 2022 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libngtcp2_crypto_wolfssl
+Description: ngtcp2 wolfSSL crypto library
+URL: https://github.com/ngtcp2/ngtcp2
+Version: @VERSION@
+Libs: -L${libdir} -lngtcp2_crypto_wolfssl
+Cflags: -I${includedir}
diff --git a/third_party/ngtcp2/crypto/wolfssl/wolfssl.c b/third_party/ngtcp2/crypto/wolfssl/wolfssl.c
new file mode 100644 (file)
index 0000000..bc9d9d8
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <assert.h>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+
+#include "shared.h"
+
+#define PRINTF_DEBUG 0
+#if PRINTF_DEBUG
+#  define DEBUG_MSG(...) fprintf(stderr, __VA_ARGS__)
+#else /* !PRINTF_DEBUG */
+#  define DEBUG_MSG(...) (void)0
+#endif /* !PRINTF_DEBUG */
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)wolfSSL_EVP_aes_128_gcm());
+}
+
+ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) {
+  md->native_handle = (void *)wolfSSL_EVP_sha256();
+  return md;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) {
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)wolfSSL_EVP_aes_128_gcm());
+  ctx->md.native_handle = (void *)wolfSSL_EVP_sha256();
+  ctx->hp.native_handle = (void *)wolfSSL_EVP_aes_128_ctr();
+  ctx->max_encryption = 0;
+  ctx->max_decryption_failure = 0;
+  return ctx;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead,
+                                            void *aead_native_handle) {
+  aead->native_handle = aead_native_handle;
+  aead->max_overhead = wolfSSL_quic_get_aead_tag_len(
+    (const WOLFSSL_EVP_CIPHER *)(aead_native_handle));
+  return aead;
+}
+
+ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) {
+  return ngtcp2_crypto_aead_init(aead, (void *)wolfSSL_EVP_aes_128_gcm());
+}
+
+static uint64_t
+crypto_aead_get_aead_max_encryption(const WOLFSSL_EVP_CIPHER *aead) {
+  if (wolfSSL_quic_aead_is_gcm(aead)) {
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM;
+  }
+  if (wolfSSL_quic_aead_is_chacha20(aead)) {
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305;
+  }
+  if (wolfSSL_quic_aead_is_ccm(aead)) {
+    return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM;
+  }
+  return 0;
+}
+
+static uint64_t
+crypto_aead_get_aead_max_decryption_failure(const WOLFSSL_EVP_CIPHER *aead) {
+  if (wolfSSL_quic_aead_is_gcm(aead)) {
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM;
+  }
+  if (wolfSSL_quic_aead_is_chacha20(aead)) {
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305;
+  }
+  if (wolfSSL_quic_aead_is_ccm(aead)) {
+    return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM;
+  }
+  return 0;
+}
+
+static int supported_aead(const WOLFSSL_EVP_CIPHER *aead) {
+  return wolfSSL_quic_aead_is_gcm(aead) ||
+         wolfSSL_quic_aead_is_chacha20(aead) || wolfSSL_quic_aead_is_ccm(aead);
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx,
+                                         void *tls_native_handle) {
+  WOLFSSL *ssl = tls_native_handle;
+  const WOLFSSL_EVP_CIPHER *aead = wolfSSL_quic_get_aead(ssl);
+
+  if (aead == NULL) {
+    return NULL;
+  }
+
+  if (!supported_aead(aead)) {
+    return NULL;
+  }
+
+  ngtcp2_crypto_aead_init(&ctx->aead, (void *)aead);
+  ctx->md.native_handle = (void *)wolfSSL_quic_get_md(ssl);
+  ctx->hp.native_handle = (void *)wolfSSL_quic_get_hp(ssl);
+  ctx->max_encryption = crypto_aead_get_aead_max_encryption(aead);
+  ctx->max_decryption_failure =
+    crypto_aead_get_aead_max_decryption_failure(aead);
+  return ctx;
+}
+
+ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx,
+                                               void *tls_native_handle) {
+  return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle);
+}
+
+static size_t crypto_md_hashlen(const WOLFSSL_EVP_MD *md) {
+  return (size_t)wolfSSL_EVP_MD_size(md);
+}
+
+size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) {
+  return crypto_md_hashlen(md->native_handle);
+}
+
+static size_t crypto_aead_keylen(const WOLFSSL_EVP_CIPHER *aead) {
+  return (size_t)wolfSSL_EVP_Cipher_key_length(aead);
+}
+
+size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_keylen(aead->native_handle);
+}
+
+static size_t crypto_aead_noncelen(const WOLFSSL_EVP_CIPHER *aead) {
+  return (size_t)wolfSSL_EVP_CIPHER_iv_length(aead);
+}
+
+size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) {
+  return crypto_aead_noncelen(aead->native_handle);
+}
+
+int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const WOLFSSL_EVP_CIPHER *cipher = aead->native_handle;
+  WOLFSSL_EVP_CIPHER_CTX *actx;
+  static const uint8_t iv[AES_BLOCK_SIZE] = {0};
+
+  (void)noncelen;
+  actx = wolfSSL_quic_crypt_new(cipher, key, iv, /* encrypt */ 1);
+  if (actx == NULL) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+  return 0;
+}
+
+int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx,
+                                        const ngtcp2_crypto_aead *aead,
+                                        const uint8_t *key, size_t noncelen) {
+  const WOLFSSL_EVP_CIPHER *cipher = aead->native_handle;
+  WOLFSSL_EVP_CIPHER_CTX *actx;
+  static const uint8_t iv[AES_BLOCK_SIZE] = {0};
+
+  (void)noncelen;
+  actx = wolfSSL_quic_crypt_new(cipher, key, iv, /* encrypt */ 0);
+  if (actx == NULL) {
+    return -1;
+  }
+
+  aead_ctx->native_handle = actx;
+  return 0;
+}
+
+void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (aead_ctx->native_handle) {
+    wolfSSL_EVP_CIPHER_CTX_free(aead_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx,
+                                          const ngtcp2_crypto_cipher *cipher,
+                                          const uint8_t *key) {
+  WOLFSSL_EVP_CIPHER_CTX *actx;
+
+  actx =
+    wolfSSL_quic_crypt_new(cipher->native_handle, key, NULL, /* encrypt */ 1);
+  if (actx == NULL) {
+    return -1;
+  }
+
+  cipher_ctx->native_handle = actx;
+  return 0;
+}
+
+void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (cipher_ctx->native_handle) {
+    wolfSSL_EVP_CIPHER_CTX_free(cipher_ctx->native_handle);
+  }
+}
+
+int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md,
+                               const uint8_t *secret, size_t secretlen,
+                               const uint8_t *salt, size_t saltlen) {
+  if (wolfSSL_quic_hkdf_extract(dest, md->native_handle, secret, secretlen,
+                                salt, saltlen) != WOLFSSL_SUCCESS) {
+    DEBUG_MSG("WOLFSSL: wolfSSL_quic_hkdf_extract FAILED\n");
+    return -1;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen,
+                              const ngtcp2_crypto_md *md, const uint8_t *secret,
+                              size_t secretlen, const uint8_t *info,
+                              size_t infolen) {
+  if (wolfSSL_quic_hkdf_expand(dest, destlen, md->native_handle, secret,
+                               secretlen, info, infolen) != WOLFSSL_SUCCESS) {
+    DEBUG_MSG("WOLFSSL: wolfSSL_quic_hkdf_expand FAILED\n");
+    return -1;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen,
+                       const ngtcp2_crypto_md *md, const uint8_t *secret,
+                       size_t secretlen, const uint8_t *salt, size_t saltlen,
+                       const uint8_t *info, size_t infolen) {
+  if (wolfSSL_quic_hkdf(dest, destlen, md->native_handle, secret, secretlen,
+                        salt, saltlen, info, infolen) != WOLFSSL_SUCCESS) {
+    DEBUG_MSG("WOLFSSL: wolfSSL_quic_hkdf FAILED\n");
+    return -1;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *plaintext, size_t plaintextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  (void)aead;
+  (void)noncelen;
+  if (wolfSSL_quic_aead_encrypt(dest, aead_ctx->native_handle, plaintext,
+                                plaintextlen, nonce, aad,
+                                aadlen) != WOLFSSL_SUCCESS) {
+    DEBUG_MSG("WOLFSSL: encrypt FAILED\n");
+    return -1;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                          const ngtcp2_crypto_aead_ctx *aead_ctx,
+                          const uint8_t *ciphertext, size_t ciphertextlen,
+                          const uint8_t *nonce, size_t noncelen,
+                          const uint8_t *aad, size_t aadlen) {
+  (void)aead;
+  (void)noncelen;
+  if (wolfSSL_quic_aead_decrypt(dest, aead_ctx->native_handle, ciphertext,
+                                ciphertextlen, nonce, aad,
+                                aadlen) != WOLFSSL_SUCCESS) {
+    DEBUG_MSG("WOLFSSL: decrypt FAILED\n");
+    return -1;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                          const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                          const uint8_t *sample) {
+  static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
+  WOLFSSL_EVP_CIPHER_CTX *actx = hp_ctx->native_handle;
+  int len;
+
+  (void)hp;
+
+  if (wolfSSL_EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) !=
+        WOLFSSL_SUCCESS ||
+      wolfSSL_EVP_CipherUpdate(actx, dest, &len, PLAINTEXT,
+                               sizeof(PLAINTEXT) - 1) != WOLFSSL_SUCCESS ||
+      wolfSSL_EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len) !=
+        WOLFSSL_SUCCESS) {
+    DEBUG_MSG("WOLFSSL: hp_mask FAILED\n");
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_read_write_crypto_data(
+  ngtcp2_conn *conn, ngtcp2_encryption_level encryption_level,
+  const uint8_t *data, size_t datalen) {
+  WOLFSSL *ssl = ngtcp2_conn_get_tls_native_handle(conn);
+  WOLFSSL_ENCRYPTION_LEVEL level =
+    ngtcp2_crypto_wolfssl_from_ngtcp2_encryption_level(encryption_level);
+  int rv;
+  int err;
+
+  DEBUG_MSG("WOLFSSL: read/write crypto data, level=%d len=%lu\n", level,
+            datalen);
+  if (datalen > 0) {
+    rv = wolfSSL_provide_quic_data(ssl, level, data, datalen);
+    if (rv != WOLFSSL_SUCCESS) {
+      DEBUG_MSG("WOLFSSL: read/write crypto data FAILED, rv=%d\n", rv);
+      return -1;
+    }
+  }
+
+  if (!ngtcp2_conn_get_handshake_completed(conn)) {
+    rv = wolfSSL_quic_do_handshake(ssl);
+    if (rv <= 0) {
+      err = wolfSSL_get_error(ssl, rv);
+      DEBUG_MSG("WOLFSSL: do_handshake, rv=%d, err=%d\n", rv, err);
+      switch (err) {
+      case SSL_ERROR_WANT_READ:
+      case SSL_ERROR_WANT_WRITE:
+        return 0;
+      case SSL_ERROR_SSL:
+        return -1;
+      default:
+        return -1;
+      }
+    }
+
+    DEBUG_MSG("WOLFSSL: handshake done\n");
+    ngtcp2_conn_tls_handshake_completed(conn);
+  }
+
+  rv = wolfSSL_process_quic_post_handshake(ssl);
+  DEBUG_MSG("WOLFSSL: process post handshake, rv=%d\n", rv);
+  if (rv != 1) {
+    err = wolfSSL_get_error(ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+    case SSL_ERROR_WANT_WRITE:
+      return 0;
+    case SSL_ERROR_SSL:
+    case SSL_ERROR_ZERO_RETURN:
+      return -1;
+    default:
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) {
+  WOLFSSL *ssl = tls;
+  const uint8_t *tp;
+  size_t tplen;
+  int rv;
+
+  wolfSSL_get_peer_quic_transport_params(ssl, &tp, &tplen);
+  DEBUG_MSG("WOLFSSL: get peer transport params, len=%lu\n", tplen);
+
+  rv = ngtcp2_conn_decode_and_set_remote_transport_params(conn, tp, tplen);
+  if (rv != 0) {
+    DEBUG_MSG("WOLFSSL: decode peer transport params failed, rv=%d\n", rv);
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf,
+                                             size_t len) {
+  WOLFSSL *ssl = tls;
+  DEBUG_MSG("WOLFSSL: set local peer transport params, len=%lu\n", len);
+  if (wolfSSL_set_quic_transport_params(ssl, buf, len) != WOLFSSL_SUCCESS) {
+    return -1;
+  }
+
+  return 0;
+}
+
+ngtcp2_encryption_level ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level(
+  WOLFSSL_ENCRYPTION_LEVEL wolfssl_level) {
+  switch (wolfssl_level) {
+  case wolfssl_encryption_initial:
+    return NGTCP2_ENCRYPTION_LEVEL_INITIAL;
+  case wolfssl_encryption_early_data:
+    return NGTCP2_ENCRYPTION_LEVEL_0RTT;
+  case wolfssl_encryption_handshake:
+    return NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE;
+  case wolfssl_encryption_application:
+    return NGTCP2_ENCRYPTION_LEVEL_1RTT;
+  default:
+    assert(0);
+    abort(); /* if NDEBUG is set */
+  }
+}
+
+WOLFSSL_ENCRYPTION_LEVEL
+ngtcp2_crypto_wolfssl_from_ngtcp2_encryption_level(
+  ngtcp2_encryption_level encryption_level) {
+  switch (encryption_level) {
+  case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
+    return wolfssl_encryption_initial;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    return wolfssl_encryption_handshake;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    return wolfssl_encryption_application;
+  case NGTCP2_ENCRYPTION_LEVEL_0RTT:
+    return wolfssl_encryption_early_data;
+  default:
+    assert(0);
+    abort(); /* if NDEBUG is set */
+  }
+}
+
+int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data,
+                                             void *user_data) {
+  (void)conn;
+  (void)user_data;
+
+  DEBUG_MSG("WOLFSSL: get path challenge data\n");
+  if (wolfSSL_RAND_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+  return 0;
+}
+
+int ngtcp2_crypto_random(uint8_t *data, size_t datalen) {
+  DEBUG_MSG("WOLFSSL: get random\n");
+  if (wolfSSL_RAND_bytes(data, (int)datalen) != 1) {
+    return -1;
+  }
+  return 0;
+}
+
+static int set_encryption_secrets(WOLFSSL *ssl,
+                                  WOLFSSL_ENCRYPTION_LEVEL wolfssl_level,
+                                  const uint8_t *rx_secret,
+                                  const uint8_t *tx_secret, size_t secretlen) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level(wolfssl_level);
+
+  DEBUG_MSG("WOLFSSL: set encryption secrets, level=%d, rxlen=%lu, txlen=%lu\n",
+            wolfssl_level, rx_secret ? secretlen : 0,
+            tx_secret ? secretlen : 0);
+  if (rx_secret &&
+      ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level,
+                                              rx_secret, secretlen) != 0) {
+    return 0;
+  }
+
+  if (tx_secret &&
+      ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level,
+                                              tx_secret, secretlen) != 0) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int add_handshake_data(WOLFSSL *ssl,
+                              WOLFSSL_ENCRYPTION_LEVEL wolfssl_level,
+                              const uint8_t *data, size_t datalen) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  ngtcp2_encryption_level level =
+    ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level(wolfssl_level);
+  int rv;
+
+  DEBUG_MSG("WOLFSSL: add handshake data, level=%d len=%lu\n", wolfssl_level,
+            datalen);
+  rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen);
+  if (rv != 0) {
+    ngtcp2_conn_set_tls_error(conn, rv);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int flush_flight(WOLFSSL *ssl) {
+  (void)ssl;
+  return 1;
+}
+
+static int send_alert(WOLFSSL *ssl, enum wolfssl_encryption_level_t level,
+                      uint8_t alert) {
+  ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl);
+  ngtcp2_conn *conn = conn_ref->get_conn(conn_ref);
+  (void)level;
+
+  DEBUG_MSG("WOLFSSL: send alert, level=%d alert=%d\n", level, alert);
+  ngtcp2_conn_set_tls_alert(conn, alert);
+
+  return 1;
+}
+
+static WOLFSSL_QUIC_METHOD quic_method = {
+  set_encryption_secrets,
+  add_handshake_data,
+  flush_flight,
+  send_alert,
+};
+
+static void crypto_wolfssl_configure_context(WOLFSSL_CTX *ssl_ctx) {
+  wolfSSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
+  wolfSSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
+  wolfSSL_CTX_set_quic_method(ssl_ctx, &quic_method);
+}
+
+int ngtcp2_crypto_wolfssl_configure_server_context(WOLFSSL_CTX *ssl_ctx) {
+  crypto_wolfssl_configure_context(ssl_ctx);
+#if PRINTF_DEBUG
+  wolfSSL_Debugging_ON();
+#endif /* PRINTF_DEBUG */
+  return 0;
+}
+
+int ngtcp2_crypto_wolfssl_configure_client_context(WOLFSSL_CTX *ssl_ctx) {
+  crypto_wolfssl_configure_context(ssl_ctx);
+  wolfSSL_CTX_UseSessionTicket(ssl_ctx);
+#if PRINTF_DEBUG
+  wolfSSL_Debugging_ON();
+#endif /* PRINTF_DEBUG */
+  return 0;
+}
diff --git a/third_party/ngtcp2/lib/.gitignore b/third_party/ngtcp2/lib/.gitignore
new file mode 100644 (file)
index 0000000..febcd8e
--- /dev/null
@@ -0,0 +1,2 @@
+includes/ngtcp2/version.h
+libngtcp2.pc
diff --git a/third_party/ngtcp2/lib/CMakeLists.txt b/third_party/ngtcp2/lib/CMakeLists.txt
new file mode 100644 (file)
index 0000000..dc390e7
--- /dev/null
@@ -0,0 +1,146 @@
+
+# ngtcp2
+
+# Copyright (c) 2016 ngtcp2
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+add_subdirectory(includes)
+
+add_definitions(-DBUILDING_NGTCP2)
+
+set(ngtcp2_SOURCES
+  ngtcp2_pkt.c
+  ngtcp2_conv.c
+  ngtcp2_str.c
+  ngtcp2_vec.c
+  ngtcp2_buf.c
+  ngtcp2_conn.c
+  ngtcp2_mem.c
+  ngtcp2_pq.c
+  ngtcp2_map.c
+  ngtcp2_rob.c
+  ngtcp2_ppe.c
+  ngtcp2_crypto.c
+  ngtcp2_err.c
+  ngtcp2_range.c
+  ngtcp2_acktr.c
+  ngtcp2_rtb.c
+  ngtcp2_frame_chain.c
+  ngtcp2_strm.c
+  ngtcp2_idtr.c
+  ngtcp2_gaptr.c
+  ngtcp2_ringbuf.c
+  ngtcp2_log.c
+  ngtcp2_qlog.c
+  ngtcp2_cid.c
+  ngtcp2_ksl.c
+  ngtcp2_cc.c
+  ngtcp2_bbr.c
+  ngtcp2_addr.c
+  ngtcp2_path.c
+  ngtcp2_pv.c
+  ngtcp2_pmtud.c
+  ngtcp2_version.c
+  ngtcp2_rst.c
+  ngtcp2_window_filter.c
+  ngtcp2_opl.c
+  ngtcp2_balloc.c
+  ngtcp2_objalloc.c
+  ngtcp2_unreachable.c
+  ngtcp2_transport_params.c
+  ngtcp2_settings.c
+  ngtcp2_dcidtr.c
+)
+
+set(ngtcp2_INCLUDE_DIRS
+  "${CMAKE_CURRENT_SOURCE_DIR}/includes"
+  "${CMAKE_CURRENT_BINARY_DIR}/includes"
+)
+
+set(NGTCP2_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
+set(NGTCP2_VERSION_CONFIG "${NGTCP2_GENERATED_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
+set(NGTCP2_PROJECT_CONFIG "${NGTCP2_GENERATED_DIR}/${PROJECT_NAME}Config.cmake")
+set(NGTCP2_TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
+set(NGTCP2_CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}")
+set(NGTCP2_NAMESPACE "${PROJECT_NAME}::")
+set(NGTCP2_VERSION ${PROJECT_VERSION})
+
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+  "${NGTCP2_VERSION_CONFIG}" VERSION ${NGTCP2_VERSION} COMPATIBILITY SameMajorVersion
+)
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.cmake.in" "${NGTCP2_PROJECT_CONFIG}" @ONLY)
+
+# Install cmake config files
+install(
+  FILES "${NGTCP2_PROJECT_CONFIG}" "${NGTCP2_VERSION_CONFIG}"
+  DESTINATION "${NGTCP2_CONFIG_INSTALL_DIR}")
+
+# Public shared library
+if(ENABLE_SHARED_LIB)
+  add_library(ngtcp2 SHARED ${ngtcp2_SOURCES})
+  set_target_properties(ngtcp2 PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION}
+    C_VISIBILITY_PRESET hidden
+    POSITION_INDEPENDENT_CODE ON
+  )
+  foreach(include_DIR IN LISTS ngtcp2_INCLUDE_DIRS)
+    target_include_directories(ngtcp2 PUBLIC $<BUILD_INTERFACE:${include_DIR}>)
+  endforeach()
+
+  target_include_directories(ngtcp2 PUBLIC $<INSTALL_INTERFACE:./include>)
+
+  install(TARGETS ngtcp2
+    EXPORT ${NGTCP2_TARGETS_EXPORT_NAME}
+    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+if(ENABLE_STATIC_LIB)
+  # Public static library
+  add_library(ngtcp2_static STATIC ${ngtcp2_SOURCES})
+  set_target_properties(ngtcp2_static PROPERTIES
+    COMPILE_FLAGS "${WARNCFLAGS}"
+    VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION}
+    ARCHIVE_OUTPUT_NAME ngtcp2${STATIC_LIB_SUFFIX}
+    C_VISIBILITY_PRESET hidden
+  )
+  target_compile_definitions(ngtcp2_static PUBLIC "-DNGTCP2_STATICLIB")
+  foreach(include_DIR IN LISTS ngtcp2_INCLUDE_DIRS)
+    target_include_directories(ngtcp2_static PUBLIC $<BUILD_INTERFACE:${include_DIR}>)
+  endforeach()
+
+  target_include_directories(ngtcp2_static PUBLIC $<INSTALL_INTERFACE:./include>)
+
+  install(TARGETS ngtcp2_static
+    EXPORT ${NGTCP2_TARGETS_EXPORT_NAME}
+    DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+endif()
+
+install(
+  EXPORT "${NGTCP2_TARGETS_EXPORT_NAME}"
+  NAMESPACE "${NGTCP2_NAMESPACE}"
+  DESTINATION "${NGTCP2_CONFIG_INSTALL_DIR}")
+
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2.pc"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/third_party/ngtcp2/lib/Makefile.am b/third_party/ngtcp2/lib/Makefile.am
new file mode 100644 (file)
index 0000000..c523111
--- /dev/null
@@ -0,0 +1,130 @@
+# ngtcp2
+
+# Copyright (c) 2016 - 2019 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+SUBDIRS = includes
+
+EXTRA_DIST = CMakeLists.txt config.cmake.in
+
+AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG)
+AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes -DBUILDING_NGTCP2
+AM_LDFLAGS = ${LIBTOOL_LDFLAGS}
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libngtcp2.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LTLIBRARIES = libngtcp2.la
+
+OBJECTS = \
+       ngtcp2_pkt.c \
+       ngtcp2_conv.c \
+       ngtcp2_str.c \
+       ngtcp2_vec.c \
+       ngtcp2_buf.c \
+       ngtcp2_conn.c \
+       ngtcp2_mem.c \
+       ngtcp2_pq.c \
+       ngtcp2_map.c \
+       ngtcp2_rob.c \
+       ngtcp2_ppe.c \
+       ngtcp2_crypto.c \
+       ngtcp2_err.c \
+       ngtcp2_range.c \
+       ngtcp2_acktr.c \
+       ngtcp2_rtb.c \
+       ngtcp2_frame_chain.c \
+       ngtcp2_strm.c \
+       ngtcp2_idtr.c \
+       ngtcp2_gaptr.c \
+       ngtcp2_ringbuf.c \
+       ngtcp2_log.c \
+       ngtcp2_qlog.c \
+       ngtcp2_cid.c \
+       ngtcp2_ksl.c \
+       ngtcp2_cc.c \
+       ngtcp2_bbr.c \
+       ngtcp2_addr.c \
+       ngtcp2_path.c \
+       ngtcp2_pv.c \
+       ngtcp2_pmtud.c \
+       ngtcp2_version.c \
+       ngtcp2_rst.c \
+       ngtcp2_window_filter.c \
+       ngtcp2_opl.c \
+       ngtcp2_balloc.c \
+       ngtcp2_objalloc.c \
+       ngtcp2_unreachable.c \
+       ngtcp2_transport_params.c \
+       ngtcp2_settings.c \
+       ngtcp2_dcidtr.c
+
+HFILES = \
+       ngtcp2_pkt.h \
+       ngtcp2_conv.h \
+       ngtcp2_str.h \
+       ngtcp2_vec.h \
+       ngtcp2_buf.h \
+       ngtcp2_conn.h \
+       ngtcp2_mem.h \
+       ngtcp2_pq.h \
+       ngtcp2_map.h \
+       ngtcp2_rob.h \
+       ngtcp2_ppe.h \
+       ngtcp2_crypto.h \
+       ngtcp2_err.h \
+       ngtcp2_range.h \
+       ngtcp2_acktr.h \
+       ngtcp2_rtb.h \
+       ngtcp2_frame_chain.h \
+       ngtcp2_strm.h \
+       ngtcp2_idtr.h \
+       ngtcp2_gaptr.h \
+       ngtcp2_ringbuf.h \
+       ngtcp2_log.h \
+       ngtcp2_qlog.h \
+       ngtcp2_cid.h \
+       ngtcp2_ksl.h \
+       ngtcp2_cc.h \
+       ngtcp2_bbr.h \
+       ngtcp2_addr.h \
+       ngtcp2_path.h \
+       ngtcp2_pv.h \
+       ngtcp2_pmtud.h \
+       ngtcp2_macro.h \
+       ngtcp2_rst.h \
+       ngtcp2_window_filter.h \
+       ngtcp2_opl.h \
+       ngtcp2_balloc.h \
+       ngtcp2_objalloc.h \
+       ngtcp2_rcvry.h \
+       ngtcp2_net.h \
+       ngtcp2_unreachable.h \
+       ngtcp2_transport_params.h \
+       ngtcp2_settings.h \
+       ngtcp2_dcidtr.h \
+       ngtcp2_conn_stat.h \
+       ngtcp2_pktns_id.h \
+       ngtcp2_tstamp.h
+
+libngtcp2_la_SOURCES = $(HFILES) $(OBJECTS)
+libngtcp2_la_LDFLAGS = -no-undefined \
+       -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE)
diff --git a/third_party/ngtcp2/lib/config.cmake.in b/third_party/ngtcp2/lib/config.cmake.in
new file mode 100644 (file)
index 0000000..435a4d4
--- /dev/null
@@ -0,0 +1,3 @@
+include(CMakeFindDependencyMacro)
+
+include("${CMAKE_CURRENT_LIST_DIR}/@NGTCP2_TARGETS_EXPORT_NAME@.cmake")
diff --git a/third_party/ngtcp2/lib/includes/CMakeLists.txt b/third_party/ngtcp2/lib/includes/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5eabf73
--- /dev/null
@@ -0,0 +1,4 @@
+install(FILES
+    ngtcp2/ngtcp2.h
+    "${CMAKE_CURRENT_BINARY_DIR}/ngtcp2/version.h"
+  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2")
diff --git a/third_party/ngtcp2/lib/includes/Makefile.am b/third_party/ngtcp2/lib/includes/Makefile.am
new file mode 100644 (file)
index 0000000..5ecb6a4
--- /dev/null
@@ -0,0 +1,25 @@
+# ngtcp2
+
+# Copyright (c) 2016 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = CMakeLists.txt
+
+nobase_include_HEADERS = ngtcp2/ngtcp2.h ngtcp2/version.h
diff --git a/third_party/ngtcp2/lib/includes/ngtcp2/ngtcp2.h b/third_party/ngtcp2/lib/includes/ngtcp2/ngtcp2.h
new file mode 100644 (file)
index 0000000..d7a27b9
--- /dev/null
@@ -0,0 +1,5969 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2017 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_H
+#define NGTCP2_H
+
+/* Define WIN32 when build target is Win32 API (borrowed from
+   libcurl) */
+#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32)
+#  define WIN32
+#endif /* (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) */
+
+#ifdef _MSC_VER
+#  pragma warning(push)
+#  pragma warning(disable : 4324)
+#endif /* defined(_MSC_VER) */
+
+#include <stdlib.h>
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+/* MSVC < 2013 does not have inttypes.h because it is not C99
+   compliant.  See compiler macros and version number in
+   https://sourceforge.net/p/predef/wiki/Compilers/ */
+#  include <stdint.h>
+#else /* !(defined(_MSC_VER) && (_MSC_VER < 1800)) */
+#  include <inttypes.h>
+#endif /* !(defined(_MSC_VER) && (_MSC_VER < 1800)) */
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stddef.h>
+
+#ifndef NGTCP2_USE_GENERIC_SOCKADDR
+#  ifdef WIN32
+#    ifndef WIN32_LEAN_AND_MEAN
+#      define WIN32_LEAN_AND_MEAN
+#    endif /* !defined(WIN32_LEAN_AND_MEAN) */
+#    include <ws2tcpip.h>
+#  else /* !defined(WIN32) */
+#    include <sys/socket.h>
+#    include <netinet/in.h>
+#  endif /* !defined(WIN32) */
+#endif   /* !defined(NGTCP2_USE_GENERIC_SOCKADDR) */
+
+#include <ngtcp2/version.h>
+
+#ifdef NGTCP2_STATICLIB
+#  define NGTCP2_EXTERN
+#elif defined(WIN32)
+#  ifdef BUILDING_NGTCP2
+#    define NGTCP2_EXTERN __declspec(dllexport)
+#  else /* !defined(BUILDING_NGTCP2) */
+#    define NGTCP2_EXTERN __declspec(dllimport)
+#  endif /* !defined(BUILDING_NGTCP2) */
+#else    /* !(defined(NGTCP2_STATICLIB) || defined(WIN32)) */
+#  ifdef BUILDING_NGTCP2
+#    define NGTCP2_EXTERN __attribute__((visibility("default")))
+#  else /* !defined(BUILDING_NGTCP2) */
+#    define NGTCP2_EXTERN
+#  endif /* !defined(BUILDING_NGTCP2) */
+#endif   /* !(defined(NGTCP2_STATICLIB) || defined(WIN32)) */
+
+#ifdef _MSC_VER
+#  define NGTCP2_ALIGN(N) __declspec(align(N))
+#else /* !defined(_MSC_VER) */
+#  define NGTCP2_ALIGN(N) __attribute__((aligned(N)))
+#endif /* !defined(_MSC_VER) */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/**
+ * @typedef
+ *
+ * :type:`ngtcp2_ssize` is signed counterpart of size_t.
+ */
+typedef ptrdiff_t ngtcp2_ssize;
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_malloc` is a custom memory allocator to replace
+ * :manpage:`malloc(3)`.  The |user_data| is
+ * :member:`ngtcp2_mem.user_data`.
+ */
+typedef void *(*ngtcp2_malloc)(size_t size, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_free` is a custom memory allocator to replace
+ * :manpage:`free(3)`.  The |user_data| is
+ * :member:`ngtcp2_mem.user_data`.
+ */
+typedef void (*ngtcp2_free)(void *ptr, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_calloc` is a custom memory allocator to replace
+ * :manpage:`calloc(3)`.  The |user_data| is the
+ * :member:`ngtcp2_mem.user_data`.
+ */
+typedef void *(*ngtcp2_calloc)(size_t nmemb, size_t size, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_realloc` is a custom memory allocator to replace
+ * :manpage:`realloc(3)`.  The |user_data| is the
+ * :member:`ngtcp2_mem.user_data`.
+ */
+typedef void *(*ngtcp2_realloc)(void *ptr, size_t size, void *user_data);
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_mem` is a custom memory allocator.  The
+ * :member:`user_data` field is passed to each allocator function.
+ * This can be used, for example, to achieve per-connection memory
+ * pool.
+ *
+ * In the following example code, ``my_malloc``, ``my_free``,
+ * ``my_calloc`` and ``my_realloc`` are the replacement of the
+ * standard allocators :manpage:`malloc(3)`, :manpage:`free(3)`,
+ * :manpage:`calloc(3)` and :manpage:`realloc(3)` respectively::
+ *
+ *     void *my_malloc_cb(size_t size, void *user_data) {
+ *       (void)user_data;
+ *       return my_malloc(size);
+ *     }
+ *
+ *     void my_free_cb(void *ptr, void *user_data) {
+ *       (void)user_data;
+ *       my_free(ptr);
+ *     }
+ *
+ *     void *my_calloc_cb(size_t nmemb, size_t size, void *user_data) {
+ *       (void)user_data;
+ *       return my_calloc(nmemb, size);
+ *     }
+ *
+ *     void *my_realloc_cb(void *ptr, size_t size, void *user_data) {
+ *       (void)user_data;
+ *       return my_realloc(ptr, size);
+ *     }
+ *
+ *     void conn_new() {
+ *       ngtcp2_mem mem = {
+ *         .malloc = my_malloc_cb,
+ *         .free = my_free_cb,
+ *         .calloc = my_calloc_cb,
+ *         .realloc = my_realloc_cb,
+ *       };
+ *
+ *       ...
+ *     }
+ */
+typedef struct ngtcp2_mem {
+  /**
+   * :member:`user_data` is an arbitrary user supplied data.  This
+   * is passed to each allocator function.
+   */
+  void *user_data;
+  /**
+   * :member:`malloc` is a custom allocator function to replace
+   * :manpage:`malloc(3)`.
+   */
+  ngtcp2_malloc malloc;
+  /**
+   * :member:`free` is a custom allocator function to replace
+   * :manpage:`free(3)`.
+   */
+  ngtcp2_free free;
+  /**
+   * :member:`calloc` is a custom allocator function to replace
+   * :manpage:`calloc(3)`.
+   */
+  ngtcp2_calloc calloc;
+  /**
+   * :member:`realloc` is a custom allocator function to replace
+   * :manpage:`realloc(3)`.
+   */
+  ngtcp2_realloc realloc;
+} ngtcp2_mem;
+
+/**
+ * @macrosection
+ *
+ * Time related macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_NANOSECONDS` is a count of tick which corresponds to
+ * 1 nanosecond.
+ */
+#define NGTCP2_NANOSECONDS ((ngtcp2_duration)1ULL)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MICROSECONDS` is a count of tick which corresponds
+ * to 1 microsecond.
+ */
+#define NGTCP2_MICROSECONDS ((ngtcp2_duration)(1000ULL * NGTCP2_NANOSECONDS))
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MILLISECONDS` is a count of tick which corresponds
+ * to 1 millisecond.
+ */
+#define NGTCP2_MILLISECONDS ((ngtcp2_duration)(1000ULL * NGTCP2_MICROSECONDS))
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_SECONDS` is a count of tick which corresponds to 1
+ * second.
+ */
+#define NGTCP2_SECONDS ((ngtcp2_duration)(1000ULL * NGTCP2_MILLISECONDS))
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MINUTES` is a count of tick which corresponds to 1
+ * minute.
+ */
+#define NGTCP2_MINUTES ((ngtcp2_duration)(60ULL * NGTCP2_SECONDS))
+
+/**
+ * @macrosection
+ *
+ * QUIC protocol version macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PROTO_VER_V1` is the QUIC version 1.
+ */
+#define NGTCP2_PROTO_VER_V1 ((uint32_t)0x00000001u)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PROTO_VER_V2` is the QUIC version 2.  See
+ * :rfc:`9369`.
+ */
+#define NGTCP2_PROTO_VER_V2 ((uint32_t)0x6b3343cfu)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PROTO_VER_MAX` is the highest QUIC version that this
+ * library supports.  Deprecated since v1.1.0.
+ */
+#define NGTCP2_PROTO_VER_MAX NGTCP2_PROTO_VER_V1
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PROTO_VER_MIN` is the lowest QUIC version that this
+ * library supports.  Deprecated since v1.1.0.
+ */
+#define NGTCP2_PROTO_VER_MIN NGTCP2_PROTO_VER_V1
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_RESERVED_VERSION_MASK` is the bit mask of reserved
+ * version.
+ */
+#define NGTCP2_RESERVED_VERSION_MASK 0x0a0a0a0au
+
+/**
+ * @macrosection
+ *
+ * UDP datagram related macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE` is the default maximum UDP
+ * datagram payload size that the local endpoint transmits.
+ */
+#define NGTCP2_MAX_UDP_PAYLOAD_SIZE 1200
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE` is the maximum UDP
+ * datagram payload size that Path MTU Discovery can discover.
+ */
+#define NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE 1452
+
+/**
+ * @macrosection
+ *
+ * QUIC specific macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MAX_VARINT` is the maximum value which can be
+ * encoded in variable-length integer encoding.
+ */
+#define NGTCP2_MAX_VARINT ((1ULL << 62) - 1)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` is the length of Stateless
+ * Reset Token.
+ */
+#define NGTCP2_STATELESS_RESET_TOKENLEN 16
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN` is the minimum length
+ * of random bytes (Unpredictable Bits) in Stateless Reset packet.
+ */
+#define NGTCP2_MIN_STATELESS_RESET_RANDLEN 5
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` is the length of
+ * PATH_CHALLENGE data.
+ */
+#define NGTCP2_PATH_CHALLENGE_DATALEN 8
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_RETRY_KEY_V1` is an encryption key to create
+ * integrity tag of Retry packet.  It is used for QUIC v1.
+ */
+#define NGTCP2_RETRY_KEY_V1                                                    \
+  "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_RETRY_NONCE_V1` is nonce used when generating
+ * integrity tag of Retry packet.  It is used for QUIC v1.
+ */
+#define NGTCP2_RETRY_NONCE_V1 "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_RETRY_KEY_V2` is an encryption key to create
+ * integrity tag of Retry packet.  It is used for QUIC v2.  See
+ * :rfc:`9369`.
+ */
+#define NGTCP2_RETRY_KEY_V2                                                    \
+  "\x8f\xb4\xb0\x1b\x56\xac\x48\xe2\x60\xfb\xcb\xce\xad\x7c\xcc\x92"
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_RETRY_NONCE_V2` is nonce used when generating
+ * integrity tag of Retry packet.  It is used for QUIC v2.  See
+ * :rfc:`9369`.
+ */
+#define NGTCP2_RETRY_NONCE_V2 "\xd8\x69\x69\xbc\x2d\x7c\x6d\x99\x90\xef\xb0\x4a"
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_HP_MASKLEN` is the length of header protection mask.
+ */
+#define NGTCP2_HP_MASKLEN 5
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_HP_SAMPLELEN` is the number bytes sampled when
+ * encrypting a packet header.
+ */
+#define NGTCP2_HP_SAMPLELEN 16
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_DEFAULT_INITIAL_RTT` is a default initial RTT.
+ */
+#define NGTCP2_DEFAULT_INITIAL_RTT (333 * NGTCP2_MILLISECONDS)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MAX_CIDLEN` is the maximum length of Connection ID.
+ */
+#define NGTCP2_MAX_CIDLEN 20
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MIN_CIDLEN` is the minimum length of Connection ID.
+ */
+#define NGTCP2_MIN_CIDLEN 1
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_MIN_INITIAL_DCIDLEN` is the minimum length of
+ * Destination Connection ID in Client Initial packet if it does not
+ * bear token from Retry packet.
+ */
+#define NGTCP2_MIN_INITIAL_DCIDLEN 8
+
+/**
+ * @macrosection
+ *
+ * ECN related macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ECN_NOT_ECT` indicates no ECN marking.
+ */
+#define NGTCP2_ECN_NOT_ECT 0x0
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ECN_ECT_1` is ECT(1) codepoint.
+ */
+#define NGTCP2_ECN_ECT_1 0x1
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ECN_ECT_0` is ECT(0) codepoint.
+ */
+#define NGTCP2_ECN_ECT_0 0x2
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ECN_CE` is CE codepoint.
+ */
+#define NGTCP2_ECN_CE 0x3
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ECN_MASK` is a bit mask to get ECN marking.
+ */
+#define NGTCP2_ECN_MASK 0x3
+
+#define NGTCP2_PKT_INFO_V1 1
+#define NGTCP2_PKT_INFO_VERSION NGTCP2_PKT_INFO_V1
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_pkt_info` is a packet metadata.
+ */
+typedef struct NGTCP2_ALIGN(8) ngtcp2_pkt_info {
+  /**
+   * :member:`ecn` is ECN marking, and when it is passed to
+   * `ngtcp2_conn_read_pkt()`, it should be either
+   * :macro:`NGTCP2_ECN_NOT_ECT`, :macro:`NGTCP2_ECN_ECT_1`,
+   * :macro:`NGTCP2_ECN_ECT_0`, or :macro:`NGTCP2_ECN_CE`.
+   */
+  uint8_t ecn;
+} ngtcp2_pkt_info;
+
+/**
+ * @macrosection
+ *
+ * ngtcp2 library error codes
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` indicates that a passed
+ * argument is invalid.
+ */
+#define NGTCP2_ERR_INVALID_ARGUMENT -201
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_NOBUF` indicates that a provided buffer does not
+ * have enough space to store data.
+ */
+#define NGTCP2_ERR_NOBUF -202
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_PROTO` indicates a general protocol error.
+ */
+#define NGTCP2_ERR_PROTO -203
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_INVALID_STATE` indicates that a requested
+ * operation is not allowed at the current connection state.
+ */
+#define NGTCP2_ERR_INVALID_STATE -204
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_ACK_FRAME` indicates that an invalid ACK frame
+ * is received.
+ */
+#define NGTCP2_ERR_ACK_FRAME -205
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` indicates that there is no
+ * spare stream ID available.
+ */
+#define NGTCP2_ERR_STREAM_ID_BLOCKED -206
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_STREAM_IN_USE` indicates that a stream ID is
+ * already in use.
+ */
+#define NGTCP2_ERR_STREAM_IN_USE -207
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` indicates that stream data
+ * cannot be sent because of flow control.
+ */
+#define NGTCP2_ERR_STREAM_DATA_BLOCKED -208
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_FLOW_CONTROL` indicates flow control error.
+ */
+#define NGTCP2_ERR_FLOW_CONTROL -209
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_CONNECTION_ID_LIMIT` indicates that the number
+ * of received Connection ID exceeds acceptable limit.
+ */
+#define NGTCP2_ERR_CONNECTION_ID_LIMIT -210
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_STREAM_LIMIT` indicates that a remote endpoint
+ * opens more streams that is permitted.
+ */
+#define NGTCP2_ERR_STREAM_LIMIT -211
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_FINAL_SIZE` indicates that inconsistent final
+ * size of a stream.
+ */
+#define NGTCP2_ERR_FINAL_SIZE -212
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_CRYPTO` indicates crypto (TLS) related error.
+ */
+#define NGTCP2_ERR_CRYPTO -213
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` indicates that packet number
+ * is exhausted.
+ */
+#define NGTCP2_ERR_PKT_NUM_EXHAUSTED -214
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` indicates that a
+ * required transport parameter is missing.
+ */
+#define NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM -215
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` indicates that a
+ * transport parameter is malformed.
+ */
+#define NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM -216
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_FRAME_ENCODING` indicates there is an error in
+ * frame encoding.
+ */
+#define NGTCP2_ERR_FRAME_ENCODING -217
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_DECRYPT` indicates a decryption failure.
+ */
+#define NGTCP2_ERR_DECRYPT -218
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_STREAM_SHUT_WR` indicates no more data can be
+ * sent to a stream.
+ */
+#define NGTCP2_ERR_STREAM_SHUT_WR -219
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` indicates that a stream was
+ * not found.
+ */
+#define NGTCP2_ERR_STREAM_NOT_FOUND -220
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_STREAM_STATE` indicates that a requested
+ * operation is not allowed at the current stream state.
+ */
+#define NGTCP2_ERR_STREAM_STATE -221
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION` indicates that Version
+ * Negotiation packet was received.
+ */
+#define NGTCP2_ERR_RECV_VERSION_NEGOTIATION -222
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_CLOSING` indicates that connection is in closing
+ * state.
+ */
+#define NGTCP2_ERR_CLOSING -223
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_DRAINING` indicates that connection is in
+ * draining state.
+ */
+#define NGTCP2_ERR_DRAINING -224
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_TRANSPORT_PARAM` indicates a general transport
+ * parameter error.
+ */
+#define NGTCP2_ERR_TRANSPORT_PARAM -225
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_DISCARD_PKT` indicates a packet was discarded.
+ */
+#define NGTCP2_ERR_DISCARD_PKT -226
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED` indicates that there is no
+ * spare Connection ID available.
+ */
+#define NGTCP2_ERR_CONN_ID_BLOCKED -227
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_INTERNAL` indicates an internal error.
+ */
+#define NGTCP2_ERR_INTERNAL -228
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED` indicates that a crypto
+ * buffer exceeded.
+ */
+#define NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED -229
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_WRITE_MORE` indicates
+ * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is used and a function call
+ * succeeded.
+ */
+#define NGTCP2_ERR_WRITE_MORE -230
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_RETRY` indicates that server should send Retry
+ * packet.
+ */
+#define NGTCP2_ERR_RETRY -231
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_DROP_CONN` indicates that an endpoint should
+ * drop connection immediately.
+ */
+#define NGTCP2_ERR_DROP_CONN -232
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_AEAD_LIMIT_REACHED` indicates AEAD encryption
+ * limit is reached and key update is not available.  An endpoint
+ * should drop connection immediately.
+ */
+#define NGTCP2_ERR_AEAD_LIMIT_REACHED -233
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_NO_VIABLE_PATH` indicates that path validation
+ * could not probe that a path is capable of sending UDP datagram
+ * payload of size at least 1200 bytes.
+ */
+#define NGTCP2_ERR_NO_VIABLE_PATH -234
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION` indicates that server
+ * should send Version Negotiation packet.
+ */
+#define NGTCP2_ERR_VERSION_NEGOTIATION -235
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_HANDSHAKE_TIMEOUT` indicates that QUIC
+ * connection is not established before the specified deadline.
+ */
+#define NGTCP2_ERR_HANDSHAKE_TIMEOUT -236
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` indicates the
+ * version negotiation failed.
+ */
+#define NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE -237
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_IDLE_CLOSE` indicates the connection should be
+ * closed silently because of idle timeout.
+ */
+#define NGTCP2_ERR_IDLE_CLOSE -238
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_FATAL` indicates that error codes less than this
+ * value is fatal error.  When this error is returned, an endpoint
+ * should close connection immediately.
+ */
+#define NGTCP2_ERR_FATAL -500
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_NOMEM` indicates out of memory.
+ */
+#define NGTCP2_ERR_NOMEM -501
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` indicates that user defined
+ * callback function failed.
+ */
+#define NGTCP2_ERR_CALLBACK_FAILURE -502
+
+/**
+ * @macrosection
+ *
+ * QUIC packet header flags
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PKT_FLAG_NONE` indicates no flag set.
+ */
+#define NGTCP2_PKT_FLAG_NONE 0x00u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` indicates the Long header packet
+ * header.
+ */
+#define NGTCP2_PKT_FLAG_LONG_FORM 0x01u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR` indicates that Fixed Bit
+ * (aka QUIC bit) is not set.
+ */
+#define NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR 0x02u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PKT_FLAG_KEY_PHASE` indicates Key Phase bit set.
+ */
+#define NGTCP2_PKT_FLAG_KEY_PHASE 0x04u
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_pkt_type` defines QUIC version-independent QUIC
+ * packet types.
+ */
+typedef enum ngtcp2_pkt_type {
+  /**
+   * :enum:`NGTCP2_PKT_VERSION_NEGOTIATION` is defined by libngtcp2
+   * for convenience.
+   */
+  NGTCP2_PKT_VERSION_NEGOTIATION = 0x80,
+  /**
+   * :enum:`NGTCP2_PKT_STATELESS_RESET` is defined by libngtcp2 for
+   * convenience.
+   */
+  NGTCP2_PKT_STATELESS_RESET = 0x81,
+  /**
+   * :enum:`NGTCP2_PKT_INITIAL` indicates Initial packet.
+   */
+  NGTCP2_PKT_INITIAL = 0x10,
+  /**
+   * :enum:`NGTCP2_PKT_0RTT` indicates 0-RTT packet.
+   */
+  NGTCP2_PKT_0RTT = 0x11,
+  /**
+   * :enum:`NGTCP2_PKT_HANDSHAKE` indicates Handshake packet.
+   */
+  NGTCP2_PKT_HANDSHAKE = 0x12,
+  /**
+   * :enum:`NGTCP2_PKT_RETRY` indicates Retry packet.
+   */
+  NGTCP2_PKT_RETRY = 0x13,
+  /**
+   * :enum:`NGTCP2_PKT_1RTT` is defined by libngtcp2 for convenience.
+   */
+  NGTCP2_PKT_1RTT = 0x40
+} ngtcp2_pkt_type;
+
+/**
+ * @macrosection
+ *
+ * QUIC transport error code
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_NO_ERROR` is QUIC transport error code ``NO_ERROR``.
+ */
+#define NGTCP2_NO_ERROR 0x0u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_INTERNAL_ERROR` is QUIC transport error code
+ * ``INTERNAL_ERROR``.
+ */
+#define NGTCP2_INTERNAL_ERROR 0x1u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CONNECTION_REFUSED` is QUIC transport error code
+ * ``CONNECTION_REFUSED``.
+ */
+#define NGTCP2_CONNECTION_REFUSED 0x2u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_FLOW_CONTROL_ERROR` is QUIC transport error code
+ * ``FLOW_CONTROL_ERROR``.
+ */
+#define NGTCP2_FLOW_CONTROL_ERROR 0x3u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_STREAM_LIMIT_ERROR` is QUIC transport error code
+ * ``STREAM_LIMIT_ERROR``.
+ */
+#define NGTCP2_STREAM_LIMIT_ERROR 0x4u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_STREAM_STATE_ERROR` is QUIC transport error code
+ * ``STREAM_STATE_ERROR``.
+ */
+#define NGTCP2_STREAM_STATE_ERROR 0x5u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_FINAL_SIZE_ERROR` is QUIC transport error code
+ * ``FINAL_SIZE_ERROR``.
+ */
+#define NGTCP2_FINAL_SIZE_ERROR 0x6u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_FRAME_ENCODING_ERROR` is QUIC transport error code
+ * ``FRAME_ENCODING_ERROR``.
+ */
+#define NGTCP2_FRAME_ENCODING_ERROR 0x7u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_TRANSPORT_PARAMETER_ERROR` is QUIC transport error
+ * code ``TRANSPORT_PARAMETER_ERROR``.
+ */
+#define NGTCP2_TRANSPORT_PARAMETER_ERROR 0x8u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CONNECTION_ID_LIMIT_ERROR` is QUIC transport error
+ * code ``CONNECTION_ID_LIMIT_ERROR``.
+ */
+#define NGTCP2_CONNECTION_ID_LIMIT_ERROR 0x9u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PROTOCOL_VIOLATION` is QUIC transport error code
+ * ``PROTOCOL_VIOLATION``.
+ */
+#define NGTCP2_PROTOCOL_VIOLATION 0xau
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_INVALID_TOKEN` is QUIC transport error code
+ * ``INVALID_TOKEN``.
+ */
+#define NGTCP2_INVALID_TOKEN 0xbu
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_APPLICATION_ERROR` is QUIC transport error code
+ * ``APPLICATION_ERROR``.
+ */
+#define NGTCP2_APPLICATION_ERROR 0xcu
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_BUFFER_EXCEEDED` is QUIC transport error code
+ * ``CRYPTO_BUFFER_EXCEEDED``.
+ */
+#define NGTCP2_CRYPTO_BUFFER_EXCEEDED 0xdu
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_KEY_UPDATE_ERROR` is QUIC transport error code
+ * ``KEY_UPDATE_ERROR``.
+ */
+#define NGTCP2_KEY_UPDATE_ERROR 0xeu
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_AEAD_LIMIT_REACHED` is QUIC transport error code
+ * ``AEAD_LIMIT_REACHED``.
+ */
+#define NGTCP2_AEAD_LIMIT_REACHED 0xfu
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_NO_VIABLE_PATH` is QUIC transport error code
+ * ``NO_VIABLE_PATH``.
+ */
+#define NGTCP2_NO_VIABLE_PATH 0x10u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_CRYPTO_ERROR` is QUIC transport error code
+ * ``CRYPTO_ERROR``.
+ */
+#define NGTCP2_CRYPTO_ERROR 0x100u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_VERSION_NEGOTIATION_ERROR` is QUIC transport error
+ * code ``VERSION_NEGOTIATION_ERROR``.  See :rfc:`9368`.
+ */
+#define NGTCP2_VERSION_NEGOTIATION_ERROR 0x11
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_path_validation_result` defines path validation
+ * result code.
+ */
+typedef enum ngtcp2_path_validation_result {
+  /**
+   * :enum:`NGTCP2_PATH_VALIDATION_RESULT_SUCCESS` indicates
+   * successful validation.
+   */
+  NGTCP2_PATH_VALIDATION_RESULT_SUCCESS,
+  /**
+   * :enum:`NGTCP2_PATH_VALIDATION_RESULT_FAILURE` indicates
+   * validation failure.
+   */
+  NGTCP2_PATH_VALIDATION_RESULT_FAILURE,
+  /**
+   * :enum:`NGTCP2_PATH_VALIDATION_RESULT_ABORTED` indicates that path
+   * validation was aborted.
+   */
+  NGTCP2_PATH_VALIDATION_RESULT_ABORTED
+} ngtcp2_path_validation_result;
+
+/**
+ * @typedef
+ *
+ * :type:`ngtcp2_tstamp` is a timestamp with nanosecond resolution.
+ * ``UINT64_MAX`` is an invalid value, and it is often used to
+ * indicate that no value is set.
+ */
+typedef uint64_t ngtcp2_tstamp;
+
+/**
+ * @typedef
+ *
+ * :type:`ngtcp2_duration` is a period of time in nanosecond
+ * resolution.  ``UINT64_MAX`` is an invalid value, and it is often
+ * used to indicate that no value is set.
+ */
+typedef uint64_t ngtcp2_duration;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_cid` holds a Connection ID.
+ */
+typedef struct ngtcp2_cid {
+  /**
+   * :member:`datalen` is the length of Connection ID.
+   */
+  size_t datalen;
+  /**
+   * :member:`data` is the buffer to store Connection ID.
+   */
+  uint8_t data[NGTCP2_MAX_CIDLEN];
+} ngtcp2_cid;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_vec` is struct iovec compatible structure to
+ * reference arbitrary array of bytes.
+ */
+typedef struct ngtcp2_vec {
+  /**
+   * :member:`base` points to the data.
+   */
+  uint8_t *base;
+  /**
+   * :member:`len` is the number of bytes which the buffer pointed by
+   * base contains.
+   */
+  size_t len;
+} ngtcp2_vec;
+
+/**
+ * @function
+ *
+ * `ngtcp2_cid_init` initializes Connection ID |cid| with the byte
+ * string pointed by |data| and its length is |datalen|.  |datalen|
+ * must be at most :macro:`NGTCP2_MAX_CIDLEN`.
+ */
+NGTCP2_EXTERN void ngtcp2_cid_init(ngtcp2_cid *cid, const uint8_t *data,
+                                   size_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_cid_eq` returns nonzero if |a| and |b| share the same
+ * Connection ID.
+ */
+NGTCP2_EXTERN int ngtcp2_cid_eq(const ngtcp2_cid *a, const ngtcp2_cid *b);
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_pkt_hd` represents QUIC packet header.
+ */
+typedef struct ngtcp2_pkt_hd {
+  /**
+   * :member:`dcid` is Destination Connection ID.
+   */
+  ngtcp2_cid dcid;
+  /**
+   * :member:`scid` is Source Connection ID.
+   */
+  ngtcp2_cid scid;
+  /**
+   * :member:`pkt_num` is a packet number.
+   */
+  int64_t pkt_num;
+  /**
+   * :member:`token` contains token.  Only Initial packet may contain
+   * token.  NULL if no token is present.
+   */
+  const uint8_t *token;
+  /**
+   * :member:`tokenlen` is the length of :member:`token`.  0 if no
+   * token is present.
+   */
+  size_t tokenlen;
+  /**
+   * :member:`pkt_numlen` is the number of bytes spent to encode
+   * :member:`pkt_num`.
+   */
+  size_t pkt_numlen;
+  /**
+   * :member:`len` is the sum of :member:`pkt_numlen` and the length
+   * of QUIC packet payload.
+   */
+  size_t len;
+  /**
+   * :member:`version` is QUIC version.
+   */
+  uint32_t version;
+  /**
+   * :member:`type` is a type of QUIC packet.  This field does not
+   * have a QUIC packet type defined for a specific QUIC version.
+   * Instead, it contains version independent packet type defined by
+   * this library.  See :type:`ngtcp2_pkt_type`.
+   */
+  uint8_t type;
+  /**
+   * :member:`flags` is zero or more of :macro:`NGTCP2_PKT_FLAG_*
+   * <NGTCP2_PKT_FLAG_NONE>`.
+   */
+  uint8_t flags;
+} ngtcp2_pkt_hd;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_pkt_stateless_reset` represents Stateless Reset.
+ */
+typedef struct ngtcp2_pkt_stateless_reset {
+  /**
+   * :member:`stateless_reset_token` contains stateless reset token.
+   */
+  uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN];
+  /**
+   * :member:`rand` points a buffer which contains random bytes
+   * section.
+   */
+  const uint8_t *rand;
+  /**
+   * :member:`randlen` is the number of random bytes.
+   */
+  size_t randlen;
+} ngtcp2_pkt_stateless_reset;
+
+/**
+ * @macrosection
+ *
+ * QUIC transport parameters related macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE` is the default
+ * value of max_udp_payload_size transport parameter.
+ */
+#define NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE 65527
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_DEFAULT_ACK_DELAY_EXPONENT` is a default value of
+ * scaling factor of ACK Delay field in ACK frame.
+ */
+#define NGTCP2_DEFAULT_ACK_DELAY_EXPONENT 3
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_DEFAULT_MAX_ACK_DELAY` is a default value of the
+ * maximum amount of time in nanoseconds by which endpoint delays
+ * sending acknowledgement.
+ */
+#define NGTCP2_DEFAULT_MAX_ACK_DELAY (25 * NGTCP2_MILLISECONDS)
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT` is the default
+ * value of active_connection_id_limit transport parameter value if
+ * omitted.
+ */
+#define NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT 2
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1` is TLS
+ * extension type of quic_transport_parameters.
+ */
+#define NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1 0x39u
+
+#ifdef NGTCP2_USE_GENERIC_SOCKADDR
+#  ifndef NGTCP2_AF_INET
+#    error NGTCP2_AF_INET must be defined
+#  endif /* !defined(NGTCP2_AF_INET) */
+
+#  ifndef NGTCP2_AF_INET6
+#    error NGTCP2_AF_INET6 must be defined
+#  endif /* !defined(NGTCP2_AF_INET6) */
+
+typedef unsigned short int ngtcp2_sa_family;
+typedef uint16_t ngtcp2_in_port;
+
+typedef struct ngtcp2_sockaddr {
+  ngtcp2_sa_family sa_family;
+  uint8_t sa_data[14];
+} ngtcp2_sockaddr;
+
+typedef struct ngtcp2_in_addr {
+  uint32_t s_addr;
+} ngtcp2_in_addr;
+
+typedef struct ngtcp2_sockaddr_in {
+  ngtcp2_sa_family sin_family;
+  ngtcp2_in_port sin_port;
+  ngtcp2_in_addr sin_addr;
+  uint8_t sin_zero[8];
+} ngtcp2_sockaddr_in;
+
+typedef struct ngtcp2_in6_addr {
+  uint8_t in6_addr[16];
+} ngtcp2_in6_addr;
+
+typedef struct ngtcp2_sockaddr_in6 {
+  ngtcp2_sa_family sin6_family;
+  ngtcp2_in_port sin6_port;
+  uint32_t sin6_flowinfo;
+  ngtcp2_in6_addr sin6_addr;
+  uint32_t sin6_scope_id;
+} ngtcp2_sockaddr_in6;
+
+typedef uint32_t ngtcp2_socklen;
+#else /* !defined(NGTCP2_USE_GENERIC_SOCKADDR) */
+#  define NGTCP2_AF_INET AF_INET
+#  define NGTCP2_AF_INET6 AF_INET6
+
+/**
+ * @typedef
+ *
+ * :type:`ngtcp2_sockaddr` is typedefed to struct sockaddr.  If
+ * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to
+ * the generic struct sockaddr defined in ngtcp2.h.
+ */
+typedef struct sockaddr ngtcp2_sockaddr;
+/**
+ * @typedef
+ *
+ * :type:`ngtcp2_sockaddr_in` is typedefed to struct sockaddr_in.  If
+ * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to
+ * the generic struct sockaddr_in defined in ngtcp2.h.
+ */
+typedef struct sockaddr_in ngtcp2_sockaddr_in;
+/**
+ * @typedef
+ *
+ * :type:`ngtcp2_sockaddr_in6` is typedefed to struct sockaddr_in6.
+ * If :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed
+ * to the generic struct sockaddr_in6 defined in ngtcp2.h.
+ */
+typedef struct sockaddr_in6 ngtcp2_sockaddr_in6;
+/**
+ * @typedef
+ *
+ * :type:`ngtcp2_socklen` is typedefed to socklen_t.  If
+ * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to
+ * uint32_t.
+ */
+typedef socklen_t ngtcp2_socklen;
+#endif /* !defined(NGTCP2_USE_GENERIC_SOCKADDR) */
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_sockaddr_union` conveniently includes all supported
+ * address types.
+ */
+typedef union ngtcp2_sockaddr_union {
+  ngtcp2_sockaddr sa;
+  ngtcp2_sockaddr_in in;
+  ngtcp2_sockaddr_in6 in6;
+} ngtcp2_sockaddr_union;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_preferred_addr` represents preferred address
+ * structure.
+ */
+typedef struct ngtcp2_preferred_addr {
+  /**
+   * :member:`cid` is a Connection ID.
+   */
+  ngtcp2_cid cid;
+  /**
+   * :member:`ipv4` contains IPv4 address and port.
+   */
+  ngtcp2_sockaddr_in ipv4;
+  /**
+   * :member:`ipv6` contains IPv6 address and port.
+   */
+  ngtcp2_sockaddr_in6 ipv6;
+  /**
+   * :member:`ipv4_present` indicates that :member:`ipv4` contains
+   * IPv4 address and port.
+   */
+  uint8_t ipv4_present;
+  /**
+   * :member:`ipv6_present` indicates that :member:`ipv6` contains
+   * IPv6 address and port.
+   */
+  uint8_t ipv6_present;
+  /**
+   * :member:`stateless_reset_token` contains stateless reset token.
+   */
+  uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN];
+} ngtcp2_preferred_addr;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_version_info` represents version_information
+ * structure.  See :rfc:`9368`.
+ */
+typedef struct ngtcp2_version_info {
+  /**
+   * :member:`chosen_version` is the version chosen by the sender.
+   */
+  uint32_t chosen_version;
+  /**
+   * :member:`available_versions` points the wire image of
+   * available_versions field.  The each version is therefore in
+   * network byte order.
+   */
+  const uint8_t *available_versions;
+  /**
+   * :member:`available_versionslen` is the number of bytes pointed by
+   * :member:`available_versions`, not the number of versions
+   * included.
+   */
+  size_t available_versionslen;
+} ngtcp2_version_info;
+
+#define NGTCP2_TRANSPORT_PARAMS_V1 1
+#define NGTCP2_TRANSPORT_PARAMS_VERSION NGTCP2_TRANSPORT_PARAMS_V1
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_transport_params` represents QUIC transport
+ * parameters.
+ */
+typedef struct ngtcp2_transport_params {
+  /**
+   * :member:`preferred_addr` contains preferred address if
+   * :member:`preferred_addr_present` is nonzero.
+   */
+  ngtcp2_preferred_addr preferred_addr;
+  /**
+   * :member:`original_dcid` is the Destination Connection ID field
+   * from the first Initial packet from client.  Server must specify
+   * this field and set :member:`original_dcid_present` to nonzero.
+   * It is expected that application knows the original Destination
+   * Connection ID even if it sends Retry packet, for example, by
+   * including it in retry token.  Otherwise, application should not
+   * specify this field.
+   */
+  ngtcp2_cid original_dcid;
+  /**
+   * :member:`initial_scid` is the Source Connection ID field from the
+   * first Initial packet the local endpoint sends.  Application
+   * should not specify this field.  If :member:`initial_scid_present`
+   * is set to nonzero, it indicates this field is set.
+   */
+  ngtcp2_cid initial_scid;
+  /**
+   * :member:`retry_scid` is the Source Connection ID field from Retry
+   * packet.  Only server uses this field.  If server application
+   * received Initial packet with retry token from client, and server
+   * successfully verified its token, server application must set
+   * Destination Connection ID field from the Initial packet to this
+   * field, and set :member:`retry_scid_present` to nonzero.  Server
+   * application must verify that the Destination Connection ID from
+   * Initial packet was sent in Retry packet by, for example,
+   * including the Connection ID in a token, or including it in AAD
+   * when encrypting a token.
+   */
+  ngtcp2_cid retry_scid;
+  /**
+   * :member:`initial_max_stream_data_bidi_local` is the size of flow
+   * control window of locally initiated stream.  This is the number
+   * of bytes that the remote endpoint can send, and the local
+   * endpoint must ensure that it has enough buffer to receive them.
+   */
+  uint64_t initial_max_stream_data_bidi_local;
+  /**
+   * :member:`initial_max_stream_data_bidi_remote` is the size of flow
+   * control window of remotely initiated stream.  This is the number
+   * of bytes that the remote endpoint can send, and the local
+   * endpoint must ensure that it has enough buffer to receive them.
+   */
+  uint64_t initial_max_stream_data_bidi_remote;
+  /**
+   * :member:`initial_max_stream_data_uni` is the size of flow control
+   * window of remotely initiated unidirectional stream.  This is the
+   * number of bytes that the remote endpoint can send, and the local
+   * endpoint must ensure that it has enough buffer to receive them.
+   */
+  uint64_t initial_max_stream_data_uni;
+  /**
+   * :member:`initial_max_data` is the connection level flow control
+   * window.
+   */
+  uint64_t initial_max_data;
+  /**
+   * :member:`initial_max_streams_bidi` is the number of concurrent
+   * streams that the remote endpoint can create.
+   */
+  uint64_t initial_max_streams_bidi;
+  /**
+   * :member:`initial_max_streams_uni` is the number of concurrent
+   * unidirectional streams that the remote endpoint can create.
+   */
+  uint64_t initial_max_streams_uni;
+  /**
+   * :member:`max_idle_timeout` is a duration during which sender
+   * allows quiescent.  0 means no idle timeout.  It must not be
+   * UINT64_MAX.
+   */
+  ngtcp2_duration max_idle_timeout;
+  /**
+   * :member:`max_udp_payload_size` is the maximum UDP payload size
+   * that the local endpoint can receive.
+   */
+  uint64_t max_udp_payload_size;
+  /**
+   * :member:`active_connection_id_limit` is the maximum number of
+   * Connection ID that sender can store.  If specified, it must be in
+   * the range of [:macro:`NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT`,
+   * 8], inclusive.
+   */
+  uint64_t active_connection_id_limit;
+  /**
+   * :member:`ack_delay_exponent` is the exponent used in ACK Delay
+   * field in ACK frame.
+   */
+  uint64_t ack_delay_exponent;
+  /**
+   * :member:`max_ack_delay` is the maximum acknowledgement delay by
+   * which the local endpoint will delay sending acknowledgements.  It
+   * must be strictly less than (1 << 14) milliseconds.
+   * Sub-millisecond part is dropped when sending it in a QUIC
+   * transport parameter.
+   */
+  ngtcp2_duration max_ack_delay;
+  /**
+   * :member:`max_datagram_frame_size` is the maximum size of DATAGRAM
+   * frame that the local endpoint willingly receives.  Specifying 0
+   * disables DATAGRAM support.  See :rfc:`9221`.
+   */
+  uint64_t max_datagram_frame_size;
+  /**
+   * :member:`stateless_reset_token_present` is nonzero if
+   * :member:`stateless_reset_token` field is set.
+   */
+  uint8_t stateless_reset_token_present;
+  /**
+   * :member:`disable_active_migration` is nonzero if the local
+   * endpoint does not support active connection migration.
+   */
+  uint8_t disable_active_migration;
+  /**
+   * :member:`original_dcid_present` is nonzero if
+   * :member:`original_dcid` field is set.
+   */
+  uint8_t original_dcid_present;
+  /**
+   * :member:`initial_scid_present` is nonzero if
+   * :member:`initial_scid` field is set.
+   */
+  uint8_t initial_scid_present;
+  /**
+   * :member:`retry_scid_present` is nonzero if :member:`retry_scid`
+   * field is set.
+   */
+  uint8_t retry_scid_present;
+  /**
+   * :member:`preferred_addr_present` is nonzero if
+   * :member:`preferred_address` is set.
+   */
+  uint8_t preferred_addr_present;
+  /**
+   * :member:`stateless_reset_token` contains stateless reset token.
+   */
+  uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN];
+  /**
+   * :member:`grease_quic_bit` is nonzero if sender supports "Greasing
+   * the QUIC Bit" extension.  See :rfc:`9287`.
+   */
+  uint8_t grease_quic_bit;
+  /**
+   * :member:`version_info` contains version_information field if
+   * :member:`version_info_present` is nonzero.  Application should
+   * not specify this field.
+   */
+  ngtcp2_version_info version_info;
+  /**
+   * :member:`version_info_present` is nonzero if
+   * :member:`version_info` is set.  Application should not specify
+   * this field.
+   */
+  uint8_t version_info_present;
+} ngtcp2_transport_params;
+
+#define NGTCP2_CONN_INFO_V1 1
+#define NGTCP2_CONN_INFO_VERSION NGTCP2_CONN_INFO_V1
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_conn_info` holds various connection statistics.
+ */
+typedef struct ngtcp2_conn_info {
+  /**
+   * :member:`latest_rtt` is the latest RTT sample which is not
+   * adjusted by acknowledgement delay.
+   */
+  ngtcp2_duration latest_rtt;
+  /**
+   * :member:`min_rtt` is the minimum RTT seen so far.  It is not
+   * adjusted by acknowledgement delay.
+   */
+  ngtcp2_duration min_rtt;
+  /**
+   * :member:`smoothed_rtt` is the smoothed RTT.
+   */
+  ngtcp2_duration smoothed_rtt;
+  /**
+   * :member:`rttvar` is a mean deviation of observed RTT.
+   */
+  ngtcp2_duration rttvar;
+  /**
+   * :member:`cwnd` is the size of congestion window.
+   */
+  uint64_t cwnd;
+  /**
+   * :member:`ssthresh` is slow start threshold.
+   */
+  uint64_t ssthresh;
+  /**
+   * :member:`bytes_in_flight` is the number in bytes of all sent
+   * packets which have not been acknowledged.
+   */
+  uint64_t bytes_in_flight;
+} ngtcp2_conn_info;
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_cc_algo` defines congestion control algorithms.
+ */
+typedef enum ngtcp2_cc_algo {
+  /**
+   * :enum:`NGTCP2_CC_ALGO_RENO` represents Reno.
+   */
+  NGTCP2_CC_ALGO_RENO = 0x00,
+  /**
+   * :enum:`NGTCP2_CC_ALGO_CUBIC` represents Cubic.
+   */
+  NGTCP2_CC_ALGO_CUBIC = 0x01,
+  /**
+   * :enum:`NGTCP2_CC_ALGO_BBR` represents BBR v2.
+   */
+  NGTCP2_CC_ALGO_BBR = 0x02
+} ngtcp2_cc_algo;
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_printf` is a callback function for logging.
+ * |user_data| is the same object passed to `ngtcp2_conn_client_new`
+ * or `ngtcp2_conn_server_new`.
+ */
+typedef void (*ngtcp2_printf)(void *user_data, const char *format, ...);
+
+/**
+ * @macrosection
+ *
+ * QLog related macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_QLOG_WRITE_FLAG_NONE` indicates no flag set.
+ */
+#define NGTCP2_QLOG_WRITE_FLAG_NONE 0x00u
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_QLOG_WRITE_FLAG_FIN` indicates that this is the
+ * final call to :type:`ngtcp2_qlog_write` in the current connection.
+ */
+#define NGTCP2_QLOG_WRITE_FLAG_FIN 0x01u
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_rand_ctx` is a wrapper around native random number
+ * generator.  It is opaque to the ngtcp2 library.  This might be
+ * useful if application needs to specify random number generator per
+ * thread or per connection.
+ */
+typedef struct ngtcp2_rand_ctx {
+  /**
+   * :member:`native_handle` is a pointer to an underlying random
+   * number generator.
+   */
+  void *native_handle;
+} ngtcp2_rand_ctx;
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_qlog_write` is a callback function which is called to
+ * write qlog |data| of length |datalen| bytes.  |flags| is bitwise OR
+ * of zero or more of :macro:`NGTCP2_QLOG_WRITE_FLAG_*
+ * <NGTCP2_QLOG_WRITE_FLAG_NONE>`.  If
+ * :macro:`NGTCP2_QLOG_WRITE_FLAG_FIN` is set, |datalen| may be 0.
+ */
+typedef void (*ngtcp2_qlog_write)(void *user_data, uint32_t flags,
+                                  const void *data, size_t datalen);
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_token_type` defines the type of token.
+ */
+typedef enum ngtcp2_token_type {
+  /**
+   * :enum:`NGTCP2_TOKEN_TYPE_UNKNOWN` indicates that the type of
+   * token is unknown.
+   */
+  NGTCP2_TOKEN_TYPE_UNKNOWN,
+  /**
+   * :enum:`NGTCP2_TOKEN_TYPE_RETRY` indicates that a token comes from
+   * Retry packet.
+   */
+  NGTCP2_TOKEN_TYPE_RETRY,
+  /**
+   * :enum:`NGTCP2_TOKEN_TYPE_NEW_TOKEN` indicates that a token comes
+   * from NEW_TOKEN frame.
+   */
+  NGTCP2_TOKEN_TYPE_NEW_TOKEN
+} ngtcp2_token_type;
+
+#define NGTCP2_SETTINGS_V1 1
+#define NGTCP2_SETTINGS_V2 2
+#define NGTCP2_SETTINGS_VERSION NGTCP2_SETTINGS_V2
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_settings` defines QUIC connection settings.
+ */
+typedef struct ngtcp2_settings {
+  /**
+   * :member:`qlog_write` is a callback function to write qlog.
+   * Setting ``NULL`` disables qlog.
+   */
+  ngtcp2_qlog_write qlog_write;
+  /**
+   * :member:`cc_algo` specifies congestion control algorithm.
+   */
+  ngtcp2_cc_algo cc_algo;
+  /**
+   * :member:`initial_ts` is an initial timestamp given to the
+   * library.
+   */
+  ngtcp2_tstamp initial_ts;
+  /**
+   * :member:`initial_rtt` is an initial RTT.
+   */
+  ngtcp2_duration initial_rtt;
+  /**
+   * :member:`log_printf` is a function that the library uses to write
+   * logs.  ``NULL`` means no logging output.  It is nothing to do
+   * with qlog.
+   */
+  ngtcp2_printf log_printf;
+  /**
+   * :member:`max_tx_udp_payload_size` is the maximum size of UDP
+   * datagram payload that the local endpoint transmits.
+   */
+  size_t max_tx_udp_payload_size;
+  /**
+   * :member:`token` is a token from Retry packet or NEW_TOKEN frame.
+   *
+   * Server sets this field if it received the token in Client Initial
+   * packet and successfully validated.  It should also set
+   * :member:`token_type` field.
+   *
+   * Client sets this field if it intends to send token in its Initial
+   * packet.
+   *
+   * `ngtcp2_conn_server_new` and `ngtcp2_conn_client_new` make a copy
+   * of token.
+   *
+   * Set NULL if there is no token.
+   */
+  const uint8_t *token;
+  /**
+   * :member:`tokenlen` is the length of :member:`token`.  Set 0 if
+   * there is no token.
+   */
+  size_t tokenlen;
+  /**
+   * :member:`token_type` is the type of token.  Server application
+   * should set this field.
+   */
+  ngtcp2_token_type token_type;
+  /**
+   * :member:`rand_ctx` is an optional random number generator to be
+   * passed to :type:`ngtcp2_rand` callback.
+   */
+  ngtcp2_rand_ctx rand_ctx;
+  /**
+   * :member:`max_window` is the maximum connection-level flow control
+   * window if connection-level window auto-tuning is enabled.  The
+   * connection-level window auto tuning is enabled if nonzero value
+   * is specified in this field.  The initial value of window size is
+   * :member:`ngtcp2_transport_params.initial_max_data`.  The window
+   * size is scaled up to the value specified in this field.
+   */
+  uint64_t max_window;
+  /**
+   * :member:`max_stream_window` is the maximum stream-level flow
+   * control window if stream-level window auto-tuning is enabled.
+   * The stream-level window auto-tuning is enabled if nonzero value
+   * is specified in this field.  The initial value of window size is
+   * :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_remote`,
+   * :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_local`,
+   * or :member:`ngtcp2_transport_params.initial_max_stream_data_uni`,
+   * depending on the type of stream.  The window size is scaled up to
+   * the value specified in this field.
+   *
+   * Please note that the auto-tuning is done per stream.  Even if the
+   * previous stream gets larger window as a result of auto-tuning,
+   * the new stream still starts with the initial value set in
+   * transport parameters.  This might become a bottleneck if
+   * congestion window of a remote server is wide open.  If this
+   * causes an issue, do not enable auto-tuning.
+   */
+  uint64_t max_stream_window;
+  /**
+   * :member:`ack_thresh` is the minimum number of the received ACK
+   * eliciting packets that trigger the immediate acknowledgement from
+   * the local endpoint.
+   */
+  size_t ack_thresh;
+  /**
+   * :member:`no_tx_udp_payload_size_shaping`, if set to nonzero,
+   * instructs the library not to limit the UDP payload size to
+   * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE` (which can be extended by
+   * Path MTU Discovery), and instead use the minimum size among the
+   * given buffer size, :member:`max_tx_udp_payload_size`, and the
+   * received max_udp_payload_size QUIC transport parameter.
+   */
+  uint8_t no_tx_udp_payload_size_shaping;
+  /**
+   * :member:`handshake_timeout` is the period of time before giving
+   * up QUIC connection establishment.  If QUIC handshake is not
+   * complete within this period, `ngtcp2_conn_handle_expiry` returns
+   * :macro:`NGTCP2_ERR_HANDSHAKE_TIMEOUT` error.  The deadline is
+   * :member:`initial_ts` + :member:`handshake_timeout`.  If this
+   * field is set to ``UINT64_MAX``, no handshake timeout is set.
+   */
+  ngtcp2_duration handshake_timeout;
+  /**
+   * :member:`preferred_versions` is the array of versions that are
+   * preferred by the local endpoint.  All versions set in this array
+   * must be supported by the library, and compatible to QUIC v1.  The
+   * reserved versions are not allowed.  They are sorted in the order
+   * of preference.
+   *
+   * On compatible version negotiation, server will negotiate one of
+   * those versions contained in this array if there is some overlap
+   * between these versions and the versions offered by the client.
+   * If there is no overlap, but the client chosen version is
+   * supported by the library, the server chooses the client chosen
+   * version as the negotiated version.  This version set corresponds
+   * to Offered Versions described in :rfc:`9368`, and it should be
+   * included in Version Negotiation packet.
+   *
+   * Client uses this field and :member:`original_version` to prevent
+   * version downgrade attack if it reacted upon Version Negotiation
+   * packet.  If this field is specified, client must include
+   * |client_chosen_version| passed to `ngtcp2_conn_client_new` unless
+   * |client_chosen_version| is a reserved version.
+   */
+  const uint32_t *preferred_versions;
+  /**
+   * :member:`preferred_versionslen` is the number of versions that
+   * are contained in the array pointed by
+   * :member:`preferred_versions`.
+   */
+  size_t preferred_versionslen;
+  /**
+   * :member:`available_versions` is the array of versions that are
+   * going to be set in :member:`available_versions
+   * <ngtcp2_version_info.available_versions>` field of outgoing
+   * version_information QUIC transport parameter.
+   *
+   * For server, this corresponds to Fully-Deployed Versions described
+   * in :rfc:`9368`.  If this field is not set, it is set to
+   * :member:`preferred_versions` internally if
+   * :member:`preferred_versionslen` is not zero.  If this field is
+   * not set, and :member:`preferred_versionslen` is zero, this field
+   * is set to :macro:`NGTCP2_PROTO_VER_V1` internally.
+   *
+   * Client must include |client_chosen_version| passed to
+   * `ngtcp2_conn_client_new` in this array if this field is set and
+   * |client_chosen_version| is not a reserved version.  If this field
+   * is not set, |client_chosen_version| passed to
+   * `ngtcp2_conn_client_new` will be set in this field internally
+   * unless |client_chosen_version| is a reserved version.
+   */
+  const uint32_t *available_versions;
+  /**
+   * :member:`available_versionslen` is the number of versions that
+   * are contained in the array pointed by
+   * :member:`available_versions`.
+   */
+  size_t available_versionslen;
+  /**
+   * :member:`original_version` is the original version that client
+   * initially used to make a connection attempt.  If it is set, and
+   * it differs from |client_chosen_version| passed to
+   * `ngtcp2_conn_client_new`, the library assumes that client reacted
+   * upon Version Negotiation packet.  Server does not use this field.
+   */
+  uint32_t original_version;
+  /**
+   * :member:`no_pmtud`, if set to nonzero, disables Path MTU
+   * Discovery.
+   */
+  uint8_t no_pmtud;
+  /**
+   * :member:`initial_pkt_num` is the initial packet number for each
+   * packet number space.  It must be in range [0, INT32_MAX],
+   * inclusive.
+   */
+  uint32_t initial_pkt_num;
+  /* The following fields have been added since NGTCP2_SETTINGS_V2. */
+  /**
+   * :member:`pmtud_probes` is the array of UDP datagram payload size
+   * to probe during Path MTU Discovery.  The discovery is done in the
+   * order appeared in this array.  The size must be strictly larger
+   * than 1200, otherwise the behavior is undefined.  The maximum
+   * value in this array should be set to
+   * :member:`max_tx_udp_payload_size`.  If this field is not set, the
+   * predefined PMTUD probes are made.  This field has been available
+   * since v1.4.0.
+   */
+  const uint16_t *pmtud_probes;
+  /**
+   * :member:`pmtud_probeslen` is the number of elements that are
+   * contained in the array pointed by :member:`pmtud_probes`.  This
+   * field has been available since v1.4.0.
+   */
+  size_t pmtud_probeslen;
+} ngtcp2_settings;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_addr` is the endpoint address.
+ */
+typedef struct ngtcp2_addr {
+  /**
+   * :member:`addr` points to the buffer which contains endpoint
+   * address.  It must not be ``NULL``.
+   */
+  ngtcp2_sockaddr *addr;
+  /**
+   * :member:`addrlen` is the length of :member:`addr`.  It must not
+   * be longer than sizeof(:type:`ngtcp2_sockaddr_union`).
+   */
+  ngtcp2_socklen addrlen;
+} ngtcp2_addr;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_path` is the network endpoints where a packet is sent
+ * and received.
+ */
+typedef struct ngtcp2_path {
+  /**
+   * :member:`local` is the address of local endpoint.
+   */
+  ngtcp2_addr local;
+  /**
+   * :member:`remote` is the address of remote endpoint.
+   */
+  ngtcp2_addr remote;
+  /**
+   * :member:`user_data` is an arbitrary data and opaque to the
+   * library.
+   *
+   * Note that :type:`ngtcp2_path` is generally passed to
+   * :type:`ngtcp2_conn` by an application, and :type:`ngtcp2_conn`
+   * stores their copies.  Unfortunately, there is no way for the
+   * application to know when :type:`ngtcp2_conn` finished using a
+   * specific :type:`ngtcp2_path` object in mid connection, which
+   * means that the application cannot free the data pointed by this
+   * field.  Therefore, it is advised to use this field only when the
+   * data pointed by this field persists in an entire lifetime of the
+   * connection.
+   */
+  void *user_data;
+} ngtcp2_path;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_path_storage` is a convenient struct to have buffers
+ * to store the longest addresses.
+ */
+typedef struct ngtcp2_path_storage {
+  /**
+   * :member:`path` stores network path.
+   */
+  ngtcp2_path path;
+  /**
+   * :member:`local_addrbuf` is a buffer to store local address.
+   */
+  ngtcp2_sockaddr_union local_addrbuf;
+  /**
+   * :member:`remote_addrbuf` is a buffer to store remote address.
+   */
+  ngtcp2_sockaddr_union remote_addrbuf;
+} ngtcp2_path_storage;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_md` is a wrapper around native message digest
+ * object.
+ */
+typedef struct ngtcp2_crypto_md {
+  /**
+   * :member:`native_handle` is a pointer to an underlying message
+   * digest object.
+   */
+  void *native_handle;
+} ngtcp2_crypto_md;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_aead` is a wrapper around native AEAD object.
+ */
+typedef struct ngtcp2_crypto_aead {
+  /**
+   * :member:`native_handle` is a pointer to an underlying AEAD
+   * object.
+   */
+  void *native_handle;
+  /**
+   * :member:`max_overhead` is the number of additional bytes which
+   * AEAD encryption needs on encryption.
+   */
+  size_t max_overhead;
+} ngtcp2_crypto_aead;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_cipher` is a wrapper around native cipher
+ * object.
+ */
+typedef struct ngtcp2_crypto_cipher {
+  /**
+   * :member:`native_handle` is a pointer to an underlying cipher
+   * object.
+   */
+  void *native_handle;
+} ngtcp2_crypto_cipher;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_aead_ctx` is a wrapper around native AEAD
+ * cipher context object.  It should be initialized with a specific
+ * key.  ngtcp2 library reuses this context object to encrypt or
+ * decrypt multiple packets.
+ */
+typedef struct ngtcp2_crypto_aead_ctx {
+  /**
+   * :member:`native_handle` is a pointer to an underlying AEAD
+   * context object.
+   */
+  void *native_handle;
+} ngtcp2_crypto_aead_ctx;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_cipher_ctx` is a wrapper around native cipher
+ * context object.  It should be initialized with a specific key.
+ * ngtcp2 library reuses this context object to encrypt or decrypt
+ * multiple packet headers.
+ */
+typedef struct ngtcp2_crypto_cipher_ctx {
+  /**
+   * :member:`native_handle` is a pointer to an underlying cipher
+   * context object.
+   */
+  void *native_handle;
+} ngtcp2_crypto_cipher_ctx;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_crypto_ctx` is a convenient structure to bind all
+ * crypto related objects in one place.  Use
+ * `ngtcp2_crypto_ctx_initial` to initialize this struct for Initial
+ * packet encryption.  For Handshake and 1-RTT packets, use
+ * `ngtcp2_crypto_ctx_tls`.  For 0-RTT packets, use
+ * `ngtcp2_crypto_ctx_tls_early`.
+ */
+typedef struct ngtcp2_crypto_ctx {
+  /**
+   * :member:`aead` is AEAD object.
+   */
+  ngtcp2_crypto_aead aead;
+  /**
+   * :member:`md` is message digest object.
+   */
+  ngtcp2_crypto_md md;
+  /**
+   * :member:`hp` is header protection cipher.
+   */
+  ngtcp2_crypto_cipher hp;
+  /**
+   * :member:`max_encryption` is the number of encryption which this
+   * key can be used with.
+   */
+  uint64_t max_encryption;
+  /**
+   * :member:`max_decryption_failure` is the number of decryption
+   * failure with this key.
+   */
+  uint64_t max_decryption_failure;
+} ngtcp2_crypto_ctx;
+
+/**
+ * @function
+ *
+ * `ngtcp2_transport_params_encode` encodes |params| in |dest| of
+ * length |destlen|.
+ *
+ * If |dest| is NULL, and |destlen| is zero, this function just
+ * returns the number of bytes required to store the encoded transport
+ * parameters.
+ *
+ * This function returns the number of bytes written, or one of the
+ * following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_transport_params_encode_versioned(
+  uint8_t *dest, size_t destlen, int transport_params_version,
+  const ngtcp2_transport_params *params);
+
+/**
+ * @function
+ *
+ * `ngtcp2_transport_params_decode` decodes transport parameters in
+ * |data| of length |datalen|, and stores the result in the object
+ * pointed by |params|.
+ *
+ * If an optional parameter is missing, the default value is assigned.
+ *
+ * The following fields may point to somewhere inside the buffer
+ * pointed by |data| of length |datalen|:
+ *
+ * - :member:`ngtcp2_transport_params.version_info.available_versions
+ *   <ngtcp2_version_info.available_versions>`
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM`
+ *     The input is malformed.
+ */
+NGTCP2_EXTERN int
+ngtcp2_transport_params_decode_versioned(int transport_params_version,
+                                         ngtcp2_transport_params *params,
+                                         const uint8_t *data, size_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_transport_params_decode_new` decodes transport parameters
+ * in |data| of length |datalen|, and stores the result in the object
+ * allocated dynamically.  The pointer to the allocated object is
+ * assigned to |*pparams|.  Unlike `ngtcp2_transport_params_decode`,
+ * all direct and indirect fields are also allocated dynamically if
+ * needed.
+ *
+ * |mem| is a memory allocator to allocate memory.  If |mem| is
+ * ``NULL``, the memory allocator returned by `ngtcp2_mem_default()`
+ * is used.
+ *
+ * If the optional parameters are missing, the default value is
+ * assigned.
+ *
+ * `ngtcp2_transport_params_del` frees the memory allocated by this
+ * function.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM`
+ *     The input is malformed.
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int
+ngtcp2_transport_params_decode_new(ngtcp2_transport_params **pparams,
+                                   const uint8_t *data, size_t datalen,
+                                   const ngtcp2_mem *mem);
+
+/**
+ * @function
+ *
+ * `ngtcp2_transport_params_del` frees the |params| which must be
+ * dynamically allocated by `ngtcp2_transport_params_decode_new`.
+ *
+ * |mem| is a memory allocator that allocated |params|.  If |mem| is
+ * ``NULL``, the memory allocator returned by `ngtcp2_mem_default()`
+ * is used.
+ *
+ * If |params| is ``NULL``, this function does nothing.
+ */
+NGTCP2_EXTERN void ngtcp2_transport_params_del(ngtcp2_transport_params *params,
+                                               const ngtcp2_mem *mem);
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_version_cid` is a convenient struct to store the
+ * result of `ngtcp2_pkt_decode_version_cid`.
+ */
+typedef struct ngtcp2_version_cid {
+  /**
+   * :member:`version` stores QUIC version.
+   */
+  uint32_t version;
+  /**
+   * :member:`dcid` points to the Destination Connection ID.
+   */
+  const uint8_t *dcid;
+  /**
+   * :member:`dcidlen` is the length of the Destination Connection ID
+   * pointed by :member:`dcid`.
+   */
+  size_t dcidlen;
+  /**
+   * :member:`scid` points to the Source Connection ID.
+   */
+  const uint8_t *scid;
+  /**
+   * :member:`scidlen` is the length of the Source Connection ID
+   * pointed by :member:`scid`.
+   */
+  size_t scidlen;
+} ngtcp2_version_cid;
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_decode_version_cid` extracts QUIC version, Destination
+ * Connection ID and Source Connection ID from the packet pointed by
+ * |data| of length |datalen|.  This function can handle Connection ID
+ * up to 255 bytes unlike `ngtcp2_pkt_decode_hd_long` or
+ * `ngtcp2_pkt_decode_hd_short` which are only capable of handling
+ * Connection ID less than or equal to :macro:`NGTCP2_MAX_CIDLEN`.
+ * Longer Connection ID is only valid if the version is unsupported
+ * QUIC version.
+ *
+ * If the given packet is Long header packet, this function extracts
+ * the version from the packet, and assigns it to
+ * :member:`dest->version <ngtcp2_version_cid.version>`.  It also
+ * extracts the pointer to the Destination Connection ID and its
+ * length, and assigns them to :member:`dest->dcid
+ * <ngtcp2_version_cid.dcid>` and :member:`dest->dcidlen
+ * <ngtcp2_version_cid.dcidlen>` respectively.  Similarly, it extracts
+ * the pointer to the Source Connection ID and its length, and assigns
+ * them to :member:`dest->scid <ngtcp2_version_cid.scid>` and
+ * :member:`dest->scidlen <ngtcp2_version_cid.scidlen>` respectively.
+ * |short_dcidlen| is ignored.
+ *
+ * If the given packet is Short header packet, :member:`dest->version
+ * <ngtcp2_version_cid.version>` will be 0, :member:`dest->scid
+ * <ngtcp2_version_cid.scid>` will be ``NULL``, and
+ * :member:`dest->scidlen <ngtcp2_version_cid.scidlen>` will be 0.
+ * Because the Short header packet does not have the length of
+ * Destination Connection ID, the caller has to pass the length in
+ * |short_dcidlen|.  This function extracts the pointer to the
+ * Destination Connection ID, and assigns it to :member:`dest->dcid
+ * <ngtcp2_version_cid.dcid>`.  |short_dcidlen| is assigned to
+ * :member:`dest->dcidlen <ngtcp2_version_cid.dcidlen>`.
+ *
+ * If Version Negotiation is required, this function returns
+ * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`.  Unlike the other error
+ * cases, all fields of |dest| are assigned as described above.
+ *
+ * This function returns 0 if it succeeds.  Otherwise, one of the
+ * following negative error code:
+ *
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     The function could not decode the packet header.
+ * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`
+ *     Version Negotiation packet should be sent.
+ */
+NGTCP2_EXTERN int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest,
+                                                const uint8_t *data,
+                                                size_t datalen,
+                                                size_t short_dcidlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_decode_hd_long` decodes QUIC long packet header in
+ * |pkt| of length |pktlen|.  This function only parses the input just
+ * before packet number field.
+ *
+ * This function does not verify that length field is correct.  In
+ * other words, this function succeeds even if length > |pktlen|.
+ *
+ * This function can handle Connection ID up to
+ * :macro:`NGTCP2_MAX_CIDLEN`.  Consider to use
+ * `ngtcp2_pkt_decode_version_cid` to get longer Connection ID.
+ *
+ * This function handles Version Negotiation specially.  If version
+ * field is 0, |pkt| must contain Version Negotiation packet.  Version
+ * Negotiation packet has random type in wire format.  For
+ * convenience, this function sets
+ * :enum:`ngtcp2_pkt_type.NGTCP2_PKT_VERSION_NEGOTIATION` to
+ * :member:`dest->type <ngtcp2_pkt_hd.type>`, clears
+ * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` flag from :member:`dest->flags
+ * <ngtcp2_pkt_hd.flags>`, and sets 0 to :member:`dest->len
+ * <ngtcp2_pkt_hd.len>`.  Version Negotiation packet occupies a single
+ * packet.
+ *
+ * It stores the result in the object pointed by |dest|, and returns
+ * the number of bytes decoded to read the packet header if it
+ * succeeds, or one of the following error codes:
+ *
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     Packet is too short; or it is not a long header
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest,
+                                                     const uint8_t *pkt,
+                                                     size_t pktlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_decode_hd_short` decodes QUIC short header in |pkt| of
+ * length |pktlen|.  Short header packet does not encode the length of
+ * Connection ID, thus we need the input from the outside.  |dcidlen|
+ * is the length of Destination Connection ID in packet header.  This
+ * function only parses the input just before packet number field.
+ * This function can handle Connection ID up to
+ * :macro:`NGTCP2_MAX_CIDLEN`.  Consider to use
+ * `ngtcp2_pkt_decode_version_cid` to get longer Connection ID.  It
+ * stores the result in the object pointed by |dest|, and returns the
+ * number of bytes decoded to read the packet header if it succeeds,
+ * or one of the following error codes:
+ *
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     Packet is too short; or it is not a short header
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest,
+                                                      const uint8_t *pkt,
+                                                      size_t pktlen,
+                                                      size_t dcidlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_write_stateless_reset` writes Stateless Reset packet in
+ * the buffer pointed by |dest| whose length is |destlen|.
+ * |stateless_reset_token| is a pointer to the Stateless Reset Token,
+ * and its length must be :macro:`NGTCP2_STATELESS_RESET_TOKENLEN`
+ * bytes long.  |rand| specifies the random octets preceding Stateless
+ * Reset Token.  The length of |rand| is specified by |randlen| which
+ * must be at least :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN` bytes
+ * long.
+ *
+ * If |randlen| is too long to write them all in the buffer, |rand| is
+ * written to the buffer as much as possible, and is truncated.
+ *
+ * This function returns the number of bytes written to the buffer, or
+ * one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small.
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     |randlen| is strictly less than
+ *     :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN`.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_stateless_reset(
+  uint8_t *dest, size_t destlen, const uint8_t *stateless_reset_token,
+  const uint8_t *rand, size_t randlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_write_version_negotiation` writes Version Negotiation
+ * packet in the buffer pointed by |dest| whose length is |destlen|.
+ * |unused_random| should be generated randomly.  |dcid| is a
+ * Connection ID which appeared in a packet as a Source Connection ID
+ * sent by client which caused version negotiation.  Similarly, |scid|
+ * is a Connection ID which appeared in a packet as a Destination
+ * Connection ID sent by client.  |sv| is a list of supported
+ * versions, and |nsv| specifies the number of supported versions
+ * included in |sv|.
+ *
+ * This function returns the number of bytes written to the buffer, or
+ * one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_version_negotiation(
+  uint8_t *dest, size_t destlen, uint8_t unused_random, const uint8_t *dcid,
+  size_t dcidlen, const uint8_t *scid, size_t scidlen, const uint32_t *sv,
+  size_t nsv);
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_conn` represents a single QUIC connection.
+ */
+typedef struct ngtcp2_conn ngtcp2_conn;
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_client_initial` is invoked when client application
+ * asks TLS stack to produce first TLS cryptographic handshake data.
+ *
+ * This implementation of this callback must get the first handshake
+ * data from TLS stack, and pass it to ngtcp2 library using
+ * `ngtcp2_conn_submit_crypto_data` function.  Make sure that before
+ * calling `ngtcp2_conn_submit_crypto_data` function, client
+ * application must create initial packet protection keys and IVs, and
+ * provide them to ngtcp2 library using
+ * `ngtcp2_conn_install_initial_key`.
+ *
+ * This callback function must return 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call
+ * return immediately.
+ */
+typedef int (*ngtcp2_client_initial)(ngtcp2_conn *conn, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_client_initial` is invoked when server receives
+ * Initial packet from client.  An server application must implement
+ * this callback, and generate initial keys and IVs for both
+ * transmission and reception.  Install them using
+ * `ngtcp2_conn_install_initial_key`.  |dcid| is the Destination
+ * Connection ID in Initial packet received from client.  It is used
+ * to derive initial packet protection keys.
+ *
+ * The callback function must return 0 if it succeeds.  If an error
+ * occurs, return :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the
+ * library call return immediately.
+ */
+typedef int (*ngtcp2_recv_client_initial)(ngtcp2_conn *conn,
+                                          const ngtcp2_cid *dcid,
+                                          void *user_data);
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_encryption_level` is QUIC encryption level.
+ */
+typedef enum ngtcp2_encryption_level {
+  /**
+   * :enum:`NGTCP2_ENCRYPTION_LEVEL_INITIAL` is Initial encryption
+   * level.
+   */
+  NGTCP2_ENCRYPTION_LEVEL_INITIAL,
+  /**
+   * :enum:`NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE` is Handshake encryption
+   * level.
+   */
+  NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE,
+  /**
+   * :enum:`NGTCP2_ENCRYPTION_LEVEL_1RTT` is 1-RTT encryption level.
+   */
+  NGTCP2_ENCRYPTION_LEVEL_1RTT,
+  /**
+   * :enum:`NGTCP2_ENCRYPTION_LEVEL_0RTT` is 0-RTT encryption level.
+   */
+  NGTCP2_ENCRYPTION_LEVEL_0RTT
+} ngtcp2_encryption_level;
+
+/**
+ * @functypedef
+ *
+ * :type`ngtcp2_recv_crypto_data` is invoked when crypto data is
+ * received.  The received data is pointed by |data|, and its length
+ * is |datalen|.  The |offset| specifies the offset where |data| is
+ * positioned.  |user_data| is the arbitrary pointer passed to
+ * `ngtcp2_conn_client_new` or `ngtcp2_conn_server_new`.  The ngtcp2
+ * library ensures that the crypto data is passed to the application
+ * in the increasing order of |offset|.  |datalen| is always strictly
+ * greater than 0.  |encryption_level| indicates the encryption level
+ * where this data is received.  Crypto data can never be received in
+ * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_0RTT`.
+ *
+ * The application should provide the given data to TLS stack.
+ *
+ * The callback function must return 0 if it succeeds, or one of the
+ * following negative error codes:
+ *
+ * - :macro:`NGTCP2_ERR_CRYPTO`
+ * - :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM`
+ * - :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM`
+ * - :macro:`NGTCP2_ERR_TRANSPORT_PARAM`
+ * - :macro:`NGTCP2_ERR_PROTO`
+ * - :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE`
+ * - :macro:`NGTCP2_ERR_NOMEM`
+ * - :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *
+ * If the other value is returned, it is treated as
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`.
+ *
+ * If application encounters fatal error, return
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call
+ * return immediately.
+ */
+typedef int (*ngtcp2_recv_crypto_data)(ngtcp2_conn *conn,
+                                       ngtcp2_encryption_level encryption_level,
+                                       uint64_t offset, const uint8_t *data,
+                                       size_t datalen, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_handshake_completed` is invoked when QUIC
+ * cryptographic handshake has completed.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_handshake_completed)(ngtcp2_conn *conn, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_handshake_confirmed` is invoked when QUIC
+ * cryptographic handshake is confirmed.  The handshake confirmation
+ * means that both endpoints agree that handshake has finished.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_handshake_confirmed)(ngtcp2_conn *conn, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_version_negotiation` is invoked when Version
+ * Negotiation packet is received.  |hd| is the pointer to the QUIC
+ * packet header object.  The vector |sv| of |nsv| elements contains
+ * the QUIC version the server supports.  Since Version Negotiation is
+ * only sent by server, this callback function is used by client only.
+ *
+ * The callback function must return 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call
+ * return immediately.
+ */
+typedef int (*ngtcp2_recv_version_negotiation)(ngtcp2_conn *conn,
+                                               const ngtcp2_pkt_hd *hd,
+                                               const uint32_t *sv, size_t nsv,
+                                               void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_retry` is invoked when Retry packet is received.
+ * This callback is client use only.
+ *
+ * Application must regenerate packet protection key, IV, and header
+ * protection key for Initial packets using the Destination Connection
+ * ID obtained by :member:`hd->scid <ngtcp2_pkt_hd.scid>`, and install
+ * them by calling `ngtcp2_conn_install_initial_key`.
+ *
+ * 0-RTT data accepted by the ngtcp2 library will be automatically
+ * retransmitted as 0-RTT data by the library.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_recv_retry)(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
+                                 void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_encrypt` is invoked when the ngtcp2 library asks the
+ * application to encrypt packet payload.  The packet payload to
+ * encrypt is passed as |plaintext| of length |plaintextlen|.  The
+ * AEAD cipher is |aead|.  |aead_ctx| is the AEAD cipher context
+ * object which is initialized with the specific encryption key.  The
+ * nonce is passed as |nonce| of length |noncelen|.  The Additional
+ * Authenticated Data is passed as |aad| of length |aadlen|.
+ *
+ * The implementation of this callback must encrypt |plaintext| using
+ * the negotiated cipher suite, and write the ciphertext into the
+ * buffer pointed by |dest|.  |dest| has enough capacity to store the
+ * ciphertext and any additional AEAD tag data.
+ *
+ * |dest| and |plaintext| may point to the same buffer.
+ *
+ * The callback function must return 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call
+ * return immediately.
+ */
+typedef int (*ngtcp2_encrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                              const ngtcp2_crypto_aead_ctx *aead_ctx,
+                              const uint8_t *plaintext, size_t plaintextlen,
+                              const uint8_t *nonce, size_t noncelen,
+                              const uint8_t *aad, size_t aadlen);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_decrypt` is invoked when the ngtcp2 library asks the
+ * application to decrypt packet payload.  The packet payload to
+ * decrypt is passed as |ciphertext| of length |ciphertextlen|.  The
+ * AEAD cipher is |aead|.  |aead_ctx| is the AEAD cipher context
+ * object which is initialized with the specific decryption key.  The
+ * nonce is passed as |nonce| of length |noncelen|.  The Additional
+ * Authenticated Data is passed as |aad| of length |aadlen|.
+ *
+ * The implementation of this callback must decrypt |ciphertext| using
+ * the negotiated cipher suite, and write the ciphertext into the
+ * buffer pointed by |dest|.  |dest| has enough capacity to store the
+ * cleartext.
+ *
+ * |dest| and |ciphertext| may point to the same buffer.
+ *
+ * The callback function must return 0 if it succeeds.  If TLS stack
+ * fails to decrypt data, return :macro:`NGTCP2_ERR_DECRYPT`.  For any
+ * other errors, return :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which
+ * makes the library call return immediately.
+ */
+typedef int (*ngtcp2_decrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                              const ngtcp2_crypto_aead_ctx *aead_ctx,
+                              const uint8_t *ciphertext, size_t ciphertextlen,
+                              const uint8_t *nonce, size_t noncelen,
+                              const uint8_t *aad, size_t aadlen);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_hp_mask` is invoked when the ngtcp2 library asks the
+ * application to produce a mask to encrypt or decrypt packet header.
+ * The encryption cipher is |hp|.  |hp_ctx| is the cipher context
+ * object which is initialized with the specific header protection
+ * key.  The sample is passed as |sample| which is
+ * :macro:`NGTCP2_HP_SAMPLELEN` bytes long.
+ *
+ * The implementation of this callback must produce a mask using the
+ * header protection cipher suite specified by QUIC specification, and
+ * write the result into the buffer pointed by |dest|.  The length of
+ * the mask must be at least :macro:`NGTCP2_HP_MASKLEN`.  The library
+ * only uses the first :macro:`NGTCP2_HP_MASKLEN` bytes of the
+ * produced mask.  The buffer pointed by |dest| is guaranteed to have
+ * at least :macro:`NGTCP2_HP_SAMPLELEN` bytes available for
+ * convenience.
+ *
+ * The callback function must return 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call
+ * return immediately.
+ */
+typedef int (*ngtcp2_hp_mask)(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+                              const ngtcp2_crypto_cipher_ctx *hp_ctx,
+                              const uint8_t *sample);
+
+/**
+ * @macrosection
+ *
+ * STREAM frame data flags
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_STREAM_DATA_FLAG_NONE` indicates no flag set.
+ */
+#define NGTCP2_STREAM_DATA_FLAG_NONE 0x00u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_STREAM_DATA_FLAG_FIN` indicates that this chunk of
+ * data is final piece of an incoming stream.
+ */
+#define NGTCP2_STREAM_DATA_FLAG_FIN 0x01u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_STREAM_DATA_FLAG_0RTT` indicates that this chunk of
+ * data contains data received in 0-RTT packet, and the handshake has
+ * not completed yet, which means that the data might be replayed.
+ */
+#define NGTCP2_STREAM_DATA_FLAG_0RTT 0x02u
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_stream_data` is invoked when stream data is
+ * received.  The stream is specified by |stream_id|.  |flags| is the
+ * bitwise-OR of zero or more of :macro:`NGTCP2_STREAM_DATA_FLAG_*
+ * <NGTCP2_STREAM_DATA_FLAG_NONE>`.  If |flags| &
+ * :macro:`NGTCP2_STREAM_DATA_FLAG_FIN` is nonzero, this portion of
+ * the data is the last data in this stream.  |offset| is the offset
+ * where this data begins.  The library ensures that data is passed to
+ * the application in the non-decreasing order of |offset| without any
+ * overlap.  The data is passed as |data| of length |datalen|.
+ * |datalen| may be 0 if and only if |fin| is nonzero.
+ *
+ * If :macro:`NGTCP2_STREAM_DATA_FLAG_0RTT` is set in |flags|, it
+ * indicates that a part of or whole data was received in 0-RTT
+ * packet, and a handshake has not completed yet.
+ *
+ * The callback function must return 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return
+ * immediately.
+ */
+typedef int (*ngtcp2_recv_stream_data)(ngtcp2_conn *conn, uint32_t flags,
+                                       int64_t stream_id, uint64_t offset,
+                                       const uint8_t *data, size_t datalen,
+                                       void *user_data, void *stream_user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_stream_open` is a callback function which is called
+ * when remote stream is opened by a remote endpoint.  This function
+ * is not called if stream is opened by implicitly (we might
+ * reconsider this behaviour later).
+ *
+ * The implementation of this callback should return 0 if it succeeds.
+ * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library
+ * call return immediately.
+ */
+typedef int (*ngtcp2_stream_open)(ngtcp2_conn *conn, int64_t stream_id,
+                                  void *user_data);
+
+/**
+ * @macrosection
+ *
+ * Stream close flags
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_STREAM_CLOSE_FLAG_NONE` indicates no flag set.
+ */
+#define NGTCP2_STREAM_CLOSE_FLAG_NONE 0x00u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` indicates that
+ * app_error_code parameter is set.
+ */
+#define NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET 0x01u
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_stream_close` is invoked when a stream is closed.
+ * This callback is not called when QUIC connection is closed before
+ * existing streams are closed.  |flags| is the bitwise-OR of zero or
+ * more of :macro:`NGTCP2_STREAM_CLOSE_FLAG_*
+ * <NGTCP2_STREAM_CLOSE_FLAG_NONE>`.  |app_error_code| indicates the
+ * error code of this closure if
+ * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` is set in
+ * |flags|.  If it is not set, the stream was closed without any error
+ * code, which generally means success.
+ *
+ * |app_error_code| is the first application error code sent by a
+ * local endpoint, or received from a remote endpoint.  If a stream is
+ * closed cleanly, no application error code is exchanged.  Since QUIC
+ * stack does not know the application error code which indicates "no
+ * errors", |app_error_code| is set to 0 and
+ * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` is not set in
+ * |flags| in this case.
+ *
+ * The implementation of this callback should return 0 if it succeeds.
+ * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library
+ * call return immediately.
+ */
+typedef int (*ngtcp2_stream_close)(ngtcp2_conn *conn, uint32_t flags,
+                                   int64_t stream_id, uint64_t app_error_code,
+                                   void *user_data, void *stream_user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_stream_reset` is invoked when a stream identified by
+ * |stream_id| is reset by a remote endpoint.
+ *
+ * The implementation of this callback should return 0 if it succeeds.
+ * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library
+ * call return immediately.
+ */
+typedef int (*ngtcp2_stream_reset)(ngtcp2_conn *conn, int64_t stream_id,
+                                   uint64_t final_size, uint64_t app_error_code,
+                                   void *user_data, void *stream_user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_acked_stream_data_offset` is a callback function
+ * which is called when stream data in range [|offset|, |offset| +
+ * |datalen|) is acknowledged, and application can free the portion of
+ * data.  For a given |stream_id|, this callback is called
+ * sequentially in increasing order of |offset| without any overlap.
+ * |datalen| is normally strictly greater than 0.  One exception is
+ * that when a STREAM frame has fin flag set and 0 length data, this
+ * callback is invoked with |datalen| == 0.
+ *
+ * If a stream is closed prematurely, and stream data is still
+ * in-flight, this callback function is not called for those data.
+ * After :member:`ngtcp2_callbacks.stream_close` is called for a
+ * particular stream, |conn| does not touch data for the closed stream
+ * again, and application can free all unacknowledged stream data.
+ *
+ * The implementation of this callback should return 0 if it succeeds.
+ * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library
+ * call return immediately.
+ */
+typedef int (*ngtcp2_acked_stream_data_offset)(
+  ngtcp2_conn *conn, int64_t stream_id, uint64_t offset, uint64_t datalen,
+  void *user_data, void *stream_user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_stateless_reset` is a callback function which is
+ * called when Stateless Reset packet is received.  The stateless
+ * reset details are given in |sr|.
+ *
+ * The implementation of this callback should return 0 if it succeeds.
+ * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library
+ * call return immediately.
+ */
+typedef int (*ngtcp2_recv_stateless_reset)(ngtcp2_conn *conn,
+                                           const ngtcp2_pkt_stateless_reset *sr,
+                                           void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_extend_max_streams` is a callback function which is
+ * called every time max stream ID is strictly extended.
+ * |max_streams| is the cumulative number of streams which an endpoint
+ * can open.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_extend_max_streams)(ngtcp2_conn *conn,
+                                         uint64_t max_streams, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_extend_max_stream_data` is a callback function which
+ * is invoked when max stream data is extended.  |stream_id|
+ * identifies the stream.  |max_data| is a cumulative number of bytes
+ * an endpoint can send on this stream.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_extend_max_stream_data)(ngtcp2_conn *conn,
+                                             int64_t stream_id,
+                                             uint64_t max_data, void *user_data,
+                                             void *stream_user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_rand` is a callback function to get random data of
+ * length |destlen|.  Application must fill random |destlen| bytes to
+ * the buffer pointed by |dest|.  The generated data is used only in
+ * non-cryptographic context.  But it is strongly recommended to use a
+ * secure random number generator.
+ */
+typedef void (*ngtcp2_rand)(uint8_t *dest, size_t destlen,
+                            const ngtcp2_rand_ctx *rand_ctx);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_get_new_connection_id` is a callback function to ask
+ * an application for new connection ID.  Application must generate
+ * new unused connection ID with the exact |cidlen| bytes, and store
+ * it in |cid|.  It also has to generate a stateless reset token, and
+ * store it in |token|.  The length of stateless reset token is
+ * :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` and it is guaranteed that
+ * the buffer pointed by |token| has the sufficient space to store the
+ * token.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_get_new_connection_id)(ngtcp2_conn *conn, ngtcp2_cid *cid,
+                                            uint8_t *token, size_t cidlen,
+                                            void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_remove_connection_id` is a callback function which
+ * notifies the application that connection ID |cid| is no longer used
+ * by a remote endpoint.  This Connection ID was previously offered by
+ * a local endpoint, and a remote endpoint could use it as Destination
+ * Connection ID when sending QUIC packet.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_remove_connection_id)(ngtcp2_conn *conn,
+                                           const ngtcp2_cid *cid,
+                                           void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_update_key` is a callback function which tells the
+ * application that it must generate new packet protection keying
+ * materials and AEAD cipher context objects with new keys.  The
+ * current set of secrets are given as |current_rx_secret| and
+ * |current_tx_secret| of length |secretlen|.  They are decryption and
+ * encryption secrets respectively.
+ *
+ * The application must generate new secrets and keys for both
+ * encryption and decryption.  It must write decryption secret and IV
+ * to the buffer pointed by |rx_secret| and |rx_iv| respectively.  It
+ * also must create new AEAD cipher context object with new decryption
+ * key and initialize |rx_aead_ctx| with it.  Similarly, write
+ * encryption secret and IV to the buffer pointed by |tx_secret| and
+ * |tx_iv|.  Create new AEAD cipher context object with new encryption
+ * key and initialize |tx_aead_ctx| with it.  All given buffers have
+ * the enough capacity to store secret, key and IV.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_update_key)(
+  ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+  ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+  ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+  const uint8_t *current_rx_secret, const uint8_t *current_tx_secret,
+  size_t secretlen, void *user_data);
+
+/**
+ * @macrosection
+ *
+ * Path validation related macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PATH_VALIDATION_FLAG_NONE` indicates no flag set.
+ */
+#define NGTCP2_PATH_VALIDATION_FLAG_NONE 0x00u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR` indicates the
+ * validation involving server preferred address.  This flag is only
+ * set for client.
+ */
+#define NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR 0x01u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN` indicates that
+ * server should send NEW_TOKEN frame for the new remote address.
+ * This flag is only set for server.
+ */
+#define NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN 0x02u
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_path_validation` is a callback function which tells
+ * an application the outcome of path validation.  |flags| is zero or
+ * more of :macro:`NGTCP2_PATH_VALIDATION_FLAG_*
+ * <NGTCP2_PATH_VALIDATION_FLAG_NONE>`.  |path| is the path that was
+ * validated.  |old_path| is the path that is previously used before a
+ * local endpoint has migrated to |path| if |old_path| is not NULL.
+ * If |res| is
+ * :enum:`ngtcp2_path_validation_result.NGTCP2_PATH_VALIDATION_RESULT_SUCCESS`,
+ * the path validation succeeded.  If |res| is
+ * :enum:`ngtcp2_path_validation_result.NGTCP2_PATH_VALIDATION_RESULT_FAILURE`,
+ * the path validation failed.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_path_validation)(ngtcp2_conn *conn, uint32_t flags,
+                                      const ngtcp2_path *path,
+                                      const ngtcp2_path *old_path,
+                                      ngtcp2_path_validation_result res,
+                                      void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_select_preferred_addr` is a callback function which
+ * asks a client application to choose server address from preferred
+ * addresses |paddr| received from server.  An application should
+ * write a network path for a selected preferred address in |dest|.
+ * More specifically, the selected preferred address must be set to
+ * :member:`dest->remote <ngtcp2_path.remote>`, a client source
+ * address must be set to :member:`dest->local <ngtcp2_path.local>`.
+ * If a client source address does not change for the new server
+ * address, leave :member:`dest->local <ngtcp2_path.local>`
+ * unmodified, or copy the value of :member:`local
+ * <ngtcp2_path.local>` field of the current network path obtained
+ * from `ngtcp2_conn_get_path()`.  Both :member:`dest->local.addr
+ * <ngtcp2_addr.addr>` and :member:`dest->remote.addr
+ * <ngtcp2_addr.addr>` point to buffers which are at least
+ * sizeof(:type:`ngtcp2_sockaddr_union`) bytes long, respectively.  If
+ * an application denies the preferred addresses, just leave |dest|
+ * unmodified (or set :member:`dest->remote.addrlen
+ * <ngtcp2_addr.addrlen>` to 0), and return 0.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_select_preferred_addr)(ngtcp2_conn *conn,
+                                            ngtcp2_path *dest,
+                                            const ngtcp2_preferred_addr *paddr,
+                                            void *user_data);
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_connection_id_status_type` defines a set of status
+ * for Destination Connection ID.
+ */
+typedef enum ngtcp2_connection_id_status_type {
+  /**
+   * :enum:`NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE` indicates that
+   * a local endpoint starts using new Destination Connection ID.
+   */
+  NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE,
+  /**
+   * :enum:`NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE` indicates
+   * that a local endpoint stops using a given Destination Connection
+   * ID.
+   */
+  NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE
+} ngtcp2_connection_id_status_type;
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_connection_id_status` is a callback function which is
+ * called when the status of Destination Connection ID changes.
+ *
+ * |token| is the associated stateless reset token, and it is ``NULL``
+ * if no token is present.
+ *
+ * |type| is the one of the value defined in
+ * :type:`ngtcp2_connection_id_status_type`.  The new value might be
+ * added in the future release.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_connection_id_status)(
+  ngtcp2_conn *conn, ngtcp2_connection_id_status_type type, uint64_t seq,
+  const ngtcp2_cid *cid, const uint8_t *token, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_new_token` is a callback function which is
+ * called when new token is received from server.  This callback is
+ * client use only.
+ *
+ * |token| is the received token of length |tokenlen| bytes long.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_recv_new_token)(ngtcp2_conn *conn, const uint8_t *token,
+                                     size_t tokenlen, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_delete_crypto_aead_ctx` is a callback function which
+ * must delete the native object pointed by
+ * :member:`aead_ctx->native_handle
+ * <ngtcp2_crypto_aead_ctx.native_handle>`.
+ */
+typedef void (*ngtcp2_delete_crypto_aead_ctx)(ngtcp2_conn *conn,
+                                              ngtcp2_crypto_aead_ctx *aead_ctx,
+                                              void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_delete_crypto_cipher_ctx` is a callback function
+ * which must delete the native object pointed by
+ * :member:`cipher_ctx->native_handle
+ * <ngtcp2_crypto_cipher_ctx.native_handle>`.
+ */
+typedef void (*ngtcp2_delete_crypto_cipher_ctx)(
+  ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data);
+
+/**
+ * @macrosection
+ *
+ * DATAGRAM frame flags
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_DATAGRAM_FLAG_NONE` indicates no flag set.
+ */
+#define NGTCP2_DATAGRAM_FLAG_NONE 0x00u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_DATAGRAM_FLAG_0RTT` indicates that DATAGRAM frame is
+ * received in 0-RTT packet, and the handshake has not completed yet,
+ * which means that the data might be replayed.
+ */
+#define NGTCP2_DATAGRAM_FLAG_0RTT 0x01u
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_datagram` is invoked when DATAGRAM frame is
+ * received.  |flags| is bitwise-OR of zero or more of
+ * :macro:`NGTCP2_DATAGRAM_FLAG_* <NGTCP2_DATAGRAM_FLAG_NONE>`.
+ *
+ * If :macro:`NGTCP2_DATAGRAM_FLAG_0RTT` is set in |flags|, it
+ * indicates that DATAGRAM frame was received in 0-RTT packet, and a
+ * handshake has not completed yet.
+ *
+ * The callback function must return 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return
+ * immediately.
+ */
+typedef int (*ngtcp2_recv_datagram)(ngtcp2_conn *conn, uint32_t flags,
+                                    const uint8_t *data, size_t datalen,
+                                    void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_ack_datagram` is invoked when a packet which contains
+ * DATAGRAM frame which is identified by |dgram_id| is acknowledged.
+ * |dgram_id| is the valued passed to `ngtcp2_conn_writev_datagram`.
+ *
+ * The callback function must return 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return
+ * immediately.
+ */
+typedef int (*ngtcp2_ack_datagram)(ngtcp2_conn *conn, uint64_t dgram_id,
+                                   void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_lost_datagram` is invoked when a packet which
+ * contains DATAGRAM frame which is identified by |dgram_id| is
+ * declared lost.  |dgram_id| is the valued passed to
+ * `ngtcp2_conn_writev_datagram`.  Note that the loss might be
+ * spurious, and DATAGRAM frame might be acknowledged later.
+ *
+ * The callback function must return 0 if it succeeds, or
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return
+ * immediately.
+ */
+typedef int (*ngtcp2_lost_datagram)(ngtcp2_conn *conn, uint64_t dgram_id,
+                                    void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_get_path_challenge_data` is a callback function to
+ * ask an application for new data that is sent in PATH_CHALLENGE
+ * frame.  Application must generate new unpredictable, exactly
+ * :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` bytes of random data, and
+ * store them into the buffer pointed by |data|.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_get_path_challenge_data)(ngtcp2_conn *conn, uint8_t *data,
+                                              void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_stream_stop_sending` is invoked when a stream is no
+ * longer read by a local endpoint before it receives all stream data.
+ * This function is called at most once per stream.  |app_error_code|
+ * is the error code passed to `ngtcp2_conn_shutdown_stream_read` or
+ * `ngtcp2_conn_shutdown_stream`.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_stream_stop_sending)(ngtcp2_conn *conn, int64_t stream_id,
+                                          uint64_t app_error_code,
+                                          void *user_data,
+                                          void *stream_user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_version_negotiation` is invoked when the compatible
+ * version negotiation takes place.  For client, it is called when it
+ * sees a change in version field of a long header packet.  This
+ * callback function might be called multiple times for client.  For
+ * server, it is called once when the version is negotiated.
+ *
+ * The implementation of this callback must install new Initial keys
+ * for |version| and Destination Connection ID |client_dcid| from
+ * client.  Use `ngtcp2_conn_install_vneg_initial_key` to install
+ * keys.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_version_negotiation)(ngtcp2_conn *conn, uint32_t version,
+                                          const ngtcp2_cid *client_dcid,
+                                          void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_recv_key` is invoked when new key is installed to
+ * |conn| during QUIC cryptographic handshake.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_recv_key)(ngtcp2_conn *conn, ngtcp2_encryption_level level,
+                               void *user_data);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_tls_early_data_rejected` is invoked when early data
+ * was rejected by server during TLS handshake, or client decided not
+ * to attempt early data.
+ *
+ * The callback function must return 0 if it succeeds.  Returning
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
+ * immediately.
+ */
+typedef int (*ngtcp2_tls_early_data_rejected)(ngtcp2_conn *conn,
+                                              void *user_data);
+
+#define NGTCP2_CALLBACKS_V1 1
+#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_V1
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_callbacks` holds a set of callback functions.
+ */
+typedef struct ngtcp2_callbacks {
+  /**
+   * :member:`client_initial` is a callback function which is invoked
+   * when client asks TLS stack to produce first TLS cryptographic
+   * handshake message.  This callback function must be specified for
+   * a client application.
+   */
+  ngtcp2_client_initial client_initial;
+  /**
+   * :member:`recv_client_initial` is a callback function which is
+   * invoked when a server receives the first Initial packet from
+   * client.  This callback function must be specified for a server
+   * application.
+   */
+  ngtcp2_recv_client_initial recv_client_initial;
+  /**
+   * :member:`recv_crypto_data` is a callback function which is
+   * invoked when cryptographic data (CRYPTO frame, in other words,
+   * TLS message) is received.  This callback function must be
+   * specified.
+   */
+  ngtcp2_recv_crypto_data recv_crypto_data;
+  /**
+   * :member:`handshake_completed` is a callback function which is
+   * invoked when QUIC cryptographic handshake has completed.  This
+   * callback function is optional.
+   */
+  ngtcp2_handshake_completed handshake_completed;
+  /**
+   * :member:`recv_version_negotiation` is a callback function which
+   * is invoked when Version Negotiation packet is received by a
+   * client.  This callback function is optional.
+   */
+  ngtcp2_recv_version_negotiation recv_version_negotiation;
+  /**
+   * :member:`encrypt` is a callback function which is invoked to
+   * encrypt a QUIC packet.  This callback function must be specified.
+   */
+  ngtcp2_encrypt encrypt;
+  /**
+   * :member:`decrypt` is a callback function which is invoked to
+   * decrypt a QUIC packet.  This callback function must be specified.
+   */
+  ngtcp2_decrypt decrypt;
+  /**
+   * :member:`hp_mask` is a callback function which is invoked to get
+   * a mask to encrypt or decrypt QUIC packet header.  This callback
+   * function must be specified.
+   */
+  ngtcp2_hp_mask hp_mask;
+  /**
+   * :member:`recv_stream_data` is a callback function which is
+   * invoked when stream data, which includes application data, is
+   * received.  This callback function is optional.
+   */
+  ngtcp2_recv_stream_data recv_stream_data;
+  /**
+   * :member:`acked_stream_data_offset` is a callback function which
+   * is invoked when stream data, which includes application data, is
+   * acknowledged by a remote endpoint.  It tells an application the
+   * largest offset of acknowledged stream data without a gap so that
+   * application can free memory for the data up to that offset.  This
+   * callback function is optional.
+   */
+  ngtcp2_acked_stream_data_offset acked_stream_data_offset;
+  /**
+   * :member:`stream_open` is a callback function which is invoked
+   * when new remote stream is opened by a remote endpoint.  This
+   * callback function is optional.
+   */
+  ngtcp2_stream_open stream_open;
+  /**
+   * :member:`stream_close` is a callback function which is invoked
+   * when a stream is closed.  This callback function is optional.
+   */
+  ngtcp2_stream_close stream_close;
+  /**
+   * :member:`recv_stateless_reset` is a callback function which is
+   * invoked when Stateless Reset packet is received.  This callback
+   * function is optional.
+   */
+  ngtcp2_recv_stateless_reset recv_stateless_reset;
+  /**
+   * :member:`recv_retry` is a callback function which is invoked when
+   * a client receives Retry packet.  For client, this callback
+   * function must be specified.  Server never receive Retry packet.
+   */
+  ngtcp2_recv_retry recv_retry;
+  /**
+   * :member:`extend_max_local_streams_bidi` is a callback function
+   * which is invoked when the number of bidirectional stream which a
+   * local endpoint can open is increased.  This callback function is
+   * optional.
+   */
+  ngtcp2_extend_max_streams extend_max_local_streams_bidi;
+  /**
+   * :member:`extend_max_local_streams_uni` is a callback function
+   * which is invoked when the number of unidirectional stream which a
+   * local endpoint can open is increased.  This callback function is
+   * optional.
+   */
+  ngtcp2_extend_max_streams extend_max_local_streams_uni;
+  /**
+   * :member:`rand` is a callback function which is invoked when the
+   * library needs random data.  This callback function must be
+   * specified.
+   */
+  ngtcp2_rand rand;
+  /**
+   * :member:`get_new_connection_id` is a callback function which is
+   * invoked when the library needs new connection ID.  This callback
+   * function must be specified.
+   */
+  ngtcp2_get_new_connection_id get_new_connection_id;
+  /**
+   * :member:`remove_connection_id` is a callback function which
+   * notifies an application that connection ID is no longer used by a
+   * remote endpoint.  This callback function is optional.
+   */
+  ngtcp2_remove_connection_id remove_connection_id;
+  /**
+   * :member:`update_key` is a callback function which is invoked when
+   * the library tells an application that it must update keying
+   * materials, and install new keys.  This callback function must be
+   * specified.
+   */
+  ngtcp2_update_key update_key;
+  /**
+   * :member:`path_validation` is a callback function which is invoked
+   * when path validation completed.  This callback function is
+   * optional.
+   */
+  ngtcp2_path_validation path_validation;
+  /**
+   * :member:`select_preferred_addr` is a callback function which is
+   * invoked when the library asks a client to select preferred
+   * address presented by a server.  If not set, client ignores
+   * preferred addresses.  This callback function is optional.
+   */
+  ngtcp2_select_preferred_addr select_preferred_addr;
+  /**
+   * :member:`stream_reset` is a callback function which is invoked
+   * when a stream is reset by a remote endpoint.  This callback
+   * function is optional.
+   */
+  ngtcp2_stream_reset stream_reset;
+  /**
+   * :member:`extend_max_remote_streams_bidi` is a callback function
+   * which is invoked when the number of bidirectional streams which a
+   * remote endpoint can open is increased.  This callback function is
+   * optional.
+   */
+  ngtcp2_extend_max_streams extend_max_remote_streams_bidi;
+  /**
+   * :member:`extend_max_remote_streams_uni` is a callback function
+   * which is invoked when the number of unidirectional streams which
+   * a remote endpoint can open is increased.  This callback function
+   * is optional.
+   */
+  ngtcp2_extend_max_streams extend_max_remote_streams_uni;
+  /**
+   * :member:`extend_max_stream_data` is callback function which is
+   * invoked when the maximum offset of stream data that a local
+   * endpoint can send is increased.  This callback function is
+   * optional.
+   */
+  ngtcp2_extend_max_stream_data extend_max_stream_data;
+  /**
+   * :member:`dcid_status` is a callback function which is invoked
+   * when the new Destination Connection ID is activated, or the
+   * activated Destination Connection ID is now deactivated.  This
+   * callback function is optional.
+   */
+  ngtcp2_connection_id_status dcid_status;
+  /**
+   * :member:`handshake_confirmed` is a callback function which is
+   * invoked when both endpoints agree that handshake has finished.
+   * This field is ignored by server because
+   * :member:`handshake_completed` also indicates the handshake
+   * confirmation for server.  This callback function is optional.
+   */
+  ngtcp2_handshake_confirmed handshake_confirmed;
+  /**
+   * :member:`recv_new_token` is a callback function which is invoked
+   * when new token is received from server.  This field is ignored by
+   * server.  This callback function is optional.
+   */
+  ngtcp2_recv_new_token recv_new_token;
+  /**
+   * :member:`delete_crypto_aead_ctx` is a callback function which
+   * deletes a given AEAD cipher context object.  This callback
+   * function must be specified.
+   */
+  ngtcp2_delete_crypto_aead_ctx delete_crypto_aead_ctx;
+  /**
+   * :member:`delete_crypto_cipher_ctx` is a callback function which
+   * deletes a given cipher context object.  This callback function
+   * must be specified.
+   */
+  ngtcp2_delete_crypto_cipher_ctx delete_crypto_cipher_ctx;
+  /**
+   * :member:`recv_datagram` is a callback function which is invoked
+   * when DATAGRAM frame is received.  This callback function is
+   * optional.
+   */
+  ngtcp2_recv_datagram recv_datagram;
+  /**
+   * :member:`ack_datagram` is a callback function which is invoked
+   * when a QUIC packet containing DATAGRAM frame is acknowledged by a
+   * remote endpoint.  This callback function is optional.
+   */
+  ngtcp2_ack_datagram ack_datagram;
+  /**
+   * :member:`lost_datagram` is a callback function which is invoked
+   * when a QUIC packet containing DATAGRAM frame is declared lost.
+   * This callback function is optional.
+   */
+  ngtcp2_lost_datagram lost_datagram;
+  /**
+   * :member:`get_path_challenge_data` is a callback function which is
+   * invoked when the library needs new data sent along with
+   * PATH_CHALLENGE frame.  This callback must be specified.
+   */
+  ngtcp2_get_path_challenge_data get_path_challenge_data;
+  /**
+   * :member:`stream_stop_sending` is a callback function which is
+   * invoked when a local endpoint no longer reads from a stream
+   * before it receives all stream data.  This callback function is
+   * optional.
+   */
+  ngtcp2_stream_stop_sending stream_stop_sending;
+  /**
+   * :member:`version_negotiation` is a callback function which is
+   * invoked when the compatible version negotiation takes place.
+   * This callback function must be specified.
+   */
+  ngtcp2_version_negotiation version_negotiation;
+  /**
+   * :member:`recv_rx_key` is a callback function which is invoked
+   * when a new key for decrypting packets is installed during QUIC
+   * cryptographic handshake.  It is not called for
+   * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_INITIAL`.
+   */
+  ngtcp2_recv_key recv_rx_key;
+  /**
+   * :member:`recv_tx_key` is a callback function which is invoked
+   * when a new key for encrypting packets is installed during QUIC
+   * cryptographic handshake.  It is not called for
+   * :enum:`ngtcp2_encryption_level.NGTCP2_ENCRYPTION_LEVEL_INITIAL`.
+   */
+  ngtcp2_recv_key recv_tx_key;
+  /**
+   * :member:`tls_early_data_rejected` is a callback function which is
+   * invoked when server rejected early data during TLS handshake, or
+   * client decided not to attempt early data.  This callback function
+   * is only used by client.
+   */
+  ngtcp2_tls_early_data_rejected tls_early_data_rejected;
+} ngtcp2_callbacks;
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_write_connection_close` writes Initial packet
+ * containing CONNECTION_CLOSE frame with the given |error_code| and
+ * the optional |reason| of length |reasonlen| to the buffer pointed
+ * by |dest| of length |destlen|.  All encryption parameters are for
+ * Initial packet encryption.  The packet number is always 0.
+ *
+ * The primary use case of this function is for server to send
+ * CONNECTION_CLOSE frame in Initial packet to close connection
+ * without committing any state when validating Retry token fails.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     Callback function failed.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_connection_close(
+  uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid,
+  const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason,
+  size_t reasonlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead,
+  const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv,
+  ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp,
+  const ngtcp2_crypto_cipher_ctx *hp_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_write_retry` writes Retry packet in the buffer pointed
+ * by |dest| whose length is |destlen|.  |dcid| is the Connection ID
+ * which appeared in a packet as a Source Connection ID sent by
+ * client.  |scid| is a server chosen Source Connection ID.  |odcid|
+ * specifies Original Destination Connection ID which appeared in a
+ * packet as a Destination Connection ID sent by client.  |token|
+ * specifies Retry Token, and |tokenlen| specifies its length.  |aead|
+ * must be AEAD_AES_128_GCM.  |aead_ctx| must be initialized with
+ * :macro:`NGTCP2_RETRY_KEY` as an encryption key.
+ *
+ * This function returns the number of bytes written to the buffer, or
+ * one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     Callback function failed.
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     :member:`odcid->datalen <ngtcp2_cid.datalen>` is less than
+ *     :macro:`NGTCP2_MIN_INITIAL_DCIDLEN`.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_retry(
+  uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid,
+  const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token,
+  size_t tokenlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead,
+  const ngtcp2_crypto_aead_ctx *aead_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_accept` is used by server implementation, and decides
+ * whether packet |pkt| of length |pktlen| from client is acceptable
+ * for the very first packet to a connection.
+ *
+ * If |dest| is not ``NULL`` and the function returns 0, the decoded
+ * packet header is stored in the object pointed by |dest|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     The packet is not acceptable for the very first packet to a new
+ *     connection; or the function failed to parse the packet header.
+ */
+NGTCP2_EXTERN int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
+                                size_t pktlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_client_new` creates new :type:`ngtcp2_conn`, and
+ * initializes it as client.  On success, it stores the pointer to the
+ * newly allocated object in |*pconn|.  |dcid| is a randomized
+ * Destination Connection ID which must be longer than or equal to
+ * :macro:`NGTCP2_MIN_INITIAL_DCIDLEN`.  |scid| is a Source Connection
+ * ID chosen by client.  |client_chosen_version| is a QUIC version
+ * that a client chooses.  |path| is the network path where this QUIC
+ * connection is being established, and must not be ``NULL``.
+ * |callbacks|, |settings|, and |params| must not be ``NULL``, and the
+ * function makes a copy of each of them.  |params| is a local QUIC
+ * transport parameters, and sent to a remote endpoint during
+ * handshake.  |user_data| is the arbitrary pointer which is passed to
+ * the user-defined callback functions.  If |mem| is ``NULL``, the
+ * memory allocator returned by `ngtcp2_mem_default()` is used.
+ *
+ * Call `ngtcp2_conn_del` to free memory allocated for |*pconn|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_client_new_versioned(
+  ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
+  const ngtcp2_path *path, uint32_t client_chosen_version,
+  int callbacks_version, const ngtcp2_callbacks *callbacks,
+  int settings_version, const ngtcp2_settings *settings,
+  int transport_params_version, const ngtcp2_transport_params *params,
+  const ngtcp2_mem *mem, void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_server_new` creates new :type:`ngtcp2_conn`, and
+ * initializes it as server.  On success, it stores the pointer to the
+ * newly allocated object in |*pconn|.  |dcid| is a Destination
+ * Connection ID, and is usually the Connection ID that appears in
+ * client Initial packet as Source Connection ID.  |scid| is a Source
+ * Connection ID chosen by server.  |path| is the network path where
+ * this QUIC connection is being established, and must not be
+ * ``NULL``.  |client_chosen_version| is a QUIC version that a client
+ * chooses.  |callbacks|, |settings|, and |params| must not be
+ * ``NULL``, and the function makes a copy of each of them.  |params|
+ * is a local QUIC transport parameters, and sent to a remote endpoint
+ * during handshake.  |user_data| is the arbitrary pointer which is
+ * passed to the user-defined callback functions.  If |mem| is
+ * ``NULL``, the memory allocator returned by `ngtcp2_mem_default()`
+ * is used.
+ *
+ * Call `ngtcp2_conn_del` to free memory allocated for |*pconn|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_server_new_versioned(
+  ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
+  const ngtcp2_path *path, uint32_t client_chosen_version,
+  int callbacks_version, const ngtcp2_callbacks *callbacks,
+  int settings_version, const ngtcp2_settings *settings,
+  int transport_params_version, const ngtcp2_transport_params *params,
+  const ngtcp2_mem *mem, void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_del` frees resources allocated for |conn|.  It also
+ * frees memory pointed by |conn|.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_del(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_read_pkt` decrypts QUIC packet given in |pkt| of
+ * length |pktlen| and processes it.  |path| is the network path the
+ * packet is delivered and must not be ``NULL``.  |pi| is packet
+ * metadata and may be ``NULL``. This function performs QUIC handshake
+ * as well.
+ *
+ * This function must not be called from inside the callback
+ * functions.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_RETRY`
+ *    Server must perform address validation by sending Retry packet
+ *    (see `ngtcp2_crypto_write_retry` and `ngtcp2_pkt_write_retry`),
+ *    and discard the connection state.  Client application does not
+ *    get this error code.
+ * :macro:`NGTCP2_ERR_DROP_CONN`
+ *    Server application must drop the connection silently (without
+ *    sending any CONNECTION_CLOSE frame), and discard connection
+ *    state.  Client application does not get this error code.
+ * :macro:`NGTCP2_ERR_DRAINING`
+ *    A connection has entered the draining state, and no further
+ *    packet transmission is allowed.
+ * :macro:`NGTCP2_ERR_CLOSING`
+ *    A connection has entered the closing state, and no further
+ *    packet transmission is allowed.  Calling
+ *    `ngtcp2_conn_write_connection_close` makes a connection enter
+ *    this state.
+ * :macro:`NGTCP2_ERR_CRYPTO`
+ *    An error happened in TLS stack.  `ngtcp2_conn_get_tls_alert`
+ *    returns TLS alert if set.
+ *
+ * If any other negative error is returned, call
+ * `ngtcp2_conn_write_connection_close` to get terminal packet, and
+ * sending it makes QUIC connection enter the closing state.
+ */
+NGTCP2_EXTERN int
+ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path,
+                               int pkt_info_version, const ngtcp2_pkt_info *pi,
+                               const uint8_t *pkt, size_t pktlen,
+                               ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_write_pkt` is equivalent to calling
+ * `ngtcp2_conn_writev_stream` with -1 as |stream_id|, no stream data,
+ * and :macro:`NGTCP2_WRITE_STREAM_FLAG_NONE` as flags.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_pkt_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_tls_handshake_completed` tells |conn| that the TLS
+ * stack declares TLS handshake completion.  This does not mean QUIC
+ * handshake has completed.  The library needs extra conditions to be
+ * met.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_tls_handshake_completed(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_handshake_completed` returns nonzero if QUIC
+ * handshake has completed.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_install_initial_key` installs packet protection keying
+ * materials for Initial packets.  |rx_aead_ctx| is AEAD cipher
+ * context object, and must be initialized with a decryption key.
+ * |rx_iv| is IV of length |rx_ivlen| for decryption.  |rx_hp_ctx| is
+ * a packet header protection cipher context object for decryption.
+ * Similarly, |tx_aead_ctx|, |tx_iv| and |tx_hp_ctx| are for
+ * encrypting outgoing packets, and are the same length with the
+ * decryption counterpart .  If they have already been set, they are
+ * overwritten.
+ *
+ * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if
+ * that is larger.
+ *
+ * If this function succeeds, |conn| takes ownership of |rx_aead_ctx|,
+ * |rx_hp_ctx|, |tx_aead_ctx|, and |tx_hp_ctx|.
+ * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and
+ * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called
+ * to delete these objects when they are no longer used.  If this
+ * function fails, the caller is responsible to delete them.
+ *
+ * After receiving Retry packet, a Destination Connection ID that
+ * client sends in Initial packet most likely changes.  In that case,
+ * client application must generate these keying materials again based
+ * on new Destination Connection ID, and install them again with this
+ * function.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_install_initial_key(
+  ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *rx_aead_ctx,
+  const uint8_t *rx_iv, const ngtcp2_crypto_cipher_ctx *rx_hp_ctx,
+  const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv,
+  const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_install_vneg_initial_key` installs packet protection
+ * keying materials for Initial packets on compatible version
+ * negotiation for |version|.  |rx_aead_ctx| is AEAD cipher context
+ * object, and must be initialized with a decryption key.  |rx_iv| is
+ * IV of length |rx_ivlen| for decryption.  |rx_hp_ctx| is a packet
+ * header protection cipher context object for decryption.  Similarly,
+ * |tx_aead_ctx|, |tx_iv| and |tx_hp_ctx| are for encrypting outgoing
+ * packets, and are the same length with the decryption counterpart.
+ * If they have already been set, they are overwritten.
+ *
+ * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if
+ * that is larger.
+ *
+ * If this function succeeds, |conn| takes ownership of |rx_aead_ctx|,
+ * |rx_hp_ctx|, |tx_aead_ctx|, and |tx_hp_ctx|.
+ * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and
+ * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called
+ * to delete these objects when they are no longer used.  If this
+ * function fails, the caller is responsible to delete them.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_install_vneg_initial_key(
+  ngtcp2_conn *conn, uint32_t version,
+  const ngtcp2_crypto_aead_ctx *rx_aead_ctx, const uint8_t *rx_iv,
+  const ngtcp2_crypto_cipher_ctx *rx_hp_ctx,
+  const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv,
+  const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_install_rx_handshake_key` installs packet protection
+ * keying materials for decrypting incoming Handshake packets.
+ * |aead_ctx| is AEAD cipher context object which must be initialized
+ * with a decryption key.  |iv| is IV of length |ivlen|.  |hp_ctx| is
+ * a packet header protection cipher context object.
+ *
+ * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if
+ * that is larger.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx|,
+ * and |hp_ctx|.  :member:`ngtcp2_callbacks.delete_crypto_aead_ctx`
+ * and :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be
+ * called to delete these objects when they are no longer used.  If
+ * this function fails, the caller is responsible to delete them.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_install_rx_handshake_key(
+  ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv,
+  size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_install_tx_handshake_key` installs packet protection
+ * keying materials for encrypting outgoing Handshake packets.
+ * |aead_ctx| is AEAD cipher context object which must be initialized
+ * with an encryption key.  |iv| is IV of length |ivlen|.  |hp_ctx| is
+ * a packet header protection cipher context object.
+ *
+ * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if
+ * that is larger.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx| and
+ * |hp_ctx|.  :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and
+ * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called
+ * to delete these objects when they are no longer used.  If this
+ * function fails, the caller is responsible to delete them.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_install_tx_handshake_key(
+  ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv,
+  size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_install_0rtt_key` installs packet protection AEAD
+ * cipher context object |aead_ctx|, IV |iv| of length |ivlen|, and
+ * packet header protection cipher context object |hp_ctx| to encrypt
+ * (for client) or decrypt (for server) 0-RTT packets.
+ *
+ * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if
+ * that is larger.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx| and
+ * |hp_ctx|.  :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and
+ * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called
+ * to delete these objects when they are no longer used.  If this
+ * function fails, the caller is responsible to delete them.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_install_0rtt_key(
+  ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv,
+  size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_install_rx_key` installs packet protection keying
+ * materials for decrypting 1-RTT packets.  |secret| of length
+ * |secretlen| is the decryption secret which is used to derive keying
+ * materials passed to this function.  |aead_ctx| is AEAD cipher
+ * context object which must be initialized with a decryption key.
+ * |iv| is IV of length |ivlen|.  |hp_ctx| is a packet header
+ * protection cipher context object.
+ *
+ * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if
+ * that is larger.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx| and
+ * |hp_ctx|.  :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and
+ * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called
+ * to delete these objects when they are no longer used.  If this
+ * function fails, the caller is responsible to delete them.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_install_rx_key(
+  ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen,
+  const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, size_t ivlen,
+  const ngtcp2_crypto_cipher_ctx *hp_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_install_tx_key` installs packet protection keying
+ * materials for encrypting 1-RTT packets.  |secret| of length
+ * |secretlen| is the encryption secret which is used to derive keying
+ * materials passed to this function.  |aead_ctx| is AEAD cipher
+ * context object which must be initialized with an encryption key.
+ * |iv| is IV of length |ivlen|.  |hp_ctx| is a packet header
+ * protection cipher context object.
+ *
+ * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if
+ * that is larger.
+ *
+ * If this function succeeds, |conn| takes ownership of |aead_ctx| and
+ * |hp_ctx|.  :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` and
+ * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` will be called
+ * to delete these objects when they are no longer used.  If this
+ * function fails, the caller is responsible to delete them.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_install_tx_key(
+  ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen,
+  const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, size_t ivlen,
+  const ngtcp2_crypto_cipher_ctx *hp_ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_initiate_key_update` initiates the key update.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * 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.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_initiate_key_update(ngtcp2_conn *conn,
+                                                  ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_tls_error` sets the TLS related error |liberr| in
+ * |conn|.  |liberr| must be one of ngtcp2 library error codes (which
+ * is defined as NGTCP2_ERR_* macro, such as
+ * :macro:`NGTCP2_ERR_DECRYPT`).  In general, error code should be
+ * propagated via return value, but sometimes ngtcp2 API is called
+ * inside callback function of TLS stack, and it does not allow to
+ * return ngtcp2 error code directly.  In this case, implementation
+ * can set the error code (e.g.,
+ * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM`) using this function.
+ *
+ * See also `ngtcp2_conn_get_tls_error`.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_set_tls_error(ngtcp2_conn *conn, int liberr);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_tls_error` returns the value set by
+ * `ngtcp2_conn_set_tls_error`.  If no value is set, this function
+ * returns 0.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_get_tls_error(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_tls_alert` sets a TLS alert |alert| generated by a
+ * TLS stack of a local endpoint to |conn|.
+ *
+ * See also `ngtcp2_conn_get_tls_alert`.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_set_tls_alert(ngtcp2_conn *conn, uint8_t alert);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_tls_alert` returns the value set by
+ * `ngtcp2_conn_set_tls_alert`.  If no value is set, this function
+ * returns 0.
+ */
+NGTCP2_EXTERN uint8_t ngtcp2_conn_get_tls_alert(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_keep_alive_timeout` sets keep-alive timeout.  If
+ * nonzero value is given, after a connection is idle at least in a
+ * given amount of time, a keep-alive packet is sent.  If UINT64_MAX
+ * is set, keep-alive functionality is disabled, and this is the
+ * default.  Specifying 0 in |timeout| is reserved for a future
+ * extension, and for now it is treated as if UINT64_MAX is given.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_set_keep_alive_timeout(ngtcp2_conn *conn,
+                                                      ngtcp2_duration timeout);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_expiry` returns the next expiry time.  It returns
+ * ``UINT64_MAX`` if there is no next expiry.
+ *
+ * Call `ngtcp2_conn_handle_expiry` and then
+ * `ngtcp2_conn_writev_stream` (or `ngtcp2_conn_writev_datagram`) when
+ * the expiry time has passed.
+ */
+NGTCP2_EXTERN ngtcp2_tstamp ngtcp2_conn_get_expiry(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_handle_expiry` handles expired timer.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn,
+                                            ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_pto` returns Probe Timeout (PTO).
+ */
+NGTCP2_EXTERN ngtcp2_duration ngtcp2_conn_get_pto(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_decode_and_set_remote_transport_params` decodes QUIC
+ * transport parameters from the buffer pointed by |data| of length
+ * |datalen|, and sets the result to |conn|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM`
+ *     The required parameter is missing.
+ * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM`
+ *     The input is malformed.
+ * :macro:`NGTCP2_ERR_TRANSPORT_PARAM`
+ *     Failed to validate the remote QUIC transport parameters.
+ * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE`
+ *     Version negotiation failure.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     User callback failed
+ */
+NGTCP2_EXTERN int ngtcp2_conn_decode_and_set_remote_transport_params(
+  ngtcp2_conn *conn, const uint8_t *data, size_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_remote_transport_params` returns a pointer to the
+ * remote QUIC transport parameters.  If no remote transport
+ * parameters are set, it returns NULL.
+ */
+NGTCP2_EXTERN const ngtcp2_transport_params *
+ngtcp2_conn_get_remote_transport_params(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_encode_0rtt_transport_params` encodes the QUIC
+ * transport parameters that are used for 0-RTT data in the buffer
+ * pointed by |dest| of length |destlen|.  It includes at least the
+ * following fields:
+ *
+ * - :member:`ngtcp2_transport_params.initial_max_streams_bidi`
+ * - :member:`ngtcp2_transport_params.initial_max_streams_uni`
+ * - :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_local`
+ * - :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_remote`
+ * - :member:`ngtcp2_transport_params.initial_max_stream_data_uni`
+ * - :member:`ngtcp2_transport_params.initial_max_data`
+ * - :member:`ngtcp2_transport_params.active_connection_id_limit`
+ * - :member:`ngtcp2_transport_params.max_datagram_frame_size`
+ *
+ * If |conn| is initialized as server, the following additional fields
+ * are also included:
+ *
+ * - :member:`ngtcp2_transport_params.max_idle_timeout`
+ * - :member:`ngtcp2_transport_params.max_udp_payload_size`
+ * - :member:`ngtcp2_transport_params.disable_active_migration`
+ *
+ * If |conn| is initialized as client, these parameters are
+ * synthesized from the remote transport parameters received from
+ * server.  Otherwise, it is the local transport parameters that are
+ * set by the local endpoint.
+ *
+ * This function returns the number of bytes written, or one of the
+ * following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small.
+ */
+NGTCP2_EXTERN
+ngtcp2_ssize ngtcp2_conn_encode_0rtt_transport_params(ngtcp2_conn *conn,
+                                                      uint8_t *dest,
+                                                      size_t destlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_decode_and_set_0rtt_transport_params` decodes QUIC
+ * transport parameters from |data| of length |datalen|, which is
+ * assumed to be the parameters received from the server in the
+ * previous connection, and sets it to |conn|.  These parameters are
+ * used to send 0-RTT data.  QUIC requires that client application
+ * should remember transport parameters along with a session ticket.
+ *
+ * At least following fields should be included:
+ *
+ * - :member:`ngtcp2_transport_params.initial_max_streams_bidi`
+ * - :member:`ngtcp2_transport_params.initial_max_streams_uni`
+ * - :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_local`
+ * - :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_remote`
+ * - :member:`ngtcp2_transport_params.initial_max_stream_data_uni`
+ * - :member:`ngtcp2_transport_params.initial_max_data`
+ * - :member:`ngtcp2_transport_params.active_connection_id_limit`
+ * - :member:`ngtcp2_transport_params.max_datagram_frame_size` (if
+ *   DATAGRAM extension was negotiated)
+ *
+ * This function must only be used by client.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM`
+ *     The input is malformed.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_decode_and_set_0rtt_transport_params(
+  ngtcp2_conn *conn, const uint8_t *data, size_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_local_transport_params` sets the local transport
+ * parameters |params|.  This function can only be called by server.
+ * Although the local transport parameters are passed to
+ * `ngtcp2_conn_server_new`, server might want to update them after
+ * ALPN is chosen.  In that case, server can update the transport
+ * parameters with this function.  Server must call this function
+ * before calling `ngtcp2_conn_install_tx_handshake_key`.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     `ngtcp2_conn_install_tx_handshake_key` has been called.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_set_local_transport_params_versioned(
+  ngtcp2_conn *conn, int transport_params_version,
+  const ngtcp2_transport_params *params);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_local_transport_params` returns a pointer to the
+ * local QUIC transport parameters.
+ */
+NGTCP2_EXTERN const ngtcp2_transport_params *
+ngtcp2_conn_get_local_transport_params(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_encode_local_transport_params` encodes the local QUIC
+ * transport parameters in |dest| of length |destlen|.
+ *
+ * This function returns the number of bytes written, or one of the
+ * following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_encode_local_transport_params(
+  ngtcp2_conn *conn, uint8_t *dest, size_t destlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_open_bidi_stream` opens new bidirectional stream.  The
+ * |stream_user_data| is the user data specific to the stream.  The
+ * stream ID of the opened stream is stored in |*pstream_id|.
+ *
+ * Application can call this function before handshake completes.  For
+ * 0-RTT packet, application can call this function after calling
+ * `ngtcp2_conn_decode_and_set_0rtt_transport_params`.  For 1-RTT
+ * packet, application can call this function after calling
+ * `ngtcp2_conn_decode_and_set_remote_transport_params` and
+ * `ngtcp2_conn_install_tx_key`.  If ngtcp2 crypto support library is
+ * used, application can call this function after calling
+ * `ngtcp2_crypto_derive_and_install_tx_key` for 1-RTT packet.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED`
+ *     The remote endpoint does not allow |stream_id| yet.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_open_bidi_stream(ngtcp2_conn *conn,
+                                               int64_t *pstream_id,
+                                               void *stream_user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_open_uni_stream` opens new unidirectional stream.  The
+ * |stream_user_data| is the user data specific to the stream.  The
+ * stream ID of the opened stream is stored in |*pstream_id|.
+ *
+ * Application can call this function before handshake completes.  For
+ * 0-RTT packet, application can call this function after calling
+ * `ngtcp2_conn_decode_and_set_0rtt_transport_params`.  For 1-RTT
+ * packet, application can call this function after calling
+ * `ngtcp2_conn_decode_and_set_remote_transport_params` and
+ * `ngtcp2_conn_install_tx_key`.  If ngtcp2 crypto support library is
+ * used, application can call this function after calling
+ * `ngtcp2_crypto_derive_and_install_tx_key` for 1-RTT packet.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED`
+ *     The remote endpoint does not allow |stream_id| yet.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_open_uni_stream(ngtcp2_conn *conn,
+                                              int64_t *pstream_id,
+                                              void *stream_user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_shutdown_stream` closes a stream denoted by
+ * |stream_id| abruptly.  |app_error_code| is one of application error
+ * codes, and indicates the reason of shutdown.  Successful call of
+ * this function does not immediately erase the state of the stream.
+ * The actual deletion is done when the remote endpoint sends
+ * acknowledgement.  Calling this function is equivalent to call
+ * `ngtcp2_conn_shutdown_stream_read`, and
+ * `ngtcp2_conn_shutdown_stream_write` sequentially with the following
+ * differences.  If |stream_id| refers to a local unidirectional
+ * stream, this function only shutdowns write side of the stream.  If
+ * |stream_id| refers to a remote unidirectional stream, this function
+ * only shutdowns read side of the stream.
+ *
+ * |flags| is currently unused, and should be set to 0.
+ *
+ * This function returns 0 if a stream denoted by |stream_id| is not
+ * found.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ */
+NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream(ngtcp2_conn *conn, uint32_t flags,
+                                              int64_t stream_id,
+                                              uint64_t app_error_code);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_shutdown_stream_write` closes write-side of a stream
+ * denoted by |stream_id| abruptly.  |app_error_code| is one of
+ * application error codes, and indicates the reason of shutdown.  If
+ * this function succeeds, no further application data is sent to the
+ * remote endpoint.  It discards all data which has not been
+ * acknowledged yet.
+ *
+ * |flags| is currently unused, and should be set to 0.
+ *
+ * This function returns 0 if a stream denoted by |stream_id| is not
+ * found.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     |stream_id| refers to a remote unidirectional stream.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_write(ngtcp2_conn *conn,
+                                                    uint32_t flags,
+                                                    int64_t stream_id,
+                                                    uint64_t app_error_code);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_shutdown_stream_read` closes read-side of a stream
+ * denoted by |stream_id| abruptly.  |app_error_code| is one of
+ * application error codes, and indicates the reason of shutdown.  If
+ * this function succeeds, no further application data is forwarded to
+ * an application layer.
+ *
+ * |flags| is currently unused, and should be set to 0.
+ *
+ * This function returns 0 if a stream denoted by |stream_id| is not
+ * found.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     |stream_id| refers to a local unidirectional stream.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn,
+                                                   uint32_t flags,
+                                                   int64_t stream_id,
+                                                   uint64_t app_error_code);
+
+/**
+ * @macrosection
+ *
+ * Write stream data flags
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_WRITE_STREAM_FLAG_NONE` indicates no flag set.
+ */
+#define NGTCP2_WRITE_STREAM_FLAG_NONE 0x00u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` indicates that more data may
+ * come, and should be coalesced into the same packet if possible.
+ */
+#define NGTCP2_WRITE_STREAM_FLAG_MORE 0x01u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` indicates that a passed data
+ * is the final part of a stream.
+ */
+#define NGTCP2_WRITE_STREAM_FLAG_FIN 0x02u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_WRITE_STREAM_FLAG_PADDING` indicates that a
+ * non-empty 0 RTT or 1 RTT ack-eliciting packet is padded to the
+ * minimum length of a sending path MTU or a given packet buffer when
+ * finalizing it.  PATH_CHALLENGE, PATH_RESPONSE, CONNECTION_CLOSE
+ * only packets and PMTUD packets are excluded.
+ */
+#define NGTCP2_WRITE_STREAM_FLAG_PADDING 0x04u
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_write_stream` is just like
+ * `ngtcp2_conn_writev_stream`.  The only difference is that it
+ * conveniently accepts a single buffer.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_stream_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  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);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_writev_stream` writes a packet containing stream data
+ * of a stream denoted by |stream_id|.  The buffer of the packet is
+ * pointed by |dest| of length |destlen|.  This function performs QUIC
+ * handshake as well.
+ *
+ * |destlen| should be at least
+ * :member:`ngtcp2_settings.max_tx_udp_payload_size`.  It must be at
+ * least :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE`.
+ *
+ * Specifying -1 to |stream_id| means no new stream data to send.
+ *
+ * If |path| is not ``NULL``, this function stores the network path
+ * with which the packet should be sent.  Each addr field
+ * (:member:`ngtcp2_path.local` and :member:`ngtcp2_path.remote`) must
+ * point to the buffer which should be at least
+ * sizeof(:type:`sockaddr_union`) bytes long.  The assignment might
+ * not be done if nothing is written to |dest|.
+ *
+ * If |pi| is not ``NULL``, this function stores packet metadata in it
+ * if it succeeds.  The metadata includes ECN markings.  When calling
+ * this function again after it returns
+ * :macro:`NGTCP2_ERR_WRITE_MORE`, caller must pass the same |pi| to
+ * this function.
+ *
+ * Stream data is specified as vector of data |datav|.  |datavcnt|
+ * specifies the number of :type:`ngtcp2_vec` that |datav| includes.
+ *
+ * If all given data is encoded as STREAM frame in |dest|, and if
+ * |flags| & :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` is nonzero, fin
+ * flag is set to outgoing STREAM frame.  Otherwise, fin flag in
+ * STREAM frame is not set.
+ *
+ * This packet may contain frames other than STREAM frame.  The packet
+ * might not contain STREAM frame if other frames occupy the packet.
+ * In that case, |*pdatalen| would be -1 if |pdatalen| is not
+ * ``NULL``.
+ *
+ * Empty data is treated specially, and it is only accepted if no
+ * data, including the empty data, is submitted to a stream or
+ * :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` is set in |flags|.  If 0
+ * length STREAM frame is successfully serialized, |*pdatalen| would
+ * be 0.
+ *
+ * The number of data encoded in STREAM frame is stored in |*pdatalen|
+ * if it is not ``NULL``.  The caller must keep the portion of data
+ * covered by |*pdatalen| bytes in tact until
+ * :member:`ngtcp2_callbacks.acked_stream_data_offset` indicates that
+ * they are acknowledged by a remote endpoint or the stream is closed.
+ *
+ * If the given stream data is small (e.g., few bytes), the packet
+ * might be severely under filled.  Too many small packet might
+ * increase overall packet processing costs.  Unless there are
+ * retransmissions, by default, application can only send 1 STREAM
+ * frame in one QUIC packet.  In order to include more than 1 STREAM
+ * frame in one QUIC packet, specify
+ * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` in |flags|.  This is
+ * analogous to ``MSG_MORE`` flag in :manpage:`send(2)`.  If the
+ * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is used, there are 4
+ * outcomes:
+ *
+ * - The function returns the written length of packet just like
+ *   without :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE`.  This is because
+ *   packet is nearly full, and the library decided to make a complete
+ *   packet.  |*pdatalen| might be -1 or >= 0.  It may return 0 which
+ *   indicates that no packet transmission is possible at the moment
+ *   for some reason.
+ *
+ * - The function returns :macro:`NGTCP2_ERR_WRITE_MORE`.  In this
+ *   case, |*pdatalen| >= 0 is asserted.  It indicates that
+ *   application can still call this function with different stream
+ *   data (or `ngtcp2_conn_writev_datagram` if it has data to send in
+ *   unreliable datagram) to pack them into the same packet.
+ *   Application has to specify the same |conn|, |path|, |pi|, |dest|,
+ *   |destlen|, and |ts| parameters, otherwise the behaviour is
+ *   undefined.  The application can change |flags|.
+ *
+ * - The function returns one of the following negative error codes:
+ *   :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED`,
+ *   :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`, or
+ *   :macro:`NGTCP2_ERR_STREAM_SHUT_WR`.  In this case, |*pdatalen| ==
+ *   -1 is asserted.  Application can still write the stream data of
+ *   the other streams by calling this function (or
+ *   `ngtcp2_conn_writev_datagram` if it has data to send in
+ *   unreliable datagram) to pack them into the same packet.
+ *   Application has to specify the same |conn|, |path|, |pi|, |dest|,
+ *   |destlen|, and |ts| parameters, otherwise the behaviour is
+ *   undefined.  The application can change |flags|.
+ *
+ * - The other negative error codes might be returned just like
+ *   without :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE`.  These errors
+ *   should be treated as a connection error.
+ *
+ * When application uses :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` at
+ * least once, it must not call other ngtcp2 API functions
+ * (application can still call `ngtcp2_conn_write_connection_close` to
+ * handle error from this function.  It can also call
+ * `ngtcp2_conn_shutdown_stream_read`,
+ * `ngtcp2_conn_shutdown_stream_write`, and
+ * `ngtcp2_conn_shutdown_stream`), just keep calling this function (or
+ * `ngtcp2_conn_writev_datagram`) until it returns 0, a positive
+ * number (which indicates a complete packet is ready), or the error
+ * codes other than :macro:`NGTCP2_ERR_WRITE_MORE`,
+ * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED`,
+ * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`, and
+ * :macro:`NGTCP2_ERR_STREAM_SHUT_WR`.  If there is no stream data to
+ * include, call this function with |stream_id| as -1 to stop
+ * coalescing and write a packet.
+ *
+ * If :macro:`NGTCP2_WRITE_STREAM_FLAG_PADDING` is set in |flags| when
+ * finalizing a non-empty 0 RTT or 1 RTT ack-eliciting packet, the
+ * packet is padded to the minimum length of a sending path MTU or a
+ * given packet buffer.
+ *
+ * This function returns 0 if it cannot write any frame because buffer
+ * is too small, or packet is congestion limited.  Application should
+ * keep reading and wait for congestion window to grow.
+ *
+ * This function must not be called from inside the callback
+ * functions.
+ *
+ * `ngtcp2_conn_update_pkt_tx_time` must be called after this
+ * function.  Application may call this function multiple times before
+ * calling `ngtcp2_conn_update_pkt_tx_time`.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`
+ *     Stream does not exist
+ * :macro:`NGTCP2_ERR_STREAM_SHUT_WR`
+ *     Stream is half closed (local); or stream is being reset.
+ * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED`
+ *     Packet number is exhausted, and cannot send any more packet.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     User callback failed
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     The total length of stream data is too large.
+ * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED`
+ *     Stream is blocked because of flow control.
+ * :macro:`NGTCP2_ERR_WRITE_MORE`
+ *     (Only when :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is specified)
+ *     Application can call this function to pack more stream data
+ *     into the same packet.  See above to know how it works.
+ *
+ * If any other negative error is returned, call
+ * `ngtcp2_conn_write_connection_close` to get terminal packet, and
+ * sending it makes QUIC connection enter the closing state.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_stream_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen,
+  uint32_t flags, int64_t stream_id, const ngtcp2_vec *datav, size_t datavcnt,
+  ngtcp2_tstamp ts);
+
+/**
+ * @macrosection
+ *
+ * Write datagram flags
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_NONE` indicates no flag set.
+ */
+#define NGTCP2_WRITE_DATAGRAM_FLAG_NONE 0x00u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` indicates that more data
+ * may come, and should be coalesced into the same packet if possible.
+ */
+#define NGTCP2_WRITE_DATAGRAM_FLAG_MORE 0x01u
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_PADDING` indicates that a
+ * non-empty 0 RTT or 1 RTT ack-eliciting packet is padded to the
+ * minimum length of a sending path MTU or a given packet buffer when
+ * finalizing it.  PATH_CHALLENGE, PATH_RESPONSE, CONNECTION_CLOSE
+ * only packets and PMTUD packets are excluded.
+ */
+#define NGTCP2_WRITE_DATAGRAM_FLAG_PADDING 0x02u
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_write_datagram` is just like
+ * `ngtcp2_conn_writev_datagram`.  The only difference is that it
+ * conveniently accepts a single buffer.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_datagram_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted,
+  uint32_t flags, uint64_t dgram_id, const uint8_t *data, size_t datalen,
+  ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_writev_datagram` writes a packet containing unreliable
+ * data in DATAGRAM frame.  The buffer of the packet is pointed by
+ * |dest| of length |destlen|.  This function performs QUIC handshake
+ * as well.
+ *
+ * |destlen| should be at least
+ * :member:`ngtcp2_settings.max_tx_udp_payload_size`.  It must be at
+ * least :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE`.
+ *
+ * For |path| and |pi| parameters, refer to
+ * `ngtcp2_conn_writev_stream`.
+ *
+ * Stream data is specified as vector of data |datav|.  |datavcnt|
+ * specifies the number of :type:`ngtcp2_vec` that |datav| includes.
+ *
+ * If the given data is written to the buffer, nonzero value is
+ * assigned to |*paccepted| if it is not NULL.  The data in DATAGRAM
+ * frame cannot be fragmented; writing partial data is not possible.
+ *
+ * |dgram_id| is an opaque identifier which should uniquely identify
+ * the given DATAGRAM data.  It is passed to
+ * :member:`ngtcp2_callbacks.ack_datagram` callback when a packet that
+ * contains DATAGRAM frame is acknowledged.  It is also passed to
+ * :member:`ngtcp2_callbacks.lost_datagram` callback when a packet
+ * that contains DATAGRAM frame is declared lost.  If an application
+ * uses neither of those callbacks, it can sets 0 to this parameter.
+ *
+ * This function might write other frames other than DATAGRAM frame,
+ * just like `ngtcp2_conn_writev_stream`.
+ *
+ * If the function returns 0, it means that no more data cannot be
+ * sent because of congestion control limit; or, data does not fit
+ * into the provided buffer; or, a local endpoint, as a server, is
+ * unable to send data because of its amplification limit.  In this
+ * case, |*paccepted| is assigned zero if it is not NULL.
+ *
+ * If :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` is set in |flags|,
+ * there are 3 outcomes:
+ *
+ * - The function returns the written length of packet just like
+ *   without :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE`.  This is
+ *   because packet is nearly full and the library decided to make a
+ *   complete packet.  |*paccepted| might be zero or nonzero.
+ *
+ * - The function returns :macro:`NGTCP2_ERR_WRITE_MORE`.  In this
+ *   case, |*paccepted| != 0 is asserted.  This indicates that
+ *   application can call this function with another unreliable data
+ *   (or `ngtcp2_conn_writev_stream` if it has stream data to send) to
+ *   pack them into the same packet.  Application has to specify the
+ *   same |conn|, |path|, |pi|, |dest|, |destlen|, and |ts|
+ *   parameters, otherwise the behaviour is undefined.  The
+ *   application can change |flags|.
+ *
+ * - The other error might be returned just like without
+ *   :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE`.
+ *
+ * When application sees :macro:`NGTCP2_ERR_WRITE_MORE`, it must not
+ * call other ngtcp2 API functions (application can still call
+ * `ngtcp2_conn_write_connection_close` to handle error from this
+ * function.  It can also call `ngtcp2_conn_shutdown_stream_read`,
+ * `ngtcp2_conn_shutdown_stream_write`, and
+ * `ngtcp2_conn_shutdown_stream`).  Just keep calling this function
+ * (or `ngtcp2_conn_writev_stream`) until it returns a positive number
+ * (which indicates a complete packet is ready).
+ *
+ * If :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_PADDING` is set in |flags|
+ * when finalizing a non-empty 0 RTT or 1 RTT ack-eliciting packet,
+ * the packet is padded to the minimum length of a sending path MTU or
+ * a given packet buffer.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED`
+ *     Packet number is exhausted, and cannot send any more packet.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     User callback failed
+ * :macro:`NGTCP2_ERR_WRITE_MORE`
+ *     (Only when :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` is
+ *     specified) Application can call this function to pack more data
+ *     into the same packet.  See above to know how it works.
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     A remote endpoint did not express the DATAGRAM frame support.
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     The provisional DATAGRAM frame size exceeds the maximum
+ *     DATAGRAM frame size that a remote endpoint can receive.
+ *
+ * If any other negative error is returned, call
+ * `ngtcp2_conn_write_connection_close` to get terminal packet, and
+ * sending it makes QUIC connection enter the closing state.
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_datagram_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted,
+  uint32_t flags, uint64_t dgram_id, const ngtcp2_vec *datav, size_t datavcnt,
+  ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_in_closing_period` returns nonzero if |conn| is in the
+ * closing period.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_in_closing_period(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_in_draining_period` returns nonzero if |conn| is in
+ * the draining period.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_in_draining_period(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_extend_max_stream_offset` extends the maximum stream
+ * data that a remote endpoint can send by |datalen|.  |stream_id|
+ * specifies the stream ID.  This function only extends stream-level
+ * flow control window.
+ *
+ * This function returns 0 if a stream denoted by |stream_id| is not
+ * found.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     |stream_id| refers to a local unidirectional stream.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn,
+                                                       int64_t stream_id,
+                                                       uint64_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_extend_max_offset` extends max data offset by
+ * |datalen|.  This function only extends connection-level flow
+ * control window.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_extend_max_offset(ngtcp2_conn *conn,
+                                                 uint64_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_extend_max_streams_bidi` extends the number of maximum
+ * remote bidirectional streams that a remote endpoint can open by
+ * |n|.
+ *
+ * The library does not increase maximum stream limit automatically.
+ * The exception is when a stream is closed without
+ * :member:`ngtcp2_callbacks.stream_open` callback being called.  In
+ * this case, stream limit is increased automatically.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_extend_max_streams_bidi(ngtcp2_conn *conn,
+                                                       size_t n);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_extend_max_streams_uni` extends the number of maximum
+ * remote unidirectional streams that a remote endpoint can open by
+ * |n|.
+ *
+ * The library does not increase maximum stream limit automatically.
+ * The exception is when a stream is closed without
+ * :member:`ngtcp2_callbacks.stream_open` callback being called.  In
+ * this case, stream limit is increased automatically.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_extend_max_streams_uni(ngtcp2_conn *conn,
+                                                      size_t n);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_dcid` returns the non-NULL pointer to the current
+ * Destination Connection ID.  If no Destination Connection ID is
+ * present, the return value is not ``NULL``, and its :member:`datalen
+ * <ngtcp2_cid.datalen>` field is 0.
+ */
+NGTCP2_EXTERN const ngtcp2_cid *ngtcp2_conn_get_dcid(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_client_initial_dcid` returns the non-NULL pointer
+ * to the Destination Connection ID that client sent in its Initial
+ * packet.  If the Destination Connection ID is not present, the
+ * return value is not ``NULL``, and its :member:`datalen
+ * <ngtcp2_cid.datalen>` field is 0.
+ */
+NGTCP2_EXTERN const ngtcp2_cid *
+ngtcp2_conn_get_client_initial_dcid(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_scid` writes the all Source Connection IDs which a
+ * local endpoint has provided to a remote endpoint, and are not
+ * retired in |dest|.  If |dest| is NULL, this function does not write
+ * anything, and returns the number of Source Connection IDs that
+ * would otherwise be written to the provided buffer.  The buffer
+ * pointed by |dest| must have sizeof(:type:`ngtcp2_cid`) * n bytes
+ * available, where n is the return value of `ngtcp2_conn_get_scid`
+ * with |dest| == NULL.
+ */
+NGTCP2_EXTERN size_t ngtcp2_conn_get_scid(ngtcp2_conn *conn, ngtcp2_cid *dest);
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_cid_token` is the convenient struct to store
+ * Connection ID, its associated path, and stateless reset token.
+ */
+typedef struct ngtcp2_cid_token {
+  /**
+   * :member:`seq` is the sequence number of this Connection ID.
+   */
+  uint64_t seq;
+  /**
+   * :member:`cid` is Connection ID.
+   */
+  ngtcp2_cid cid;
+  /**
+   * :member:`ps` is the path which this Connection ID is associated
+   * with.
+   */
+  ngtcp2_path_storage ps;
+  /**
+   * :member:`token` is the stateless reset token for this Connection
+   * ID.
+   */
+  uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN];
+  /**
+   * :member:`token_present` is nonzero if token contains stateless
+   * reset token.
+   */
+  uint8_t token_present;
+} ngtcp2_cid_token;
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_active_dcid` writes the all active Destination
+ * Connection IDs and their tokens to |dest|.  Before handshake
+ * completes, this function returns 0.  If |dest| is NULL, this
+ * function does not write anything, and returns the number of
+ * Destination Connection IDs that would otherwise be written to the
+ * provided buffer.  The buffer pointed by |dest| must have
+ * sizeof(:type:`ngtcp2_cid_token`) * n bytes available, where n is
+ * the return value of `ngtcp2_conn_get_active_dcid` with |dest| ==
+ * NULL.
+ */
+NGTCP2_EXTERN size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn,
+                                                 ngtcp2_cid_token *dest);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_client_chosen_version` returns the client chosen
+ * version.
+ */
+NGTCP2_EXTERN uint32_t ngtcp2_conn_get_client_chosen_version(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_negotiated_version` returns the negotiated
+ * version.
+ *
+ * Until the version is negotiated, this function returns 0.
+ */
+NGTCP2_EXTERN uint32_t ngtcp2_conn_get_negotiated_version(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `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:
+ *
+ * - Any opened streams.
+ * - Stream identifier allocations.
+ * - Max data extended by `ngtcp2_conn_extend_max_offset`.
+ * - Max bidi streams extended by `ngtcp2_conn_extend_max_streams_bidi`.
+ * - Max uni streams extended by `ngtcp2_conn_extend_max_streams_uni`.
+ *
+ * Application which wishes to retransmit early data, it has to open
+ * streams, and send stream data again.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     User callback failed
+ */
+NGTCP2_EXTERN int ngtcp2_conn_tls_early_data_rejected(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_tls_early_data_rejected` returns nonzero if
+ * `ngtcp2_conn_tls_early_data_rejected` has been called.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_get_tls_early_data_rejected(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_conn_info` assigns connection statistics data to
+ * |*cinfo|.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_get_conn_info_versioned(ngtcp2_conn *conn,
+                                                       int conn_info_version,
+                                                       ngtcp2_conn_info *cinfo);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_submit_crypto_data` submits crypto data |data| of
+ * length |datalen| to the library for transmission.
+ * |encryption_level| specifies the encryption level of data.
+ *
+ * The library makes a copy of the buffer pointed by |data| of length
+ * |datalen|.  Application can discard |data|.
+ */
+NGTCP2_EXTERN int
+ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn,
+                               ngtcp2_encryption_level encryption_level,
+                               const uint8_t *data, const size_t datalen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_submit_new_token` submits address validation token.
+ * It is sent in NEW_TOKEN frame.  Only server can call this function.
+ * |tokenlen| must not be 0.
+ *
+ * This function makes a copy of the buffer pointed by |token| of
+ * length |tokenlen|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn,
+                                               const uint8_t *token,
+                                               size_t tokenlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_local_addr` sets local endpoint address |addr| to
+ * the current path of |conn|.  This function is provided for testing
+ * purpose only.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_set_local_addr(ngtcp2_conn *conn,
+                                              const ngtcp2_addr *addr);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_path_user_data` sets the |path_user_data| to the
+ * current path (see :member:`ngtcp2_path.user_data`).
+ */
+NGTCP2_EXTERN void ngtcp2_conn_set_path_user_data(ngtcp2_conn *conn,
+                                                  void *path_user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_path` returns the current path.
+ */
+NGTCP2_EXTERN const ngtcp2_path *ngtcp2_conn_get_path(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_max_tx_udp_payload_size` returns the maximum UDP
+ * payload size that this local endpoint would send.  This is the
+ * value of :member:`ngtcp2_settings.max_tx_udp_payload_size` that is
+ * passed to `ngtcp2_conn_client_new` or `ngtcp2_conn_server_new`.
+ */
+NGTCP2_EXTERN size_t ngtcp2_conn_get_max_tx_udp_payload_size(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_path_max_tx_udp_payload_size` returns the maximum
+ * UDP payload size for the current path.  If
+ * :member:`ngtcp2_settings.no_tx_udp_payload_size_shaping` is set to
+ * nonzero, this function is equivalent to
+ * `ngtcp2_conn_get_max_tx_udp_payload_size`.  Otherwise, it returns
+ * the maximum UDP payload size that is probed for the current path.
+ */
+NGTCP2_EXTERN size_t
+ngtcp2_conn_get_path_max_tx_udp_payload_size(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_initiate_immediate_migration` starts connection
+ * migration to the given |path|.  Only client can initiate migration.
+ * This function does immediate migration; while the path validation
+ * is nonetheless performed, this function does not wait for it to
+ * succeed.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     Migration is disabled; or handshake is not yet confirmed; or
+ *     client is migrating to server's preferred address.
+ * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED`
+ *     No unused connection ID is available.
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     :member:`local <ngtcp2_path.local>` field of |path| equals the
+ *     current local address.
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ */
+NGTCP2_EXTERN int ngtcp2_conn_initiate_immediate_migration(
+  ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_initiate_migration` starts connection migration to the
+ * given |path|.  Only client can initiate migration.  Unlike
+ * `ngtcp2_conn_initiate_immediate_migration`, this function starts a
+ * path validation with a new path, and migrate to the new path after
+ * successful path validation.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     Migration is disabled; or handshake is not yet confirmed; or
+ *     client is migrating to server's preferred address.
+ * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED`
+ *     No unused connection ID is available.
+ * :macro:`NGTCP2_ERR_INVALID_ARGUMENT`
+ *     :member:`local <ngtcp2_path.local>` field of |path| equals the
+ *     current local address.
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ */
+NGTCP2_EXTERN int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn,
+                                                 const ngtcp2_path *path,
+                                                 ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_max_data_left` returns the number of bytes that
+ * this local endpoint can send in this connection without violating
+ * connection-level flow control.
+ */
+NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_data_left(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_max_stream_data_left` returns the number of bytes
+ * that this local endpoint can send to a stream identified by
+ * |stream_id| without violating stream-level flow control.  If no
+ * such stream is found, this function returns 0.
+ */
+NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_stream_data_left(ngtcp2_conn *conn,
+                                                            int64_t stream_id);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_streams_bidi_left` returns the number of
+ * bidirectional streams which the local endpoint can open without
+ * violating stream concurrency limit.
+ */
+NGTCP2_EXTERN uint64_t ngtcp2_conn_get_streams_bidi_left(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_streams_uni_left` returns the number of
+ * unidirectional streams which the local endpoint can open without
+ * violating stream concurrency limit.
+ */
+NGTCP2_EXTERN uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_cwnd_left` returns the cwnd minus the number of
+ * bytes in flight on the current path.  If the former is smaller than
+ * the latter, this function returns 0.
+ */
+NGTCP2_EXTERN uint64_t ngtcp2_conn_get_cwnd_left(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_initial_crypto_ctx` sets |ctx| for Initial packet
+ * encryption.  The passed data will be passed to
+ * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and
+ * :type:`ngtcp2_hp_mask` callbacks.
+ */
+NGTCP2_EXTERN void
+ngtcp2_conn_set_initial_crypto_ctx(ngtcp2_conn *conn,
+                                   const ngtcp2_crypto_ctx *ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_initial_crypto_ctx` returns
+ * :type:`ngtcp2_crypto_ctx` object for Initial packet encryption.
+ */
+NGTCP2_EXTERN const ngtcp2_crypto_ctx *
+ngtcp2_conn_get_initial_crypto_ctx(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_crypto_ctx` sets |ctx| for Handshake/1-RTT packet
+ * encryption.  The passed data will be passed to
+ * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and
+ * :type:`ngtcp2_hp_mask` callbacks.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_set_crypto_ctx(ngtcp2_conn *conn,
+                                              const ngtcp2_crypto_ctx *ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_crypto_ctx` returns :type:`ngtcp2_crypto_ctx`
+ * object for Handshake/1-RTT packet encryption.
+ */
+NGTCP2_EXTERN const ngtcp2_crypto_ctx *
+ngtcp2_conn_get_crypto_ctx(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_0rtt_crypto_ctx` sets |ctx| for 0-RTT packet
+ * encryption.  The passed data will be passed to
+ * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and
+ * :type:`ngtcp2_hp_mask` callbacks.
+ */
+NGTCP2_EXTERN void
+ngtcp2_conn_set_0rtt_crypto_ctx(ngtcp2_conn *conn,
+                                const ngtcp2_crypto_ctx *ctx);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_0rtt_crypto_ctx` returns :type:`ngtcp2_crypto_ctx`
+ * object for 0-RTT packet encryption.
+ */
+NGTCP2_EXTERN const ngtcp2_crypto_ctx *
+ngtcp2_conn_get_0rtt_crypto_ctx(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_tls_native_handle` returns TLS native handle set
+ * by `ngtcp2_conn_set_tls_native_handle`.
+ */
+NGTCP2_EXTERN void *ngtcp2_conn_get_tls_native_handle(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_tls_native_handle` sets TLS native handle
+ * |tls_native_handle| to |conn|.  Internally, it is used as an opaque
+ * pointer.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_set_tls_native_handle(ngtcp2_conn *conn,
+                                                     void *tls_native_handle);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_retry_aead` sets |aead| and |aead_ctx| for Retry
+ * integrity tag verification.  |aead| must be AEAD_AES_128_GCM.
+ * |aead_ctx| must be initialized with :macro:`NGTCP2_RETRY_KEY` as
+ * encryption key.  This function must be called if |conn| is
+ * initialized as client.  Server does not verify the tag, and has no
+ * need to call this function.
+ *
+ * |conn| takes ownership of |aead_ctx|.
+ * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` will be called to
+ * delete this object when it is no longer used.
+ */
+NGTCP2_EXTERN void
+ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn, const ngtcp2_crypto_aead *aead,
+                           const ngtcp2_crypto_aead_ctx *aead_ctx);
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_ccerr_type` defines connection error type.
+ */
+typedef enum ngtcp2_ccerr_type {
+  /**
+   * :enum:`NGTCP2_CCERR_TYPE_TRANSPORT` indicates the QUIC transport
+   * error, and the error code is QUIC transport error code.
+   */
+  NGTCP2_CCERR_TYPE_TRANSPORT,
+  /**
+   * :enum:`NGTCP2_CCERR_TYPE_APPLICATION` indicates an application
+   * error, and the error code is application error code.
+   */
+  NGTCP2_CCERR_TYPE_APPLICATION,
+  /**
+   * :enum:`NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION` is a special case
+   * of QUIC transport error, and it indicates that client receives
+   * Version Negotiation packet.
+   */
+  NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION,
+  /**
+   * :enum:`NGTCP2_CCERR_TYPE_IDLE_CLOSE` is a special case of QUIC
+   * transport error, and it indicates that connection is closed
+   * because of idle timeout.
+   */
+  NGTCP2_CCERR_TYPE_IDLE_CLOSE,
+  /**
+   * :enum:`NGTCP2_CCERR_TYPE_DROP_CONN` is a special case of QUIC
+   * transport error, and it indicates that connection should be
+   * dropped without sending a CONNECTION_CLOSE frame.
+   */
+  NGTCP2_CCERR_TYPE_DROP_CONN,
+  /**
+   * :enum:`NGTCP2_CCERR_TYPE_RETRY` is a special case of QUIC
+   * transport error, and it indicates that RETRY packet should be
+   * sent to a client.
+   */
+  NGTCP2_CCERR_TYPE_RETRY
+} ngtcp2_ccerr_type;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_ccerr` contains connection error code, its type, a
+ * frame type that caused this error, and the optional reason phrase.
+ */
+typedef struct ngtcp2_ccerr {
+  /**
+   * :member:`type` is the type of this error.
+   */
+  ngtcp2_ccerr_type type;
+  /**
+   * :member:`error_code` is the error code for connection closure.
+   * Its interpretation depends on :member:`type`.
+   */
+  uint64_t error_code;
+  /**
+   * :member:`frame_type` is the type of QUIC frame which triggers
+   * this connection error.  This field is set to 0 if the frame type
+   * is unknown.
+   */
+  uint64_t frame_type;
+  /**
+   * :member:`reason` points to the buffer which contains a reason
+   * phrase.  It may be NULL if there is no reason phrase.  If it is
+   * received from a remote endpoint, it is truncated to at most 1024
+   * bytes.
+   */
+  const uint8_t *reason;
+  /**
+   * :member:`reasonlen` is the length of data pointed by
+   * :member:`reason`.
+   */
+  size_t reasonlen;
+} ngtcp2_ccerr;
+
+/**
+ * @function
+ *
+ * `ngtcp2_ccerr_default` initializes |ccerr| with the default values.
+ * It sets the following fields:
+ *
+ * - :member:`type <ngtcp2_ccerr.type>` =
+ *   :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`
+ * - :member:`error_code <ngtcp2_ccerr.error_code>` =
+ *   :macro:`NGTCP2_NO_ERROR`.
+ * - :member:`frame_type <ngtcp2_ccerr.frame_type>` = 0
+ * - :member:`reason <ngtcp2_ccerr.reason>` = NULL
+ * - :member:`reasonlen <ngtcp2_ccerr.reasonlen>` = 0
+ */
+NGTCP2_EXTERN void ngtcp2_ccerr_default(ngtcp2_ccerr *ccerr);
+
+/**
+ * @function
+ *
+ * `ngtcp2_ccerr_set_transport_error` sets :member:`ccerr->type
+ * <ngtcp2_ccerr.type>` to
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, and
+ * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to
+ * |error_code|.  |reason| is the reason phrase of length |reasonlen|.
+ * This function does not make a copy of the reason phrase.
+ */
+NGTCP2_EXTERN void ngtcp2_ccerr_set_transport_error(ngtcp2_ccerr *ccerr,
+                                                    uint64_t error_code,
+                                                    const uint8_t *reason,
+                                                    size_t reasonlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_ccerr_set_liberr` sets type and error_code based on
+ * |liberr|.
+ *
+ * |reason| is the reason phrase of length |reasonlen|.  This function
+ * does not make a copy of the reason phrase.
+ *
+ * If |liberr| is :macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION`,
+ * :member:`ccerr->type <ngtcp2_ccerr.type>` is set to
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION`,
+ * and :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to
+ * :macro:`NGTCP2_NO_ERROR`.
+ *
+ * If |liberr| is :macro:`NGTCP2_ERR_IDLE_CLOSE`, :member:`ccerr->type
+ * <ngtcp2_ccerr.type>` is set to
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_IDLE_CLOSE`, and
+ * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to
+ * :macro:`NGTCP2_NO_ERROR`.
+ *
+ * If |liberr| is :macro:`NGTCP2_ERR_DROP_CONN`, :member:`ccerr->type
+ * <ngtcp2_ccerr.type>` is set to
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_DROP_CONN`, and
+ * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to
+ * :macro:`NGTCP2_NO_ERROR`.
+ *
+ * If |liberr| is :macro:`NGTCP2_ERR_RETRY`, :member:`ccerr->type
+ * <ngtcp2_ccerr.type>` is set to
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_RETRY`, and
+ * :member:`ccerr->error_type <ngtcp2_ccerr.error_code>` to
+ * :macro:`NGTCP2_NO_ERROR`.
+ *
+ * Otherwise, :member:`ccerr->type <ngtcp2_ccerr.type>` is set to
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, and
+ * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` is set to an
+ * error code inferred by |liberr| (see
+ * `ngtcp2_err_infer_quic_transport_error_code`).
+ */
+NGTCP2_EXTERN void ngtcp2_ccerr_set_liberr(ngtcp2_ccerr *ccerr, int liberr,
+                                           const uint8_t *reason,
+                                           size_t reasonlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_ccerr_set_tls_alert` sets :member:`ccerr->type
+ * <ngtcp2_ccerr.type>` to
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, and
+ * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to bitwise-OR
+ * of :macro:`NGTCP2_CRYPTO_ERROR` and |tls_alert|.  |reason| is the
+ * reason phrase of length |reasonlen|.  This function does not make a
+ * copy of the reason phrase.
+ */
+NGTCP2_EXTERN void ngtcp2_ccerr_set_tls_alert(ngtcp2_ccerr *ccerr,
+                                              uint8_t tls_alert,
+                                              const uint8_t *reason,
+                                              size_t reasonlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_ccerr_set_application_error` sets :member:`ccerr->type
+ * <ngtcp2_ccerr.type>` to
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_APPLICATION`, and
+ * :member:`ccerr->error_code <ngtcp2_ccerr.error_code>` to
+ * |error_code|.  |reason| is the reason phrase of length |reasonlen|.
+ * This function does not make a copy of the reason phrase.
+ */
+NGTCP2_EXTERN void ngtcp2_ccerr_set_application_error(ngtcp2_ccerr *ccerr,
+                                                      uint64_t error_code,
+                                                      const uint8_t *reason,
+                                                      size_t reasonlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_write_connection_close` writes a packet which contains
+ * CONNECTION_CLOSE frame(s) (type 0x1c or 0x1d) in the buffer pointed
+ * by |dest| whose capacity is |destlen|.
+ *
+ * For client, |destlen| should be at least
+ * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE`.
+ *
+ * If |path| is not ``NULL``, this function stores the network path
+ * with which the packet should be sent.  Each addr field must point
+ * to the buffer which should be at least
+ * sizeof(:type:`ngtcp2_sockaddr_union`) bytes long.  The assignment
+ * might not be done if nothing is written to |dest|.
+ *
+ * If |pi| is not ``NULL``, this function stores packet metadata in it
+ * if it succeeds.  The metadata includes ECN markings.
+ *
+ * If :member:`ccerr->type <ngtcp2_ccerr.type>` ==
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_TRANSPORT`, this
+ * function sends CONNECTION_CLOSE (type 0x1c) frame.  If
+ * :member:`ccerr->type <ngtcp2_ccerr.type>` ==
+ * :enum:`ngtcp2_ccerr_type.NGTCP2_CCERR_TYPE_APPLICATION`, it sends
+ * CONNECTION_CLOSE (type 0x1d) frame.  Otherwise, it does not produce
+ * any data, and returns 0.
+ *
+ * |destlen| could be shorten by some factors (e.g., server side
+ * amplification limit).  This function returns
+ * :macro:`NGTCP2_ERR_NOBUF` if the resulting buffer is too small even
+ * if the given buffer has enough space.
+ *
+ * This function must not be called from inside the callback
+ * functions.
+ *
+ * At the moment, successful call to this function makes connection
+ * close.  We may change this behaviour in the future to allow
+ * graceful shutdown.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     The current state does not allow sending CONNECTION_CLOSE
+ *     frame.
+ * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED`
+ *     Packet number is exhausted, and cannot send any more packet.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     User callback failed
+ */
+NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_connection_close_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, const ngtcp2_ccerr *ccerr,
+  ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_ccerr` returns the received connection close
+ * error.  If no connection error is received, it returns
+ * :type:`ngtcp2_ccerr` that is initialized by `ngtcp2_ccerr_default`.
+ */
+NGTCP2_EXTERN const ngtcp2_ccerr *ngtcp2_conn_get_ccerr(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_is_local_stream` returns nonzero if |stream_id|
+ * denotes a locally initiated stream.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_is_local_stream(ngtcp2_conn *conn,
+                                              int64_t stream_id);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_is_server` returns nonzero if |conn| is initialized as
+ * server.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_is_server(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_after_retry` returns nonzero if |conn| as a client has
+ * received Retry packet from server, and successfully validated it.
+ */
+NGTCP2_EXTERN int ngtcp2_conn_after_retry(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_stream_user_data` sets |stream_user_data| to the
+ * stream identified by |stream_id|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`
+ *     Stream does not exist
+ */
+NGTCP2_EXTERN int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn,
+                                                   int64_t stream_id,
+                                                   void *stream_user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_update_pkt_tx_time` sets the time instant of the next
+ * packet transmission to pace packets.  This function must be called
+ * after (multiple invocation of) `ngtcp2_conn_writev_stream`.  If
+ * packet aggregation (e.g., packet batching, GSO) is used, call this
+ * function after all aggregated datagrams are sent, which indicates
+ * multiple invocation of `ngtcp2_conn_writev_stream`.
+ */
+NGTCP2_EXTERN void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn,
+                                                  ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_send_quantum` returns the maximum number of bytes
+ * that can be sent in one go without packet spacing.
+ */
+NGTCP2_EXTERN size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_stream_loss_count` returns the number of packets
+ * that contain STREAM frame for a stream identified by |stream_id|
+ * and are declared to be lost.  The number may include the spurious
+ * losses.  If no stream identified by |stream_id| is found, this
+ * function returns 0.
+ */
+NGTCP2_EXTERN size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn,
+                                                       int64_t stream_id);
+
+/**
+ * @function
+ *
+ * `ngtcp2_strerror` returns the text representation of |liberr|.
+ * |liberr| must be one of ngtcp2 library error codes (which is
+ * defined as :macro:`NGTCP2_ERR_* <NGTCP2_ERR_INVALID_ARGUMENT>`
+ * macros).
+ */
+NGTCP2_EXTERN const char *ngtcp2_strerror(int liberr);
+
+/**
+ * @function
+ *
+ * `ngtcp2_err_is_fatal` returns nonzero if |liberr| is a fatal error.
+ * |liberr| must be one of ngtcp2 library error codes (which is
+ * defined as :macro:`NGTCP2_ERR_* <NGTCP2_ERR_INVALID_ARGUMENT>`
+ * macros).
+ */
+NGTCP2_EXTERN int ngtcp2_err_is_fatal(int liberr);
+
+/**
+ * @function
+ *
+ * `ngtcp2_err_infer_quic_transport_error_code` returns a QUIC
+ * transport error code which corresponds to |liberr|.  |liberr| must
+ * be one of ngtcp2 library error codes (which is defined as
+ * :macro:`NGTCP2_ERR_* <NGTCP2_ERR_INVALID_ARGUMENT>` macros).
+ */
+NGTCP2_EXTERN uint64_t ngtcp2_err_infer_quic_transport_error_code(int liberr);
+
+/**
+ * @function
+ *
+ * `ngtcp2_addr_init` initializes |dest| with the given arguments and
+ * returns |dest|.
+ */
+NGTCP2_EXTERN ngtcp2_addr *ngtcp2_addr_init(ngtcp2_addr *dest,
+                                            const ngtcp2_sockaddr *addr,
+                                            ngtcp2_socklen addrlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_addr_copy_byte` copies |addr| of length |addrlen| into the
+ * buffer pointed by :member:`dest->addr <ngtcp2_addr.addr>`.
+ * :member:`dest->addrlen <ngtcp2_addr.addrlen>` is updated to have
+ * |addrlen|.  This function assumes that :member:`dest->addr
+ * <ngtcp2_addr.addr>` points to a buffer which has a sufficient
+ * capacity to store the copy.
+ */
+NGTCP2_EXTERN void ngtcp2_addr_copy_byte(ngtcp2_addr *dest,
+                                         const ngtcp2_sockaddr *addr,
+                                         ngtcp2_socklen addrlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_path_storage_init` initializes |ps| with the given
+ * arguments.  This function copies |local_addr| and |remote_addr|.
+ */
+NGTCP2_EXTERN void ngtcp2_path_storage_init(ngtcp2_path_storage *ps,
+                                            const ngtcp2_sockaddr *local_addr,
+                                            ngtcp2_socklen local_addrlen,
+                                            const ngtcp2_sockaddr *remote_addr,
+                                            ngtcp2_socklen remote_addrlen,
+                                            void *user_data);
+
+/**
+ * @function
+ *
+ * `ngtcp2_path_storage_zero` initializes |ps| with the zero length
+ * addresses.
+ */
+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:
+ *
+ * * :type:`cc_algo <ngtcp2_settings.cc_algo>` =
+ *   :enum:`ngtcp2_cc_algo.NGTCP2_CC_ALGO_CUBIC`
+ * * :type:`initial_rtt <ngtcp2_settings.initial_rtt>` =
+ *   :macro:`NGTCP2_DEFAULT_INITIAL_RTT`
+ * * :type:`ack_thresh <ngtcp2_settings.ack_thresh>` = 2
+ * * :type:`max_tx_udp_payload_size
+ *   <ngtcp2_settings.max_tx_udp_payload_size>` = 1452
+ * * :type:`handshake_timeout <ngtcp2_settings.handshake_timeout>` =
+ *   ``UINT64_MAX``
+ */
+NGTCP2_EXTERN void ngtcp2_settings_default_versioned(int settings_version,
+                                                     ngtcp2_settings *settings);
+
+/**
+ * @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:
+ *
+ * * :type:`max_udp_payload_size
+ *   <ngtcp2_transport_params.max_udp_payload_size>` =
+ *   :macro:`NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE`
+ * * :type:`ack_delay_exponent
+ *   <ngtcp2_transport_params.ack_delay_exponent>` =
+ *   :macro:`NGTCP2_DEFAULT_ACK_DELAY_EXPONENT`
+ * * :type:`max_ack_delay <ngtcp2_transport_params.max_ack_delay>` =
+ *   :macro:`NGTCP2_DEFAULT_MAX_ACK_DELAY`
+ * * :type:`active_connection_id_limit
+ *   <ngtcp2_transport_params.active_connection_id_limit>` =
+ *   :macro:`NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT`
+ */
+NGTCP2_EXTERN void
+ngtcp2_transport_params_default_versioned(int transport_params_version,
+                                          ngtcp2_transport_params *params);
+
+/**
+ * @function
+ *
+ * `ngtcp2_mem_default` returns the default, system standard memory
+ * allocator.
+ */
+NGTCP2_EXTERN const ngtcp2_mem *ngtcp2_mem_default(void);
+
+/**
+ * @macrosection
+ *
+ * ngtcp2_info macros
+ */
+
+/**
+ * @macro
+ *
+ * :macro:`NGTCP2_VERSION_AGE` is the age of :type:`ngtcp2_info`
+ */
+#define NGTCP2_VERSION_AGE 1
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_info` is what `ngtcp2_version` returns.  It holds
+ * information about the particular ngtcp2 version.
+ */
+typedef struct ngtcp2_info {
+  /**
+   * :member:`age` is the age of this struct.  This instance of ngtcp2
+   * sets it to :macro:`NGTCP2_VERSION_AGE` but a future version may
+   * bump it and add more struct fields at the bottom
+   */
+  int age;
+  /**
+   * :member:`version_num` is the :macro:`NGTCP2_VERSION_NUM` number
+   * (since :member:`age` ==1)
+   */
+  int version_num;
+  /**
+   * :member:`version_str` points to the :macro:`NGTCP2_VERSION`
+   * string (since :member:`age` ==1)
+   */
+  const char *version_str;
+  /* -------- the above fields all exist when age == 1 */
+} ngtcp2_info;
+
+/**
+ * @function
+ *
+ * `ngtcp2_version` returns a pointer to a :type:`ngtcp2_info` struct
+ * with version information about the run-time library in use.  The
+ * |least_version| argument can be set to a 24 bit numerical value for
+ * the least accepted version number, and if the condition is not met,
+ * this function will return a ``NULL``.  Pass in 0 to skip the
+ * version checking.
+ */
+NGTCP2_EXTERN const ngtcp2_info *ngtcp2_version(int least_version);
+
+/**
+ * @function
+ *
+ * `ngtcp2_is_bidi_stream` returns nonzero if |stream_id| denotes
+ * bidirectional stream.
+ */
+NGTCP2_EXTERN int ngtcp2_is_bidi_stream(int64_t stream_id);
+
+/**
+ * @function
+ *
+ * `ngtcp2_path_copy` copies |src| into |dest|.  This function assumes
+ * that |dest| has enough buffer to store the deep copy of
+ * :member:`src->local <ngtcp2_path.local>` and :member:`src->remote
+ * <ngtcp2_path.remote>`.
+ */
+NGTCP2_EXTERN void ngtcp2_path_copy(ngtcp2_path *dest, const ngtcp2_path *src);
+
+/**
+ * @function
+ *
+ * `ngtcp2_path_eq` returns nonzero if |a| and |b| shares the same
+ * local and remote addresses.
+ */
+NGTCP2_EXTERN int ngtcp2_path_eq(const ngtcp2_path *a, const ngtcp2_path *b);
+
+/**
+ * @function
+ *
+ * `ngtcp2_is_supported_version` returns nonzero if the library
+ * supports QUIC version |version|.
+ */
+NGTCP2_EXTERN int ngtcp2_is_supported_version(uint32_t version);
+
+/**
+ * @function
+ *
+ * `ngtcp2_is_reserved_version` returns nonzero if |version| is a
+ * reserved version.
+ */
+NGTCP2_EXTERN int ngtcp2_is_reserved_version(uint32_t version);
+
+/**
+ * @function
+ *
+ * `ngtcp2_select_version` selects and returns a version from the
+ * version set |offered_versions| of |offered_versionslen| elements.
+ * |preferred_versions| of |preferred_versionslen| elements specifies
+ * the preference of versions, which is sorted in the order of
+ * preference.  All versions included in |preferred_versions| must be
+ * supported by the library, that is, passing any version in the array
+ * to `ngtcp2_is_supported_version` must return nonzero.  This
+ * function is intended to be used by client when it receives Version
+ * Negotiation packet.  If no version is selected, this function
+ * returns 0.
+ */
+NGTCP2_EXTERN uint32_t ngtcp2_select_version(const uint32_t *preferred_versions,
+                                             size_t preferred_versionslen,
+                                             const uint32_t *offered_versions,
+                                             size_t offered_versionslen);
+
+/*
+ * Versioned function wrappers
+ */
+
+/*
+ * `ngtcp2_conn_read_pkt` is a wrapper around
+ * `ngtcp2_conn_read_pkt_versioned` to set the correct struct version.
+ */
+#define ngtcp2_conn_read_pkt(CONN, PATH, PI, PKT, PKTLEN, TS)                  \
+  ngtcp2_conn_read_pkt_versioned((CONN), (PATH), NGTCP2_PKT_INFO_VERSION,      \
+                                 (PI), (PKT), (PKTLEN), (TS))
+
+/*
+ * `ngtcp2_conn_write_pkt` is a wrapper around
+ * `ngtcp2_conn_write_pkt_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_conn_write_pkt(CONN, PATH, PI, DEST, DESTLEN, TS)               \
+  ngtcp2_conn_write_pkt_versioned((CONN), (PATH), NGTCP2_PKT_INFO_VERSION,     \
+                                  (PI), (DEST), (DESTLEN), (TS))
+
+/*
+ * `ngtcp2_conn_write_stream` is a wrapper around
+ * `ngtcp2_conn_write_stream_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_conn_write_stream(CONN, PATH, PI, DEST, DESTLEN, PDATALEN,      \
+                                 FLAGS, STREAM_ID, DATA, DATALEN, TS)          \
+  ngtcp2_conn_write_stream_versioned(                                          \
+    (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN),          \
+    (PDATALEN), (FLAGS), (STREAM_ID), (DATA), (DATALEN), (TS))
+
+/*
+ * `ngtcp2_conn_writev_stream` is a wrapper around
+ * `ngtcp2_conn_writev_stream_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_conn_writev_stream(CONN, PATH, PI, DEST, DESTLEN, PDATALEN,     \
+                                  FLAGS, STREAM_ID, DATAV, DATAVCNT, TS)       \
+  ngtcp2_conn_writev_stream_versioned(                                         \
+    (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN),          \
+    (PDATALEN), (FLAGS), (STREAM_ID), (DATAV), (DATAVCNT), (TS))
+
+/*
+ * `ngtcp2_conn_write_datagram` is a wrapper around
+ * `ngtcp2_conn_write_datagram_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_conn_write_datagram(CONN, PATH, PI, DEST, DESTLEN, PACCEPTED,   \
+                                   FLAGS, DGRAM_ID, DATA, DATALEN, TS)         \
+  ngtcp2_conn_write_datagram_versioned(                                        \
+    (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN),          \
+    (PACCEPTED), (FLAGS), (DGRAM_ID), (DATA), (DATALEN), (TS))
+
+/*
+ * `ngtcp2_conn_writev_datagram` is a wrapper around
+ * `ngtcp2_conn_writev_datagram_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_conn_writev_datagram(CONN, PATH, PI, DEST, DESTLEN, PACCEPTED,  \
+                                    FLAGS, DGRAM_ID, DATAV, DATAVCNT, TS)      \
+  ngtcp2_conn_writev_datagram_versioned(                                       \
+    (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN),          \
+    (PACCEPTED), (FLAGS), (DGRAM_ID), (DATAV), (DATAVCNT), (TS))
+
+/*
+ * `ngtcp2_conn_write_connection_close` is a wrapper around
+ * `ngtcp2_conn_write_connection_close_versioned` to set the correct
+ * struct version.
+ */
+#define ngtcp2_conn_write_connection_close(CONN, PATH, PI, DEST, DESTLEN,      \
+                                           CCERR, TS)                          \
+  ngtcp2_conn_write_connection_close_versioned(                                \
+    (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), (CCERR), \
+    (TS))
+
+/*
+ * `ngtcp2_transport_params_encode` is a wrapper around
+ * `ngtcp2_transport_params_encode_versioned` to set the correct
+ * struct version.
+ */
+#define ngtcp2_transport_params_encode(DEST, DESTLEN, PARAMS)                  \
+  ngtcp2_transport_params_encode_versioned(                                    \
+    (DEST), (DESTLEN), NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS))
+
+/*
+ * `ngtcp2_transport_params_decode` is a wrapper around
+ * `ngtcp2_transport_params_decode_versioned` to set the correct
+ * struct version.
+ */
+#define ngtcp2_transport_params_decode(PARAMS, DATA, DATALEN)                  \
+  ngtcp2_transport_params_decode_versioned(NGTCP2_TRANSPORT_PARAMS_VERSION,    \
+                                           (PARAMS), (DATA), (DATALEN))
+
+/*
+ * `ngtcp2_conn_client_new` is a wrapper around
+ * `ngtcp2_conn_client_new_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_conn_client_new(PCONN, DCID, SCID, PATH, VERSION, CALLBACKS,    \
+                               SETTINGS, PARAMS, MEM, USER_DATA)               \
+  ngtcp2_conn_client_new_versioned(                                            \
+    (PCONN), (DCID), (SCID), (PATH), (VERSION), NGTCP2_CALLBACKS_VERSION,      \
+    (CALLBACKS), NGTCP2_SETTINGS_VERSION, (SETTINGS),                          \
+    NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS), (MEM), (USER_DATA))
+
+/*
+ * `ngtcp2_conn_server_new` is a wrapper around
+ * `ngtcp2_conn_server_new_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_conn_server_new(PCONN, DCID, SCID, PATH, VERSION, CALLBACKS,    \
+                               SETTINGS, PARAMS, MEM, USER_DATA)               \
+  ngtcp2_conn_server_new_versioned(                                            \
+    (PCONN), (DCID), (SCID), (PATH), (VERSION), NGTCP2_CALLBACKS_VERSION,      \
+    (CALLBACKS), NGTCP2_SETTINGS_VERSION, (SETTINGS),                          \
+    NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS), (MEM), (USER_DATA))
+
+/*
+ * `ngtcp2_conn_set_local_transport_params` is a wrapper around
+ * `ngtcp2_conn_set_local_transport_params_versioned` to set the
+ * correct struct version.
+ */
+#define ngtcp2_conn_set_local_transport_params(CONN, PARAMS)                   \
+  ngtcp2_conn_set_local_transport_params_versioned(                            \
+    (CONN), NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS))
+
+/*
+ * `ngtcp2_transport_params_default` is a wrapper around
+ * `ngtcp2_transport_params_default_versioned` to set the correct
+ * struct version.
+ */
+#define ngtcp2_transport_params_default(PARAMS)                                \
+  ngtcp2_transport_params_default_versioned(NGTCP2_TRANSPORT_PARAMS_VERSION,   \
+                                            (PARAMS))
+
+/*
+ * `ngtcp2_conn_get_conn_info` is a wrapper around
+ * `ngtcp2_conn_get_conn_info_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_conn_get_conn_info(CONN, CINFO)                                 \
+  ngtcp2_conn_get_conn_info_versioned((CONN), NGTCP2_CONN_INFO_VERSION, (CINFO))
+
+/*
+ * `ngtcp2_settings_default` is a wrapper around
+ * `ngtcp2_settings_default_versioned` to set the correct struct
+ * version.
+ */
+#define ngtcp2_settings_default(SETTINGS)                                      \
+  ngtcp2_settings_default_versioned(NGTCP2_SETTINGS_VERSION, (SETTINGS))
+
+#ifdef _MSC_VER
+#  pragma warning(pop)
+#endif /* defined(_MSC_VER) */
+
+#ifdef __cplusplus
+}
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(NGTCP2_H) */
diff --git a/third_party/ngtcp2/lib/includes/ngtcp2/version.h.in b/third_party/ngtcp2/lib/includes/ngtcp2/version.h.in
new file mode 100644 (file)
index 0000000..41fa03e
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2016 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_VERSION_H
+#define NGTCP2_VERSION_H
+
+/**
+ * @macrosection
+ *
+ * Library version macros
+ */
+
+/**
+ * @macro
+ *
+ * Version number of the ngtcp2 library release.
+ */
+#define NGTCP2_VERSION "@PACKAGE_VERSION@"
+
+/**
+ * @macro
+ *
+ * Numerical representation of the version number of the ngtcp2
+ * library release. This is a 24 bit number with 8 bits for major
+ * number, 8 bits for minor and 8 bits for patch. Version 1.2.3
+ * becomes 0x010203.
+ */
+#define NGTCP2_VERSION_NUM @PACKAGE_VERSION_NUM@
+
+#endif /* !defined(NGTCP2_VERSION_H) */
diff --git a/third_party/ngtcp2/lib/libngtcp2.pc.in b/third_party/ngtcp2/lib/libngtcp2.pc.in
new file mode 100644 (file)
index 0000000..6737391
--- /dev/null
@@ -0,0 +1,33 @@
+# ngtcp2
+
+# Copyright (c) 2016 ngtcp2 contributors
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libngtcp2
+Description: ngtcp2 library
+URL: https://github.com/ngtcp2/ngtcp2
+Version: @VERSION@
+Libs: -L${libdir} -lngtcp2
+Cflags: -I${includedir}
diff --git a/third_party/ngtcp2/lib/ngtcp2_acktr.c b/third_party/ngtcp2/lib/ngtcp2_acktr.c
new file mode 100644 (file)
index 0000000..776dc0c
--- /dev/null
@@ -0,0 +1,439 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_acktr.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "ngtcp2_macro.h"
+#include "ngtcp2_tstamp.h"
+
+ngtcp2_objalloc_def(acktr_entry, ngtcp2_acktr_entry, oplent)
+
+static void acktr_entry_init(ngtcp2_acktr_entry *ent, int64_t pkt_num,
+                             ngtcp2_tstamp tstamp) {
+  ent->pkt_num = pkt_num;
+  ent->len = 1;
+  ent->tstamp = tstamp;
+}
+
+int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num,
+                                    ngtcp2_tstamp tstamp,
+                                    ngtcp2_objalloc *objalloc) {
+  *ent = ngtcp2_objalloc_acktr_entry_get(objalloc);
+  if (*ent == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  acktr_entry_init(*ent, pkt_num, tstamp);
+
+  return 0;
+}
+
+void ngtcp2_acktr_entry_objalloc_del(ngtcp2_acktr_entry *ent,
+                                     ngtcp2_objalloc *objalloc) {
+  ngtcp2_objalloc_acktr_entry_release(objalloc, ent);
+}
+
+void ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log,
+                       const ngtcp2_mem *mem) {
+  ngtcp2_objalloc_acktr_entry_init(&acktr->objalloc, NGTCP2_ACKTR_MAX_ENT + 1,
+                                   mem);
+
+  ngtcp2_static_ringbuf_acks_init(&acktr->acks);
+
+  ngtcp2_ksl_init(&acktr->ents, ngtcp2_ksl_int64_greater,
+                  ngtcp2_ksl_int64_greater_search, sizeof(int64_t), mem);
+
+  acktr->log = log;
+  acktr->flags = NGTCP2_ACKTR_FLAG_NONE;
+  acktr->first_unacked_ts = UINT64_MAX;
+  acktr->rx_npkt = 0;
+  acktr->max_pkt_num = -1;
+  acktr->max_pkt_ts = UINT64_MAX;
+  memset(&acktr->ecn, 0, sizeof(acktr->ecn));
+}
+
+void ngtcp2_acktr_free(ngtcp2_acktr *acktr) {
+#ifdef NOMEMPOOL
+  ngtcp2_ksl_it it;
+#endif /* defined(NOMEMPOOL) */
+
+  if (acktr == NULL) {
+    return;
+  }
+
+#ifdef NOMEMPOOL
+  for (it = ngtcp2_ksl_begin(&acktr->ents); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    ngtcp2_acktr_entry_objalloc_del(ngtcp2_ksl_it_get(&it), &acktr->objalloc);
+  }
+#endif /* defined(NOMEMPOOL) */
+
+  ngtcp2_ksl_free(&acktr->ents);
+
+  ngtcp2_objalloc_free(&acktr->objalloc);
+}
+
+int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack,
+                     ngtcp2_tstamp ts) {
+  ngtcp2_ksl_it it, prev_it;
+  ngtcp2_acktr_entry *ent, *prev_ent, *delent;
+  int rv;
+  int added = 0;
+
+  if (ngtcp2_ksl_len(&acktr->ents)) {
+    it = ngtcp2_ksl_lower_bound(&acktr->ents, &pkt_num);
+    if (ngtcp2_ksl_it_end(&it)) {
+      ngtcp2_ksl_it_prev(&it);
+      ent = ngtcp2_ksl_it_get(&it);
+
+      assert(ent->pkt_num >= pkt_num + (int64_t)ent->len);
+
+      if (ent->pkt_num == pkt_num + (int64_t)ent->len) {
+        ++ent->len;
+        added = 1;
+      }
+    } else {
+      ent = ngtcp2_ksl_it_get(&it);
+
+      assert(ent->pkt_num != pkt_num);
+
+      if (ngtcp2_ksl_it_begin(&it)) {
+        if (ent->pkt_num + 1 == pkt_num) {
+          ngtcp2_ksl_update_key(&acktr->ents, &ent->pkt_num, &pkt_num);
+          ent->pkt_num = pkt_num;
+          ent->tstamp = ts;
+          ++ent->len;
+          added = 1;
+        }
+      } else {
+        prev_it = it;
+        ngtcp2_ksl_it_prev(&prev_it);
+        prev_ent = ngtcp2_ksl_it_get(&prev_it);
+
+        assert(prev_ent->pkt_num >= pkt_num + (int64_t)prev_ent->len);
+
+        if (ent->pkt_num + 1 == pkt_num) {
+          if (prev_ent->pkt_num == pkt_num + (int64_t)prev_ent->len) {
+            prev_ent->len += ent->len + 1;
+            ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &ent->pkt_num);
+            ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc);
+            added = 1;
+          } else {
+            ngtcp2_ksl_update_key(&acktr->ents, &ent->pkt_num, &pkt_num);
+            ent->pkt_num = pkt_num;
+            ent->tstamp = ts;
+            ++ent->len;
+            added = 1;
+          }
+        } else if (prev_ent->pkt_num == pkt_num + (int64_t)prev_ent->len) {
+          ++prev_ent->len;
+          added = 1;
+        }
+      }
+    }
+  }
+
+  if (!added) {
+    rv = ngtcp2_acktr_entry_objalloc_new(&ent, pkt_num, ts, &acktr->objalloc);
+    if (rv != 0) {
+      return rv;
+    }
+    rv = ngtcp2_ksl_insert(&acktr->ents, NULL, &ent->pkt_num, ent);
+    if (rv != 0) {
+      ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc);
+      return rv;
+    }
+  }
+
+  if (active_ack) {
+    acktr->flags |= NGTCP2_ACKTR_FLAG_ACTIVE_ACK;
+    if (acktr->first_unacked_ts == UINT64_MAX) {
+      acktr->first_unacked_ts = ts;
+    }
+  }
+
+  if (ngtcp2_ksl_len(&acktr->ents) > NGTCP2_ACKTR_MAX_ENT) {
+    it = ngtcp2_ksl_end(&acktr->ents);
+    ngtcp2_ksl_it_prev(&it);
+    delent = ngtcp2_ksl_it_get(&it);
+    ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &delent->pkt_num);
+    ngtcp2_acktr_entry_objalloc_del(delent, &acktr->objalloc);
+  }
+
+  if (acktr->max_pkt_num < pkt_num) {
+    acktr->max_pkt_num = pkt_num;
+    acktr->max_pkt_ts = ts;
+  }
+
+  return 0;
+}
+
+void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent) {
+  ngtcp2_ksl_it it;
+
+  it = ngtcp2_ksl_lower_bound(&acktr->ents, &ent->pkt_num);
+  assert(*(int64_t *)ngtcp2_ksl_it_key(&it) == (int64_t)ent->pkt_num);
+
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    ent = ngtcp2_ksl_it_get(&it);
+    ngtcp2_ksl_remove_hint(&acktr->ents, &it, &it, &ent->pkt_num);
+    ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc);
+  }
+}
+
+ngtcp2_ksl_it ngtcp2_acktr_get(const ngtcp2_acktr *acktr) {
+  return ngtcp2_ksl_begin(&acktr->ents);
+}
+
+int ngtcp2_acktr_empty(const ngtcp2_acktr *acktr) {
+  ngtcp2_ksl_it it = ngtcp2_ksl_begin(&acktr->ents);
+  return ngtcp2_ksl_it_end(&it);
+}
+
+ngtcp2_acktr_ack_entry *ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr,
+                                             int64_t pkt_num,
+                                             int64_t largest_ack) {
+  ngtcp2_acktr_ack_entry *ent = ngtcp2_ringbuf_push_front(&acktr->acks.rb);
+
+  ent->largest_ack = largest_ack;
+  ent->pkt_num = pkt_num;
+
+  return ent;
+}
+
+/*
+ * acktr_remove removes |ent| from |acktr|.  |it| must point to the
+ * node whose key identifies |ent|.  The iterator which points to the
+ * entry next to |ent| is assigned to |it|.
+ */
+static void acktr_remove(ngtcp2_acktr *acktr, ngtcp2_ksl_it *it,
+                         ngtcp2_acktr_entry *ent) {
+  ngtcp2_ksl_remove_hint(&acktr->ents, it, it, &ent->pkt_num);
+  ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc);
+}
+
+static void acktr_on_ack(ngtcp2_acktr *acktr, ngtcp2_ringbuf *rb,
+                         size_t ack_ent_offset) {
+  ngtcp2_acktr_ack_entry *ack_ent;
+  ngtcp2_acktr_entry *ent;
+  ngtcp2_ksl_it it;
+
+  assert(ngtcp2_ringbuf_len(rb));
+
+  ack_ent = ngtcp2_ringbuf_get(rb, ack_ent_offset);
+
+  /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */
+  it = ngtcp2_ksl_lower_bound(&acktr->ents, &ack_ent->largest_ack);
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    ent = ngtcp2_ksl_it_get(&it);
+    acktr_remove(acktr, &it, ent);
+  }
+
+  if (ngtcp2_ksl_len(&acktr->ents)) {
+    assert(ngtcp2_ksl_it_end(&it));
+
+    ngtcp2_ksl_it_prev(&it);
+    ent = ngtcp2_ksl_it_get(&it);
+
+    assert(ent->pkt_num > ack_ent->largest_ack);
+
+    if (ack_ent->largest_ack + (int64_t)ent->len > ent->pkt_num) {
+      ent->len = (size_t)(ent->pkt_num - ack_ent->largest_ack);
+    }
+  }
+
+  ngtcp2_ringbuf_resize(rb, ack_ent_offset);
+}
+
+void ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr) {
+  ngtcp2_acktr_ack_entry *ent;
+  int64_t largest_ack = fr->largest_ack, min_ack;
+  size_t i, j;
+  ngtcp2_ringbuf *rb = &acktr->acks.rb;
+  size_t nacks = ngtcp2_ringbuf_len(rb);
+
+  /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */
+  for (j = 0; j < nacks; ++j) {
+    ent = ngtcp2_ringbuf_get(rb, j);
+    if (largest_ack >= ent->pkt_num) {
+      break;
+    }
+  }
+  if (j == nacks) {
+    return;
+  }
+
+  min_ack = largest_ack - (int64_t)fr->first_ack_range;
+
+  if (min_ack <= ent->pkt_num) {
+    acktr_on_ack(acktr, rb, j);
+    return;
+  }
+
+  for (i = 0; i < fr->rangecnt && j < nacks; ++i) {
+    largest_ack = min_ack - (int64_t)fr->ranges[i].gap - 2;
+    min_ack = largest_ack - (int64_t)fr->ranges[i].len;
+
+    for (;;) {
+      if (ent->pkt_num > largest_ack) {
+        if (++j == nacks) {
+          return;
+        }
+        ent = ngtcp2_ringbuf_get(rb, j);
+        continue;
+      }
+      if (ent->pkt_num < min_ack) {
+        break;
+      }
+      acktr_on_ack(acktr, rb, j);
+      return;
+    }
+  }
+}
+
+void ngtcp2_acktr_commit_ack(ngtcp2_acktr *acktr) {
+  acktr->flags &= (uint16_t) ~(NGTCP2_ACKTR_FLAG_ACTIVE_ACK |
+                               NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK |
+                               NGTCP2_ACKTR_FLAG_CANCEL_TIMER);
+  acktr->first_unacked_ts = UINT64_MAX;
+  acktr->rx_npkt = 0;
+}
+
+int ngtcp2_acktr_require_active_ack(const ngtcp2_acktr *acktr,
+                                    ngtcp2_duration max_ack_delay,
+                                    ngtcp2_tstamp ts) {
+  return ngtcp2_tstamp_elapsed(acktr->first_unacked_ts, max_ack_delay, ts);
+}
+
+void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr) {
+  acktr->flags |= NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK;
+}
+
+ngtcp2_frame *ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr,
+                                            ngtcp2_frame *fr, uint8_t type,
+                                            ngtcp2_tstamp ts,
+                                            ngtcp2_duration ack_delay,
+                                            uint64_t ack_delay_exponent) {
+  int64_t last_pkt_num;
+  ngtcp2_ack_range *range;
+  ngtcp2_ksl_it it;
+  ngtcp2_acktr_entry *rpkt;
+  ngtcp2_ack *ack = &fr->ack;
+  ngtcp2_tstamp largest_ack_ts;
+  size_t num_acks;
+
+  if (acktr->flags & NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK) {
+    ack_delay = 0;
+  }
+
+  if (!ngtcp2_acktr_require_active_ack(acktr, ack_delay, ts)) {
+    return NULL;
+  }
+
+  it = ngtcp2_acktr_get(acktr);
+  if (ngtcp2_ksl_it_end(&it)) {
+    ngtcp2_acktr_commit_ack(acktr);
+    return NULL;
+  }
+
+  num_acks = ngtcp2_ksl_len(&acktr->ents);
+
+  if (acktr->ecn.ect0 || acktr->ecn.ect1 || acktr->ecn.ce) {
+    ack->type = NGTCP2_FRAME_ACK_ECN;
+    ack->ecn.ect0 = acktr->ecn.ect0;
+    ack->ecn.ect1 = acktr->ecn.ect1;
+    ack->ecn.ce = acktr->ecn.ce;
+  } else {
+    ack->type = NGTCP2_FRAME_ACK;
+  }
+  ack->rangecnt = 0;
+
+  rpkt = ngtcp2_ksl_it_get(&it);
+
+  if (rpkt->pkt_num == acktr->max_pkt_num) {
+    last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1);
+    largest_ack_ts = rpkt->tstamp;
+    ack->largest_ack = rpkt->pkt_num;
+    ack->first_ack_range = rpkt->len - 1;
+
+    ngtcp2_ksl_it_next(&it);
+    --num_acks;
+  } else if (rpkt->pkt_num + 1 == acktr->max_pkt_num) {
+    last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1);
+    largest_ack_ts = acktr->max_pkt_ts;
+    ack->largest_ack = acktr->max_pkt_num;
+    ack->first_ack_range = rpkt->len;
+
+    ngtcp2_ksl_it_next(&it);
+    --num_acks;
+  } else {
+    assert(rpkt->pkt_num < acktr->max_pkt_num);
+
+    last_pkt_num = acktr->max_pkt_num;
+    largest_ack_ts = acktr->max_pkt_ts;
+    ack->largest_ack = acktr->max_pkt_num;
+    ack->first_ack_range = 0;
+  }
+
+  if (type == NGTCP2_PKT_1RTT) {
+    ack->ack_delay_unscaled = ts - largest_ack_ts;
+    ack->ack_delay = ack->ack_delay_unscaled / NGTCP2_MICROSECONDS /
+                     (1ULL << ack_delay_exponent);
+  } else {
+    ack->ack_delay_unscaled = 0;
+    ack->ack_delay = 0;
+  }
+
+  num_acks = ngtcp2_min_size(num_acks, NGTCP2_MAX_ACK_RANGES);
+
+  for (; ack->rangecnt < num_acks; ngtcp2_ksl_it_next(&it)) {
+    rpkt = ngtcp2_ksl_it_get(&it);
+
+    range = &ack->ranges[ack->rangecnt++];
+    range->gap = (uint64_t)(last_pkt_num - rpkt->pkt_num - 2);
+    range->len = rpkt->len - 1;
+
+    last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1);
+  }
+
+  return fr;
+}
+
+void ngtcp2_acktr_increase_ecn_counts(ngtcp2_acktr *acktr,
+                                      const ngtcp2_pkt_info *pi) {
+  switch (pi->ecn & NGTCP2_ECN_MASK) {
+  case NGTCP2_ECN_ECT_0:
+    ++acktr->ecn.ect0;
+    break;
+  case NGTCP2_ECN_ECT_1:
+    ++acktr->ecn.ect1;
+    break;
+  case NGTCP2_ECN_CE:
+    ++acktr->ecn.ce;
+    break;
+  }
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_acktr.h b/third_party/ngtcp2/lib/ngtcp2_acktr.h
new file mode 100644 (file)
index 0000000..cf75a77
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_ACKTR_H
+#define NGTCP2_ACKTR_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+#include "ngtcp2_ringbuf.h"
+#include "ngtcp2_ksl.h"
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_objalloc.h"
+
+/* NGTCP2_ACKTR_MAX_ENT is the maximum number of ngtcp2_acktr_entry
+   which ngtcp2_acktr stores. */
+#define NGTCP2_ACKTR_MAX_ENT (NGTCP2_MAX_ACK_RANGES + 1)
+
+typedef struct ngtcp2_log ngtcp2_log;
+
+/*
+ * ngtcp2_acktr_entry is a range of packets which need to be acked.
+ */
+typedef struct ngtcp2_acktr_entry {
+  union {
+    struct {
+      /* pkt_num is the largest packet number to acknowledge in this
+         range. */
+      int64_t pkt_num;
+      /* len is the consecutive packets started from pkt_num which
+         includes pkt_num itself counting in decreasing order.  So pkt_num
+         = 987 and len = 2, this entry includes packet 987 and 986. */
+      size_t len;
+      /* tstamp is the timestamp when a packet denoted by pkt_num is
+         received. */
+      ngtcp2_tstamp tstamp;
+    };
+
+    ngtcp2_opl_entry oplent;
+  };
+} ngtcp2_acktr_entry;
+
+ngtcp2_objalloc_decl(acktr_entry, ngtcp2_acktr_entry, oplent)
+
+/*
+ * ngtcp2_acktr_entry_objalloc_new allocates memory for ent, and
+ * initializes it with the given parameters.  The pointer to the
+ * allocated object is stored to |*ent|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num,
+                                    ngtcp2_tstamp tstamp,
+                                    ngtcp2_objalloc *objalloc);
+
+/*
+ * ngtcp2_acktr_entry_objalloc_del deallocates memory allocated for
+ * |ent|.
+ */
+void ngtcp2_acktr_entry_objalloc_del(ngtcp2_acktr_entry *ent,
+                                     ngtcp2_objalloc *objalloc);
+
+typedef struct ngtcp2_acktr_ack_entry {
+  /* largest_ack is the largest packet number in outgoing ACK frame */
+  int64_t largest_ack;
+  /* pkt_num is the packet number that ACK frame is included. */
+  int64_t pkt_num;
+} ngtcp2_acktr_ack_entry;
+
+/* NGTCP2_ACKTR_FLAG_NONE indicates that no flag set. */
+#define NGTCP2_ACKTR_FLAG_NONE 0x00u
+/* NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK indicates that immediate
+   acknowledgement is required. */
+#define NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK 0x01u
+/* NGTCP2_ACKTR_FLAG_ACTIVE_ACK indicates that there are pending
+   protected packet to be acknowledged. */
+#define NGTCP2_ACKTR_FLAG_ACTIVE_ACK 0x02u
+/* NGTCP2_ACKTR_FLAG_CANCEL_TIMER is set when ACK delay timer is
+   expired and canceled. */
+#define NGTCP2_ACKTR_FLAG_CANCEL_TIMER 0x0100u
+
+ngtcp2_static_ringbuf_def(acks, 32, sizeof(ngtcp2_acktr_ack_entry))
+
+/*
+ * ngtcp2_acktr tracks received packets which we have to send ack.
+ */
+typedef struct ngtcp2_acktr {
+  ngtcp2_objalloc objalloc;
+  ngtcp2_static_ringbuf_acks acks;
+  /* ents includes ngtcp2_acktr_entry sorted by decreasing order of
+     packet number. */
+  ngtcp2_ksl ents;
+  ngtcp2_log *log;
+  /* flags is bitwise OR of zero, or more of NGTCP2_ACKTR_FLAG_*. */
+  uint16_t flags;
+  /* first_unacked_ts is timestamp when ngtcp2_acktr_entry is added
+     first time after the last outgoing ACK frame. */
+  ngtcp2_tstamp first_unacked_ts;
+  /* rx_npkt is the number of ACK eliciting packets received without
+     sending ACK. */
+  size_t rx_npkt;
+  /* max_pkt_num is the largest packet number received so far. */
+  int64_t max_pkt_num;
+  /* max_pkt_ts is the timestamp when max_pkt_num packet is
+     received. */
+  ngtcp2_tstamp max_pkt_ts;
+
+  struct {
+    /* ect0, ect1, and ce are the number of QUIC packets received
+       with those markings. */
+    size_t ect0;
+    size_t ect1;
+    size_t ce;
+    struct {
+      /* ect0, ect1, ce are the ECN counts received in the latest
+         ACK frame. */
+      uint64_t ect0;
+      uint64_t ect1;
+      uint64_t ce;
+    } ack;
+  } ecn;
+} ngtcp2_acktr;
+
+/*
+ * ngtcp2_acktr_init initializes |acktr|.
+ */
+void ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log,
+                       const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_acktr_free frees resources allocated for |acktr|.  It frees
+ * any ngtcp2_acktr_entry added to |acktr|.
+ */
+void ngtcp2_acktr_free(ngtcp2_acktr *acktr);
+
+/*
+ * ngtcp2_acktr_add adds packet number |pkt_num| to |acktr|.
+ * |active_ack| is nonzero if |pkt_num| is retransmittable packet.
+ *
+ * This function assumes that |acktr| does not contain |pkt_num|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     OUt of memory.
+ */
+int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack,
+                     ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_acktr_forget removes all entries which have the packet
+ * number that is equal to or less than ent->pkt_num.  This function
+ * assumes that |acktr| includes |ent|.
+ */
+void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent);
+
+/*
+ * ngtcp2_acktr_get returns the iterator to pointer to the entry which
+ * has the largest packet number to be acked.  If there is no entry,
+ * returned value satisfies ngtcp2_ksl_it_end(&it) != 0.
+ */
+ngtcp2_ksl_it ngtcp2_acktr_get(const ngtcp2_acktr *acktr);
+
+/*
+ * ngtcp2_acktr_empty returns nonzero if it has no packet to
+ * acknowledge.
+ */
+int ngtcp2_acktr_empty(const ngtcp2_acktr *acktr);
+
+/*
+ * ngtcp2_acktr_add_ack records outgoing ACK frame whose largest
+ * acknowledged packet number is |largest_ack|.  |pkt_num| is the
+ * packet number of a packet in which ACK frame is included.  This
+ * function returns a pointer to the object it adds.
+ */
+ngtcp2_acktr_ack_entry *
+ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr, int64_t pkt_num, int64_t largest_ack);
+
+/*
+ * ngtcp2_acktr_recv_ack processes the incoming ACK frame |fr|.
+ * |pkt_num| is a packet number which includes |fr|.  If we receive
+ * ACK which acknowledges the ACKs added by ngtcp2_acktr_add_ack,
+ * ngtcp2_acktr_entry which the outgoing ACK acknowledges is removed.
+ */
+void ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr);
+
+/*
+ * ngtcp2_acktr_commit_ack tells |acktr| that ACK frame is generated.
+ */
+void ngtcp2_acktr_commit_ack(ngtcp2_acktr *acktr);
+
+/*
+ * ngtcp2_acktr_require_active_ack returns nonzero if ACK frame should
+ * be generated actively.
+ */
+int ngtcp2_acktr_require_active_ack(const ngtcp2_acktr *acktr,
+                                    ngtcp2_duration max_ack_delay,
+                                    ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_acktr_immediate_ack tells |acktr| that immediate
+ * acknowledgement is required.
+ */
+void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr);
+
+/*
+ * ngtcp2_acktr_create_ack_frame creates ACK frame in the object
+ * pointed by |fr|, and returns |fr| if there are any received packets
+ * to acknowledge.  If there are no packets to acknowledge, this
+ * function returns NULL.  fr->ack.ranges must be able to contain at
+ * least NGTCP2_MAX_ACK_RANGES elements.
+ *
+ * Call ngtcp2_acktr_commit_ack after a created ACK frame is
+ * successfully serialized into a packet.
+ */
+ngtcp2_frame *ngtcp2_acktr_create_ack_frame(ngtcp2_acktr *acktr,
+                                            ngtcp2_frame *fr, uint8_t type,
+                                            ngtcp2_tstamp ts,
+                                            ngtcp2_duration ack_delay,
+                                            uint64_t ack_delay_exponent);
+
+/*
+ * ngtcp2_acktr_increase_ecn_counts increases ECN counts from |pi|.
+ */
+void ngtcp2_acktr_increase_ecn_counts(ngtcp2_acktr *acktr,
+                                      const ngtcp2_pkt_info *pi);
+
+#endif /* !defined(NGTCP2_ACKTR_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_addr.c b/third_party/ngtcp2/lib/ngtcp2_addr.c
new file mode 100644 (file)
index 0000000..1fb273d
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_addr.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_unreachable.h"
+
+ngtcp2_addr *ngtcp2_addr_init(ngtcp2_addr *dest, const ngtcp2_sockaddr *addr,
+                              ngtcp2_socklen addrlen) {
+  dest->addrlen = addrlen;
+  dest->addr = (ngtcp2_sockaddr *)addr;
+  return dest;
+}
+
+void ngtcp2_addr_copy(ngtcp2_addr *dest, const ngtcp2_addr *src) {
+  dest->addrlen = src->addrlen;
+  if (src->addrlen) {
+    memcpy(dest->addr, src->addr, (size_t)src->addrlen);
+  }
+}
+
+void ngtcp2_addr_copy_byte(ngtcp2_addr *dest, const ngtcp2_sockaddr *addr,
+                           ngtcp2_socklen addrlen) {
+  dest->addrlen = addrlen;
+  if (addrlen) {
+    memcpy(dest->addr, addr, (size_t)addrlen);
+  }
+}
+
+int ngtcp2_sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b) {
+  if (a->sa_family != b->sa_family) {
+    return 0;
+  }
+
+  switch (a->sa_family) {
+  case NGTCP2_AF_INET: {
+    const ngtcp2_sockaddr_in *ai = (const ngtcp2_sockaddr_in *)(void *)a,
+                             *bi = (const ngtcp2_sockaddr_in *)(void *)b;
+    return ai->sin_port == bi->sin_port &&
+           memcmp(&ai->sin_addr, &bi->sin_addr, sizeof(ai->sin_addr)) == 0;
+  }
+  case NGTCP2_AF_INET6: {
+    const ngtcp2_sockaddr_in6 *ai = (const ngtcp2_sockaddr_in6 *)(void *)a,
+                              *bi = (const ngtcp2_sockaddr_in6 *)(void *)b;
+    return ai->sin6_port == bi->sin6_port &&
+           memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr)) == 0;
+  }
+  default:
+    ngtcp2_unreachable();
+  }
+}
+
+int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b) {
+  return ngtcp2_sockaddr_eq(a->addr, b->addr);
+}
+
+uint32_t ngtcp2_addr_cmp(const ngtcp2_addr *aa, const ngtcp2_addr *bb) {
+  uint32_t flags = NGTCP2_ADDR_CMP_FLAG_NONE;
+  const ngtcp2_sockaddr *a = aa->addr;
+  const ngtcp2_sockaddr *b = bb->addr;
+
+  if (a->sa_family != b->sa_family) {
+    return NGTCP2_ADDR_CMP_FLAG_FAMILY;
+  }
+
+  switch (a->sa_family) {
+  case NGTCP2_AF_INET: {
+    const ngtcp2_sockaddr_in *ai = (const ngtcp2_sockaddr_in *)(void *)a,
+                             *bi = (const ngtcp2_sockaddr_in *)(void *)b;
+    if (memcmp(&ai->sin_addr, &bi->sin_addr, sizeof(ai->sin_addr))) {
+      flags |= NGTCP2_ADDR_CMP_FLAG_ADDR;
+    }
+    if (ai->sin_port != bi->sin_port) {
+      flags |= NGTCP2_ADDR_CMP_FLAG_PORT;
+    }
+    return flags;
+  }
+  case NGTCP2_AF_INET6: {
+    const ngtcp2_sockaddr_in6 *ai = (const ngtcp2_sockaddr_in6 *)(void *)a,
+                              *bi = (const ngtcp2_sockaddr_in6 *)(void *)b;
+    if (memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr))) {
+      flags |= NGTCP2_ADDR_CMP_FLAG_ADDR;
+    }
+    if (ai->sin6_port != bi->sin6_port) {
+      flags |= NGTCP2_ADDR_CMP_FLAG_PORT;
+    }
+    return flags;
+  }
+  default:
+    ngtcp2_unreachable();
+  }
+}
+
+int ngtcp2_addr_empty(const ngtcp2_addr *addr) { return addr->addrlen == 0; }
diff --git a/third_party/ngtcp2/lib/ngtcp2_addr.h b/third_party/ngtcp2/lib/ngtcp2_addr.h
new file mode 100644 (file)
index 0000000..c2224f8
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_ADDR_H
+#define NGTCP2_ADDR_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/*
+ * ngtcp2_addr_copy copies |src| to |dest|.  This function assumes
+ * that dest->addr points to a buffer which have sufficient size to
+ * store the copy.
+ */
+void ngtcp2_addr_copy(ngtcp2_addr *dest, const ngtcp2_addr *src);
+
+/**
+ * @function
+ *
+ * `ngtcp2_addr_eq` returns nonzero if |a| equals |b|.
+ */
+int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b);
+
+/* NGTCP2_ADDR_CMP_FLAG_NONE indicates that no flag set. */
+#define NGTCP2_ADDR_CMP_FLAG_NONE 0x0u
+/* NGTCP2_ADDR_CMP_FLAG_ADDR indicates IP addresses do not match. */
+#define NGTCP2_ADDR_CMP_FLAG_ADDR 0x1u
+/* NGTCP2_ADDR_CMP_FLAG_PORT indicates ports do not match. */
+#define NGTCP2_ADDR_CMP_FLAG_PORT 0x2u
+/* NGTCP2_ADDR_CMP_FLAG_FAMILY indicates address families do not
+   match. */
+#define NGTCP2_ADDR_CMP_FLAG_FAMILY 0x4u
+
+/*
+ * ngtcp2_addr_cmp compares address and port between |a| and |b|, and
+ * returns zero or more of NGTCP2_ADDR_CMP_FLAG_*.
+ */
+uint32_t ngtcp2_addr_cmp(const ngtcp2_addr *a, const ngtcp2_addr *b);
+
+/*
+ * ngtcp2_addr_empty returns nonzero if |addr| has zero length
+ * address.
+ */
+int ngtcp2_addr_empty(const ngtcp2_addr *addr);
+
+/**
+ * @function
+ *
+ * `ngtcp2_sockaddr_eq` returns nonzero if |a| equals |b|.
+ */
+int ngtcp2_sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b);
+
+#endif /* !defined(NGTCP2_ADDR_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_balloc.c b/third_party/ngtcp2/lib/ngtcp2_balloc.c
new file mode 100644 (file)
index 0000000..4a67976
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_balloc.h"
+
+#include <assert.h>
+
+#include "ngtcp2_mem.h"
+
+void ngtcp2_balloc_init(ngtcp2_balloc *balloc, size_t blklen,
+                        const ngtcp2_mem *mem) {
+  assert((blklen & 0xfu) == 0);
+
+  balloc->mem = mem;
+  balloc->blklen = blklen;
+  balloc->head = NULL;
+  ngtcp2_buf_init(&balloc->buf, (void *)"", 0);
+}
+
+void ngtcp2_balloc_free(ngtcp2_balloc *balloc) {
+  if (balloc == NULL) {
+    return;
+  }
+
+  ngtcp2_balloc_clear(balloc);
+}
+
+void ngtcp2_balloc_clear(ngtcp2_balloc *balloc) {
+  ngtcp2_memblock_hd *p, *next;
+
+  for (p = balloc->head; p; p = next) {
+    next = p->next;
+    ngtcp2_mem_free(balloc->mem, p);
+  }
+
+  balloc->head = NULL;
+  ngtcp2_buf_init(&balloc->buf, (void *)"", 0);
+}
+
+int ngtcp2_balloc_get(ngtcp2_balloc *balloc, void **pbuf, size_t n) {
+  uint8_t *p;
+  ngtcp2_memblock_hd *hd;
+
+  assert(n <= balloc->blklen);
+
+  if (ngtcp2_buf_left(&balloc->buf) < n) {
+    p = ngtcp2_mem_malloc(balloc->mem,
+                          sizeof(ngtcp2_memblock_hd) + 0x8u + balloc->blklen);
+    if (p == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+
+    hd = (ngtcp2_memblock_hd *)(void *)p;
+    hd->next = balloc->head;
+    balloc->head = hd;
+    ngtcp2_buf_init(
+      &balloc->buf,
+      (uint8_t *)(((uintptr_t)p + sizeof(ngtcp2_memblock_hd) + 0xfu) &
+                  ~(uintptr_t)0xfu),
+      balloc->blklen);
+  }
+
+  assert(((uintptr_t)balloc->buf.last & 0xfu) == 0);
+
+  *pbuf = balloc->buf.last;
+  balloc->buf.last += (n + 0xfu) & ~(uintptr_t)0xfu;
+
+  return 0;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_balloc.h b/third_party/ngtcp2/lib/ngtcp2_balloc.h
new file mode 100644 (file)
index 0000000..c0e2a3f
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_BALLOC_H
+#define NGTCP2_BALLOC_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_buf.h"
+
+typedef struct ngtcp2_memblock_hd ngtcp2_memblock_hd;
+
+/*
+ * ngtcp2_memblock_hd is the header of memory block.
+ */
+struct ngtcp2_memblock_hd {
+  union {
+    ngtcp2_memblock_hd *next;
+    uint64_t pad;
+  };
+};
+
+/*
+ * ngtcp2_balloc is a custom memory allocator.  It allocates |blklen|
+ * bytes of memory at once on demand, and returns its slice when the
+ * allocation is requested.
+ */
+typedef struct ngtcp2_balloc {
+  /* mem is the underlying memory allocator. */
+  const ngtcp2_mem *mem;
+  /* blklen is the size of memory block. */
+  size_t blklen;
+  /* head points to the list of memory block allocated so far. */
+  ngtcp2_memblock_hd *head;
+  /* buf wraps the current memory block for allocation requests. */
+  ngtcp2_buf buf;
+} ngtcp2_balloc;
+
+/*
+ * ngtcp2_balloc_init initializes |balloc| with |blklen| which is the
+ * size of memory block.  |blklen| must be divisible by 16.
+ */
+void ngtcp2_balloc_init(ngtcp2_balloc *balloc, size_t blklen,
+                        const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_balloc_free releases all allocated memory blocks.
+ */
+void ngtcp2_balloc_free(ngtcp2_balloc *balloc);
+
+/*
+ * ngtcp2_balloc_get allocates |n| bytes of memory and assigns its
+ * pointer to |*pbuf|.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_balloc_get(ngtcp2_balloc *balloc, void **pbuf, size_t n);
+
+/*
+ * ngtcp2_balloc_clear releases all allocated memory blocks and
+ * initializes its state.
+ */
+void ngtcp2_balloc_clear(ngtcp2_balloc *balloc);
+
+#endif /* !defined(NGTCP2_BALLOC_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_bbr.c b/third_party/ngtcp2/lib/ngtcp2_bbr.c
new file mode 100644 (file)
index 0000000..04612f1
--- /dev/null
@@ -0,0 +1,1421 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2021 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_bbr.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "ngtcp2_log.h"
+#include "ngtcp2_macro.h"
+#include "ngtcp2_mem.h"
+#include "ngtcp2_rcvry.h"
+#include "ngtcp2_rst.h"
+#include "ngtcp2_conn_stat.h"
+
+#define NGTCP2_BBR_MAX_BW_FILTERLEN 2
+
+#define NGTCP2_BBR_EXTRA_ACKED_FILTERLEN 10
+
+#define NGTCP2_BBR_STARTUP_PACING_GAIN_H 277
+#define NGTCP2_BBR_DRAIN_PACING_GAIN_H 50
+
+#define NGTCP2_BBR_DEFAULT_CWND_GAIN_H 200
+
+#define NGTCP2_BBR_PROBE_RTT_CWND_GAIN_H 50
+
+#define NGTCP2_BBR_BETA_NUMER 7
+#define NGTCP2_BBR_BETA_DENOM 10
+
+#define NGTCP2_BBR_LOSS_THRESH_NUMER 2
+#define NGTCP2_BBR_LOSS_THRESH_DENOM 100
+
+#define NGTCP2_BBR_HEADROOM_NUMER 15
+#define NGTCP2_BBR_HEADROOM_DENOM 100
+
+#define NGTCP2_BBR_PROBE_RTT_INTERVAL (5 * NGTCP2_SECONDS)
+#define NGTCP2_BBR_MIN_RTT_FILTERLEN (10 * NGTCP2_SECONDS)
+
+#define NGTCP2_BBR_PROBE_RTT_DURATION (200 * NGTCP2_MILLISECONDS)
+
+#define NGTCP2_BBR_PACING_MARGIN_PERCENT 1
+
+static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                        ngtcp2_tstamp initial_ts);
+
+static void bbr_on_transmit(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                            ngtcp2_tstamp ts);
+
+static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr);
+
+static void bbr_reset_lower_bounds(ngtcp2_cc_bbr *bbr);
+
+static void bbr_init_round_counting(ngtcp2_cc_bbr *bbr);
+
+static void bbr_reset_full_bw(ngtcp2_cc_bbr *bbr);
+
+static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr,
+                                          ngtcp2_conn_stat *cstat,
+                                          uint64_t pacing_gain_h);
+
+static void bbr_set_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static void bbr_enter_startup(ngtcp2_cc_bbr *bbr);
+
+static void bbr_check_startup_done(ngtcp2_cc_bbr *bbr);
+
+static void bbr_update_on_ack(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                              const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts);
+
+static void bbr_update_model_and_state(ngtcp2_cc_bbr *cc,
+                                       ngtcp2_conn_stat *cstat,
+                                       const ngtcp2_cc_ack *ack,
+                                       ngtcp2_tstamp ts);
+
+static void bbr_update_control_parameters(ngtcp2_cc_bbr *cc,
+                                          ngtcp2_conn_stat *cstat,
+                                          const ngtcp2_cc_ack *ack);
+
+static void bbr_update_on_loss(ngtcp2_cc_bbr *cc, ngtcp2_conn_stat *cstat,
+                               const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts);
+
+static void bbr_update_latest_delivery_signals(ngtcp2_cc_bbr *bbr,
+                                               ngtcp2_conn_stat *cstat);
+
+static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr,
+                                                ngtcp2_conn_stat *cstat);
+
+static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr,
+                                          ngtcp2_conn_stat *cstat,
+                                          const ngtcp2_cc_ack *ack);
+
+static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr,
+                                                   ngtcp2_conn_stat *cstat);
+
+static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static void bbr_loss_lower_bounds(ngtcp2_cc_bbr *bbr);
+
+static void bbr_bound_bw_for_model(ngtcp2_cc_bbr *bbr);
+
+static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                              const ngtcp2_cc_ack *ack);
+
+static void bbr_update_round(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack);
+
+static void bbr_start_round(ngtcp2_cc_bbr *bbr);
+
+static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr);
+
+static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr);
+
+static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr,
+                                       ngtcp2_conn_stat *cstat,
+                                       const ngtcp2_cc_ack *ack,
+                                       ngtcp2_tstamp ts);
+
+static void bbr_enter_drain(ngtcp2_cc_bbr *bbr);
+
+static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                 ngtcp2_tstamp ts);
+
+static void bbr_enter_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts);
+
+static void bbr_start_probe_bw_down(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts);
+
+static void bbr_start_probe_bw_cruise(ngtcp2_cc_bbr *bbr);
+
+static void bbr_start_probe_bw_refill(ngtcp2_cc_bbr *bbr);
+
+static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr,
+                                            ngtcp2_conn_stat *cstat,
+                                            const ngtcp2_cc_ack *ack,
+                                            ngtcp2_tstamp ts);
+
+static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                 ngtcp2_tstamp ts);
+
+static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static int bbr_has_elapsed_in_phase(ngtcp2_cc_bbr *bbr,
+                                    ngtcp2_duration interval, ngtcp2_tstamp ts);
+
+static uint64_t bbr_inflight_with_headroom(ngtcp2_cc_bbr *bbr,
+                                           ngtcp2_conn_stat *cstat);
+
+static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr,
+                                              ngtcp2_conn_stat *cstat);
+
+static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr,
+                                               ngtcp2_conn_stat *cstat,
+                                               const ngtcp2_cc_ack *ack);
+
+static void bbr_adapt_upper_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                   const ngtcp2_cc_ack *ack);
+
+static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                   ngtcp2_tstamp ts);
+
+static void bbr_pick_probe_wait(ngtcp2_cc_bbr *bbr);
+
+static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr,
+                                              ngtcp2_conn_stat *cstat);
+
+static uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr,
+                                    ngtcp2_conn_stat *cstat);
+
+static int bbr_is_inflight_too_high(ngtcp2_cc_bbr *bbr);
+
+static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr,
+                                         ngtcp2_conn_stat *cstat,
+                                         ngtcp2_tstamp ts);
+
+static void bbr_note_loss(ngtcp2_cc_bbr *bbr);
+
+static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                   const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts);
+
+static uint64_t
+bbr_inflight_longterm_from_lost_packet(ngtcp2_cc_bbr *bbr,
+                                       const ngtcp2_cc_pkt *pkt);
+
+static void bbr_update_min_rtt(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack,
+                               ngtcp2_tstamp ts);
+
+static void bbr_check_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                ngtcp2_tstamp ts);
+
+static void bbr_enter_probe_rtt(ngtcp2_cc_bbr *bbr);
+
+static void bbr_handle_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                 ngtcp2_tstamp ts);
+
+static void bbr_check_probe_rtt_done(ngtcp2_cc_bbr *bbr,
+                                     ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts);
+
+static void bbr_mark_connection_app_limited(ngtcp2_cc_bbr *bbr,
+                                            ngtcp2_conn_stat *cstat);
+
+static void bbr_exit_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts);
+
+static void bbr_handle_restart_from_idle(ngtcp2_cc_bbr *bbr,
+                                         ngtcp2_conn_stat *cstat,
+                                         ngtcp2_tstamp ts);
+
+static uint64_t bbr_bdp_multiple(ngtcp2_cc_bbr *bbr, uint64_t gain_h);
+
+static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr,
+                                        ngtcp2_conn_stat *cstat,
+                                        uint64_t inflight);
+
+static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                             uint64_t gain_h);
+
+static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr,
+                                    ngtcp2_conn_stat *cstat);
+
+static void bbr_update_offload_budget(ngtcp2_cc_bbr *bbr,
+                                      ngtcp2_conn_stat *cstat);
+
+static uint64_t min_pipe_cwnd(size_t max_udp_payload_size);
+
+static void bbr_advance_max_bw_filter(ngtcp2_cc_bbr *bbr);
+
+static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static void bbr_restore_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_cc_bbr *bbr,
+                                         ngtcp2_conn_stat *cstat);
+
+static void bbr_set_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                         const ngtcp2_cc_ack *ack);
+
+static void bbr_bound_cwnd_for_model(ngtcp2_cc_bbr *bbr,
+                                     ngtcp2_conn_stat *cstat);
+
+static void bbr_set_send_quantum(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat);
+
+static int in_congestion_recovery(const ngtcp2_conn_stat *cstat,
+                                  ngtcp2_tstamp sent_time);
+
+static void bbr_handle_recovery(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                const ngtcp2_cc_ack *ack);
+
+static void bbr_on_init(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                        ngtcp2_tstamp initial_ts) {
+  ngtcp2_window_filter_init(&bbr->max_bw_filter, NGTCP2_BBR_MAX_BW_FILTERLEN);
+  ngtcp2_window_filter_init(&bbr->extra_acked_filter,
+                            NGTCP2_BBR_EXTRA_ACKED_FILTERLEN);
+
+  bbr->min_rtt =
+    cstat->first_rtt_sample_ts == UINT64_MAX ? UINT64_MAX : cstat->smoothed_rtt;
+  bbr->min_rtt_stamp = initial_ts;
+  /* remark: Use UINT64_MAX instead of 0 for consistency. */
+  bbr->probe_rtt_done_stamp = UINT64_MAX;
+  bbr->probe_rtt_round_done = 0;
+  bbr->prior_cwnd = 0;
+  bbr->idle_restart = 0;
+  bbr->extra_acked_interval_start = initial_ts;
+  bbr->extra_acked_delivered = 0;
+  bbr->full_bw_reached = 0;
+
+  bbr_reset_congestion_signals(bbr);
+  bbr_reset_lower_bounds(bbr);
+  bbr_init_round_counting(bbr);
+  bbr_reset_full_bw(bbr);
+  bbr_init_pacing_rate(bbr, cstat);
+  bbr_enter_startup(bbr);
+
+  cstat->send_quantum = cstat->max_tx_udp_payload_size * 10;
+
+  /* Missing in documentation */
+  bbr->loss_round_start = 0;
+  bbr->loss_round_delivered = UINT64_MAX;
+
+  bbr->rounds_since_bw_probe = 0;
+
+  bbr->max_bw = 0;
+  bbr->bw = 0;
+
+  bbr->cycle_count = 0;
+
+  bbr->extra_acked = 0;
+
+  bbr->bytes_lost_in_round = 0;
+  bbr->loss_events_in_round = 0;
+
+  bbr->offload_budget = 0;
+
+  bbr->probe_up_cnt = UINT64_MAX;
+  bbr->cycle_stamp = UINT64_MAX;
+  bbr->ack_phase = 0;
+  bbr->bw_probe_wait = 0;
+  bbr->bw_probe_samples = 0;
+  bbr->bw_probe_up_rounds = 0;
+  bbr->bw_probe_up_acks = 0;
+
+  bbr->inflight_longterm = UINT64_MAX;
+
+  bbr->probe_rtt_expired = 0;
+  bbr->probe_rtt_min_delay = UINT64_MAX;
+  bbr->probe_rtt_min_stamp = initial_ts;
+
+  bbr->in_loss_recovery = 0;
+  bbr->round_count_at_recovery = UINT64_MAX;
+
+  bbr->max_inflight = 0;
+
+  bbr->congestion_recovery_start_ts = UINT64_MAX;
+
+  bbr->bdp = 0;
+}
+
+static void bbr_reset_congestion_signals(ngtcp2_cc_bbr *bbr) {
+  bbr->loss_in_round = 0;
+  bbr->bw_latest = 0;
+  bbr->inflight_latest = 0;
+}
+
+static void bbr_reset_lower_bounds(ngtcp2_cc_bbr *bbr) {
+  bbr->bw_shortterm = UINT64_MAX;
+  bbr->inflight_shortterm = UINT64_MAX;
+}
+
+static void bbr_init_round_counting(ngtcp2_cc_bbr *bbr) {
+  bbr->next_round_delivered = 0;
+  bbr->round_start = 0;
+  bbr->round_count = 0;
+}
+
+static void bbr_reset_full_bw(ngtcp2_cc_bbr *bbr) {
+  bbr->full_bw = 0;
+  bbr->full_bw_count = 0;
+  bbr->full_bw_now = 0;
+}
+
+static void bbr_check_full_bw_reached(ngtcp2_cc_bbr *bbr,
+                                      ngtcp2_conn_stat *cstat) {
+  if (bbr->full_bw_now || !bbr->round_start || bbr->rst->rs.is_app_limited) {
+    return;
+  }
+
+  if (cstat->delivery_rate_sec * 100 >= bbr->full_bw * 125) {
+    bbr_reset_full_bw(bbr);
+    bbr->full_bw = cstat->delivery_rate_sec;
+
+    return;
+  }
+
+  ++bbr->full_bw_count;
+
+  bbr->full_bw_now = bbr->full_bw_count >= 3;
+  if (!bbr->full_bw_now) {
+    return;
+  }
+
+  bbr->full_bw_reached = 1;
+
+  ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA,
+                  "bbr reached full bandwidth, full_bw=%" PRIu64, bbr->full_bw);
+}
+
+static void bbr_check_startup_high_loss(ngtcp2_cc_bbr *bbr) {
+  if (bbr->full_bw_reached || bbr->loss_events_in_round <= 6 ||
+      (bbr->in_loss_recovery &&
+       bbr->round_count <= bbr->round_count_at_recovery) ||
+      !bbr_is_inflight_too_high(bbr)) {
+    return;
+  }
+
+  bbr->full_bw_reached = 1;
+  bbr->inflight_longterm = ngtcp2_max_uint64(
+    bbr_bdp_multiple(bbr, bbr->cwnd_gain_h), bbr->inflight_latest);
+}
+
+static void bbr_init_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) {
+  cstat->pacing_interval_m =
+    ((cstat->first_rtt_sample_ts == UINT64_MAX ? NGTCP2_MILLISECONDS
+                                               : cstat->smoothed_rtt)
+     << 10) *
+    100 / NGTCP2_BBR_STARTUP_PACING_GAIN_H / bbr->initial_cwnd;
+}
+
+static void bbr_set_pacing_rate_with_gain(ngtcp2_cc_bbr *bbr,
+                                          ngtcp2_conn_stat *cstat,
+                                          uint64_t pacing_gain_h) {
+  uint64_t interval_m;
+
+  if (bbr->bw == 0) {
+    return;
+  }
+
+  interval_m = (NGTCP2_SECONDS << 10) * 100 * 100 / pacing_gain_h / bbr->bw /
+               (100 - NGTCP2_BBR_PACING_MARGIN_PERCENT);
+  interval_m = ngtcp2_max_uint64(interval_m, 1);
+
+  if (bbr->full_bw_reached || interval_m < cstat->pacing_interval_m) {
+    cstat->pacing_interval_m = interval_m;
+  }
+}
+
+static void bbr_set_pacing_rate(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) {
+  bbr_set_pacing_rate_with_gain(bbr, cstat, bbr->pacing_gain_h);
+}
+
+static void bbr_enter_startup(ngtcp2_cc_bbr *bbr) {
+  ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr enter Startup");
+
+  bbr->state = NGTCP2_BBR_STATE_STARTUP;
+  bbr->pacing_gain_h = NGTCP2_BBR_STARTUP_PACING_GAIN_H;
+  bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H;
+}
+
+static void bbr_check_startup_done(ngtcp2_cc_bbr *bbr) {
+  bbr_check_startup_high_loss(bbr);
+
+  if (bbr->state == NGTCP2_BBR_STATE_STARTUP && bbr->full_bw_reached) {
+    bbr_enter_drain(bbr);
+  }
+}
+
+static void bbr_on_transmit(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                            ngtcp2_tstamp ts) {
+  bbr_handle_restart_from_idle(bbr, cstat, ts);
+}
+
+static void bbr_update_on_ack(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                              const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) {
+  bbr_update_model_and_state(bbr, cstat, ack, ts);
+  bbr_update_control_parameters(bbr, cstat, ack);
+}
+
+static void bbr_update_model_and_state(ngtcp2_cc_bbr *bbr,
+                                       ngtcp2_conn_stat *cstat,
+                                       const ngtcp2_cc_ack *ack,
+                                       ngtcp2_tstamp ts) {
+  bbr_update_latest_delivery_signals(bbr, cstat);
+  bbr_update_congestion_signals(bbr, cstat, ack);
+  bbr_update_ack_aggregation(bbr, cstat, ack, ts);
+  bbr_check_full_bw_reached(bbr, cstat);
+  bbr_check_startup_done(bbr);
+  bbr_check_drain_done(bbr, cstat, ts);
+  bbr_update_probe_bw_cycle_phase(bbr, cstat, ack, ts);
+  bbr_update_min_rtt(bbr, ack, ts);
+  bbr_check_probe_rtt(bbr, cstat, ts);
+  bbr_advance_latest_delivery_signals(bbr, cstat);
+  bbr_bound_bw_for_model(bbr);
+}
+
+static void bbr_update_control_parameters(ngtcp2_cc_bbr *bbr,
+                                          ngtcp2_conn_stat *cstat,
+                                          const ngtcp2_cc_ack *ack) {
+  bbr_set_pacing_rate(bbr, cstat);
+  bbr_set_send_quantum(bbr, cstat);
+  bbr_set_cwnd(bbr, cstat, ack);
+}
+
+static void bbr_update_on_loss(ngtcp2_cc_bbr *cc, ngtcp2_conn_stat *cstat,
+                               const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) {
+  bbr_handle_lost_packet(cc, cstat, pkt, ts);
+}
+
+static void bbr_update_latest_delivery_signals(ngtcp2_cc_bbr *bbr,
+                                               ngtcp2_conn_stat *cstat) {
+  bbr->loss_round_start = 0;
+  bbr->bw_latest = ngtcp2_max_uint64(bbr->bw_latest, cstat->delivery_rate_sec);
+  bbr->inflight_latest =
+    ngtcp2_max_uint64(bbr->inflight_latest, bbr->rst->rs.delivered);
+
+  if (bbr->rst->rs.prior_delivered >= bbr->loss_round_delivered) {
+    bbr->loss_round_delivered = bbr->rst->delivered;
+    bbr->loss_round_start = 1;
+  }
+}
+
+static void bbr_advance_latest_delivery_signals(ngtcp2_cc_bbr *bbr,
+                                                ngtcp2_conn_stat *cstat) {
+  if (bbr->loss_round_start) {
+    bbr->bw_latest = cstat->delivery_rate_sec;
+    bbr->inflight_latest = bbr->rst->rs.delivered;
+  }
+}
+
+static void bbr_update_congestion_signals(ngtcp2_cc_bbr *bbr,
+                                          ngtcp2_conn_stat *cstat,
+                                          const ngtcp2_cc_ack *ack) {
+  bbr_update_max_bw(bbr, cstat, ack);
+
+  if (ack->bytes_lost) {
+    bbr->bytes_lost_in_round += ack->bytes_lost;
+    ++bbr->loss_events_in_round;
+  }
+
+  if (!bbr->loss_round_start) {
+    return;
+  }
+
+  bbr_adapt_lower_bounds_from_congestion(bbr, cstat);
+
+  bbr->loss_in_round = 0;
+}
+
+static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_cc_bbr *bbr,
+                                                   ngtcp2_conn_stat *cstat) {
+  if (bbr_is_probing_bw(bbr)) {
+    return;
+  }
+
+  if (bbr->loss_in_round) {
+    bbr_init_lower_bounds(bbr, cstat);
+    bbr_loss_lower_bounds(bbr);
+  }
+}
+
+static void bbr_init_lower_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) {
+  if (bbr->bw_shortterm == UINT64_MAX) {
+    bbr->bw_shortterm = bbr->max_bw;
+  }
+
+  if (bbr->inflight_shortterm == UINT64_MAX) {
+    bbr->inflight_shortterm = cstat->cwnd;
+  }
+}
+
+static void bbr_loss_lower_bounds(ngtcp2_cc_bbr *bbr) {
+  bbr->bw_shortterm = ngtcp2_max_uint64(
+    bbr->bw_latest,
+    bbr->bw_shortterm * NGTCP2_BBR_BETA_NUMER / NGTCP2_BBR_BETA_DENOM);
+  bbr->inflight_shortterm = ngtcp2_max_uint64(
+    bbr->inflight_latest,
+    bbr->inflight_shortterm * NGTCP2_BBR_BETA_NUMER / NGTCP2_BBR_BETA_DENOM);
+}
+
+static void bbr_bound_bw_for_model(ngtcp2_cc_bbr *bbr) {
+  bbr->bw = ngtcp2_min_uint64(bbr->max_bw, bbr->bw_shortterm);
+}
+
+static void bbr_update_max_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                              const ngtcp2_cc_ack *ack) {
+  bbr_update_round(bbr, ack);
+
+  if (cstat->delivery_rate_sec >= bbr->max_bw || !bbr->rst->rs.is_app_limited) {
+    ngtcp2_window_filter_update(&bbr->max_bw_filter, cstat->delivery_rate_sec,
+                                bbr->cycle_count);
+
+    bbr->max_bw = ngtcp2_window_filter_get_best(&bbr->max_bw_filter);
+  }
+}
+
+static void bbr_update_round(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack) {
+  if (ack->pkt_delivered >= bbr->next_round_delivered) {
+    bbr_start_round(bbr);
+
+    ++bbr->round_count;
+    ++bbr->rounds_since_bw_probe;
+    bbr->round_start = 1;
+
+    bbr->bytes_lost_in_round = 0;
+    bbr->loss_events_in_round = 0;
+
+    bbr->rst->is_cwnd_limited = 0;
+
+    return;
+  }
+
+  bbr->round_start = 0;
+}
+
+static void bbr_start_round(ngtcp2_cc_bbr *bbr) {
+  bbr->next_round_delivered = bbr->rst->delivered;
+}
+
+static int bbr_is_in_probe_bw_state(ngtcp2_cc_bbr *bbr) {
+  switch (bbr->state) {
+  case NGTCP2_BBR_STATE_PROBE_BW_DOWN:
+  case NGTCP2_BBR_STATE_PROBE_BW_CRUISE:
+  case NGTCP2_BBR_STATE_PROBE_BW_REFILL:
+  case NGTCP2_BBR_STATE_PROBE_BW_UP:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+static int bbr_is_probing_bw(ngtcp2_cc_bbr *bbr) {
+  switch (bbr->state) {
+  case NGTCP2_BBR_STATE_STARTUP:
+  case NGTCP2_BBR_STATE_PROBE_BW_REFILL:
+  case NGTCP2_BBR_STATE_PROBE_BW_UP:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+static void bbr_update_ack_aggregation(ngtcp2_cc_bbr *bbr,
+                                       ngtcp2_conn_stat *cstat,
+                                       const ngtcp2_cc_ack *ack,
+                                       ngtcp2_tstamp ts) {
+  ngtcp2_duration interval = ts - bbr->extra_acked_interval_start;
+  uint64_t expected_delivered = bbr->bw * interval / NGTCP2_SECONDS;
+  uint64_t extra;
+
+  if (bbr->extra_acked_delivered <= expected_delivered) {
+    bbr->extra_acked_delivered = 0;
+    bbr->extra_acked_interval_start = ts;
+    expected_delivered = 0;
+  }
+
+  bbr->extra_acked_delivered += ack->bytes_delivered;
+
+  if (bbr->extra_acked_delivered <= expected_delivered) {
+    extra = 0;
+  } else {
+    extra = bbr->extra_acked_delivered - expected_delivered;
+    extra = ngtcp2_min_uint64(extra, cstat->cwnd);
+  }
+
+  if (bbr->full_bw_reached) {
+    bbr->extra_acked_filter.window_length = NGTCP2_BBR_EXTRA_ACKED_FILTERLEN;
+  } else {
+    bbr->extra_acked_filter.window_length = 1;
+  }
+
+  ngtcp2_window_filter_update(&bbr->extra_acked_filter, extra,
+                              bbr->round_count);
+
+  bbr->extra_acked = ngtcp2_window_filter_get_best(&bbr->extra_acked_filter);
+}
+
+static void bbr_enter_drain(ngtcp2_cc_bbr *bbr) {
+  ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr enter Drain");
+
+  bbr->state = NGTCP2_BBR_STATE_DRAIN;
+  bbr->pacing_gain_h = NGTCP2_BBR_DRAIN_PACING_GAIN_H;
+  bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H;
+}
+
+static void bbr_check_drain_done(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                 ngtcp2_tstamp ts) {
+  if (bbr->state == NGTCP2_BBR_STATE_DRAIN &&
+      cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, 100)) {
+    bbr_enter_probe_bw(bbr, ts);
+  }
+}
+
+static void bbr_enter_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts) {
+  bbr_start_probe_bw_down(bbr, ts);
+}
+
+static void bbr_start_probe_bw_down(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts) {
+  ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr start ProbeBW_DOWN");
+
+  bbr_reset_congestion_signals(bbr);
+
+  bbr->probe_up_cnt = UINT64_MAX;
+
+  bbr_pick_probe_wait(bbr);
+
+  bbr->cycle_stamp = ts;
+  bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING;
+
+  bbr_start_round(bbr);
+
+  bbr->state = NGTCP2_BBR_STATE_PROBE_BW_DOWN;
+  bbr->pacing_gain_h = 90;
+  bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H;
+}
+
+static void bbr_start_probe_bw_cruise(ngtcp2_cc_bbr *bbr) {
+  ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA,
+                  "bbr start ProbeBW_CRUISE");
+
+  bbr->state = NGTCP2_BBR_STATE_PROBE_BW_CRUISE;
+  bbr->pacing_gain_h = 100;
+  bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H;
+}
+
+static void bbr_start_probe_bw_refill(ngtcp2_cc_bbr *bbr) {
+  ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA,
+                  "bbr start ProbeBW_REFILL");
+
+  bbr_reset_lower_bounds(bbr);
+
+  bbr->bw_probe_up_rounds = 0;
+  bbr->bw_probe_up_acks = 0;
+  bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_REFILLING;
+
+  bbr_start_round(bbr);
+
+  bbr->state = NGTCP2_BBR_STATE_PROBE_BW_REFILL;
+  bbr->pacing_gain_h = 100;
+  bbr->cwnd_gain_h = NGTCP2_BBR_DEFAULT_CWND_GAIN_H;
+}
+
+static void bbr_start_probe_bw_up(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) {
+  ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr start ProbeBW_UP");
+
+  bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING;
+
+  bbr_start_round(bbr);
+  bbr_reset_full_bw(bbr);
+
+  bbr->full_bw = cstat->delivery_rate_sec;
+  bbr->state = NGTCP2_BBR_STATE_PROBE_BW_UP;
+  bbr->pacing_gain_h = 125;
+  bbr->cwnd_gain_h = 225;
+
+  bbr_raise_inflight_longterm_slope(bbr, cstat);
+}
+
+static void bbr_update_probe_bw_cycle_phase(ngtcp2_cc_bbr *bbr,
+                                            ngtcp2_conn_stat *cstat,
+                                            const ngtcp2_cc_ack *ack,
+                                            ngtcp2_tstamp ts) {
+  if (!bbr->full_bw_reached) {
+    return;
+  }
+
+  bbr_adapt_upper_bounds(bbr, cstat, ack);
+
+  if (!bbr_is_in_probe_bw_state(bbr)) {
+    return;
+  }
+
+  switch (bbr->state) {
+  case NGTCP2_BBR_STATE_PROBE_BW_DOWN:
+    if (bbr_is_time_to_probe_bw(bbr, cstat, ts)) {
+      return;
+    }
+
+    if (bbr_is_time_to_cruise(bbr, cstat, ts)) {
+      bbr_start_probe_bw_cruise(bbr);
+    }
+
+    break;
+  case NGTCP2_BBR_STATE_PROBE_BW_CRUISE:
+    if (bbr_is_time_to_probe_bw(bbr, cstat, ts)) {
+      return;
+    }
+
+    break;
+  case NGTCP2_BBR_STATE_PROBE_BW_REFILL:
+    if (bbr->round_start) {
+      bbr->bw_probe_samples = 1;
+      bbr_start_probe_bw_up(bbr, cstat);
+    }
+
+    break;
+  case NGTCP2_BBR_STATE_PROBE_BW_UP:
+    if (bbr_is_time_to_go_down(bbr, cstat)) {
+      bbr_start_probe_bw_down(bbr, ts);
+    }
+
+    break;
+  default:
+    break;
+  }
+}
+
+static int bbr_is_time_to_cruise(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                 ngtcp2_tstamp ts) {
+  (void)ts;
+
+  if (cstat->bytes_in_flight > bbr_inflight_with_headroom(bbr, cstat)) {
+    return 0;
+  }
+
+  if (cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, 100)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int bbr_is_time_to_go_down(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) {
+  if (bbr->rst->is_cwnd_limited && cstat->cwnd >= bbr->inflight_longterm) {
+    bbr_reset_full_bw(bbr);
+    bbr->full_bw = cstat->delivery_rate_sec;
+  } else if (bbr->full_bw_now) {
+    return 1;
+  }
+
+  return 0;
+}
+
+static int bbr_has_elapsed_in_phase(ngtcp2_cc_bbr *bbr,
+                                    ngtcp2_duration interval,
+                                    ngtcp2_tstamp ts) {
+  return ts > bbr->cycle_stamp + interval;
+}
+
+static uint64_t bbr_inflight_with_headroom(ngtcp2_cc_bbr *bbr,
+                                           ngtcp2_conn_stat *cstat) {
+  uint64_t headroom;
+  uint64_t mpcwnd;
+  if (bbr->inflight_longterm == UINT64_MAX) {
+    return UINT64_MAX;
+  }
+
+  headroom =
+    ngtcp2_max_uint64(cstat->max_tx_udp_payload_size,
+                      bbr->inflight_longterm * NGTCP2_BBR_HEADROOM_NUMER /
+                        NGTCP2_BBR_HEADROOM_DENOM);
+  mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size);
+
+  if (bbr->inflight_longterm > headroom) {
+    return ngtcp2_max_uint64(bbr->inflight_longterm - headroom, mpcwnd);
+  }
+
+  return mpcwnd;
+}
+
+static void bbr_raise_inflight_longterm_slope(ngtcp2_cc_bbr *bbr,
+                                              ngtcp2_conn_stat *cstat) {
+  uint64_t growth_this_round = cstat->max_tx_udp_payload_size
+                               << bbr->bw_probe_up_rounds;
+
+  bbr->bw_probe_up_rounds = ngtcp2_min_size(bbr->bw_probe_up_rounds + 1, 30);
+  bbr->probe_up_cnt = ngtcp2_max_uint64(cstat->cwnd / growth_this_round, 1);
+}
+
+static void bbr_probe_inflight_longterm_upward(ngtcp2_cc_bbr *bbr,
+                                               ngtcp2_conn_stat *cstat,
+                                               const ngtcp2_cc_ack *ack) {
+  uint64_t delta;
+
+  if (!bbr->rst->is_cwnd_limited || cstat->cwnd < bbr->inflight_longterm) {
+    return;
+  }
+
+  bbr->bw_probe_up_acks += ack->bytes_delivered;
+
+  if (bbr->probe_up_cnt != UINT64_MAX &&
+      bbr->bw_probe_up_acks >=
+        bbr->probe_up_cnt * cstat->max_tx_udp_payload_size) {
+    delta = bbr->bw_probe_up_acks / bbr->probe_up_cnt;
+    bbr->bw_probe_up_acks -= delta * bbr->probe_up_cnt;
+    bbr->inflight_longterm += delta;
+  }
+
+  if (bbr->round_start) {
+    bbr_raise_inflight_longterm_slope(bbr, cstat);
+  }
+}
+
+static void bbr_adapt_upper_bounds(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                   const ngtcp2_cc_ack *ack) {
+  if (bbr->ack_phase == NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING &&
+      bbr->round_start) {
+    bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_FEEDBACK;
+  }
+
+  if (bbr->ack_phase == NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING &&
+      bbr->round_start) {
+    if (bbr_is_in_probe_bw_state(bbr) && !bbr->rst->rs.is_app_limited) {
+      bbr_advance_max_bw_filter(bbr);
+    }
+  }
+
+  if (!bbr_is_inflight_too_high(bbr)) {
+    if (bbr->inflight_longterm == UINT64_MAX) {
+      return;
+    }
+
+    if (bbr->rst->rs.tx_in_flight > bbr->inflight_longterm) {
+      bbr->inflight_longterm = bbr->rst->rs.tx_in_flight;
+    }
+
+    if (bbr->state == NGTCP2_BBR_STATE_PROBE_BW_UP) {
+      bbr_probe_inflight_longterm_upward(bbr, cstat, ack);
+    }
+  }
+}
+
+static int bbr_is_time_to_probe_bw(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                   ngtcp2_tstamp ts) {
+  if (bbr_has_elapsed_in_phase(bbr, bbr->bw_probe_wait, ts) ||
+      bbr_is_reno_coexistence_probe_time(bbr, cstat)) {
+    bbr_start_probe_bw_refill(bbr);
+
+    return 1;
+  }
+
+  return 0;
+}
+
+static void bbr_pick_probe_wait(ngtcp2_cc_bbr *bbr) {
+  uint8_t rand;
+
+  bbr->rand(&rand, 1, &bbr->rand_ctx);
+
+  bbr->rounds_since_bw_probe = (uint64_t)(rand * 2 / 256);
+
+  bbr->rand(&rand, 1, &bbr->rand_ctx);
+
+  bbr->bw_probe_wait = 2 * NGTCP2_SECONDS + NGTCP2_SECONDS * rand / 255;
+}
+
+static int bbr_is_reno_coexistence_probe_time(ngtcp2_cc_bbr *bbr,
+                                              ngtcp2_conn_stat *cstat) {
+  uint64_t reno_rounds =
+    bbr_target_inflight(bbr, cstat) / cstat->max_tx_udp_payload_size;
+
+  return bbr->rounds_since_bw_probe >= ngtcp2_min_uint64(reno_rounds, 63);
+}
+
+static uint64_t bbr_target_inflight(ngtcp2_cc_bbr *bbr,
+                                    ngtcp2_conn_stat *cstat) {
+  return ngtcp2_min_uint64(bbr->bdp, cstat->cwnd);
+}
+
+static int bbr_is_inflight_too_high(ngtcp2_cc_bbr *bbr) {
+  const ngtcp2_rs *rs = &bbr->rst->rs;
+  return rs->lost * NGTCP2_BBR_LOSS_THRESH_DENOM >
+         rs->tx_in_flight * NGTCP2_BBR_LOSS_THRESH_NUMER;
+}
+
+static void bbr_handle_inflight_too_high(ngtcp2_cc_bbr *bbr,
+                                         ngtcp2_conn_stat *cstat,
+                                         ngtcp2_tstamp ts) {
+  const ngtcp2_rs *rs = &bbr->rst->rs;
+
+  bbr->bw_probe_samples = 0;
+
+  if (!rs->is_app_limited) {
+    bbr->inflight_longterm = ngtcp2_max_uint64(
+      rs->tx_in_flight, bbr_target_inflight(bbr, cstat) *
+                          NGTCP2_BBR_BETA_NUMER / NGTCP2_BBR_BETA_DENOM);
+  }
+
+  if (bbr->state == NGTCP2_BBR_STATE_PROBE_BW_UP) {
+    bbr_start_probe_bw_down(bbr, ts);
+  }
+}
+
+static void bbr_note_loss(ngtcp2_cc_bbr *bbr) {
+  if (!bbr->loss_in_round) {
+    bbr->loss_round_delivered = bbr->rst->delivered;
+  }
+
+  bbr->loss_in_round = 1;
+}
+
+static void bbr_handle_lost_packet(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                   const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) {
+  ngtcp2_rs *rs = &bbr->rst->rs;
+
+  bbr_note_loss(bbr);
+
+  if (!bbr->bw_probe_samples) {
+    return;
+  }
+
+  rs->tx_in_flight = pkt->tx_in_flight;
+  /* bbr->rst->lost is not incremented for pkt yet */
+  assert(bbr->rst->lost + pkt->pktlen >= pkt->lost);
+  rs->lost = bbr->rst->lost + pkt->pktlen - pkt->lost;
+  rs->is_app_limited = pkt->is_app_limited;
+
+  if (bbr_is_inflight_too_high(bbr)) {
+    rs->tx_in_flight = bbr_inflight_longterm_from_lost_packet(bbr, pkt);
+
+    bbr_handle_inflight_too_high(bbr, cstat, ts);
+  }
+}
+
+static uint64_t
+bbr_inflight_longterm_from_lost_packet(ngtcp2_cc_bbr *bbr,
+                                       const ngtcp2_cc_pkt *pkt) {
+  ngtcp2_rs *rs = &bbr->rst->rs;
+  uint64_t inflight_prev, lost_prev, lost_prefix;
+  (void)bbr;
+
+  assert(rs->tx_in_flight >= pkt->pktlen);
+
+  inflight_prev = rs->tx_in_flight - pkt->pktlen;
+
+  assert(rs->lost >= pkt->pktlen);
+
+  lost_prev = rs->lost - pkt->pktlen;
+
+  if (inflight_prev * NGTCP2_BBR_LOSS_THRESH_NUMER <
+      lost_prev * NGTCP2_BBR_LOSS_THRESH_DENOM) {
+    return inflight_prev;
+  }
+
+  lost_prefix = (inflight_prev * NGTCP2_BBR_LOSS_THRESH_NUMER -
+                 lost_prev * NGTCP2_BBR_LOSS_THRESH_DENOM) /
+                (NGTCP2_BBR_LOSS_THRESH_DENOM - NGTCP2_BBR_LOSS_THRESH_NUMER);
+
+  return inflight_prev + lost_prefix;
+}
+
+static void bbr_update_min_rtt(ngtcp2_cc_bbr *bbr, const ngtcp2_cc_ack *ack,
+                               ngtcp2_tstamp ts) {
+  int min_rtt_expired;
+
+  bbr->probe_rtt_expired =
+    ts > bbr->probe_rtt_min_stamp + NGTCP2_BBR_PROBE_RTT_INTERVAL;
+
+  if (ack->rtt != UINT64_MAX &&
+      (ack->rtt < bbr->probe_rtt_min_delay || bbr->probe_rtt_expired)) {
+    bbr->probe_rtt_min_delay = ack->rtt;
+    bbr->probe_rtt_min_stamp = ts;
+  }
+
+  min_rtt_expired = ts > bbr->min_rtt_stamp + NGTCP2_BBR_MIN_RTT_FILTERLEN;
+
+  if (bbr->probe_rtt_min_delay < bbr->min_rtt || min_rtt_expired) {
+    bbr->min_rtt = bbr->probe_rtt_min_delay;
+    bbr->min_rtt_stamp = bbr->probe_rtt_min_stamp;
+
+    ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA,
+                    "bbr update min_rtt=%" PRIu64, bbr->min_rtt);
+  }
+}
+
+static void bbr_check_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                ngtcp2_tstamp ts) {
+  if (bbr->state != NGTCP2_BBR_STATE_PROBE_RTT && bbr->probe_rtt_expired &&
+      !bbr->idle_restart) {
+    bbr_enter_probe_rtt(bbr);
+    bbr_save_cwnd(bbr, cstat);
+
+    bbr->probe_rtt_done_stamp = UINT64_MAX;
+    bbr->ack_phase = NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING;
+
+    bbr_start_round(bbr);
+  }
+
+  if (bbr->state == NGTCP2_BBR_STATE_PROBE_RTT) {
+    bbr_handle_probe_rtt(bbr, cstat, ts);
+  }
+
+  if (bbr->rst->rs.delivered) {
+    bbr->idle_restart = 0;
+  }
+}
+
+static void bbr_enter_probe_rtt(ngtcp2_cc_bbr *bbr) {
+  ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr enter ProbeRTT");
+
+  bbr->state = NGTCP2_BBR_STATE_PROBE_RTT;
+  bbr->pacing_gain_h = 100;
+  bbr->cwnd_gain_h = NGTCP2_BBR_PROBE_RTT_CWND_GAIN_H;
+}
+
+static void bbr_handle_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                 ngtcp2_tstamp ts) {
+  bbr_mark_connection_app_limited(bbr, cstat);
+
+  if (bbr->probe_rtt_done_stamp == UINT64_MAX &&
+      cstat->bytes_in_flight <= bbr_probe_rtt_cwnd(bbr, cstat)) {
+    bbr->probe_rtt_done_stamp = ts + NGTCP2_BBR_PROBE_RTT_DURATION;
+    bbr->probe_rtt_round_done = 0;
+
+    bbr_start_round(bbr);
+
+    return;
+  }
+
+  if (bbr->probe_rtt_done_stamp != UINT64_MAX) {
+    if (bbr->round_start) {
+      bbr->probe_rtt_round_done = 1;
+    }
+
+    if (bbr->probe_rtt_round_done) {
+      bbr_check_probe_rtt_done(bbr, cstat, ts);
+    }
+  }
+}
+
+static void bbr_check_probe_rtt_done(ngtcp2_cc_bbr *bbr,
+                                     ngtcp2_conn_stat *cstat,
+                                     ngtcp2_tstamp ts) {
+  if (bbr->probe_rtt_done_stamp != UINT64_MAX &&
+      ts > bbr->probe_rtt_done_stamp) {
+    bbr->probe_rtt_min_stamp = ts;
+    bbr_restore_cwnd(bbr, cstat);
+    bbr_exit_probe_rtt(bbr, ts);
+  }
+}
+
+static void bbr_mark_connection_app_limited(ngtcp2_cc_bbr *bbr,
+                                            ngtcp2_conn_stat *cstat) {
+  uint64_t app_limited = bbr->rst->delivered + cstat->bytes_in_flight;
+
+  if (app_limited) {
+    bbr->rst->app_limited = app_limited;
+  } else {
+    bbr->rst->app_limited = cstat->max_tx_udp_payload_size;
+  }
+}
+
+static void bbr_exit_probe_rtt(ngtcp2_cc_bbr *bbr, ngtcp2_tstamp ts) {
+  bbr_reset_lower_bounds(bbr);
+
+  if (bbr->full_bw_reached) {
+    bbr_start_probe_bw_down(bbr, ts);
+    bbr_start_probe_bw_cruise(bbr);
+  } else {
+    bbr_enter_startup(bbr);
+  }
+}
+
+static void bbr_handle_restart_from_idle(ngtcp2_cc_bbr *bbr,
+                                         ngtcp2_conn_stat *cstat,
+                                         ngtcp2_tstamp ts) {
+  if (cstat->bytes_in_flight == 0 && bbr->rst->app_limited) {
+    ngtcp2_log_info(bbr->cc.log, NGTCP2_LOG_EVENT_CCA, "bbr restart from idle");
+
+    bbr->idle_restart = 1;
+    bbr->extra_acked_interval_start = ts;
+
+    if (bbr_is_in_probe_bw_state(bbr)) {
+      bbr_set_pacing_rate_with_gain(bbr, cstat, 100);
+    } else if (bbr->state == NGTCP2_BBR_STATE_PROBE_RTT) {
+      bbr_check_probe_rtt_done(bbr, cstat, ts);
+    }
+  }
+}
+
+static uint64_t bbr_bdp_multiple(ngtcp2_cc_bbr *bbr, uint64_t gain_h) {
+  if (bbr->min_rtt == UINT64_MAX) {
+    return bbr->initial_cwnd;
+  }
+
+  bbr->bdp = ngtcp2_max_uint64(bbr->bw * bbr->min_rtt / NGTCP2_SECONDS, 1);
+
+  return (uint64_t)(bbr->bdp * gain_h / 100);
+}
+
+static uint64_t min_pipe_cwnd(size_t max_udp_payload_size) {
+  return max_udp_payload_size * 4;
+}
+
+static uint64_t bbr_quantization_budget(ngtcp2_cc_bbr *bbr,
+                                        ngtcp2_conn_stat *cstat,
+                                        uint64_t inflight) {
+  bbr_update_offload_budget(bbr, cstat);
+
+  inflight = ngtcp2_max_uint64(inflight, bbr->offload_budget);
+  inflight =
+    ngtcp2_max_uint64(inflight, min_pipe_cwnd(cstat->max_tx_udp_payload_size));
+
+  if (bbr->state == NGTCP2_BBR_STATE_PROBE_BW_UP) {
+    inflight += 2 * cstat->max_tx_udp_payload_size;
+  }
+
+  return inflight;
+}
+
+static uint64_t bbr_inflight(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                             uint64_t gain_h) {
+  uint64_t inflight = bbr_bdp_multiple(bbr, gain_h);
+
+  return bbr_quantization_budget(bbr, cstat, inflight);
+}
+
+static void bbr_update_max_inflight(ngtcp2_cc_bbr *bbr,
+                                    ngtcp2_conn_stat *cstat) {
+  uint64_t inflight;
+
+  inflight = bbr_bdp_multiple(bbr, bbr->cwnd_gain_h) + bbr->extra_acked;
+  bbr->max_inflight = bbr_quantization_budget(bbr, cstat, inflight);
+}
+
+static void bbr_update_offload_budget(ngtcp2_cc_bbr *bbr,
+                                      ngtcp2_conn_stat *cstat) {
+  bbr->offload_budget = 3 * cstat->send_quantum;
+}
+
+static void bbr_advance_max_bw_filter(ngtcp2_cc_bbr *bbr) {
+  ++bbr->cycle_count;
+}
+
+static void bbr_save_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) {
+  if (!bbr->in_loss_recovery && bbr->state != NGTCP2_BBR_STATE_PROBE_RTT) {
+    bbr->prior_cwnd = cstat->cwnd;
+    return;
+  }
+
+  bbr->prior_cwnd = ngtcp2_max_uint64(bbr->prior_cwnd, cstat->cwnd);
+}
+
+static void bbr_restore_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) {
+  cstat->cwnd = ngtcp2_max_uint64(cstat->cwnd, bbr->prior_cwnd);
+}
+
+static uint64_t bbr_probe_rtt_cwnd(ngtcp2_cc_bbr *bbr,
+                                   ngtcp2_conn_stat *cstat) {
+  uint64_t probe_rtt_cwnd =
+    bbr_bdp_multiple(bbr, NGTCP2_BBR_PROBE_RTT_CWND_GAIN_H);
+  uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size);
+
+  return ngtcp2_max_uint64(probe_rtt_cwnd, mpcwnd);
+}
+
+static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_cc_bbr *bbr,
+                                         ngtcp2_conn_stat *cstat) {
+  uint64_t probe_rtt_cwnd;
+
+  if (bbr->state == NGTCP2_BBR_STATE_PROBE_RTT) {
+    probe_rtt_cwnd = bbr_probe_rtt_cwnd(bbr, cstat);
+
+    cstat->cwnd = ngtcp2_min_uint64(cstat->cwnd, probe_rtt_cwnd);
+  }
+}
+
+static void bbr_set_cwnd(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                         const ngtcp2_cc_ack *ack) {
+  uint64_t mpcwnd;
+
+  bbr_update_max_inflight(bbr, cstat);
+
+  if (bbr->full_bw_reached) {
+    cstat->cwnd += ack->bytes_delivered;
+    cstat->cwnd = ngtcp2_min_uint64(cstat->cwnd, bbr->max_inflight);
+  } else if (cstat->cwnd < bbr->max_inflight ||
+             bbr->rst->delivered < bbr->initial_cwnd) {
+    cstat->cwnd += ack->bytes_delivered;
+  }
+
+  mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size);
+  cstat->cwnd = ngtcp2_max_uint64(cstat->cwnd, mpcwnd);
+
+  bbr_bound_cwnd_for_probe_rtt(bbr, cstat);
+  bbr_bound_cwnd_for_model(bbr, cstat);
+}
+
+static void bbr_bound_cwnd_for_model(ngtcp2_cc_bbr *bbr,
+                                     ngtcp2_conn_stat *cstat) {
+  uint64_t cap = UINT64_MAX;
+  uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size);
+
+  if (bbr_is_in_probe_bw_state(bbr) &&
+      bbr->state != NGTCP2_BBR_STATE_PROBE_BW_CRUISE) {
+    cap = bbr->inflight_longterm;
+  } else if (bbr->state == NGTCP2_BBR_STATE_PROBE_RTT ||
+             bbr->state == NGTCP2_BBR_STATE_PROBE_BW_CRUISE) {
+    cap = bbr_inflight_with_headroom(bbr, cstat);
+  }
+
+  cap = ngtcp2_min_uint64(cap, bbr->inflight_shortterm);
+  cap = ngtcp2_max_uint64(cap, mpcwnd);
+
+  cstat->cwnd = ngtcp2_min_uint64(cstat->cwnd, cap);
+}
+
+static void bbr_set_send_quantum(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat) {
+  size_t send_quantum = 64 * 1024;
+  (void)bbr;
+
+  send_quantum =
+    ngtcp2_min_size(send_quantum, (size_t)((NGTCP2_MILLISECONDS << 10) /
+                                           cstat->pacing_interval_m));
+
+  cstat->send_quantum =
+    ngtcp2_max_size(send_quantum, 2 * cstat->max_tx_udp_payload_size);
+}
+
+static int in_congestion_recovery(const ngtcp2_conn_stat *cstat,
+                                  ngtcp2_tstamp sent_time) {
+  return cstat->congestion_recovery_start_ts != UINT64_MAX &&
+         sent_time <= cstat->congestion_recovery_start_ts;
+}
+
+static void bbr_handle_recovery(ngtcp2_cc_bbr *bbr, ngtcp2_conn_stat *cstat,
+                                const ngtcp2_cc_ack *ack) {
+  if (bbr->in_loss_recovery) {
+    if (ack->largest_pkt_sent_ts != UINT64_MAX &&
+        !in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) {
+      bbr->in_loss_recovery = 0;
+      bbr->round_count_at_recovery = UINT64_MAX;
+      bbr_restore_cwnd(bbr, cstat);
+    }
+
+    return;
+  }
+
+  if (bbr->congestion_recovery_start_ts != UINT64_MAX) {
+    bbr->in_loss_recovery = 1;
+    bbr->round_count_at_recovery =
+      bbr->round_start ? bbr->round_count : bbr->round_count + 1;
+    bbr_save_cwnd(bbr, cstat);
+    cstat->cwnd =
+      cstat->bytes_in_flight +
+      ngtcp2_max_uint64(ack->bytes_delivered, cstat->max_tx_udp_payload_size);
+
+    cstat->congestion_recovery_start_ts = bbr->congestion_recovery_start_ts;
+    bbr->congestion_recovery_start_ts = UINT64_MAX;
+  }
+}
+
+static void bbr_cc_on_pkt_lost(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                               const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) {
+  ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc);
+
+  bbr_update_on_loss(bbr, cstat, pkt, ts);
+}
+
+static void bbr_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                    ngtcp2_tstamp sent_ts, uint64_t bytes_lost,
+                                    ngtcp2_tstamp ts) {
+  ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc);
+  (void)bytes_lost;
+
+  if (bbr->in_loss_recovery ||
+      bbr->congestion_recovery_start_ts != UINT64_MAX ||
+      in_congestion_recovery(cstat, sent_ts)) {
+    return;
+  }
+
+  bbr->congestion_recovery_start_ts = ts;
+}
+
+static void bbr_cc_on_spurious_congestion(ngtcp2_cc *cc,
+                                          ngtcp2_conn_stat *cstat,
+                                          ngtcp2_tstamp ts) {
+  ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc);
+  (void)ts;
+
+  bbr->congestion_recovery_start_ts = UINT64_MAX;
+  cstat->congestion_recovery_start_ts = UINT64_MAX;
+
+  if (bbr->in_loss_recovery) {
+    bbr->in_loss_recovery = 0;
+    bbr->round_count_at_recovery = UINT64_MAX;
+    bbr_restore_cwnd(bbr, cstat);
+  }
+}
+
+static void bbr_cc_on_persistent_congestion(ngtcp2_cc *cc,
+                                            ngtcp2_conn_stat *cstat,
+                                            ngtcp2_tstamp ts) {
+  ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc);
+  (void)ts;
+
+  cstat->congestion_recovery_start_ts = UINT64_MAX;
+  bbr->congestion_recovery_start_ts = UINT64_MAX;
+  bbr->in_loss_recovery = 0;
+  bbr->round_count_at_recovery = UINT64_MAX;
+
+  bbr_save_cwnd(bbr, cstat);
+  cstat->cwnd = cstat->bytes_in_flight + cstat->max_tx_udp_payload_size;
+  cstat->cwnd = ngtcp2_max_uint64(
+    cstat->cwnd, min_pipe_cwnd(cstat->max_tx_udp_payload_size));
+}
+
+static void bbr_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                               const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) {
+  ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc);
+
+  bbr_handle_recovery(bbr, cstat, ack);
+  bbr_update_on_ack(bbr, cstat, ack, ts);
+}
+
+static void bbr_cc_on_pkt_sent(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                               const ngtcp2_cc_pkt *pkt) {
+  ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc);
+
+  bbr_on_transmit(bbr, cstat, pkt->sent_ts);
+}
+
+static void bbr_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                         ngtcp2_tstamp ts) {
+  ngtcp2_cc_bbr *bbr = ngtcp2_struct_of(cc, ngtcp2_cc_bbr, cc);
+
+  bbr_on_init(bbr, cstat, ts);
+}
+
+void ngtcp2_cc_bbr_init(ngtcp2_cc_bbr *bbr, ngtcp2_log *log,
+                        ngtcp2_conn_stat *cstat, ngtcp2_rst *rst,
+                        ngtcp2_tstamp initial_ts, ngtcp2_rand rand,
+                        const ngtcp2_rand_ctx *rand_ctx) {
+  memset(bbr, 0, sizeof(*bbr));
+
+  bbr->cc.log = log;
+  bbr->cc.on_pkt_lost = bbr_cc_on_pkt_lost;
+  bbr->cc.congestion_event = bbr_cc_congestion_event;
+  bbr->cc.on_spurious_congestion = bbr_cc_on_spurious_congestion;
+  bbr->cc.on_persistent_congestion = bbr_cc_on_persistent_congestion;
+  bbr->cc.on_ack_recv = bbr_cc_on_ack_recv;
+  bbr->cc.on_pkt_sent = bbr_cc_on_pkt_sent;
+  bbr->cc.reset = bbr_cc_reset;
+
+  bbr->rst = rst;
+  bbr->rand = rand;
+  bbr->rand_ctx = *rand_ctx;
+  bbr->initial_cwnd = cstat->cwnd;
+
+  bbr_on_init(bbr, cstat, initial_ts);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_bbr.h b/third_party/ngtcp2/lib/ngtcp2_bbr.h
new file mode 100644 (file)
index 0000000..e823711
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2021 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_BBR_H
+#define NGTCP2_BBR_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_cc.h"
+#include "ngtcp2_window_filter.h"
+
+typedef struct ngtcp2_rst ngtcp2_rst;
+
+typedef enum ngtcp2_bbr_state {
+  NGTCP2_BBR_STATE_STARTUP,
+  NGTCP2_BBR_STATE_DRAIN,
+  NGTCP2_BBR_STATE_PROBE_BW_DOWN,
+  NGTCP2_BBR_STATE_PROBE_BW_CRUISE,
+  NGTCP2_BBR_STATE_PROBE_BW_REFILL,
+  NGTCP2_BBR_STATE_PROBE_BW_UP,
+  NGTCP2_BBR_STATE_PROBE_RTT,
+} ngtcp2_bbr_state;
+
+typedef enum ngtcp2_bbr_ack_phase {
+  NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STARTING,
+  NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_STOPPING,
+  NGTCP2_BBR_ACK_PHASE_ACKS_PROBE_FEEDBACK,
+  NGTCP2_BBR_ACK_PHASE_ACKS_REFILLING,
+} ngtcp2_bbr_ack_phase;
+
+/*
+ * ngtcp2_cc_bbr is BBR v2 congestion controller, described in
+ * https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control-01
+ */
+typedef struct ngtcp2_cc_bbr {
+  ngtcp2_cc cc;
+
+  uint64_t initial_cwnd;
+  ngtcp2_rst *rst;
+  ngtcp2_rand rand;
+  ngtcp2_rand_ctx rand_ctx;
+
+  /* max_bw_filter for tracking the maximum recent delivery rate
+    samples for estimating max_bw. */
+  ngtcp2_window_filter max_bw_filter;
+
+  ngtcp2_window_filter extra_acked_filter;
+
+  ngtcp2_duration min_rtt;
+  ngtcp2_tstamp min_rtt_stamp;
+  ngtcp2_tstamp probe_rtt_done_stamp;
+  int probe_rtt_round_done;
+  uint64_t prior_cwnd;
+  int idle_restart;
+  ngtcp2_tstamp extra_acked_interval_start;
+  uint64_t extra_acked_delivered;
+
+  /* Congestion signals */
+  int loss_in_round;
+  uint64_t bw_latest;
+  uint64_t inflight_latest;
+
+  /* Lower bounds */
+  uint64_t bw_shortterm;
+  uint64_t inflight_shortterm;
+
+  /* Round counting */
+  uint64_t next_round_delivered;
+  int round_start;
+  uint64_t round_count;
+
+  /* Full pipe */
+  uint64_t full_bw;
+  size_t full_bw_count;
+  int full_bw_reached;
+  int full_bw_now;
+
+  /* Pacing rate */
+  uint64_t pacing_gain_h;
+
+  ngtcp2_bbr_state state;
+  uint64_t cwnd_gain_h;
+
+  int loss_round_start;
+  uint64_t loss_round_delivered;
+  uint64_t rounds_since_bw_probe;
+  uint64_t max_bw;
+  uint64_t bw;
+  uint64_t cycle_count;
+  uint64_t extra_acked;
+  uint64_t bytes_lost_in_round;
+  size_t loss_events_in_round;
+  uint64_t offload_budget;
+  uint64_t probe_up_cnt;
+  ngtcp2_tstamp cycle_stamp;
+  ngtcp2_bbr_ack_phase ack_phase;
+  ngtcp2_duration bw_probe_wait;
+  int bw_probe_samples;
+  size_t bw_probe_up_rounds;
+  uint64_t bw_probe_up_acks;
+  uint64_t inflight_longterm;
+  int probe_rtt_expired;
+  ngtcp2_duration probe_rtt_min_delay;
+  ngtcp2_tstamp probe_rtt_min_stamp;
+  int in_loss_recovery;
+  uint64_t round_count_at_recovery;
+  uint64_t max_inflight;
+  ngtcp2_tstamp congestion_recovery_start_ts;
+  uint64_t bdp;
+} ngtcp2_cc_bbr;
+
+void ngtcp2_cc_bbr_init(ngtcp2_cc_bbr *bbr, ngtcp2_log *log,
+                        ngtcp2_conn_stat *cstat, ngtcp2_rst *rst,
+                        ngtcp2_tstamp initial_ts, ngtcp2_rand rand,
+                        const ngtcp2_rand_ctx *rand_ctx);
+
+#endif /* !defined(NGTCP2_BBR_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_buf.c b/third_party/ngtcp2/lib/ngtcp2_buf.c
new file mode 100644 (file)
index 0000000..75326d6
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_buf.h"
+#include "ngtcp2_mem.h"
+
+void ngtcp2_buf_init(ngtcp2_buf *buf, uint8_t *begin, size_t len) {
+  buf->begin = buf->pos = buf->last = begin;
+  buf->end = begin + len;
+}
+
+void ngtcp2_buf_reset(ngtcp2_buf *buf) { buf->pos = buf->last = buf->begin; }
+
+size_t ngtcp2_buf_cap(const ngtcp2_buf *buf) {
+  return (size_t)(buf->end - buf->begin);
+}
+
+int ngtcp2_buf_chain_new(ngtcp2_buf_chain **pbufchain, size_t len,
+                         const ngtcp2_mem *mem) {
+  *pbufchain = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_buf_chain) + len);
+  if (*pbufchain == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  (*pbufchain)->next = NULL;
+
+  ngtcp2_buf_init(&(*pbufchain)->buf,
+                  (uint8_t *)(*pbufchain) + sizeof(ngtcp2_buf_chain), len);
+
+  return 0;
+}
+
+void ngtcp2_buf_chain_del(ngtcp2_buf_chain *bufchain, const ngtcp2_mem *mem) {
+  ngtcp2_mem_free(mem, bufchain);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_buf.h b/third_party/ngtcp2/lib/ngtcp2_buf.h
new file mode 100644 (file)
index 0000000..e87adb1
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_BUF_H
+#define NGTCP2_BUF_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+typedef struct ngtcp2_buf {
+  /* begin points to the beginning of the buffer. */
+  uint8_t *begin;
+  /* end points to the one beyond of the last byte of the buffer */
+  uint8_t *end;
+  /* pos points to the start of data.  Typically, this points to the
+     point that next data should be read.  Initially, it points to
+     |begin|. */
+  uint8_t *pos;
+  /* last points to the one beyond of the last data of the buffer.
+     Typically, new data is written at this point.  Initially, it
+     points to |begin|. */
+  uint8_t *last;
+} ngtcp2_buf;
+
+/*
+ * ngtcp2_buf_init initializes |buf| with the given buffer.
+ */
+void ngtcp2_buf_init(ngtcp2_buf *buf, uint8_t *begin, size_t len);
+
+/*
+ * ngtcp2_buf_reset resets pos and last fields to match begin field to
+ * make ngtcp2_buf_len(buf) return 0.
+ */
+void ngtcp2_buf_reset(ngtcp2_buf *buf);
+
+/*
+ * ngtcp2_buf_left returns the number of additional bytes which can be
+ * written to the underlying buffer.  In other words, it returns
+ * buf->end - buf->last.
+ */
+#define ngtcp2_buf_left(BUF) (size_t)((BUF)->end - (BUF)->last)
+
+/*
+ * ngtcp2_buf_len returns the number of bytes left to read.  In other
+ * words, it returns buf->last - buf->pos.
+ */
+#define ngtcp2_buf_len(BUF) (size_t)((BUF)->last - (BUF)->pos)
+
+/*
+ * ngtcp2_buf_cap returns the capacity of the buffer.  In other words,
+ * it returns buf->end - buf->begin.
+ */
+size_t ngtcp2_buf_cap(const ngtcp2_buf *buf);
+
+/*
+ * ngtcp2_buf_chain is a linked list of ngtcp2_buf.
+ */
+typedef struct ngtcp2_buf_chain ngtcp2_buf_chain;
+
+struct ngtcp2_buf_chain {
+  ngtcp2_buf_chain *next;
+  ngtcp2_buf buf;
+};
+
+/*
+ * ngtcp2_buf_chain_new creates new ngtcp2_buf_chain and initializes
+ * the internal buffer with |len| bytes space.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_buf_chain_new(ngtcp2_buf_chain **pbufchain, size_t len,
+                         const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_buf_chain_del deletes the resource allocated by |bufchain|.
+ * It also deletes the memory pointed by |bufchain|.
+ */
+void ngtcp2_buf_chain_del(ngtcp2_buf_chain *bufchain, const ngtcp2_mem *mem);
+
+#endif /* !defined(NGTCP2_BUF_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_cc.c b/third_party/ngtcp2/lib/ngtcp2_cc.c
new file mode 100644 (file)
index 0000000..ad3665b
--- /dev/null
@@ -0,0 +1,493 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_cc.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "ngtcp2_log.h"
+#include "ngtcp2_macro.h"
+#include "ngtcp2_mem.h"
+#include "ngtcp2_rcvry.h"
+#include "ngtcp2_conn_stat.h"
+#include "ngtcp2_rst.h"
+#include "ngtcp2_unreachable.h"
+
+uint64_t ngtcp2_cc_compute_initcwnd(size_t max_udp_payload_size) {
+  uint64_t n = 2 * max_udp_payload_size;
+  n = ngtcp2_max_uint64(n, 14720);
+  return ngtcp2_min_uint64(10 * max_udp_payload_size, n);
+}
+
+ngtcp2_cc_pkt *ngtcp2_cc_pkt_init(ngtcp2_cc_pkt *pkt, int64_t pkt_num,
+                                  size_t pktlen, ngtcp2_pktns_id pktns_id,
+                                  ngtcp2_tstamp sent_ts, uint64_t lost,
+                                  uint64_t tx_in_flight, int is_app_limited) {
+  pkt->pkt_num = pkt_num;
+  pkt->pktlen = pktlen;
+  pkt->pktns_id = pktns_id;
+  pkt->sent_ts = sent_ts;
+  pkt->lost = lost;
+  pkt->tx_in_flight = tx_in_flight;
+  pkt->is_app_limited = is_app_limited;
+
+  return pkt;
+}
+
+static void reno_cc_reset(ngtcp2_cc_reno *reno) { reno->pending_add = 0; }
+
+void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log) {
+  memset(reno, 0, sizeof(*reno));
+
+  reno->cc.log = log;
+  reno->cc.on_pkt_acked = ngtcp2_cc_reno_cc_on_pkt_acked;
+  reno->cc.congestion_event = ngtcp2_cc_reno_cc_congestion_event;
+  reno->cc.on_persistent_congestion =
+    ngtcp2_cc_reno_cc_on_persistent_congestion;
+  reno->cc.reset = ngtcp2_cc_reno_cc_reset;
+
+  reno_cc_reset(reno);
+}
+
+static int in_congestion_recovery(const ngtcp2_conn_stat *cstat,
+                                  ngtcp2_tstamp sent_time) {
+  return cstat->congestion_recovery_start_ts != UINT64_MAX &&
+         sent_time <= cstat->congestion_recovery_start_ts;
+}
+
+void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                    const ngtcp2_cc_pkt *pkt,
+                                    ngtcp2_tstamp ts) {
+  ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc);
+  uint64_t m;
+  (void)ts;
+
+  if (in_congestion_recovery(cstat, pkt->sent_ts) || pkt->is_app_limited) {
+    return;
+  }
+
+  if (cstat->cwnd < cstat->ssthresh) {
+    cstat->cwnd += pkt->pktlen;
+    ngtcp2_log_info(reno->cc.log, NGTCP2_LOG_EVENT_CCA,
+                    "pkn=%" PRId64 " acked, slow start cwnd=%" PRIu64,
+                    pkt->pkt_num, cstat->cwnd);
+    return;
+  }
+
+  m = cstat->max_tx_udp_payload_size * pkt->pktlen + reno->pending_add;
+  reno->pending_add = m % cstat->cwnd;
+
+  cstat->cwnd += m / cstat->cwnd;
+}
+
+void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                        ngtcp2_tstamp sent_ts,
+                                        uint64_t bytes_lost, ngtcp2_tstamp ts) {
+  ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc);
+  uint64_t min_cwnd;
+  (void)bytes_lost;
+
+  if (in_congestion_recovery(cstat, sent_ts)) {
+    return;
+  }
+
+  cstat->congestion_recovery_start_ts = ts;
+  cstat->cwnd >>= NGTCP2_LOSS_REDUCTION_FACTOR_BITS;
+  min_cwnd = 2 * cstat->max_tx_udp_payload_size;
+  cstat->cwnd = ngtcp2_max_uint64(cstat->cwnd, min_cwnd);
+  cstat->ssthresh = cstat->cwnd;
+
+  reno->pending_add = 0;
+
+  ngtcp2_log_info(reno->cc.log, NGTCP2_LOG_EVENT_CCA,
+                  "reduce cwnd because of packet loss cwnd=%" PRIu64,
+                  cstat->cwnd);
+}
+
+void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *cc,
+                                                ngtcp2_conn_stat *cstat,
+                                                ngtcp2_tstamp ts) {
+  (void)cc;
+  (void)ts;
+
+  cstat->cwnd = 2 * cstat->max_tx_udp_payload_size;
+  cstat->congestion_recovery_start_ts = UINT64_MAX;
+}
+
+void ngtcp2_cc_reno_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                             ngtcp2_tstamp ts) {
+  ngtcp2_cc_reno *reno = ngtcp2_struct_of(cc, ngtcp2_cc_reno, cc);
+  (void)cstat;
+  (void)ts;
+
+  reno_cc_reset(reno);
+}
+
+static void cubic_vars_reset(ngtcp2_cubic_vars *v) {
+  v->cwnd_prior = 0;
+  v->w_max = 0;
+  v->k = 0;
+  v->epoch_start = UINT64_MAX;
+  v->w_est = 0;
+
+  v->app_limited_start_ts = UINT64_MAX;
+  v->app_limited_duration = 0;
+  v->pending_bytes_delivered = 0;
+  v->pending_est_bytes_delivered = 0;
+}
+
+static void cubic_cc_reset(ngtcp2_cc_cubic *cubic) {
+  cubic_vars_reset(&cubic->current);
+  cubic_vars_reset(&cubic->undo.v);
+  cubic->undo.cwnd = 0;
+  cubic->undo.ssthresh = 0;
+
+  cubic->hs.current_round_min_rtt = UINT64_MAX;
+  cubic->hs.last_round_min_rtt = UINT64_MAX;
+  cubic->hs.curr_rtt = UINT64_MAX;
+  cubic->hs.rtt_sample_count = 0;
+  cubic->hs.css_baseline_min_rtt = UINT64_MAX;
+  cubic->hs.css_round = 0;
+
+  cubic->next_round_delivered = 0;
+}
+
+void ngtcp2_cc_cubic_init(ngtcp2_cc_cubic *cubic, ngtcp2_log *log,
+                          ngtcp2_rst *rst) {
+  memset(cubic, 0, sizeof(*cubic));
+
+  cubic->cc.log = log;
+  cubic->cc.on_ack_recv = ngtcp2_cc_cubic_cc_on_ack_recv;
+  cubic->cc.congestion_event = ngtcp2_cc_cubic_cc_congestion_event;
+  cubic->cc.on_spurious_congestion = ngtcp2_cc_cubic_cc_on_spurious_congestion;
+  cubic->cc.on_persistent_congestion =
+    ngtcp2_cc_cubic_cc_on_persistent_congestion;
+  cubic->cc.reset = ngtcp2_cc_cubic_cc_reset;
+
+  cubic->rst = rst;
+
+  cubic_cc_reset(cubic);
+}
+
+uint64_t ngtcp2_cbrt(uint64_t n) {
+  size_t s;
+  uint64_t y = 0;
+  uint64_t b;
+
+  for (s = 63; s > 0; s -= 3) {
+    y <<= 1;
+    b = 3 * y * (y + 1) + 1;
+    if ((n >> s) >= b) {
+      n -= b << s;
+      y++;
+    }
+  }
+
+  y <<= 1;
+  b = 3 * y * (y + 1) + 1;
+  if (n >= b) {
+    n -= b;
+    y++;
+  }
+
+  return y;
+}
+
+/* RFC 9406 HyStart++ constants */
+#define NGTCP2_HS_MIN_RTT_THRESH (4 * NGTCP2_MILLISECONDS)
+#define NGTCP2_HS_MAX_RTT_THRESH (16 * NGTCP2_MILLISECONDS)
+#define NGTCP2_HS_MIN_RTT_DIVISOR 8
+#define NGTCP2_HS_N_RTT_SAMPLE 8
+#define NGTCP2_HS_CSS_GROWTH_DIVISOR 4
+#define NGTCP2_HS_CSS_ROUNDS 5
+
+static int64_t cubic_cc_compute_w_cubic(ngtcp2_cc_cubic *cubic,
+                                        const ngtcp2_conn_stat *cstat,
+                                        ngtcp2_tstamp ts) {
+  ngtcp2_duration t = ts - cubic->current.epoch_start;
+  int64_t tx = (int64_t)((t << 10) / NGTCP2_SECONDS);
+  int64_t time_delta = ngtcp2_min_int64(tx - cubic->current.k, 3600 << 10);
+  int64_t delta = ((((time_delta * time_delta) >> 10) * time_delta) >> 20) *
+                  (int64_t)cstat->max_tx_udp_payload_size * 4 / 10;
+
+  return (int64_t)cubic->current.w_max + delta;
+}
+
+void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                    const ngtcp2_cc_ack *ack,
+                                    ngtcp2_tstamp ts) {
+  ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
+  int64_t w_cubic, w_cubic_next;
+  uint64_t target, m;
+  ngtcp2_duration rtt_thresh;
+  int round_start;
+  int is_app_limited =
+    cubic->rst->rs.is_app_limited && !cubic->rst->is_cwnd_limited;
+
+  if (in_congestion_recovery(cstat, ack->largest_pkt_sent_ts)) {
+    return;
+  }
+
+  if (cstat->cwnd < cstat->ssthresh) {
+    /* slow-start */
+    round_start = ack->pkt_delivered >= cubic->next_round_delivered;
+    if (round_start) {
+      cubic->next_round_delivered = cubic->rst->delivered;
+
+      cubic->rst->is_cwnd_limited = 0;
+    }
+
+    if (!is_app_limited) {
+      if (cubic->hs.css_round) {
+        cstat->cwnd += ack->bytes_delivered / NGTCP2_HS_CSS_GROWTH_DIVISOR;
+      } else {
+        cstat->cwnd += ack->bytes_delivered;
+      }
+
+      ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
+                      "%" PRIu64 " bytes acked, slow start cwnd=%" PRIu64,
+                      ack->bytes_delivered, cstat->cwnd);
+    }
+
+    if (round_start) {
+      cubic->hs.last_round_min_rtt = cubic->hs.current_round_min_rtt;
+      cubic->hs.current_round_min_rtt = UINT64_MAX;
+      cubic->hs.rtt_sample_count = 0;
+
+      if (cubic->hs.css_round) {
+        ++cubic->hs.css_round;
+      }
+    }
+
+    cubic->hs.current_round_min_rtt =
+      ngtcp2_min_uint64(cubic->hs.current_round_min_rtt, ack->rtt);
+    ++cubic->hs.rtt_sample_count;
+
+    if (cubic->hs.css_round) {
+      if (cubic->hs.current_round_min_rtt < cubic->hs.css_baseline_min_rtt) {
+        cubic->hs.css_baseline_min_rtt = UINT64_MAX;
+        cubic->hs.css_round = 0;
+        return;
+      }
+
+      if (cubic->hs.css_round >= NGTCP2_HS_CSS_ROUNDS) {
+        ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
+                        "HyStart++ exit slow start");
+
+        cubic->current.epoch_start = ts;
+        cubic->current.w_max = cstat->cwnd;
+        cstat->ssthresh = cstat->cwnd;
+        cubic->current.cwnd_prior = cstat->cwnd;
+        cubic->current.w_est = cstat->cwnd;
+      }
+
+      return;
+    }
+
+    if (cubic->hs.rtt_sample_count >= NGTCP2_HS_N_RTT_SAMPLE &&
+        cubic->hs.current_round_min_rtt != UINT64_MAX &&
+        cubic->hs.last_round_min_rtt != UINT64_MAX) {
+      rtt_thresh =
+        ngtcp2_max_uint64(NGTCP2_HS_MIN_RTT_THRESH,
+                          ngtcp2_min_uint64(cubic->hs.last_round_min_rtt /
+                                              NGTCP2_HS_MIN_RTT_DIVISOR,
+                                            NGTCP2_HS_MAX_RTT_THRESH));
+
+      if (cubic->hs.current_round_min_rtt >=
+          cubic->hs.last_round_min_rtt + rtt_thresh) {
+        cubic->hs.css_baseline_min_rtt = cubic->hs.current_round_min_rtt;
+        cubic->hs.css_round = 1;
+      }
+    }
+
+    return;
+  }
+
+  /* congestion avoidance */
+  if (is_app_limited) {
+    if (cubic->current.app_limited_start_ts == UINT64_MAX) {
+      cubic->current.app_limited_start_ts = ts;
+    }
+
+    return;
+  }
+
+  if (cubic->current.app_limited_start_ts != UINT64_MAX) {
+    cubic->current.app_limited_duration +=
+      ts - cubic->current.app_limited_start_ts;
+    cubic->current.app_limited_start_ts = UINT64_MAX;
+  }
+
+  w_cubic = cubic_cc_compute_w_cubic(cubic, cstat,
+                                     ts - cubic->current.app_limited_duration);
+  w_cubic_next = cubic_cc_compute_w_cubic(
+    cubic, cstat,
+    ts - cubic->current.app_limited_duration + cstat->smoothed_rtt);
+
+  if (w_cubic_next < (int64_t)cstat->cwnd) {
+    target = cstat->cwnd;
+  } else if (2 * w_cubic_next > 3 * (int64_t)cstat->cwnd) {
+    target = cstat->cwnd * 3 / 2;
+  } else {
+    assert(w_cubic_next >= 0);
+    target = (uint64_t)w_cubic_next;
+  }
+
+  m = ack->bytes_delivered * cstat->max_tx_udp_payload_size +
+      cubic->current.pending_est_bytes_delivered;
+  cubic->current.pending_est_bytes_delivered = m % cstat->cwnd;
+
+  if (cubic->current.w_est < cubic->current.cwnd_prior) {
+    cubic->current.w_est += m * 9 / 17 / cstat->cwnd;
+  } else {
+    cubic->current.w_est += m / cstat->cwnd;
+  }
+
+  if ((int64_t)cubic->current.w_est > w_cubic) {
+    cstat->cwnd = cubic->current.w_est;
+  } else {
+    m = (target - cstat->cwnd) * cstat->max_tx_udp_payload_size +
+        cubic->current.pending_bytes_delivered;
+    cstat->cwnd += m / cstat->cwnd;
+    cubic->current.pending_bytes_delivered = m % cstat->cwnd;
+  }
+
+  ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
+                  "%" PRIu64 " bytes acked, cubic-ca cwnd=%" PRIu64
+                  " k=%" PRIi64 " target=%" PRIu64 " w_est=%" PRIu64,
+                  ack->bytes_delivered, cstat->cwnd, cubic->current.k, target,
+                  cubic->current.w_est);
+}
+
+void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                         ngtcp2_tstamp sent_ts,
+                                         uint64_t bytes_lost,
+                                         ngtcp2_tstamp ts) {
+  ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
+  uint64_t flight_size;
+  uint64_t cwnd_delta;
+
+  if (in_congestion_recovery(cstat, sent_ts)) {
+    return;
+  }
+
+  if (cubic->undo.cwnd < cstat->cwnd) {
+    cubic->undo.v = cubic->current;
+    cubic->undo.cwnd = cstat->cwnd;
+    cubic->undo.ssthresh = cstat->ssthresh;
+  }
+
+  cstat->congestion_recovery_start_ts = ts;
+
+  cubic->current.epoch_start = ts;
+  cubic->current.app_limited_start_ts = UINT64_MAX;
+  cubic->current.app_limited_duration = 0;
+  cubic->current.pending_bytes_delivered = 0;
+  cubic->current.pending_est_bytes_delivered = 0;
+
+  if (cstat->cwnd < cubic->current.w_max) {
+    cubic->current.w_max = cstat->cwnd * 17 / 20;
+  } else {
+    cubic->current.w_max = cstat->cwnd;
+  }
+
+  cstat->ssthresh = cstat->cwnd * 7 / 10;
+
+  if (cubic->rst->rs.delivered * 2 < cstat->cwnd) {
+    flight_size = cstat->bytes_in_flight + bytes_lost;
+    cstat->ssthresh = ngtcp2_min_uint64(
+      cstat->ssthresh,
+      ngtcp2_max_uint64(cubic->rst->rs.delivered, flight_size));
+  }
+
+  cstat->ssthresh =
+    ngtcp2_max_uint64(cstat->ssthresh, 2 * cstat->max_tx_udp_payload_size);
+
+  cubic->current.cwnd_prior = cstat->cwnd;
+  cstat->cwnd = cstat->ssthresh;
+
+  cubic->current.w_est = cstat->cwnd;
+
+  if (cstat->cwnd < cubic->current.w_max) {
+    cwnd_delta = cubic->current.w_max - cstat->cwnd;
+  } else {
+    cwnd_delta = cstat->cwnd - cubic->current.w_max;
+  }
+
+  cubic->current.k = (int64_t)ngtcp2_cbrt((cwnd_delta << 30) * 10 / 4 /
+                                          cstat->max_tx_udp_payload_size);
+  if (cstat->cwnd >= cubic->current.w_max) {
+    cubic->current.k = -cubic->current.k;
+  }
+
+  ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
+                  "reduce cwnd because of packet loss cwnd=%" PRIu64,
+                  cstat->cwnd);
+}
+
+void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *cc,
+                                               ngtcp2_conn_stat *cstat,
+                                               ngtcp2_tstamp ts) {
+  ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
+  (void)ts;
+
+  cstat->congestion_recovery_start_ts = UINT64_MAX;
+
+  if (cstat->cwnd < cubic->undo.cwnd) {
+    cubic->current = cubic->undo.v;
+    cstat->cwnd = cubic->undo.cwnd;
+    cstat->ssthresh = cubic->undo.ssthresh;
+
+    ngtcp2_log_info(cubic->cc.log, NGTCP2_LOG_EVENT_CCA,
+                    "spurious congestion is detected and congestion state is "
+                    "restored cwnd=%" PRIu64,
+                    cstat->cwnd);
+  }
+
+  cubic_vars_reset(&cubic->undo.v);
+  cubic->undo.cwnd = 0;
+  cubic->undo.ssthresh = 0;
+}
+
+void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *cc,
+                                                 ngtcp2_conn_stat *cstat,
+                                                 ngtcp2_tstamp ts) {
+  ngtcp2_cc_cubic *cubic = ngtcp2_struct_of(cc, ngtcp2_cc_cubic, cc);
+  (void)ts;
+
+  cubic_cc_reset(cubic);
+
+  cstat->cwnd = 2 * cstat->max_tx_udp_payload_size;
+  cstat->congestion_recovery_start_ts = UINT64_MAX;
+}
+
+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);
+  (void)cstat;
+  (void)ts;
+
+  cubic_cc_reset(cubic);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_cc.h b/third_party/ngtcp2/lib/ngtcp2_cc.h
new file mode 100644 (file)
index 0000000..296a1a4
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CC_H
+#define NGTCP2_CC_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_pktns_id.h"
+
+#define NGTCP2_LOSS_REDUCTION_FACTOR_BITS 1
+#define NGTCP2_PERSISTENT_CONGESTION_THRESHOLD 3
+
+typedef struct ngtcp2_log ngtcp2_log;
+typedef struct ngtcp2_conn_stat ngtcp2_conn_stat;
+typedef struct ngtcp2_rst ngtcp2_rst;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_cc_pkt` is a convenient structure to include
+ * acked/lost/sent packet.
+ */
+typedef struct ngtcp2_cc_pkt {
+  /**
+   * :member:`pkt_num` is the packet number
+   */
+  int64_t pkt_num;
+  /**
+   * :member:`pktlen` is the length of packet.
+   */
+  size_t pktlen;
+  /**
+   * :member:`pktns_id` is the ID of packet number space which this
+   * packet belongs to.
+   */
+  ngtcp2_pktns_id pktns_id;
+  /**
+   * :member:`sent_ts` is the timestamp when packet is sent.
+   */
+  ngtcp2_tstamp sent_ts;
+  /**
+   * :member:`lost` is the number of bytes lost when this packet was
+   * sent.
+   */
+  uint64_t lost;
+  /**
+   * :member:`tx_in_flight` is the bytes in flight when this packet
+   * was sent.
+   */
+  uint64_t tx_in_flight;
+  /**
+   * :member:`is_app_limited` is nonzero if the connection is
+   * app-limited when this packet was sent.
+   */
+  int is_app_limited;
+} ngtcp2_cc_pkt;
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_cc_ack` is a convenient structure which stores
+ * acknowledged and lost bytes.
+ */
+typedef struct ngtcp2_cc_ack {
+  /**
+   * :member:`prior_bytes_in_flight` is the in-flight bytes before
+   * processing this ACK.
+   */
+  uint64_t prior_bytes_in_flight;
+  /**
+   * :member:`bytes_delivered` is the number of bytes acknowledged.
+   */
+  uint64_t bytes_delivered;
+  /**
+   * :member:`bytes_lost` is the number of bytes declared lost.
+   */
+  uint64_t bytes_lost;
+  /**
+   * :member:`pkt_delivered` is the cumulative acknowledged bytes when
+   * the last packet acknowledged by this ACK was sent.
+   */
+  uint64_t pkt_delivered;
+  /**
+   * :member:`largest_pkt_sent_ts` is the time when the largest
+   * acknowledged packet was sent.  It is UINT64_MAX if it is unknown.
+   */
+  ngtcp2_tstamp largest_pkt_sent_ts;
+  /**
+   * :member:`rtt` is the RTT sample.  It is UINT64_MAX if no RTT
+   * sample is available.
+   */
+  ngtcp2_duration rtt;
+} ngtcp2_cc_ack;
+
+typedef struct ngtcp2_cc ngtcp2_cc;
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_on_pkt_acked` is a callback function which is
+ * called with an acknowledged packet.
+ */
+typedef void (*ngtcp2_cc_on_pkt_acked)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                       const ngtcp2_cc_pkt *pkt,
+                                       ngtcp2_tstamp ts);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_on_pkt_lost` is a callback function which is
+ * called with a lost packet.
+ */
+typedef void (*ngtcp2_cc_on_pkt_lost)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                      const ngtcp2_cc_pkt *pkt,
+                                      ngtcp2_tstamp ts);
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_congestion_event` is a callback function which is
+ * called when congestion event happens (e.g., when packet is lost).
+ * |bytes_lost| is the number of bytes lost in this congestion event.
+ */
+typedef void (*ngtcp2_cc_congestion_event)(ngtcp2_cc *cc,
+                                           ngtcp2_conn_stat *cstat,
+                                           ngtcp2_tstamp sent_ts,
+                                           uint64_t bytes_lost,
+                                           ngtcp2_tstamp ts);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_on_spurious_congestion` is a callback function
+ * which is called when a spurious congestion is detected.
+ */
+typedef void (*ngtcp2_cc_on_spurious_congestion)(ngtcp2_cc *cc,
+                                                 ngtcp2_conn_stat *cstat,
+                                                 ngtcp2_tstamp ts);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_on_persistent_congestion` is a callback function
+ * which is called when persistent congestion is established.
+ */
+typedef void (*ngtcp2_cc_on_persistent_congestion)(ngtcp2_cc *cc,
+                                                   ngtcp2_conn_stat *cstat,
+                                                   ngtcp2_tstamp ts);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_on_ack_recv` is a callback function which is
+ * called when an acknowledgement is received.
+ */
+typedef void (*ngtcp2_cc_on_ack_recv)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                      const ngtcp2_cc_ack *ack,
+                                      ngtcp2_tstamp ts);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_on_pkt_sent` is a callback function which is
+ * called when an ack-eliciting packet is sent.
+ */
+typedef void (*ngtcp2_cc_on_pkt_sent)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                      const ngtcp2_cc_pkt *pkt);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_new_rtt_sample` is a callback function which is
+ * called when new RTT sample is obtained.
+ */
+typedef void (*ngtcp2_cc_new_rtt_sample)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                         ngtcp2_tstamp ts);
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_reset` is a callback function which is called when
+ * congestion state must be reset.
+ */
+typedef void (*ngtcp2_cc_reset)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                ngtcp2_tstamp ts);
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_cc_event_type` defines congestion control events.
+ */
+typedef enum ngtcp2_cc_event_type {
+  /**
+   * :enum:`NGTCP2_CC_EVENT_TX_START` occurs when ack-eliciting packet
+   * is sent and no other ack-eliciting packet is present.
+   */
+  NGTCP2_CC_EVENT_TYPE_TX_START
+} ngtcp2_cc_event_type;
+
+/**
+ * @functypedef
+ *
+ * :type:`ngtcp2_cc_event` is a callback function which is called when
+ * a specific event happens.
+ */
+typedef void (*ngtcp2_cc_event)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                ngtcp2_cc_event_type event, ngtcp2_tstamp ts);
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_cc` is congestion control algorithm interface shared
+ * by implementations.  All callback functions are optional.
+ */
+typedef struct ngtcp2_cc {
+  /**
+   * :member:`log` is ngtcp2 library internal logger.
+   */
+  ngtcp2_log *log;
+  /**
+   * :member:`on_pkt_acked` is a callback function which is called
+   * when a packet is acknowledged.
+   */
+  ngtcp2_cc_on_pkt_acked on_pkt_acked;
+  /**
+   * :member:`on_pkt_lost` is a callback function which is called when
+   * a packet is lost.
+   */
+  ngtcp2_cc_on_pkt_lost on_pkt_lost;
+  /**
+   * :member:`congestion_event` is a callback function which is called
+   * when congestion event happens (.e.g, packet is lost).
+   */
+  ngtcp2_cc_congestion_event congestion_event;
+  /**
+   * :member:`on_spurious_congestion` is a callback function which is
+   * called when a spurious congestion is detected.
+   */
+  ngtcp2_cc_on_spurious_congestion on_spurious_congestion;
+  /**
+   * :member:`on_persistent_congestion` is a callback function which
+   * is called when persistent congestion is established.
+   */
+  ngtcp2_cc_on_persistent_congestion on_persistent_congestion;
+  /**
+   * :member:`on_ack_recv` is a callback function which is called when
+   * an acknowledgement is received.
+   */
+  ngtcp2_cc_on_ack_recv on_ack_recv;
+  /**
+   * :member:`on_pkt_sent` is a callback function which is called when
+   * ack-eliciting packet is sent.
+   */
+  ngtcp2_cc_on_pkt_sent on_pkt_sent;
+  /**
+   * :member:`new_rtt_sample` is a callback function which is called
+   * when new RTT sample is obtained.
+   */
+  ngtcp2_cc_new_rtt_sample new_rtt_sample;
+  /**
+   * :member:`reset` is a callback function which is called when
+   * congestion control state must be reset.
+   */
+  ngtcp2_cc_reset reset;
+  /**
+   * :member:`event` is a callback function which is called when a
+   * specific event happens.
+   */
+  ngtcp2_cc_event event;
+} ngtcp2_cc;
+
+/*
+ * ngtcp2_cc_compute_initcwnd computes initial cwnd.
+ */
+uint64_t ngtcp2_cc_compute_initcwnd(size_t max_packet_size);
+
+ngtcp2_cc_pkt *ngtcp2_cc_pkt_init(ngtcp2_cc_pkt *pkt, int64_t pkt_num,
+                                  size_t pktlen, ngtcp2_pktns_id pktns_id,
+                                  ngtcp2_tstamp sent_ts, uint64_t lost,
+                                  uint64_t tx_in_flight, int is_app_limited);
+
+/* ngtcp2_cc_reno is the RENO congestion controller. */
+typedef struct ngtcp2_cc_reno {
+  ngtcp2_cc cc;
+  uint64_t pending_add;
+} ngtcp2_cc_reno;
+
+void ngtcp2_cc_reno_init(ngtcp2_cc_reno *reno, ngtcp2_log *log);
+
+void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                    const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts);
+
+void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                        ngtcp2_tstamp sent_ts,
+                                        uint64_t bytes_lost, ngtcp2_tstamp ts);
+
+void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *cc,
+                                                ngtcp2_conn_stat *cstat,
+                                                ngtcp2_tstamp ts);
+
+void ngtcp2_cc_reno_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                             ngtcp2_tstamp ts);
+
+typedef struct ngtcp2_cubic_vars {
+  uint64_t cwnd_prior;
+  uint64_t w_max;
+  int64_t k;
+  ngtcp2_tstamp epoch_start;
+  uint64_t w_est;
+
+  /* app_limited_start_ts is the timestamp where app limited period
+     started. */
+  ngtcp2_tstamp app_limited_start_ts;
+  /* app_limited_duration is the cumulative duration where a
+     connection is under app limited when ACK is received. */
+  ngtcp2_duration app_limited_duration;
+  uint64_t pending_bytes_delivered;
+  uint64_t pending_est_bytes_delivered;
+} ngtcp2_cubic_vars;
+
+/* ngtcp2_cc_cubic is CUBIC congestion controller. */
+typedef struct ngtcp2_cc_cubic {
+  ngtcp2_cc cc;
+  ngtcp2_rst *rst;
+  /* current is a set of variables that are currently in effect. */
+  ngtcp2_cubic_vars current;
+  /* undo stores the congestion state when a congestion event occurs
+     in order to restore the state when it turns out that the event is
+     spurious. */
+  struct {
+    ngtcp2_cubic_vars v;
+    uint64_t cwnd;
+    uint64_t ssthresh;
+  } undo;
+  /* HyStart++ variables */
+  struct {
+    ngtcp2_duration current_round_min_rtt;
+    ngtcp2_duration last_round_min_rtt;
+    ngtcp2_duration curr_rtt;
+    size_t rtt_sample_count;
+    ngtcp2_duration css_baseline_min_rtt;
+    size_t css_round;
+  } hs;
+  uint64_t next_round_delivered;
+} ngtcp2_cc_cubic;
+
+void ngtcp2_cc_cubic_init(ngtcp2_cc_cubic *cc, ngtcp2_log *log,
+                          ngtcp2_rst *rst);
+
+void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                    const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts);
+
+void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                                         ngtcp2_tstamp sent_ts,
+                                         uint64_t bytes_lost, ngtcp2_tstamp ts);
+
+void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *ccx,
+                                               ngtcp2_conn_stat *cstat,
+                                               ngtcp2_tstamp ts);
+
+void ngtcp2_cc_cubic_cc_on_persistent_congestion(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);
+
+uint64_t ngtcp2_cbrt(uint64_t n);
+
+#endif /* !defined(NGTCP2_CC_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_cid.c b/third_party/ngtcp2/lib/ngtcp2_cid.c
new file mode 100644 (file)
index 0000000..acbee78
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_cid.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "ngtcp2_path.h"
+#include "ngtcp2_str.h"
+
+void ngtcp2_cid_zero(ngtcp2_cid *cid) { memset(cid, 0, sizeof(*cid)); }
+
+void ngtcp2_cid_init(ngtcp2_cid *cid, const uint8_t *data, size_t datalen) {
+  assert(datalen <= NGTCP2_MAX_CIDLEN);
+
+  cid->datalen = datalen;
+
+  if (datalen) {
+    ngtcp2_cpymem(cid->data, data, datalen);
+  }
+}
+
+int ngtcp2_cid_eq(const ngtcp2_cid *cid, const ngtcp2_cid *other) {
+  return cid->datalen == other->datalen &&
+         0 == memcmp(cid->data, other->data, cid->datalen);
+}
+
+int ngtcp2_cid_less(const ngtcp2_cid *lhs, const ngtcp2_cid *rhs) {
+  int s = lhs->datalen < rhs->datalen;
+  size_t n = s ? lhs->datalen : rhs->datalen;
+  int c = memcmp(lhs->data, rhs->data, n);
+
+  return c < 0 || (c == 0 && s);
+}
+
+int ngtcp2_cid_empty(const ngtcp2_cid *cid) { return cid->datalen == 0; }
+
+void ngtcp2_scid_init(ngtcp2_scid *scid, uint64_t seq, const ngtcp2_cid *cid) {
+  scid->pe.index = NGTCP2_PQ_BAD_INDEX;
+  scid->seq = seq;
+  scid->cid = *cid;
+  scid->retired_ts = UINT64_MAX;
+  scid->flags = NGTCP2_SCID_FLAG_NONE;
+}
+
+void ngtcp2_scid_copy(ngtcp2_scid *dest, const ngtcp2_scid *src) {
+  ngtcp2_scid_init(dest, src->seq, &src->cid);
+  dest->retired_ts = src->retired_ts;
+  dest->flags = src->flags;
+}
+
+void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid,
+                      const uint8_t *token) {
+  dcid->seq = seq;
+  dcid->cid = *cid;
+
+  if (token) {
+    memcpy(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN);
+    dcid->flags = NGTCP2_DCID_FLAG_TOKEN_PRESENT;
+  } else {
+    dcid->flags = NGTCP2_DCID_FLAG_NONE;
+  }
+
+  ngtcp2_path_storage_zero(&dcid->ps);
+  dcid->retired_ts = UINT64_MAX;
+  dcid->bound_ts = UINT64_MAX;
+  dcid->bytes_sent = 0;
+  dcid->bytes_recv = 0;
+  dcid->max_udp_payload_size = NGTCP2_MAX_UDP_PAYLOAD_SIZE;
+}
+
+void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, const uint8_t *token) {
+  assert(token);
+
+  dcid->flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT;
+  memcpy(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN);
+}
+
+void ngtcp2_dcid_set_path(ngtcp2_dcid *dcid, const ngtcp2_path *path) {
+  ngtcp2_path_copy(&dcid->ps.path, path);
+}
+
+void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src) {
+  ngtcp2_dcid_init(dest, src->seq, &src->cid,
+                   (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? src->token
+                                                                 : NULL);
+  ngtcp2_path_copy(&dest->ps.path, &src->ps.path);
+  dest->retired_ts = src->retired_ts;
+  dest->bound_ts = src->bound_ts;
+  dest->flags = src->flags;
+  dest->bytes_sent = src->bytes_sent;
+  dest->bytes_recv = src->bytes_recv;
+  dest->max_udp_payload_size = src->max_udp_payload_size;
+}
+
+void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src) {
+  dest->seq = src->seq;
+  dest->cid = src->cid;
+
+  if (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) {
+    dest->flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT;
+    memcpy(dest->token, src->token, NGTCP2_STATELESS_RESET_TOKENLEN);
+  } else if (dest->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) {
+    dest->flags &= (uint8_t)~NGTCP2_DCID_FLAG_TOKEN_PRESENT;
+  }
+}
+
+int ngtcp2_dcid_verify_uniqueness(const ngtcp2_dcid *dcid, uint64_t seq,
+                                  const ngtcp2_cid *cid, const uint8_t *token) {
+  if (dcid->seq == seq) {
+    return ngtcp2_cid_eq(&dcid->cid, cid) &&
+               (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) &&
+               memcmp(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN) == 0
+             ? 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 uint8_t *token) {
+  return ngtcp2_path_eq(&dcid->ps.path, path) &&
+             (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) &&
+             ngtcp2_cmemeq(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN)
+           ? 0
+           : NGTCP2_ERR_INVALID_ARGUMENT;
+}
+
+void ngtcp2_dcid_apply_validated_path(ngtcp2_dcid *dcid,
+                                      const ngtcp2_path_history_entry *ent) {
+  dcid->flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED;
+  dcid->max_udp_payload_size = ent->max_udp_payload_size;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_cid.h b/third_party/ngtcp2/lib/ngtcp2_cid.h
new file mode 100644 (file)
index 0000000..9321cfb
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CID_H
+#define NGTCP2_CID_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_pq.h"
+#include "ngtcp2_path.h"
+
+/* NGTCP2_SCID_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_SCID_FLAG_NONE 0x00u
+/* NGTCP2_SCID_FLAG_USED indicates that a local endpoint observed that
+   a remote endpoint uses this particular Connection ID. */
+#define NGTCP2_SCID_FLAG_USED 0x01u
+/* NGTCP2_SCID_FLAG_RETIRED indicates that this particular Connection
+   ID is retired. */
+#define NGTCP2_SCID_FLAG_RETIRED 0x02u
+
+typedef struct ngtcp2_scid {
+  ngtcp2_pq_entry pe;
+  /* seq is the sequence number associated to the Connection ID. */
+  uint64_t seq;
+  /* cid is a connection ID */
+  ngtcp2_cid cid;
+  /* retired_ts is the timestamp when a remote endpoint tells that
+     this Connection ID is retired. */
+  ngtcp2_tstamp retired_ts;
+  /* flags is the bitwise OR of zero or more of NGTCP2_SCID_FLAG_*. */
+  uint8_t flags;
+} ngtcp2_scid;
+
+/* NGTCP2_DCID_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_DCID_FLAG_NONE 0x00u
+/* NGTCP2_DCID_FLAG_PATH_VALIDATED indicates that an associated path
+   has been validated. */
+#define NGTCP2_DCID_FLAG_PATH_VALIDATED 0x01u
+/* NGTCP2_DCID_FLAG_TOKEN_PRESENT indicates that a stateless reset
+   token is set in token field. */
+#define NGTCP2_DCID_FLAG_TOKEN_PRESENT 0x02u
+
+typedef struct ngtcp2_dcid {
+  /* seq is the sequence number associated to the Connection ID. */
+  uint64_t seq;
+  /* cid is a Connection ID */
+  ngtcp2_cid cid;
+  /* path is a path which cid is bound to.  The addresses are zero
+     length if cid has not been bound to a particular path yet. */
+  ngtcp2_path_storage ps;
+  /* retired_ts is the timestamp when this Connection ID is
+     retired. */
+  ngtcp2_tstamp retired_ts;
+  /* bound_ts is the timestamp when this Connection ID is bound to a
+     particular path.  It is only assigned when a Connection ID is
+     used just for sending PATH_RESPONSE, and is not zero-length. */
+  ngtcp2_tstamp bound_ts;
+  /* bytes_sent is the number of bytes sent to an associated path. */
+  uint64_t bytes_sent;
+  /* bytes_recv is the number of bytes received from an associated
+     path. */
+  uint64_t bytes_recv;
+  /* 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;
+  /* 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
+     Connection ID.  The stateless reset token is tied to the
+     connection, not to the particular Connection ID. */
+  uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN];
+} ngtcp2_dcid;
+
+/* ngtcp2_cid_zero makes |cid| zero-length. */
+void ngtcp2_cid_zero(ngtcp2_cid *cid);
+
+/*
+ * ngtcp2_cid_less returns nonzero if |lhs| is lexicographical smaller
+ * than |rhs|.
+ */
+int ngtcp2_cid_less(const ngtcp2_cid *lhs, const ngtcp2_cid *rhs);
+
+/*
+ * ngtcp2_cid_empty returns nonzero if |cid| includes empty Connection
+ * ID.
+ */
+int ngtcp2_cid_empty(const ngtcp2_cid *cid);
+
+/*
+ * ngtcp2_scid_init initializes |scid| with the given parameters.
+ */
+void ngtcp2_scid_init(ngtcp2_scid *scid, uint64_t seq, const ngtcp2_cid *cid);
+
+/*
+ * ngtcp2_scid_copy copies |src| into |dest|.
+ */
+void ngtcp2_scid_copy(ngtcp2_scid *dest, const ngtcp2_scid *src);
+
+/*
+ * ngtcp2_dcid_init initializes |dcid| with the given parameters.  If
+ * |token| is NULL, the function fills dcid->token with 0.  |token|
+ * must be NGTCP2_STATELESS_RESET_TOKENLEN bytes long.
+ */
+void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid,
+                      const uint8_t *token);
+
+/*
+ * ngtcp2_dcid_set_token sets |token| to |dcid|.  |token| must not be
+ * NULL, and must be NGTCP2_STATELESS_RESET_TOKENLEN bytes long.
+ */
+void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, const uint8_t *token);
+
+/*
+ * ngtcp2_dcid_set_path sets |path| to |dcid|.  It sets
+ * max_udp_payload_size to the minimum UDP datagram payload size
+ * supported by the IP protocol version.
+ */
+void ngtcp2_dcid_set_path(ngtcp2_dcid *dcid, const ngtcp2_path *path);
+
+/*
+ * ngtcp2_dcid_copy copies |src| into |dest|.
+ */
+void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src);
+
+/*
+ * ngtcp2_dcid_copy_cid_token behaves like ngtcp2_dcid_copy, but it
+ * only copies cid, seq, and token.  dest->flags should be initialized
+ * before this call because NGTCP2_DCID_FLAG_TOKEN_PRESENT is set or
+ * unset.
+ */
+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 uint8_t *token);
+
+/*
+ * ngtcp2_dcid_verify_stateless_reset_token verifies stateless reset
+ * token |token| received on |path| against the one included in
+ * |dcid|.  Tokens are compared in constant time.  This function
+ * returns 0 if the verification succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Tokens do not match; or |dcid| does not contain a token.
+ */
+int ngtcp2_dcid_verify_stateless_reset_token(const ngtcp2_dcid *dcid,
+                                             const ngtcp2_path *path,
+                                             const uint8_t *token);
+
+/* TODO It might be performance win if we store congestion state in
+   this entry, and restore it when migrate back to this path. */
+typedef struct ngtcp2_path_history_entry {
+  /* ps contains path. */
+  ngtcp2_path_storage ps;
+  /* 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;
+  /* ts is the timestamp when this entry is added to the path history.
+     It happens when a local endpoint migrates to the another path. */
+  ngtcp2_tstamp ts;
+} ngtcp2_path_history_entry;
+
+/*
+ * ngtcp2_dcid_apply_validated_path applies the defaults from |ent|
+ * which contains the validated path and its stored configurations.
+ */
+void ngtcp2_dcid_apply_validated_path(ngtcp2_dcid *dcid,
+                                      const ngtcp2_path_history_entry *ent);
+
+#endif /* !defined(NGTCP2_CID_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_conn.c b/third_party/ngtcp2/lib/ngtcp2_conn.c
new file mode 100644 (file)
index 0000000..6abebee
--- /dev/null
@@ -0,0 +1,13526 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_conn.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_macro.h"
+#include "ngtcp2_log.h"
+#include "ngtcp2_cid.h"
+#include "ngtcp2_conv.h"
+#include "ngtcp2_vec.h"
+#include "ngtcp2_addr.h"
+#include "ngtcp2_path.h"
+#include "ngtcp2_rcvry.h"
+#include "ngtcp2_unreachable.h"
+#include "ngtcp2_net.h"
+#include "ngtcp2_transport_params.h"
+#include "ngtcp2_settings.h"
+#include "ngtcp2_tstamp.h"
+#include "ngtcp2_frame_chain.h"
+
+/* NGTCP2_FLOW_WINDOW_RTT_FACTOR is the factor of RTT when flow
+   control window auto-tuning is triggered. */
+#define NGTCP2_FLOW_WINDOW_RTT_FACTOR 2
+/* NGTCP2_FLOW_WINDOW_SCALING_FACTOR is the growth factor of flow
+   control window. */
+#define NGTCP2_FLOW_WINDOW_SCALING_FACTOR 2
+/* NGTCP2_MIN_COALESCED_PAYLOADLEN is the minimum length of QUIC
+   packet payload that should be coalesced to a long packet. */
+#define NGTCP2_MIN_COALESCED_PAYLOADLEN 128
+/* NGTCP2_MAX_ACK_PER_PKT is the maximum number of ACK frame per an
+   incoming QUIC packet to process.  ACK frames that exceed this limit
+   are not processed. */
+#define NGTCP2_MAX_ACK_PER_PKT 1
+
+ngtcp2_objalloc_def(strm, ngtcp2_strm, oplent)
+
+/*
+ * conn_local_stream returns nonzero if |stream_id| indicates that it
+ * is the stream initiated by local endpoint.
+ */
+static int conn_local_stream(ngtcp2_conn *conn, int64_t stream_id) {
+  return (uint8_t)(stream_id & 1) == conn->server;
+}
+
+/*
+ * bidi_stream returns nonzero if |stream_id| is a bidirectional
+ * stream ID.
+ */
+static int bidi_stream(int64_t stream_id) { return (stream_id & 0x2) == 0; }
+
+static void conn_update_timestamp(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  assert(conn->log.last_ts <= ts);
+  assert(conn->qlog.last_ts <= ts);
+
+  conn->log.last_ts = ts;
+  conn->qlog.last_ts = ts;
+}
+
+/*
+ * conn_is_tls_handshake_completed returns nonzero if TLS handshake
+ * has completed and 1 RTT keys are available.
+ */
+static int conn_is_tls_handshake_completed(ngtcp2_conn *conn) {
+  return (conn->flags & NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED) &&
+         conn->pktns.crypto.rx.ckm && conn->pktns.crypto.tx.ckm;
+}
+
+static int conn_call_recv_client_initial(ngtcp2_conn *conn,
+                                         const ngtcp2_cid *dcid) {
+  int rv;
+
+  assert(conn->callbacks.recv_client_initial);
+
+  rv = conn->callbacks.recv_client_initial(conn, dcid, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_handshake_completed(ngtcp2_conn *conn) {
+  int rv;
+
+  if (!conn->callbacks.handshake_completed) {
+    return 0;
+  }
+
+  rv = conn->callbacks.handshake_completed(conn, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_recv_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                                      uint32_t flags, uint64_t offset,
+                                      const uint8_t *data, size_t datalen) {
+  int rv;
+
+  if (!conn->callbacks.recv_stream_data) {
+    return 0;
+  }
+
+  rv = conn->callbacks.recv_stream_data(conn, flags, strm->stream_id, offset,
+                                        data, datalen, conn->user_data,
+                                        strm->stream_user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_recv_crypto_data(ngtcp2_conn *conn,
+                                      ngtcp2_encryption_level encryption_level,
+                                      uint64_t offset, const uint8_t *data,
+                                      size_t datalen) {
+  int rv;
+
+  assert(conn->callbacks.recv_crypto_data);
+
+  rv = conn->callbacks.recv_crypto_data(conn, encryption_level, offset, data,
+                                        datalen, conn->user_data);
+  switch (rv) {
+  case 0:
+  case NGTCP2_ERR_CRYPTO:
+  case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM:
+  case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM:
+  case NGTCP2_ERR_TRANSPORT_PARAM:
+  case NGTCP2_ERR_PROTO:
+  case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE:
+  case NGTCP2_ERR_NOMEM:
+  case NGTCP2_ERR_CALLBACK_FAILURE:
+    return rv;
+  default:
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+}
+
+static int conn_call_stream_open(ngtcp2_conn *conn, ngtcp2_strm *strm) {
+  int rv;
+
+  if (!conn->callbacks.stream_open) {
+    return 0;
+  }
+
+  rv = conn->callbacks.stream_open(conn, strm->stream_id, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_stream_close(ngtcp2_conn *conn, ngtcp2_strm *strm) {
+  int rv;
+  uint32_t flags = NGTCP2_STREAM_CLOSE_FLAG_NONE;
+
+  if (!conn->callbacks.stream_close) {
+    return 0;
+  }
+
+  if (strm->flags & NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET) {
+    flags |= NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET;
+  }
+
+  rv = conn->callbacks.stream_close(conn, flags, strm->stream_id,
+                                    strm->app_error_code, conn->user_data,
+                                    strm->stream_user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_stream_reset(ngtcp2_conn *conn, int64_t stream_id,
+                                  uint64_t final_size, uint64_t app_error_code,
+                                  void *stream_user_data) {
+  int rv;
+
+  if (!conn->callbacks.stream_reset) {
+    return 0;
+  }
+
+  rv = conn->callbacks.stream_reset(conn, stream_id, final_size, app_error_code,
+                                    conn->user_data, stream_user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_extend_max_local_streams_bidi(ngtcp2_conn *conn,
+                                                   uint64_t max_streams) {
+  int rv;
+
+  if (!conn->callbacks.extend_max_local_streams_bidi) {
+    return 0;
+  }
+
+  rv = conn->callbacks.extend_max_local_streams_bidi(conn, max_streams,
+                                                     conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_extend_max_local_streams_uni(ngtcp2_conn *conn,
+                                                  uint64_t max_streams) {
+  int rv;
+
+  if (!conn->callbacks.extend_max_local_streams_uni) {
+    return 0;
+  }
+
+  rv = conn->callbacks.extend_max_local_streams_uni(conn, max_streams,
+                                                    conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid,
+                                           uint8_t *token, size_t cidlen) {
+  int rv;
+
+  assert(conn->callbacks.get_new_connection_id);
+
+  rv = conn->callbacks.get_new_connection_id(conn, cid, token, cidlen,
+                                             conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_remove_connection_id(ngtcp2_conn *conn,
+                                          const ngtcp2_cid *cid) {
+  int rv;
+
+  if (!conn->callbacks.remove_connection_id) {
+    return 0;
+  }
+
+  rv = conn->callbacks.remove_connection_id(conn, cid, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_path_validation(ngtcp2_conn *conn, const ngtcp2_pv *pv,
+                                     ngtcp2_path_validation_result res) {
+  int rv;
+  uint32_t flags = NGTCP2_PATH_VALIDATION_FLAG_NONE;
+  const ngtcp2_path *old_path = NULL;
+
+  if (!conn->callbacks.path_validation) {
+    return 0;
+  }
+
+  if (pv->flags & NGTCP2_PV_FLAG_PREFERRED_ADDR) {
+    flags |= NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR;
+  }
+
+  if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) {
+    old_path = &pv->fallback_dcid.ps.path;
+  }
+
+  if (conn->server && old_path &&
+      (ngtcp2_addr_cmp(&pv->dcid.ps.path.remote, &old_path->remote) &
+       (NGTCP2_ADDR_CMP_FLAG_ADDR | NGTCP2_ADDR_CMP_FLAG_FAMILY))) {
+    flags |= NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN;
+  }
+
+  rv = conn->callbacks.path_validation(conn, flags, &pv->dcid.ps.path, old_path,
+                                       res, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_select_preferred_addr(ngtcp2_conn *conn,
+                                           ngtcp2_path *dest) {
+  int rv;
+
+  if (!conn->callbacks.select_preferred_addr) {
+    return 0;
+  }
+
+  assert(conn->remote.transport_params);
+  assert(conn->remote.transport_params->preferred_addr_present);
+
+  rv = conn->callbacks.select_preferred_addr(
+    conn, dest, &conn->remote.transport_params->preferred_addr,
+    conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_extend_max_remote_streams_bidi(ngtcp2_conn *conn,
+                                                    uint64_t max_streams) {
+  int rv;
+
+  if (!conn->callbacks.extend_max_remote_streams_bidi) {
+    return 0;
+  }
+
+  rv = conn->callbacks.extend_max_remote_streams_bidi(conn, max_streams,
+                                                      conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_extend_max_remote_streams_uni(ngtcp2_conn *conn,
+                                                   uint64_t max_streams) {
+  int rv;
+
+  if (!conn->callbacks.extend_max_remote_streams_uni) {
+    return 0;
+  }
+
+  rv = conn->callbacks.extend_max_remote_streams_uni(conn, max_streams,
+                                                     conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_extend_max_stream_data(ngtcp2_conn *conn,
+                                            ngtcp2_strm *strm,
+                                            int64_t stream_id,
+                                            uint64_t datalen) {
+  int rv;
+
+  if (!conn->callbacks.extend_max_stream_data) {
+    return 0;
+  }
+
+  rv = conn->callbacks.extend_max_stream_data(
+    conn, stream_id, datalen, conn->user_data, strm->stream_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;
+
+  if (!conn->callbacks.dcid_status) {
+    return 0;
+  }
+
+  rv = conn->callbacks.dcid_status(
+    conn, type, dcid->seq, &dcid->cid,
+    (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? dcid->token : NULL,
+    conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_activate_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid) {
+  return conn_call_dcid_status(conn, NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE,
+                               dcid);
+}
+
+static int conn_call_deactivate_dcid(ngtcp2_conn *conn,
+                                     const ngtcp2_dcid *dcid) {
+  return conn_call_dcid_status(
+    conn, NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE, dcid);
+}
+
+static int conn_call_stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
+                                         uint64_t app_error_code,
+                                         void *stream_user_data) {
+  int rv;
+
+  if (!conn->callbacks.stream_stop_sending) {
+    return 0;
+  }
+
+  rv = conn->callbacks.stream_stop_sending(conn, stream_id, app_error_code,
+                                           conn->user_data, stream_user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static void conn_call_delete_crypto_aead_ctx(ngtcp2_conn *conn,
+                                             ngtcp2_crypto_aead_ctx *aead_ctx) {
+  if (!aead_ctx->native_handle) {
+    return;
+  }
+
+  assert(conn->callbacks.delete_crypto_aead_ctx);
+
+  conn->callbacks.delete_crypto_aead_ctx(conn, aead_ctx, conn->user_data);
+}
+
+static void
+conn_call_delete_crypto_cipher_ctx(ngtcp2_conn *conn,
+                                   ngtcp2_crypto_cipher_ctx *cipher_ctx) {
+  if (!cipher_ctx->native_handle) {
+    return;
+  }
+
+  assert(conn->callbacks.delete_crypto_cipher_ctx);
+
+  conn->callbacks.delete_crypto_cipher_ctx(conn, cipher_ctx, conn->user_data);
+}
+
+static int conn_call_client_initial(ngtcp2_conn *conn) {
+  int rv;
+
+  assert(conn->callbacks.client_initial);
+
+  rv = conn->callbacks.client_initial(conn, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_get_path_challenge_data(ngtcp2_conn *conn, uint8_t *data) {
+  int rv;
+
+  assert(conn->callbacks.get_path_challenge_data);
+
+  rv = conn->callbacks.get_path_challenge_data(conn, data, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_recv_version_negotiation(ngtcp2_conn *conn,
+                                              const ngtcp2_pkt_hd *hd,
+                                              const uint32_t *sv, size_t nsv) {
+  int rv;
+
+  if (!conn->callbacks.recv_version_negotiation) {
+    return 0;
+  }
+
+  rv = conn->callbacks.recv_version_negotiation(conn, hd, sv, nsv,
+                                                conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd) {
+  int rv;
+
+  assert(conn->callbacks.recv_retry);
+
+  rv = conn->callbacks.recv_retry(conn, hd, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int
+conn_call_recv_stateless_reset(ngtcp2_conn *conn,
+                               const ngtcp2_pkt_stateless_reset *sr) {
+  int rv;
+
+  if (!conn->callbacks.recv_stateless_reset) {
+    return 0;
+  }
+
+  rv = conn->callbacks.recv_stateless_reset(conn, sr, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_recv_new_token(ngtcp2_conn *conn, const uint8_t *token,
+                                    size_t tokenlen) {
+  int rv;
+
+  if (!conn->callbacks.recv_new_token) {
+    return 0;
+  }
+
+  rv = conn->callbacks.recv_new_token(conn, token, tokenlen, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_handshake_confirmed(ngtcp2_conn *conn) {
+  int rv;
+
+  if (!conn->callbacks.handshake_confirmed) {
+    return 0;
+  }
+
+  rv = conn->callbacks.handshake_confirmed(conn, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_recv_datagram(ngtcp2_conn *conn,
+                                   const ngtcp2_datagram *fr) {
+  const uint8_t *data;
+  size_t datalen;
+  int rv;
+  uint32_t flags = NGTCP2_DATAGRAM_FLAG_NONE;
+
+  if (!conn->callbacks.recv_datagram) {
+    return 0;
+  }
+
+  if (fr->datacnt) {
+    assert(fr->datacnt == 1);
+
+    data = fr->data->base;
+    datalen = fr->data->len;
+  } else {
+    data = NULL;
+    datalen = 0;
+  }
+
+  if (!conn_is_tls_handshake_completed(conn)) {
+    flags |= NGTCP2_DATAGRAM_FLAG_0RTT;
+  }
+
+  rv =
+    conn->callbacks.recv_datagram(conn, flags, data, datalen, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int
+conn_call_update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+                     ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+                     ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+                     const uint8_t *current_rx_secret,
+                     const uint8_t *current_tx_secret, size_t secretlen) {
+  int rv;
+
+  assert(conn->callbacks.update_key);
+
+  rv = conn->callbacks.update_key(
+    conn, rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, tx_iv,
+    current_rx_secret, current_tx_secret, secretlen, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_version_negotiation(ngtcp2_conn *conn, uint32_t version,
+                                         const ngtcp2_cid *dcid) {
+  int rv;
+
+  assert(conn->callbacks.version_negotiation);
+
+  rv =
+    conn->callbacks.version_negotiation(conn, version, dcid, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_recv_rx_key(ngtcp2_conn *conn,
+                                 ngtcp2_encryption_level level) {
+  int rv;
+
+  if (!conn->callbacks.recv_rx_key) {
+    return 0;
+  }
+
+  rv = conn->callbacks.recv_rx_key(conn, level, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int conn_call_recv_tx_key(ngtcp2_conn *conn,
+                                 ngtcp2_encryption_level level) {
+  int rv;
+
+  if (!conn->callbacks.recv_tx_key) {
+    return 0;
+  }
+
+  rv = conn->callbacks.recv_tx_key(conn, level, conn->user_data);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static void pktns_init(ngtcp2_pktns *pktns, ngtcp2_pktns_id pktns_id,
+                       ngtcp2_rst *rst, ngtcp2_cc *cc, int64_t initial_pkt_num,
+                       ngtcp2_log *log, ngtcp2_qlog *qlog,
+                       ngtcp2_objalloc *rtb_entry_objalloc,
+                       ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) {
+  memset(pktns, 0, sizeof(*pktns));
+
+  ngtcp2_gaptr_init(&pktns->rx.pngap, mem);
+
+  pktns->tx.last_pkt_num = initial_pkt_num - 1;
+  pktns->tx.non_ack_pkt_start_ts = UINT64_MAX;
+  pktns->rx.max_ack_eliciting_pkt_num = -1;
+  pktns->id = pktns_id;
+
+  ngtcp2_acktr_init(&pktns->acktr, log, mem);
+
+  ngtcp2_strm_init(&pktns->crypto.strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL,
+                   frc_objalloc, mem);
+
+  ngtcp2_rtb_init(&pktns->rtb, rst, cc, initial_pkt_num, log, qlog,
+                  rtb_entry_objalloc, frc_objalloc, mem);
+}
+
+static int pktns_new(ngtcp2_pktns **ppktns, ngtcp2_pktns_id pktns_id,
+                     ngtcp2_rst *rst, ngtcp2_cc *cc, int64_t initial_pkt_num,
+                     ngtcp2_log *log, ngtcp2_qlog *qlog,
+                     ngtcp2_objalloc *rtb_entry_objalloc,
+                     ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) {
+  *ppktns = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pktns));
+  if (*ppktns == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  pktns_init(*ppktns, pktns_id, rst, cc, initial_pkt_num, log, qlog,
+             rtb_entry_objalloc, frc_objalloc, mem);
+
+  return 0;
+}
+
+static int cycle_less(const ngtcp2_pq_entry *lhs, const ngtcp2_pq_entry *rhs) {
+  ngtcp2_strm *ls = ngtcp2_struct_of(lhs, ngtcp2_strm, pe);
+  ngtcp2_strm *rs = ngtcp2_struct_of(rhs, ngtcp2_strm, pe);
+
+  if (ls->cycle == rs->cycle) {
+    return ls->stream_id < rs->stream_id;
+  }
+
+  return rs->cycle - ls->cycle <= 1;
+}
+
+static void delete_buffed_pkts(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem) {
+  ngtcp2_pkt_chain *next;
+
+  for (; pc;) {
+    next = pc->next;
+    ngtcp2_pkt_chain_del(pc, mem);
+    pc = next;
+  }
+}
+
+static void delete_buf_chain(ngtcp2_buf_chain *bufchain,
+                             const ngtcp2_mem *mem) {
+  ngtcp2_buf_chain *next;
+
+  for (; bufchain;) {
+    next = bufchain->next;
+    ngtcp2_buf_chain_del(bufchain, mem);
+    bufchain = next;
+  }
+}
+
+static void pktns_free(ngtcp2_pktns *pktns, const ngtcp2_mem *mem) {
+  delete_buf_chain(pktns->crypto.tx.data, mem);
+
+  delete_buffed_pkts(pktns->rx.buffed_pkts, mem);
+
+  ngtcp2_frame_chain_list_objalloc_del(pktns->tx.frq, pktns->rtb.frc_objalloc,
+                                       mem);
+
+  ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, mem);
+  ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, mem);
+
+  ngtcp2_rtb_free(&pktns->rtb);
+  ngtcp2_strm_free(&pktns->crypto.strm);
+  ngtcp2_acktr_free(&pktns->acktr);
+  ngtcp2_gaptr_free(&pktns->rx.pngap);
+}
+
+static void pktns_del(ngtcp2_pktns *pktns, const ngtcp2_mem *mem) {
+  if (pktns == NULL) {
+    return;
+  }
+
+  pktns_free(pktns, mem);
+
+  ngtcp2_mem_free(mem, pktns);
+}
+
+static int cid_less(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) {
+  return ngtcp2_cid_less(lhs, rhs);
+}
+
+ngtcp2_ksl_search_def(cid_less, cid_less)
+
+static int retired_ts_less(const ngtcp2_pq_entry *lhs,
+                           const ngtcp2_pq_entry *rhs) {
+  const ngtcp2_scid *a = ngtcp2_struct_of(lhs, ngtcp2_scid, pe);
+  const ngtcp2_scid *b = ngtcp2_struct_of(rhs, ngtcp2_scid, pe);
+
+  return a->retired_ts < b->retired_ts;
+}
+
+/*
+ * conn_reset_conn_stat_cc resets congestion state in |cstat|.
+ */
+static void conn_reset_conn_stat_cc(ngtcp2_conn *conn,
+                                    ngtcp2_conn_stat *cstat) {
+  cstat->latest_rtt = 0;
+  cstat->min_rtt = UINT64_MAX;
+  cstat->smoothed_rtt = conn->local.settings.initial_rtt;
+  cstat->rttvar = conn->local.settings.initial_rtt / 2;
+  cstat->first_rtt_sample_ts = UINT64_MAX;
+  cstat->pto_count = 0;
+  cstat->loss_detection_timer = UINT64_MAX;
+  cstat->max_tx_udp_payload_size =
+    ngtcp2_conn_get_path_max_tx_udp_payload_size(conn);
+  cstat->cwnd = ngtcp2_cc_compute_initcwnd(cstat->max_tx_udp_payload_size);
+  cstat->ssthresh = UINT64_MAX;
+  cstat->congestion_recovery_start_ts = UINT64_MAX;
+  cstat->bytes_in_flight = 0;
+  cstat->delivery_rate_sec = 0;
+  cstat->pacing_interval_m = 0;
+  cstat->send_quantum = 64 * 1024;
+}
+
+/*
+ * reset_conn_stat_recovery resets the fields related to the recovery
+ * function
+ */
+static void reset_conn_stat_recovery(ngtcp2_conn_stat *cstat) {
+  /* Initializes them with UINT64_MAX. */
+  memset(cstat->loss_time, 0xff, sizeof(cstat->loss_time));
+  memset(cstat->last_tx_pkt_ts, 0xff, sizeof(cstat->last_tx_pkt_ts));
+}
+
+/*
+ * conn_reset_conn_stat resets |cstat|.  The following fields are not
+ * reset: initial_rtt and max_udp_payload_size.
+ */
+static void conn_reset_conn_stat(ngtcp2_conn *conn, ngtcp2_conn_stat *cstat) {
+  conn_reset_conn_stat_cc(conn, cstat);
+  reset_conn_stat_recovery(cstat);
+}
+
+static void delete_scid(ngtcp2_ksl *scids, const ngtcp2_mem *mem) {
+  ngtcp2_ksl_it it;
+
+  for (it = ngtcp2_ksl_begin(scids); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    ngtcp2_mem_free(mem, ngtcp2_ksl_it_get(&it));
+  }
+}
+
+/*
+ * compute_pto computes PTO.
+ */
+static ngtcp2_duration compute_pto(ngtcp2_duration smoothed_rtt,
+                                   ngtcp2_duration rttvar,
+                                   ngtcp2_duration max_ack_delay) {
+  ngtcp2_duration var = ngtcp2_max_uint64(4 * rttvar, NGTCP2_GRANULARITY);
+  return smoothed_rtt + var + max_ack_delay;
+}
+
+/*
+ * conn_compute_initial_pto computes PTO using the initial RTT.
+ */
+static ngtcp2_duration conn_compute_initial_pto(ngtcp2_conn *conn,
+                                                ngtcp2_pktns *pktns) {
+  ngtcp2_duration initial_rtt = conn->local.settings.initial_rtt;
+  ngtcp2_duration max_ack_delay;
+
+  if (pktns->id == NGTCP2_PKTNS_ID_APPLICATION &&
+      conn->remote.transport_params) {
+    max_ack_delay = conn->remote.transport_params->max_ack_delay;
+  } else {
+    max_ack_delay = 0;
+  }
+  return compute_pto(initial_rtt, initial_rtt / 2, max_ack_delay);
+}
+
+/*
+ * conn_compute_pto computes the current PTO.
+ */
+static ngtcp2_duration conn_compute_pto(ngtcp2_conn *conn,
+                                        ngtcp2_pktns *pktns) {
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  ngtcp2_duration max_ack_delay;
+
+  if (pktns->id == NGTCP2_PKTNS_ID_APPLICATION &&
+      conn->remote.transport_params) {
+    max_ack_delay = conn->remote.transport_params->max_ack_delay;
+  } else {
+    max_ack_delay = 0;
+  }
+  return compute_pto(cstat->smoothed_rtt, cstat->rttvar, max_ack_delay);
+}
+
+ngtcp2_duration ngtcp2_conn_compute_pto(ngtcp2_conn *conn,
+                                        ngtcp2_pktns *pktns) {
+  return conn_compute_pto(conn, pktns);
+}
+
+/*
+ * conn_compute_pv_timeout_pto returns path validation timeout using
+ * the given |pto|.
+ */
+static ngtcp2_duration conn_compute_pv_timeout_pto(ngtcp2_conn *conn,
+                                                   ngtcp2_duration pto) {
+  ngtcp2_duration initial_pto = conn_compute_initial_pto(conn, &conn->pktns);
+
+  return 3 * ngtcp2_max_uint64(pto, initial_pto);
+}
+
+/*
+ * conn_compute_pv_timeout returns path validation timeout.
+ */
+static ngtcp2_duration conn_compute_pv_timeout(ngtcp2_conn *conn) {
+  return conn_compute_pv_timeout_pto(conn,
+                                     conn_compute_pto(conn, &conn->pktns));
+}
+
+static void conn_handle_tx_ecn(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
+                               uint16_t *prtb_entry_flags, ngtcp2_pktns *pktns,
+                               const ngtcp2_pkt_hd *hd, ngtcp2_tstamp ts) {
+  assert(pi);
+
+  if (pi->ecn != NGTCP2_ECN_NOT_ECT) {
+    /* We have already made a transition of validation state and
+      deceided to send UDP datagram with ECN bit set.  Coalesced QUIC
+      packets also bear ECN bits set. */
+    if (pktns->tx.ecn.start_pkt_num == INT64_MAX) {
+      pktns->tx.ecn.start_pkt_num = hd->pkt_num;
+    }
+
+    ++pktns->tx.ecn.validation_pkt_sent;
+
+    if (prtb_entry_flags) {
+      *prtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ECN;
+    }
+
+    ++pktns->tx.ecn.ect0;
+
+    return;
+  }
+
+  switch (conn->tx.ecn.state) {
+  case NGTCP2_ECN_STATE_TESTING:
+    if (conn->tx.ecn.validation_start_ts == UINT64_MAX) {
+      assert(0 == pktns->tx.ecn.validation_pkt_sent);
+      assert(0 == pktns->tx.ecn.validation_pkt_lost);
+
+      conn->tx.ecn.validation_start_ts = ts;
+    } else if (ts - conn->tx.ecn.validation_start_ts >=
+               3 * conn_compute_pto(conn, pktns)) {
+      conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN;
+      break;
+    }
+
+    if (pktns->tx.ecn.start_pkt_num == INT64_MAX) {
+      pktns->tx.ecn.start_pkt_num = hd->pkt_num;
+    }
+
+    ++pktns->tx.ecn.validation_pkt_sent;
+
+    if (++conn->tx.ecn.dgram_sent == NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS) {
+      conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN;
+    }
+    /* fall through */
+  case NGTCP2_ECN_STATE_CAPABLE:
+    /* pi is provided per UDP datagram. */
+    assert(NGTCP2_ECN_NOT_ECT == pi->ecn);
+
+    pi->ecn = NGTCP2_ECN_ECT_0;
+
+    if (prtb_entry_flags) {
+      *prtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ECN;
+    }
+
+    ++pktns->tx.ecn.ect0;
+    break;
+  case NGTCP2_ECN_STATE_UNKNOWN:
+  case NGTCP2_ECN_STATE_FAILED:
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+}
+
+static void conn_reset_ecn_validation_state(ngtcp2_conn *conn) {
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+  ngtcp2_pktns *pktns = &conn->pktns;
+
+  conn->tx.ecn.state = NGTCP2_ECN_STATE_TESTING;
+  conn->tx.ecn.validation_start_ts = UINT64_MAX;
+  conn->tx.ecn.dgram_sent = 0;
+
+  if (in_pktns) {
+    in_pktns->tx.ecn.start_pkt_num = INT64_MAX;
+    in_pktns->tx.ecn.validation_pkt_sent = 0;
+    in_pktns->tx.ecn.validation_pkt_lost = 0;
+  }
+
+  if (hs_pktns) {
+    hs_pktns->tx.ecn.start_pkt_num = INT64_MAX;
+    hs_pktns->tx.ecn.validation_pkt_sent = 0;
+    hs_pktns->tx.ecn.validation_pkt_lost = 0;
+  }
+
+  pktns->tx.ecn.start_pkt_num = INT64_MAX;
+  pktns->tx.ecn.validation_pkt_sent = 0;
+  pktns->tx.ecn.validation_pkt_lost = 0;
+}
+
+/* server_default_available_versions is the default available_versions
+   field sent by server. */
+static uint8_t server_default_available_versions[] = {0, 0, 0, 1};
+
+/*
+ * available_versions_init writes |versions| of length |versionslen|
+ * in network byte order to the buffer pointed by |buf|, suitable for
+ * sending in available_versions field of version_information QUIC
+ * transport parameter.  This function returns the pointer to the one
+ * beyond the last byte written.
+ */
+static void *available_versions_init(void *buf, const uint32_t *versions,
+                                     size_t versionslen) {
+  size_t i;
+
+  for (i = 0; i < versionslen; ++i) {
+    buf = ngtcp2_put_uint32be(buf, versions[i]);
+  }
+
+  return 0;
+}
+
+static void
+conn_set_local_transport_params(ngtcp2_conn *conn,
+                                const ngtcp2_transport_params *params) {
+  ngtcp2_transport_params *p = &conn->local.transport_params;
+  uint32_t chosen_version = p->version_info.chosen_version;
+
+  *p = *params;
+
+  if (conn->server) {
+    p->version_info.chosen_version = chosen_version;
+  } else {
+    p->version_info.chosen_version = conn->client_chosen_version;
+  }
+  p->version_info.available_versions = conn->vneg.available_versions;
+  p->version_info.available_versionslen = conn->vneg.available_versionslen;
+  p->version_info_present = 1;
+}
+
+static void conn_update_skip_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns) {
+  uint8_t gap;
+
+  conn->callbacks.rand(&gap, 1, &conn->local.settings.rand_ctx);
+
+  pktns->tx.skip_pkt.next_pkt_num =
+    pktns->tx.last_pkt_num + 3 +
+    (int64_t)gap * (1ll << pktns->tx.skip_pkt.exponent++);
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "next skip pkn=%" PRId64,
+                  pktns->tx.skip_pkt.next_pkt_num);
+}
+
+static int conn_handle_skip_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                                ngtcp2_tstamp ts) {
+  ngtcp2_rtb_entry *rtbent;
+  ngtcp2_pkt_hd hd;
+  int rv;
+
+  assert(NGTCP2_PKTNS_ID_APPLICATION == pktns->id);
+
+  if (pktns->tx.last_pkt_num + 1 != pktns->tx.skip_pkt.next_pkt_num) {
+    return 0;
+  }
+
+  ngtcp2_pkt_hd_init(&hd, 0, NGTCP2_PKT_1RTT, NULL, NULL,
+                     pktns->tx.skip_pkt.next_pkt_num, 0, 0);
+
+  rv = ngtcp2_rtb_entry_objalloc_new(&rtbent, &hd, NULL, ts, 0,
+                                     NGTCP2_RTB_ENTRY_FLAG_SKIP,
+                                     &conn->rtb_entry_objalloc);
+  if (rv != 0) {
+    assert(ngtcp2_err_is_fatal(rv));
+    return rv;
+  }
+
+  rv = ngtcp2_rtb_add(&pktns->rtb, rtbent, &conn->cstat);
+  if (rv != 0) {
+    ngtcp2_rtb_entry_objalloc_del(rtbent, &conn->rtb_entry_objalloc,
+                                  &conn->frc_objalloc, conn->mem);
+    return rv;
+  }
+
+  ++pktns->tx.last_pkt_num;
+
+  conn_update_skip_pkt(conn, pktns);
+
+  return 0;
+}
+
+static size_t buflen_align(size_t buflen) {
+  return (buflen + 0x7) & (size_t)~0x7;
+}
+
+static void *buf_align(void *buf) {
+  return (void *)((uintptr_t)((uint8_t *)buf + 0x7) & (uintptr_t)~0x7);
+}
+
+static void *buf_advance(void *buf, size_t n) { return (uint8_t *)buf + n; }
+
+static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
+                    const ngtcp2_cid *scid, const ngtcp2_path *path,
+                    uint32_t client_chosen_version, int callbacks_version,
+                    const ngtcp2_callbacks *callbacks, int settings_version,
+                    const ngtcp2_settings *settings,
+                    int transport_params_version,
+                    const ngtcp2_transport_params *params,
+                    const ngtcp2_mem *mem, void *user_data, int server) {
+  int rv;
+  ngtcp2_scid *scident;
+  void *buf, *tokenbuf;
+  size_t buflen;
+  uint8_t fixed_bit_byte;
+  size_t i;
+  uint32_t *preferred_versions;
+  ngtcp2_settings settingsbuf;
+  ngtcp2_transport_params paramsbuf;
+  (void)callbacks_version;
+  (void)settings_version;
+
+  settings =
+    ngtcp2_settings_convert_to_latest(&settingsbuf, settings_version, settings);
+  params = ngtcp2_transport_params_convert_to_latest(
+    &paramsbuf, transport_params_version, params);
+
+  assert(settings->max_window <= NGTCP2_MAX_VARINT);
+  assert(settings->max_stream_window <= NGTCP2_MAX_VARINT);
+  assert(settings->max_tx_udp_payload_size);
+  assert(settings->max_tx_udp_payload_size <= NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE);
+  assert(settings->initial_pkt_num <= INT32_MAX);
+  assert(settings->initial_rtt);
+  assert(params->active_connection_id_limit >=
+         NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT);
+  assert(params->active_connection_id_limit <=
+         NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE);
+  assert(params->initial_max_data <= NGTCP2_MAX_VARINT);
+  assert(params->initial_max_stream_data_bidi_local <= NGTCP2_MAX_VARINT);
+  assert(params->initial_max_stream_data_bidi_remote <= NGTCP2_MAX_VARINT);
+  assert(params->initial_max_stream_data_uni <= NGTCP2_MAX_VARINT);
+  assert((server && params->original_dcid_present) ||
+         (!server && !params->original_dcid_present));
+  assert(!params->initial_scid_present);
+  assert(server || !params->stateless_reset_token_present);
+  assert(server || !params->preferred_addr_present);
+  assert(server || !params->retry_scid_present);
+  assert(params->max_idle_timeout != UINT64_MAX);
+  assert(params->max_ack_delay < (1 << 14) * NGTCP2_MILLISECONDS);
+  assert(server || callbacks->client_initial);
+  assert(!server || callbacks->recv_client_initial);
+  assert(callbacks->recv_crypto_data);
+  assert(callbacks->encrypt);
+  assert(callbacks->decrypt);
+  assert(callbacks->hp_mask);
+  assert(server || callbacks->recv_retry);
+  assert(callbacks->rand);
+  assert(callbacks->get_new_connection_id);
+  assert(callbacks->update_key);
+  assert(callbacks->delete_crypto_aead_ctx);
+  assert(callbacks->delete_crypto_cipher_ctx);
+  assert(callbacks->get_path_challenge_data);
+  assert(!server || !ngtcp2_is_reserved_version(client_chosen_version));
+
+  for (i = 0; i < settings->pmtud_probeslen; ++i) {
+    assert(settings->pmtud_probes[i] > NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+  }
+
+  if (mem == NULL) {
+    mem = ngtcp2_mem_default();
+  }
+
+  buflen = sizeof(ngtcp2_conn);
+  if (settings->qlog_write) {
+    buflen = buflen_align(buflen);
+    buflen += NGTCP2_QLOG_BUFLEN;
+  }
+
+  if (settings->pmtud_probeslen) {
+    buflen = buflen_align(buflen);
+    buflen += sizeof(settings->pmtud_probes[0]) * settings->pmtud_probeslen;
+  }
+
+  if (settings->preferred_versionslen) {
+    buflen = buflen_align(buflen);
+    buflen +=
+      sizeof(settings->preferred_versions[0]) * settings->preferred_versionslen;
+  }
+
+  if (settings->available_versionslen) {
+    buflen = buflen_align(buflen);
+    buflen +=
+      sizeof(settings->available_versions[0]) * settings->available_versionslen;
+  } else if (server) {
+    if (settings->preferred_versionslen) {
+      buflen = buflen_align(buflen);
+      buflen += sizeof(settings->preferred_versions[0]) *
+                settings->preferred_versionslen;
+    }
+  } else if (!ngtcp2_is_reserved_version(client_chosen_version)) {
+    buflen = buflen_align(buflen);
+    buflen += sizeof(client_chosen_version);
+  }
+
+  buf = ngtcp2_mem_calloc(mem, 1, buflen);
+  if (buf == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  *pconn = buf;
+  buf = buf_advance(buf, sizeof(ngtcp2_conn));
+
+  (*pconn)->server = server;
+
+  ngtcp2_objalloc_frame_chain_init(&(*pconn)->frc_objalloc, 16, mem);
+  ngtcp2_objalloc_rtb_entry_init(&(*pconn)->rtb_entry_objalloc, 16, mem);
+  ngtcp2_objalloc_strm_init(&(*pconn)->strm_objalloc, 16, mem);
+
+  ngtcp2_dcidtr_init(&(*pconn)->dcid.dtr);
+
+  ngtcp2_gaptr_init(&(*pconn)->dcid.seqgap, mem);
+
+  ngtcp2_ksl_init(&(*pconn)->scid.set, cid_less, ksl_cid_less_search,
+                  sizeof(ngtcp2_cid), mem);
+
+  ngtcp2_pq_init(&(*pconn)->scid.used, retired_ts_less, mem);
+
+  ngtcp2_map_init(&(*pconn)->strms, mem);
+
+  ngtcp2_pq_init(&(*pconn)->tx.strmq, cycle_less, mem);
+
+  ngtcp2_idtr_init(&(*pconn)->remote.bidi.idtr, mem);
+
+  ngtcp2_idtr_init(&(*pconn)->remote.uni.idtr, mem);
+
+  ngtcp2_static_ringbuf_path_challenge_init(&(*pconn)->rx.path_challenge);
+
+  ngtcp2_log_init(&(*pconn)->log, scid, settings->log_printf,
+                  settings->initial_ts, user_data);
+  ngtcp2_qlog_init(&(*pconn)->qlog, settings->qlog_write, settings->initial_ts,
+                   user_data);
+  if ((*pconn)->qlog.write) {
+    buf = buf_align(buf);
+    ngtcp2_buf_init(&(*pconn)->qlog.buf, buf, NGTCP2_QLOG_BUFLEN);
+    buf = buf_advance(buf, NGTCP2_QLOG_BUFLEN);
+  }
+
+  (*pconn)->local.settings = *settings;
+
+  if (settings->tokenlen) {
+    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;
+  } else {
+    (*pconn)->local.settings.token = NULL;
+  }
+
+  if (settings->pmtud_probeslen) {
+    (*pconn)->local.settings.pmtud_probes = buf_align(buf);
+    buf = ngtcp2_cpymem(
+      (uint16_t *)(*pconn)->local.settings.pmtud_probes, settings->pmtud_probes,
+      sizeof(settings->pmtud_probes[0]) * settings->pmtud_probeslen);
+  }
+
+  if (!(*pconn)->local.settings.original_version) {
+    (*pconn)->local.settings.original_version = client_chosen_version;
+  }
+
+  ngtcp2_dcid_init(&(*pconn)->dcid.current, 0, dcid, NULL);
+  ngtcp2_dcid_set_path(&(*pconn)->dcid.current, path);
+
+  assert((size_t)path->local.addrlen <= sizeof((*pconn)->hs_local_addr));
+
+  memcpy(&(*pconn)->hs_local_addr, path->local.addr,
+         (size_t)path->local.addrlen);
+
+  rv = ngtcp2_gaptr_push(&(*pconn)->dcid.seqgap, 0, 1);
+  if (rv != 0) {
+    goto fail_seqgap_push;
+  }
+
+  conn_reset_conn_stat(*pconn, &(*pconn)->cstat);
+  (*pconn)->cstat.initial_rtt = settings->initial_rtt;
+
+  ngtcp2_rst_init(&(*pconn)->rst);
+
+  (*pconn)->cc_algo = settings->cc_algo;
+
+  switch (settings->cc_algo) {
+  case NGTCP2_CC_ALGO_RENO:
+    ngtcp2_cc_reno_init(&(*pconn)->reno, &(*pconn)->log);
+
+    break;
+  case NGTCP2_CC_ALGO_CUBIC:
+    ngtcp2_cc_cubic_init(&(*pconn)->cubic, &(*pconn)->log, &(*pconn)->rst);
+
+    break;
+  case NGTCP2_CC_ALGO_BBR:
+    ngtcp2_cc_bbr_init(&(*pconn)->bbr, &(*pconn)->log, &(*pconn)->cstat,
+                       &(*pconn)->rst, settings->initial_ts, callbacks->rand,
+                       &settings->rand_ctx);
+
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  ngtcp2_static_ringbuf_path_history_init(&(*pconn)->path_history);
+
+  (*pconn)->callbacks = *callbacks;
+
+  rv = pktns_new(&(*pconn)->in_pktns, NGTCP2_PKTNS_ID_INITIAL, &(*pconn)->rst,
+                 &(*pconn)->cc, settings->initial_pkt_num, &(*pconn)->log,
+                 &(*pconn)->qlog, &(*pconn)->rtb_entry_objalloc,
+                 &(*pconn)->frc_objalloc, mem);
+  if (rv != 0) {
+    goto fail_in_pktns_init;
+  }
+
+  rv = pktns_new(&(*pconn)->hs_pktns, NGTCP2_PKTNS_ID_HANDSHAKE, &(*pconn)->rst,
+                 &(*pconn)->cc, settings->initial_pkt_num, &(*pconn)->log,
+                 &(*pconn)->qlog, &(*pconn)->rtb_entry_objalloc,
+                 &(*pconn)->frc_objalloc, mem);
+  if (rv != 0) {
+    goto fail_hs_pktns_init;
+  }
+
+  pktns_init(&(*pconn)->pktns, NGTCP2_PKTNS_ID_APPLICATION, &(*pconn)->rst,
+             &(*pconn)->cc, settings->initial_pkt_num, &(*pconn)->log,
+             &(*pconn)->qlog, &(*pconn)->rtb_entry_objalloc,
+             &(*pconn)->frc_objalloc, mem);
+
+  conn_update_skip_pkt(*pconn, &(*pconn)->pktns);
+
+  scident = ngtcp2_mem_malloc(mem, sizeof(*scident));
+  if (scident == NULL) {
+    rv = NGTCP2_ERR_NOMEM;
+    goto fail_scident;
+  }
+
+  /* Set stateless reset token later if it is available in the local
+     transport parameters */
+  ngtcp2_scid_init(scident, 0, scid);
+
+  rv = ngtcp2_ksl_insert(&(*pconn)->scid.set, NULL, &scident->cid, scident);
+  if (rv != 0) {
+    goto fail_scid_set_insert;
+  }
+
+  scident = NULL;
+
+  if (settings->preferred_versionslen) {
+    if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) {
+      for (i = 0; i < settings->preferred_versionslen; ++i) {
+        if (settings->preferred_versions[i] == client_chosen_version) {
+          break;
+        }
+      }
+
+      assert(i < settings->preferred_versionslen);
+    }
+
+    preferred_versions = buf_align(buf);
+    buf = buf_advance(preferred_versions, sizeof(preferred_versions[0]) *
+                                            settings->preferred_versionslen);
+
+    for (i = 0; i < settings->preferred_versionslen; ++i) {
+      assert(ngtcp2_is_supported_version(settings->preferred_versions[i]));
+
+      preferred_versions[i] = settings->preferred_versions[i];
+    }
+
+    (*pconn)->vneg.preferred_versions = preferred_versions;
+    (*pconn)->vneg.preferred_versionslen = settings->preferred_versionslen;
+  }
+
+  (*pconn)->local.settings.preferred_versions = NULL;
+  (*pconn)->local.settings.preferred_versionslen = 0;
+
+  if (settings->available_versionslen) {
+    if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) {
+      for (i = 0; i < settings->available_versionslen; ++i) {
+        if (settings->available_versions[i] == client_chosen_version) {
+          break;
+        }
+      }
+
+      assert(i < settings->available_versionslen);
+    }
+
+    for (i = 0; i < settings->available_versionslen; ++i) {
+      assert(ngtcp2_is_reserved_version(settings->available_versions[i]) ||
+             ngtcp2_is_supported_version(settings->available_versions[i]));
+    }
+
+    (*pconn)->vneg.available_versions = buf_align(buf);
+    (*pconn)->vneg.available_versionslen =
+      sizeof(uint32_t) * settings->available_versionslen;
+
+    buf = available_versions_init((*pconn)->vneg.available_versions,
+                                  settings->available_versions,
+                                  settings->available_versionslen);
+  } else if (server) {
+    if (settings->preferred_versionslen) {
+      (*pconn)->vneg.available_versions = buf_align(buf);
+      (*pconn)->vneg.available_versionslen =
+        sizeof(uint32_t) * settings->preferred_versionslen;
+
+      buf = available_versions_init((*pconn)->vneg.available_versions,
+                                    settings->preferred_versions,
+                                    settings->preferred_versionslen);
+    } else {
+      (*pconn)->vneg.available_versions = server_default_available_versions;
+      (*pconn)->vneg.available_versionslen =
+        sizeof(server_default_available_versions);
+    }
+  } else if (!ngtcp2_is_reserved_version(client_chosen_version)) {
+    (*pconn)->vneg.available_versions = buf_align(buf);
+    (*pconn)->vneg.available_versionslen = sizeof(uint32_t);
+
+    buf = available_versions_init((*pconn)->vneg.available_versions,
+                                  &client_chosen_version, 1);
+  }
+
+  (*pconn)->local.settings.available_versions = NULL;
+  (*pconn)->local.settings.available_versionslen = 0;
+
+  (*pconn)->client_chosen_version = client_chosen_version;
+
+  conn_set_local_transport_params(*pconn, params);
+
+  callbacks->rand(&fixed_bit_byte, 1, &settings->rand_ctx);
+  if (fixed_bit_byte & 1) {
+    (*pconn)->flags |= NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT;
+  }
+
+  (*pconn)->keep_alive.last_ts = UINT64_MAX;
+  (*pconn)->keep_alive.timeout = UINT64_MAX;
+
+  (*pconn)->oscid = *scid;
+  (*pconn)->mem = mem;
+  (*pconn)->user_data = user_data;
+  (*pconn)->idle_ts = settings->initial_ts;
+  (*pconn)->handshake_confirmed_ts = UINT64_MAX;
+  (*pconn)->crypto.key_update.confirmed_ts = UINT64_MAX;
+  (*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);
+
+  ngtcp2_qlog_start(&(*pconn)->qlog,
+                    server
+                      ? ((*pconn)->local.transport_params.retry_scid_present
+                           ? &(*pconn)->local.transport_params.retry_scid
+                           : &(*pconn)->local.transport_params.original_dcid)
+                      : dcid,
+                    server);
+
+  return 0;
+
+fail_scid_set_insert:
+  ngtcp2_mem_free(mem, scident);
+fail_scident:
+  pktns_del((*pconn)->hs_pktns, mem);
+fail_hs_pktns_init:
+  pktns_del((*pconn)->in_pktns, mem);
+fail_in_pktns_init:
+  ngtcp2_gaptr_free(&(*pconn)->dcid.seqgap);
+fail_seqgap_push:
+  ngtcp2_mem_free(mem, (uint8_t *)(*pconn)->local.settings.token);
+fail_token:
+  ngtcp2_mem_free(mem, *pconn);
+
+  return rv;
+}
+
+int ngtcp2_conn_client_new_versioned(
+  ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
+  const ngtcp2_path *path, uint32_t client_chosen_version,
+  int callbacks_version, const ngtcp2_callbacks *callbacks,
+  int settings_version, const ngtcp2_settings *settings,
+  int transport_params_version, const ngtcp2_transport_params *params,
+  const ngtcp2_mem *mem, void *user_data) {
+  int rv;
+
+  rv = conn_new(pconn, dcid, scid, path, client_chosen_version,
+                callbacks_version, callbacks, settings_version, settings,
+                transport_params_version, params, mem, user_data, 0);
+  if (rv != 0) {
+    return rv;
+  }
+  (*pconn)->rcid = *dcid;
+  (*pconn)->state = NGTCP2_CS_CLIENT_INITIAL;
+  (*pconn)->local.bidi.next_stream_id = 0;
+  (*pconn)->local.uni.next_stream_id = 2;
+
+  rv = ngtcp2_conn_commit_local_transport_params(*pconn);
+  if (rv != 0) {
+    ngtcp2_conn_del(*pconn);
+    return rv;
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_server_new_versioned(
+  ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
+  const ngtcp2_path *path, uint32_t client_chosen_version,
+  int callbacks_version, const ngtcp2_callbacks *callbacks,
+  int settings_version, const ngtcp2_settings *settings,
+  int transport_params_version, const ngtcp2_transport_params *params,
+  const ngtcp2_mem *mem, void *user_data) {
+  int rv;
+
+  rv = conn_new(pconn, dcid, scid, path, client_chosen_version,
+                callbacks_version, callbacks, settings_version, settings,
+                transport_params_version, params, mem, user_data, 1);
+  if (rv != 0) {
+    return rv;
+  }
+
+  (*pconn)->state = NGTCP2_CS_SERVER_INITIAL;
+  (*pconn)->local.bidi.next_stream_id = 1;
+  (*pconn)->local.uni.next_stream_id = 3;
+
+  if ((*pconn)->local.settings.tokenlen) {
+    /* Usage of token lifts amplification limit */
+    (*pconn)->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_fc_credits returns the number of bytes allowed to be sent to
+ * the given stream.  Both connection and stream level flow control
+ * credits are considered.
+ */
+static uint64_t conn_fc_credits(ngtcp2_conn *conn, ngtcp2_strm *strm) {
+  return ngtcp2_min_uint64(strm->tx.max_offset - strm->tx.offset,
+                           conn->tx.max_offset - conn->tx.offset);
+}
+
+/*
+ * conn_enforce_flow_control returns the number of bytes allowed to be
+ * sent to the given stream.  |len| might be shorted because of
+ * available flow control credits.
+ */
+static uint64_t conn_enforce_flow_control(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                                          uint64_t len) {
+  uint64_t fc_credits = conn_fc_credits(conn, strm);
+  return ngtcp2_min_uint64(len, fc_credits);
+}
+
+static int delete_strms_each(void *data, void *ptr) {
+  ngtcp2_conn *conn = ptr;
+  ngtcp2_strm *s = data;
+
+  ngtcp2_strm_free(s);
+  ngtcp2_objalloc_strm_release(&conn->strm_objalloc, s);
+
+  return 0;
+}
+
+static void conn_vneg_crypto_free(ngtcp2_conn *conn) {
+  if (conn->vneg.rx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.rx.ckm->aead_ctx);
+  }
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.rx.hp_ctx);
+
+  if (conn->vneg.tx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.tx.ckm->aead_ctx);
+  }
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.tx.hp_ctx);
+
+  ngtcp2_crypto_km_del(conn->vneg.rx.ckm, conn->mem);
+  ngtcp2_crypto_km_del(conn->vneg.tx.ckm, conn->mem);
+}
+
+void ngtcp2_conn_del(ngtcp2_conn *conn) {
+  if (conn == NULL) {
+    return;
+  }
+
+  ngtcp2_qlog_end(&conn->qlog);
+
+  if (conn->early.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &conn->early.ckm->aead_ctx);
+  }
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->early.hp_ctx);
+
+  if (conn->crypto.key_update.old_rx_ckm) {
+    conn_call_delete_crypto_aead_ctx(
+      conn, &conn->crypto.key_update.old_rx_ckm->aead_ctx);
+  }
+  if (conn->crypto.key_update.new_rx_ckm) {
+    conn_call_delete_crypto_aead_ctx(
+      conn, &conn->crypto.key_update.new_rx_ckm->aead_ctx);
+  }
+  if (conn->crypto.key_update.new_tx_ckm) {
+    conn_call_delete_crypto_aead_ctx(
+      conn, &conn->crypto.key_update.new_tx_ckm->aead_ctx);
+  }
+
+  if (conn->pktns.crypto.rx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn,
+                                     &conn->pktns.crypto.rx.ckm->aead_ctx);
+  }
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->pktns.crypto.rx.hp_ctx);
+
+  if (conn->pktns.crypto.tx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn,
+                                     &conn->pktns.crypto.tx.ckm->aead_ctx);
+  }
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->pktns.crypto.tx.hp_ctx);
+
+  if (conn->hs_pktns) {
+    if (conn->hs_pktns->crypto.rx.ckm) {
+      conn_call_delete_crypto_aead_ctx(
+        conn, &conn->hs_pktns->crypto.rx.ckm->aead_ctx);
+    }
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->hs_pktns->crypto.rx.hp_ctx);
+
+    if (conn->hs_pktns->crypto.tx.ckm) {
+      conn_call_delete_crypto_aead_ctx(
+        conn, &conn->hs_pktns->crypto.tx.ckm->aead_ctx);
+    }
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->hs_pktns->crypto.tx.hp_ctx);
+  }
+  if (conn->in_pktns) {
+    if (conn->in_pktns->crypto.rx.ckm) {
+      conn_call_delete_crypto_aead_ctx(
+        conn, &conn->in_pktns->crypto.rx.ckm->aead_ctx);
+    }
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->in_pktns->crypto.rx.hp_ctx);
+
+    if (conn->in_pktns->crypto.tx.ckm) {
+      conn_call_delete_crypto_aead_ctx(
+        conn, &conn->in_pktns->crypto.tx.ckm->aead_ctx);
+    }
+    conn_call_delete_crypto_cipher_ctx(conn, &conn->in_pktns->crypto.tx.hp_ctx);
+  }
+
+  conn_call_delete_crypto_aead_ctx(conn, &conn->crypto.retry_aead_ctx);
+
+  ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem);
+  ngtcp2_transport_params_del(conn->remote.pending_transport_params, conn->mem);
+
+  conn_vneg_crypto_free(conn);
+
+  ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_buf.base);
+  ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_hp_buf.base);
+  ngtcp2_mem_free(conn->mem, (uint8_t *)conn->local.settings.token);
+
+  ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem);
+  ngtcp2_crypto_km_del(conn->crypto.key_update.new_rx_ckm, conn->mem);
+  ngtcp2_crypto_km_del(conn->crypto.key_update.new_tx_ckm, conn->mem);
+  ngtcp2_crypto_km_del(conn->early.ckm, conn->mem);
+
+  pktns_free(&conn->pktns, conn->mem);
+  pktns_del(conn->hs_pktns, conn->mem);
+  pktns_del(conn->in_pktns, conn->mem);
+
+  ngtcp2_pmtud_del(conn->pmtud);
+  ngtcp2_pv_del(conn->pv);
+
+  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_pq_free(&conn->tx.strmq);
+  ngtcp2_map_each(&conn->strms, delete_strms_each, (void *)conn);
+  ngtcp2_map_free(&conn->strms);
+
+  ngtcp2_pq_free(&conn->scid.used);
+  delete_scid(&conn->scid.set, conn->mem);
+  ngtcp2_ksl_free(&conn->scid.set);
+  ngtcp2_gaptr_free(&conn->dcid.seqgap);
+
+  ngtcp2_objalloc_free(&conn->strm_objalloc);
+  ngtcp2_objalloc_free(&conn->rtb_entry_objalloc);
+  ngtcp2_objalloc_free(&conn->frc_objalloc);
+
+  ngtcp2_mem_free(conn->mem, conn);
+}
+
+/*
+ * conn_compute_ack_delay computes ACK delay for outgoing protected
+ * ACK.
+ */
+static ngtcp2_duration conn_compute_ack_delay(ngtcp2_conn *conn) {
+  return ngtcp2_min_uint64(
+    conn->local.transport_params.max_ack_delay,
+    ngtcp2_max_uint64(conn->cstat.smoothed_rtt / 8, NGTCP2_NANOSECONDS));
+}
+
+/*
+ * conn_ppe_write_frame writes |fr| to |ppe|.  If |hd_logged| is not
+ * NULL and |*hd_logged| is zero, packet header is logged, and 1 is
+ * assigned to |*hd_logged|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer is too small.
+ */
+static int conn_ppe_write_frame_hd_log(ngtcp2_conn *conn, ngtcp2_ppe *ppe,
+                                       int *hd_logged, const ngtcp2_pkt_hd *hd,
+                                       ngtcp2_frame *fr) {
+  int rv;
+
+  rv = ngtcp2_ppe_encode_frame(ppe, fr);
+  if (rv != 0) {
+    assert(NGTCP2_ERR_NOBUF == rv);
+    return rv;
+  }
+
+  if (hd_logged && !*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, fr);
+  ngtcp2_qlog_write_frame(&conn->qlog, fr);
+
+  return 0;
+}
+
+/*
+ * conn_ppe_write_frame writes |fr| to |ppe|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer is too small.
+ */
+static int conn_ppe_write_frame(ngtcp2_conn *conn, ngtcp2_ppe *ppe,
+                                const ngtcp2_pkt_hd *hd, ngtcp2_frame *fr) {
+  return conn_ppe_write_frame_hd_log(conn, ppe, NULL, hd, fr);
+}
+
+/*
+ * conn_on_pkt_sent is called when new non-ACK-only packet is sent.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+static int conn_on_pkt_sent(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                            ngtcp2_rtb_entry *ent) {
+  ngtcp2_rtb *rtb = &pktns->rtb;
+  int rv;
+
+  /* This function implements OnPacketSent, but it handles only
+     non-ACK-only packet. */
+  rv = ngtcp2_rtb_add(rtb, ent, &conn->cstat);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
+    conn->cstat.last_tx_pkt_ts[pktns->id] = ent->ts;
+  }
+
+  ngtcp2_conn_set_loss_detection_timer(conn, ent->ts);
+
+  return 0;
+}
+
+/*
+ * pktns_select_pkt_numlen selects shortest packet number encoding for
+ * the next packet number based on the largest acknowledged packet
+ * number.  It returns the number of bytes to encode the packet
+ * number.
+ */
+static size_t pktns_select_pkt_numlen(ngtcp2_pktns *pktns) {
+  int64_t pkt_num = pktns->tx.last_pkt_num + 1;
+  ngtcp2_rtb *rtb = &pktns->rtb;
+  int64_t n = pkt_num - rtb->largest_acked_tx_pkt_num;
+
+  if (NGTCP2_MAX_PKT_NUM / 2 < n) {
+    return 4;
+  }
+
+  n = n * 2 - 1;
+
+  if (n > 0xffffff) {
+    return 4;
+  }
+  if (n > 0xffff) {
+    return 3;
+  }
+  if (n > 0xff) {
+    return 2;
+  }
+  return 1;
+}
+
+/*
+ * conn_cwnd_is_zero returns nonzero if the number of bytes the local
+ * endpoint can sent at this time is zero.
+ */
+static int conn_cwnd_is_zero(ngtcp2_conn *conn) {
+  uint64_t bytes_in_flight = conn->cstat.bytes_in_flight;
+  uint64_t cwnd = conn->cstat.cwnd;
+
+  if (bytes_in_flight >= cwnd) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC,
+                    "cwnd limited bytes_in_flight=%lu cwnd=%lu",
+                    bytes_in_flight, cwnd);
+  }
+
+  return bytes_in_flight >= cwnd;
+}
+
+/*
+ * 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.
+ */
+static uint64_t conn_retry_early_payloadlen(ngtcp2_conn *conn) {
+  ngtcp2_frame_chain *frc;
+  ngtcp2_strm *strm;
+  uint64_t len;
+
+  if (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) {
+    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;
+    }
+
+    frc = ngtcp2_strm_streamfrq_top(strm);
+
+    len = ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt) +
+          NGTCP2_STREAM_OVERHEAD;
+
+    /* Take the min because in conn_should_pad_pkt we take max in
+       order to deal with unbreakable DATAGRAM. */
+    return ngtcp2_min_uint64(len, NGTCP2_MIN_COALESCED_PAYLOADLEN);
+  }
+
+  return 0;
+}
+
+/*
+ * 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.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     |dcid| is not known to the local endpoint.
+ */
+static int conn_verify_dcid(ngtcp2_conn *conn, int *pnew_cid_used,
+                            const ngtcp2_pkt_hd *hd) {
+  ngtcp2_ksl_it it;
+  ngtcp2_scid *scid;
+  int rv;
+
+  it = ngtcp2_ksl_lower_bound(&conn->scid.set, &hd->dcid);
+  if (ngtcp2_ksl_it_end(&it)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  scid = ngtcp2_ksl_it_get(&it);
+  if (!ngtcp2_cid_eq(&scid->cid, &hd->dcid)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (!(scid->flags & NGTCP2_SCID_FLAG_USED)) {
+    scid->flags |= NGTCP2_SCID_FLAG_USED;
+
+    if (scid->pe.index == NGTCP2_PQ_BAD_INDEX) {
+      rv = ngtcp2_pq_push(&conn->scid.used, &scid->pe);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    if (pnew_cid_used) {
+      *pnew_cid_used = 1;
+    }
+  } else if (pnew_cid_used) {
+    *pnew_cid_used = 0;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_should_pad_pkt returns nonzero if the packet should be padded.
+ * |type| is the type of packet.  |left| is the space left in packet
+ * buffer.  |write_datalen| is the number of bytes which will be sent
+ * in the next, coalesced 0-RTT packet.
+ */
+static int conn_should_pad_pkt(ngtcp2_conn *conn, uint8_t type, size_t left,
+                               uint64_t write_datalen, int ack_eliciting,
+                               int require_padding) {
+  uint64_t min_payloadlen;
+
+  if (type == NGTCP2_PKT_INITIAL) {
+    if (conn->server) {
+      if (!ack_eliciting) {
+        return 0;
+      }
+
+      if ((conn->hs_pktns->crypto.tx.ckm &&
+           (conn->hs_pktns->rtb.probe_pkt_left ||
+            !ngtcp2_strm_streamfrq_empty(&conn->hs_pktns->crypto.strm) ||
+            !ngtcp2_acktr_empty(&conn->hs_pktns->acktr))) ||
+          conn->pktns.crypto.tx.ckm) {
+        /* If we have something to send in Handshake or 1RTT packet,
+           then add PADDING in that packet. */
+        min_payloadlen = NGTCP2_MIN_COALESCED_PAYLOADLEN;
+      } else {
+        return 1;
+      }
+    } else {
+      if (conn->hs_pktns->crypto.tx.ckm &&
+          (conn->hs_pktns->rtb.probe_pkt_left ||
+           !ngtcp2_strm_streamfrq_empty(&conn->hs_pktns->crypto.strm) ||
+           !ngtcp2_acktr_empty(&conn->hs_pktns->acktr))) {
+        /* If we have something to send in Handshake packet, then add
+           PADDING in Handshake packet. */
+        min_payloadlen = NGTCP2_MIN_COALESCED_PAYLOADLEN;
+      } else if (conn->early.ckm && write_datalen > 0) {
+        /* If we have something to send in 0RTT packet, then add
+           PADDING in that packet.  Take maximum in case that
+           write_datalen includes DATAGRAM which cannot be split. */
+        min_payloadlen =
+          ngtcp2_max_uint64(write_datalen, NGTCP2_MIN_COALESCED_PAYLOADLEN);
+      } else {
+        return 1;
+      }
+    }
+  } else {
+    assert(type == NGTCP2_PKT_HANDSHAKE);
+
+    if (!require_padding) {
+      return 0;
+    }
+
+    if (!conn->pktns.crypto.tx.ckm) {
+      return 1;
+    }
+
+    min_payloadlen = NGTCP2_MIN_COALESCED_PAYLOADLEN;
+  }
+
+  /* TODO the next packet type should be taken into account */
+  return left <
+         /* TODO Assuming that pkt_num is encoded in 1 byte. */
+         NGTCP2_MIN_LONG_HEADERLEN + conn->dcid.current.cid.datalen +
+           conn->oscid.datalen + NGTCP2_PKT_LENGTHLEN - 1 + min_payloadlen +
+           NGTCP2_MAX_AEAD_OVERHEAD;
+}
+
+static void conn_restart_timer_on_write(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  conn->idle_ts = ts;
+  conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE;
+}
+
+static void conn_restart_timer_on_read(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  conn->idle_ts = ts;
+  conn->flags |= NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE;
+}
+
+/*
+ * conn_keep_alive_enabled returns nonzero if keep-alive is enabled.
+ */
+static int conn_keep_alive_enabled(ngtcp2_conn *conn) {
+  return conn->keep_alive.last_ts != UINT64_MAX &&
+         conn->keep_alive.timeout != UINT64_MAX;
+}
+
+/*
+ * conn_keep_alive_expired returns nonzero if keep-alive timer has
+ * expired.
+ */
+static int conn_keep_alive_expired(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  return ngtcp2_tstamp_elapsed(conn->keep_alive.last_ts,
+                               conn->keep_alive.timeout, ts);
+}
+
+/*
+ * conn_keep_alive_expiry returns the expiry time of keep-alive timer.
+ */
+static ngtcp2_tstamp conn_keep_alive_expiry(ngtcp2_conn *conn) {
+  if ((conn->flags & NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED) ||
+      !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED) ||
+      !conn_keep_alive_enabled(conn) ||
+      conn->keep_alive.last_ts >= UINT64_MAX - conn->keep_alive.timeout) {
+    return UINT64_MAX;
+  }
+
+  return conn->keep_alive.last_ts + conn->keep_alive.timeout;
+}
+
+/*
+ * conn_cancel_expired_keep_alive_timer cancels the expired keep-alive
+ * timer.
+ */
+static void conn_cancel_expired_keep_alive_timer(ngtcp2_conn *conn,
+                                                 ngtcp2_tstamp ts) {
+  if (!(conn->flags & NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED) &&
+      conn_keep_alive_expired(conn, ts)) {
+    conn->flags |= NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED;
+  }
+}
+
+/*
+ * conn_update_keep_alive_last_ts updates the base time point of
+ * keep-alive timer.
+ */
+static void conn_update_keep_alive_last_ts(ngtcp2_conn *conn,
+                                           ngtcp2_tstamp ts) {
+  conn->keep_alive.last_ts = ts;
+  conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED;
+}
+
+void ngtcp2_conn_set_keep_alive_timeout(ngtcp2_conn *conn,
+                                        ngtcp2_duration timeout) {
+  if (timeout == 0) {
+    timeout = UINT64_MAX;
+  }
+
+  conn->keep_alive.timeout = timeout;
+}
+
+static void conn_cancel_expired_pkt_tx_timer(ngtcp2_conn *conn,
+                                             ngtcp2_tstamp ts) {
+  if (conn->tx.pacing.next_ts == UINT64_MAX) {
+    return;
+  }
+
+  if (conn->tx.pacing.next_ts > ts) {
+    return;
+  }
+
+  if (ts > conn->tx.pacing.next_ts) {
+    conn->tx.pacing.compensation += ts - conn->tx.pacing.next_ts;
+  }
+
+  conn->tx.pacing.next_ts = UINT64_MAX;
+}
+
+static int conn_pacing_pkt_tx_allowed(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  if (conn->tx.pacing.next_ts == UINT64_MAX) {
+    return 1;
+  }
+
+  if (conn->tx.pacing.next_ts > ts) {
+    return 0;
+  }
+
+  conn->tx.pacing.compensation += ts - conn->tx.pacing.next_ts;
+  conn->tx.pacing.next_ts = UINT64_MAX;
+
+  return 1;
+}
+
+static uint8_t conn_pkt_flags(ngtcp2_conn *conn) {
+  if (conn->remote.transport_params &&
+      conn->remote.transport_params->grease_quic_bit &&
+      (conn->flags & NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT)) {
+    return NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR;
+  }
+
+  return NGTCP2_PKT_FLAG_NONE;
+}
+
+static uint8_t conn_pkt_flags_long(ngtcp2_conn *conn) {
+  return NGTCP2_PKT_FLAG_LONG_FORM | conn_pkt_flags(conn);
+}
+
+static uint8_t conn_pkt_flags_short(ngtcp2_conn *conn) {
+  return (uint8_t)(conn_pkt_flags(conn) | ((conn->pktns.crypto.tx.ckm->flags &
+                                            NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE)
+                                             ? NGTCP2_PKT_FLAG_KEY_PHASE
+                                             : NGTCP2_PKT_FLAG_NONE));
+}
+
+static size_t conn_min_pktlen(ngtcp2_conn *conn);
+
+/*
+ * conn_write_handshake_pkt writes handshake packet in the buffer
+ * pointed by |dest| whose length is |destlen|.  |dgram_offset| is the
+ * offset in UDP datagram payload where this QUIC packet is positioned
+ * at.  |type| specifies long packet type.  It should be either
+ * NGTCP2_PKT_INITIAL or NGTCP2_PKT_HANDSHAKE_PKT.
+ *
+ * |write_datalen| is the minimum length of application data ready to
+ * send in subsequent 0RTT packet.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static ngtcp2_ssize
+conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest,
+                         size_t destlen, size_t dgram_offset, uint8_t type,
+                         uint8_t flags, uint64_t write_datalen,
+                         ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_ppe ppe;
+  ngtcp2_pkt_hd hd;
+  ngtcp2_frame_chain *frq = NULL, **pfrc = &frq;
+  ngtcp2_frame_chain *nfrc;
+  ngtcp2_max_frame mfr;
+  ngtcp2_frame *ackfr = NULL, lfr;
+  ngtcp2_ssize spktlen;
+  ngtcp2_crypto_cc cc;
+  ngtcp2_rtb_entry *rtbent;
+  ngtcp2_pktns *pktns;
+  size_t left;
+  uint16_t rtb_entry_flags = NGTCP2_RTB_ENTRY_FLAG_NONE;
+  int require_padding = (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) != 0;
+  int pkt_empty = 1;
+  int min_padded = 0;
+  int padded = 0;
+  int hd_logged = 0;
+  uint64_t crypto_offset;
+  ngtcp2_ssize num_reclaimed;
+  uint32_t version;
+
+  switch (type) {
+  case NGTCP2_PKT_INITIAL:
+    if (!conn->in_pktns) {
+      return 0;
+    }
+    assert(conn->in_pktns->crypto.tx.ckm);
+    pktns = conn->in_pktns;
+    version = conn->negotiated_version ? conn->negotiated_version
+                                       : conn->client_chosen_version;
+    if (version == conn->client_chosen_version) {
+      cc.ckm = pktns->crypto.tx.ckm;
+      cc.hp_ctx = pktns->crypto.tx.hp_ctx;
+    } else {
+      assert(conn->vneg.version == version);
+
+      cc.ckm = conn->vneg.tx.ckm;
+      cc.hp_ctx = conn->vneg.tx.hp_ctx;
+    }
+    break;
+  case NGTCP2_PKT_HANDSHAKE:
+    if (!conn->hs_pktns || !conn->hs_pktns->crypto.tx.ckm) {
+      return 0;
+    }
+    pktns = conn->hs_pktns;
+    version = conn->negotiated_version;
+    cc.ckm = pktns->crypto.tx.ckm;
+    cc.hp_ctx = pktns->crypto.tx.hp_ctx;
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  cc.aead = pktns->crypto.ctx.aead;
+  cc.hp = pktns->crypto.ctx.hp;
+  cc.encrypt = conn->callbacks.encrypt;
+  cc.hp_mask = conn->callbacks.hp_mask;
+
+  ngtcp2_pkt_hd_init(
+    &hd, conn_pkt_flags_long(conn), type, &conn->dcid.current.cid, &conn->oscid,
+    pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), version);
+
+  if (!conn->server && type == NGTCP2_PKT_INITIAL &&
+      conn->local.settings.tokenlen) {
+    hd.token = conn->local.settings.token;
+    hd.tokenlen = conn->local.settings.tokenlen;
+  }
+
+  ngtcp2_ppe_init(&ppe, dest, destlen, dgram_offset, &cc);
+
+  rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
+  if (rv != 0) {
+    assert(NGTCP2_ERR_NOBUF == rv);
+    return 0;
+  }
+
+  if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) {
+    return 0;
+  }
+
+  ackfr = ngtcp2_acktr_create_ack_frame(&pktns->acktr, &mfr.fr, type, ts,
+                                        /* ack_delay = */ 0,
+                                        NGTCP2_DEFAULT_ACK_DELAY_EXPONENT);
+  if (ackfr) {
+    rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, ackfr);
+    if (rv != 0) {
+      assert(NGTCP2_ERR_NOBUF == rv);
+    } else {
+      ngtcp2_acktr_commit_ack(&pktns->acktr);
+      ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, ackfr->ack.largest_ack);
+      pkt_empty = 0;
+    }
+  }
+
+  /* Server requires at least NGTCP2_MAX_UDP_PAYLOAD_SIZE bytes in
+     order to send ack-eliciting Initial packet. */
+  if (!conn->server || type != NGTCP2_PKT_INITIAL ||
+      destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE) {
+  build_pkt:
+    for (; !ngtcp2_strm_streamfrq_empty(&pktns->crypto.strm);) {
+      left = ngtcp2_ppe_left(&ppe);
+
+      crypto_offset = ngtcp2_strm_streamfrq_unacked_offset(&pktns->crypto.strm);
+      if (crypto_offset == (uint64_t)-1) {
+        ngtcp2_strm_streamfrq_clear(&pktns->crypto.strm);
+        break;
+      }
+
+      left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left);
+      if (left == (size_t)-1) {
+        break;
+      }
+
+      rv = ngtcp2_strm_streamfrq_pop(&pktns->crypto.strm, &nfrc, left);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc,
+                                             conn->mem);
+        return rv;
+      }
+
+      if (nfrc == NULL) {
+        break;
+      }
+
+      rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &nfrc->fr);
+      if (rv != 0) {
+        ngtcp2_unreachable();
+      }
+
+      *pfrc = nfrc;
+      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;
+    }
+
+    if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) &&
+        pktns->rtb.num_retransmittable && pktns->rtb.probe_pkt_left) {
+      num_reclaimed = ngtcp2_rtb_reclaim_on_pto(&pktns->rtb, conn, pktns, 1);
+      if (num_reclaimed < 0) {
+        ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc,
+                                             conn->mem);
+        return num_reclaimed;
+      }
+      if (num_reclaimed) {
+        goto build_pkt;
+      }
+      /* 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. */
+      }
+    }
+
+    if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) &&
+        pktns->rtb.probe_pkt_left) {
+      lfr.type = NGTCP2_FRAME_PING;
+
+      rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr);
+      if (rv != 0) {
+        assert(rv == NGTCP2_ERR_NOBUF);
+      } else {
+        rtb_entry_flags |=
+          NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | NGTCP2_RTB_ENTRY_FLAG_PROBE;
+        pkt_empty = 0;
+      }
+    }
+
+    if (!pkt_empty) {
+      if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+        if (ngtcp2_tstamp_elapsed(pktns->tx.non_ack_pkt_start_ts,
+                                  conn->cstat.smoothed_rtt, ts)) {
+          lfr.type = NGTCP2_FRAME_PING;
+
+          rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr);
+          if (rv != 0) {
+            assert(rv == NGTCP2_ERR_NOBUF);
+          } else {
+            rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING;
+            pktns->tx.non_ack_pkt_start_ts = UINT64_MAX;
+            pkt_empty = 0;
+          }
+        } else if (pktns->tx.non_ack_pkt_start_ts == UINT64_MAX) {
+          pktns->tx.non_ack_pkt_start_ts = ts;
+        }
+      } else {
+        pktns->tx.non_ack_pkt_start_ts = UINT64_MAX;
+      }
+    }
+  }
+
+  if (pkt_empty && !require_padding) {
+    return 0;
+  }
+
+  /* If we cannot write another packet, then we need to add padding to
+     Initial here. */
+  if (conn_should_pad_pkt(
+        conn, type, ngtcp2_ppe_left(&ppe), write_datalen,
+        (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) != 0,
+        require_padding)) {
+    lfr.type = NGTCP2_FRAME_PADDING;
+    lfr.padding.len = ngtcp2_ppe_dgram_padding(&ppe);
+  } else if (pkt_empty) {
+    return 0;
+  } else {
+    lfr.type = NGTCP2_FRAME_PADDING;
+    lfr.padding.len = ngtcp2_ppe_padding_size(&ppe, conn_min_pktlen(conn));
+    min_padded = 1;
+  }
+
+  if (lfr.padding.len) {
+    if (!min_padded ||
+        (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+      padded = 1;
+    }
+    ngtcp2_log_tx_fr(&conn->log, &hd, &lfr);
+    ngtcp2_qlog_write_frame(&conn->qlog, &lfr);
+  }
+
+  spktlen = ngtcp2_ppe_final(&ppe, NULL);
+  if (spktlen < 0) {
+    assert(ngtcp2_err_is_fatal((int)spktlen));
+    ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem);
+    return spktlen;
+  }
+
+  ngtcp2_qlog_pkt_sent_end(&conn->qlog, &hd, (size_t)spktlen);
+
+  if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) {
+    if (pi) {
+      conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, &hd, ts);
+    }
+
+    rv =
+      ngtcp2_rtb_entry_objalloc_new(&rtbent, &hd, frq, ts, (size_t)spktlen,
+                                    rtb_entry_flags, &conn->rtb_entry_objalloc);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem);
+      return rv;
+    }
+
+    rv = conn_on_pkt_sent(conn, pktns, rtbent);
+    if (rv != 0) {
+      ngtcp2_rtb_entry_objalloc_del(rtbent, &conn->rtb_entry_objalloc,
+                                    &conn->frc_objalloc, conn->mem);
+      return rv;
+    }
+
+    if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) &&
+        (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE)) {
+      conn_restart_timer_on_write(conn, ts);
+    }
+  } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) {
+    conn_handle_tx_ecn(conn, pi, NULL, pktns, &hd, ts);
+  }
+
+  if (pktns->rtb.probe_pkt_left &&
+      (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+    --pktns->rtb.probe_pkt_left;
+  }
+
+  conn_update_keep_alive_last_ts(conn, ts);
+
+  conn->dcid.current.bytes_sent += (uint64_t)spktlen;
+
+  conn->tx.pacing.pktlen += (size_t)spktlen;
+
+  ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat);
+
+  ++pktns->tx.last_pkt_num;
+
+  return spktlen;
+}
+
+/*
+ * conn_write_ack_pkt writes QUIC packet for type |type| which only
+ * includes ACK frame in the buffer pointed by |dest| whose length is
+ * |destlen|.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static ngtcp2_ssize conn_write_ack_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
+                                       uint8_t *dest, size_t destlen,
+                                       uint8_t type, ngtcp2_tstamp ts) {
+  ngtcp2_frame *ackfr;
+  ngtcp2_pktns *pktns;
+  ngtcp2_duration ack_delay;
+  uint64_t ack_delay_exponent;
+  ngtcp2_ssize spktlen;
+  ngtcp2_max_frame mfr;
+
+  assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING));
+
+  switch (type) {
+  case NGTCP2_PKT_INITIAL:
+    assert(conn->server);
+    pktns = conn->in_pktns;
+    ack_delay = 0;
+    ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT;
+    break;
+  case NGTCP2_PKT_HANDSHAKE:
+    pktns = conn->hs_pktns;
+    ack_delay = 0;
+    ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT;
+    break;
+  case NGTCP2_PKT_1RTT:
+    pktns = &conn->pktns;
+    ack_delay = conn_compute_ack_delay(conn);
+    ack_delay_exponent = conn->local.transport_params.ack_delay_exponent;
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  if (!pktns->crypto.tx.ckm) {
+    return 0;
+  }
+
+  ackfr = ngtcp2_acktr_create_ack_frame(&pktns->acktr, &mfr.fr, type, ts,
+                                        ack_delay, ack_delay_exponent);
+  if (!ackfr) {
+    return 0;
+  }
+
+  spktlen = ngtcp2_conn_write_single_frame_pkt(
+    conn, pi, dest, destlen, type, NGTCP2_WRITE_PKT_FLAG_NONE,
+    &conn->dcid.current.cid, ackfr, NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts);
+
+  if (spktlen <= 0) {
+    return spktlen;
+  }
+
+  conn->dcid.current.bytes_sent += (uint64_t)spktlen;
+
+  return spktlen;
+}
+
+static void conn_discard_pktns(ngtcp2_conn *conn, ngtcp2_pktns **ppktns,
+                               ngtcp2_tstamp ts) {
+  ngtcp2_pktns *pktns = *ppktns;
+  uint64_t bytes_in_flight;
+
+  bytes_in_flight = pktns->rtb.cc_bytes_in_flight;
+
+  assert(conn->cstat.bytes_in_flight >= bytes_in_flight);
+
+  conn->cstat.bytes_in_flight -= bytes_in_flight;
+  conn->cstat.pto_count = 0;
+  conn->cstat.last_tx_pkt_ts[pktns->id] = UINT64_MAX;
+  conn->cstat.loss_time[pktns->id] = UINT64_MAX;
+
+  conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.rx.ckm->aead_ctx);
+  conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.rx.hp_ctx);
+  conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx);
+  conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.tx.hp_ctx);
+
+  pktns_del(pktns, conn->mem);
+  *ppktns = NULL;
+
+  ngtcp2_conn_set_loss_detection_timer(conn, ts);
+}
+
+void ngtcp2_conn_discard_initial_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  if (!conn->in_pktns) {
+    return;
+  }
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                  "discarding Initial packet number space");
+
+  conn_discard_pktns(conn, &conn->in_pktns, ts);
+
+  conn_vneg_crypto_free(conn);
+
+  memset(&conn->vneg.rx, 0, sizeof(conn->vneg.rx));
+  memset(&conn->vneg.tx, 0, sizeof(conn->vneg.tx));
+}
+
+void ngtcp2_conn_discard_handshake_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  if (!conn->hs_pktns) {
+    return;
+  }
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                  "discarding Handshake packet number space");
+
+  conn_discard_pktns(conn, &conn->hs_pktns, ts);
+}
+
+/*
+ * conn_discard_early_key discards early key.
+ */
+static void conn_discard_early_key(ngtcp2_conn *conn) {
+  assert(conn->early.ckm);
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "discarding early key");
+
+  conn_call_delete_crypto_aead_ctx(conn, &conn->early.ckm->aead_ctx);
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->early.hp_ctx);
+  memset(&conn->early.hp_ctx, 0, sizeof(conn->early.hp_ctx));
+
+  ngtcp2_crypto_km_del(conn->early.ckm, conn->mem);
+  conn->early.ckm = NULL;
+}
+
+/*
+ * conn_write_handshake_ack_pkts writes packets which contain ACK
+ * frame only.  This function writes at most 2 packets for each
+ * Initial and Handshake packet.
+ */
+static ngtcp2_ssize conn_write_handshake_ack_pkts(ngtcp2_conn *conn,
+                                                  ngtcp2_pkt_info *pi,
+                                                  uint8_t *dest, size_t destlen,
+                                                  ngtcp2_tstamp ts) {
+  ngtcp2_ssize res = 0, nwrite = 0;
+
+  /* In the most cases, client sends ACK in conn_write_handshake_pkt.
+     This function is only called when it is CWND limited or pacing
+     limited.  It is not required for client to send ACK for server
+     Initial.  This is because once it gets server Initial, it gets
+     Handshake tx key and discards Initial key.  The only good reason
+     to send ACK is give server RTT measurement early. */
+  if (conn->server && conn->in_pktns) {
+    nwrite =
+      conn_write_ack_pkt(conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, ts);
+    if (nwrite < 0) {
+      assert(nwrite != NGTCP2_ERR_NOBUF);
+      return nwrite;
+    }
+
+    res += nwrite;
+    dest += nwrite;
+    destlen -= (size_t)nwrite;
+  }
+
+  if (conn->hs_pktns->crypto.tx.ckm) {
+    nwrite =
+      conn_write_ack_pkt(conn, pi, dest, destlen, NGTCP2_PKT_HANDSHAKE, ts);
+    if (nwrite < 0) {
+      assert(nwrite != NGTCP2_ERR_NOBUF);
+      return nwrite;
+    }
+
+    res += nwrite;
+
+    if (!conn->server && nwrite) {
+      ngtcp2_conn_discard_initial_state(conn, ts);
+    }
+  }
+
+  return res;
+}
+
+/*
+ * conn_write_client_initial writes Initial packet in the buffer
+ * pointed by |dest| whose length is |destlen|.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static ngtcp2_ssize conn_write_client_initial(ngtcp2_conn *conn,
+                                              ngtcp2_pkt_info *pi,
+                                              uint8_t *dest, size_t destlen,
+                                              uint64_t early_datalen,
+                                              ngtcp2_tstamp ts) {
+  int rv;
+
+  rv = conn_call_client_initial(conn);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return conn_write_handshake_pkt(
+    conn, pi, dest, destlen, 0, NGTCP2_PKT_INITIAL, NGTCP2_WRITE_PKT_FLAG_NONE,
+    early_datalen, ts);
+}
+
+/*
+ * dcid_tx_left returns the maximum number of bytes that server is
+ * allowed to send to an unvalidated path associated to |dcid|.
+ */
+static uint64_t dcid_tx_left(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. */
+  assert(dcid->bytes_recv * 3 >= dcid->bytes_sent);
+
+  return dcid->bytes_recv * 3 - dcid->bytes_sent;
+}
+
+/*
+ * conn_server_tx_left returns the maximum number of bytes that server
+ * is allowed to send to an unvalidated path.
+ */
+static uint64_t conn_server_tx_left(ngtcp2_conn *conn, ngtcp2_dcid *dcid) {
+  assert(conn->server);
+
+  /* If pv->dcid has the current path, use conn->dcid.current.  This
+     is because conn->dcid.current gets update for bytes_recv and
+     bytes_sent. */
+  if (ngtcp2_path_eq(&dcid->ps.path, &conn->dcid.current.ps.path)) {
+    return dcid_tx_left(&conn->dcid.current);
+  }
+
+  return dcid_tx_left(dcid);
+}
+
+/*
+ * conn_write_handshake_pkts writes Initial and Handshake packets in
+ * the buffer pointed by |dest| whose length is |destlen|.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static ngtcp2_ssize conn_write_handshake_pkts(ngtcp2_conn *conn,
+                                              ngtcp2_pkt_info *pi,
+                                              uint8_t *dest, size_t destlen,
+                                              uint64_t write_datalen,
+                                              ngtcp2_tstamp ts) {
+  ngtcp2_ssize nwrite;
+  ngtcp2_ssize res = 0;
+  ngtcp2_rtb_entry *rtbent;
+  uint8_t wflags = NGTCP2_WRITE_PKT_FLAG_NONE;
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  ngtcp2_ksl_it it;
+
+  /* As a client, we would like to discard Initial packet number space
+     when sending the first Handshake packet.  When sending Handshake
+     packet, it should be one of 1) sending ACK, 2) sending PTO probe
+     packet, or 3) sending CRYPTO.  If we have pending acknowledgement
+     for Initial, then do not discard Initial packet number space.
+     Otherwise, if either 1) or 2) is satisfied, discard Initial
+     packet number space.  When sending Handshake CRYPTO, it indicates
+     that client has received Handshake CRYPTO from server.  Initial
+     packet number space is discarded because 1) is met.  If there is
+     pending Initial ACK, Initial packet number space is discarded
+     after writing the first Handshake packet.
+   */
+  if (!conn->server && conn->hs_pktns->crypto.tx.ckm && conn->in_pktns &&
+      !ngtcp2_acktr_require_active_ack(&conn->in_pktns->acktr,
+                                       /* max_ack_delay = */ 0, ts) &&
+      (ngtcp2_acktr_require_active_ack(&conn->hs_pktns->acktr,
+                                       /* max_ack_delay = */ 0, ts) ||
+       conn->hs_pktns->rtb.probe_pkt_left)) {
+    /* Discard Initial state here so that Handshake packet is not
+       padded. */
+    ngtcp2_conn_discard_initial_state(conn, ts);
+  } else if (conn->in_pktns) {
+    nwrite =
+      conn_write_handshake_pkt(conn, pi, dest, destlen, 0, NGTCP2_PKT_INITIAL,
+                               NGTCP2_WRITE_PKT_FLAG_NONE, write_datalen, ts);
+    if (nwrite < 0) {
+      assert(nwrite != NGTCP2_ERR_NOBUF);
+      return nwrite;
+    }
+
+    if (nwrite == 0) {
+      if (conn->server &&
+          (conn->in_pktns->rtb.probe_pkt_left ||
+           !ngtcp2_strm_streamfrq_empty(&conn->in_pktns->crypto.strm))) {
+        if (cstat->loss_detection_timer != UINT64_MAX &&
+            conn_server_tx_left(conn, &conn->dcid.current) <
+              NGTCP2_MAX_UDP_PAYLOAD_SIZE) {
+          ngtcp2_log_info(
+            &conn->log, NGTCP2_LOG_EVENT_LDC,
+            "loss detection timer canceled due to amplification limit");
+          ngtcp2_conn_cancel_loss_detection_timer(conn);
+        }
+
+        return 0;
+      }
+    } else {
+      res += nwrite;
+      dest += nwrite;
+      destlen -= (size_t)nwrite;
+
+      /* If initial packet size is at least
+         NGTCP2_MAX_UDP_PAYLOAD_SIZE, no extra padding is needed in a
+         subsequent packet. */
+      if (nwrite < NGTCP2_MAX_UDP_PAYLOAD_SIZE) {
+        if (conn->server) {
+          it = ngtcp2_rtb_head(&conn->in_pktns->rtb);
+          if (!ngtcp2_ksl_it_end(&it)) {
+            rtbent = ngtcp2_ksl_it_get(&it);
+            if (rtbent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
+              wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING;
+            }
+          }
+        } else {
+          wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING;
+        }
+      }
+    }
+  }
+
+  nwrite =
+    conn_write_handshake_pkt(conn, pi, dest, destlen, (size_t)res,
+                             NGTCP2_PKT_HANDSHAKE, wflags, write_datalen, ts);
+  if (nwrite < 0) {
+    assert(nwrite != NGTCP2_ERR_NOBUF);
+    return nwrite;
+  }
+
+  res += nwrite;
+
+  if (!conn->server && conn->hs_pktns->crypto.tx.ckm && nwrite) {
+    /* We don't need to send further Initial packet if we have
+       Handshake key and sent something with it.  So discard initial
+       state here. */
+    ngtcp2_conn_discard_initial_state(conn, ts);
+  }
+
+  return res;
+}
+
+/*
+ * conn_initial_stream_rx_offset returns the initial maximum offset of
+ * data for a stream denoted by |stream_id|.
+ */
+static uint64_t conn_initial_stream_rx_offset(ngtcp2_conn *conn,
+                                              int64_t stream_id) {
+  int local_stream = conn_local_stream(conn, stream_id);
+
+  if (bidi_stream(stream_id)) {
+    if (local_stream) {
+      return conn->local.transport_params.initial_max_stream_data_bidi_local;
+    }
+    return conn->local.transport_params.initial_max_stream_data_bidi_remote;
+  }
+
+  if (local_stream) {
+    return 0;
+  }
+  return conn->local.transport_params.initial_max_stream_data_uni;
+}
+
+/*
+ * conn_should_send_max_stream_data returns nonzero if MAX_STREAM_DATA
+ * frame should be send for |strm|.
+ */
+static int conn_should_send_max_stream_data(ngtcp2_conn *conn,
+                                            ngtcp2_strm *strm) {
+  uint64_t inc = strm->rx.unsent_max_offset - strm->rx.max_offset;
+  (void)conn;
+
+  return strm->rx.window < 4 * inc;
+}
+
+/*
+ * conn_should_send_max_data returns nonzero if MAX_DATA frame should
+ * be sent.
+ */
+static int conn_should_send_max_data(ngtcp2_conn *conn) {
+  uint64_t inc = conn->rx.unsent_max_offset - conn->rx.max_offset;
+
+  return conn->rx.window < 4 * inc;
+}
+
+/*
+ * conn_required_num_new_connection_id returns the number of
+ * additional connection ID the local endpoint has to provide to the
+ * remote endpoint.
+ */
+static size_t conn_required_num_new_connection_id(ngtcp2_conn *conn) {
+  uint64_t n;
+  size_t len = ngtcp2_ksl_len(&conn->scid.set);
+  size_t lim;
+
+  if (len >= NGTCP2_MAX_SCID_POOL_SIZE) {
+    return 0;
+  }
+
+  assert(NGTCP2_MAX_SCID_POOL_SIZE >= conn->scid.num_in_flight);
+
+  lim = NGTCP2_MAX_SCID_POOL_SIZE - conn->scid.num_in_flight;
+  if (lim == 0) {
+    return 0;
+  }
+
+  assert(conn->remote.transport_params);
+  assert(conn->remote.transport_params->active_connection_id_limit);
+
+  /* len includes retired CID.  We don't provide extra CID if doing so
+     exceeds NGTCP2_MAX_SCID_POOL_SIZE. */
+
+  n = conn->remote.transport_params->active_connection_id_limit +
+      conn->scid.num_retired;
+
+  n = ngtcp2_min_uint64(NGTCP2_MAX_SCID_POOL_SIZE, n) - len;
+
+  return (size_t)ngtcp2_min_uint64(lim, n);
+}
+
+/*
+ * conn_enqueue_new_connection_id generates additional connection IDs
+ * and prepares to send them to the remote endpoint.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_enqueue_new_connection_id(ngtcp2_conn *conn) {
+  size_t i, need = conn_required_num_new_connection_id(conn);
+  size_t cidlen = conn->oscid.datalen;
+  ngtcp2_cid cid;
+  uint64_t seq;
+  int rv;
+  uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN];
+  ngtcp2_frame_chain *nfrc;
+  ngtcp2_pktns *pktns = &conn->pktns;
+  ngtcp2_scid *scid;
+  ngtcp2_ksl_it it;
+
+  for (i = 0; i < need; ++i) {
+    rv = conn_call_get_new_connection_id(conn, &cid, token, cidlen);
+    if (rv != 0) {
+      return rv;
+    }
+
+    if (cid.datalen != cidlen) {
+      return NGTCP2_ERR_CALLBACK_FAILURE;
+    }
+
+    /* Assert uniqueness */
+    it = ngtcp2_ksl_lower_bound(&conn->scid.set, &cid);
+    if (!ngtcp2_ksl_it_end(&it) &&
+        ngtcp2_cid_eq(ngtcp2_ksl_it_key(&it), &cid)) {
+      return NGTCP2_ERR_CALLBACK_FAILURE;
+    }
+
+    seq = ++conn->scid.last_seq;
+
+    scid = ngtcp2_mem_malloc(conn->mem, sizeof(*scid));
+    if (scid == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+
+    ngtcp2_scid_init(scid, seq, &cid);
+
+    rv = ngtcp2_ksl_insert(&conn->scid.set, NULL, &scid->cid, scid);
+    if (rv != 0) {
+      ngtcp2_mem_free(conn->mem, scid);
+      return rv;
+    }
+
+    rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+    if (rv != 0) {
+      return rv;
+    }
+
+    nfrc->fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID;
+    nfrc->fr.new_connection_id.seq = seq;
+    nfrc->fr.new_connection_id.retire_prior_to = 0;
+    nfrc->fr.new_connection_id.cid = cid;
+    memcpy(nfrc->fr.new_connection_id.stateless_reset_token, token,
+           sizeof(token));
+    nfrc->next = pktns->tx.frq;
+    pktns->tx.frq = nfrc;
+
+    assert(NGTCP2_MAX_SCID_POOL_SIZE > conn->scid.num_in_flight);
+
+    ++conn->scid.num_in_flight;
+  }
+
+  return 0;
+}
+
+static int dcidtr_on_deactivate(const ngtcp2_dcid *dcid, void *user_data) {
+  return conn_call_deactivate_dcid(user_data, dcid);
+}
+
+/*
+ * conn_remove_retired_connection_id removes the already retired
+ * connection ID.  It waits PTO before actually removing a connection
+ * ID after it receives RETIRE_CONNECTION_ID from peer to catch
+ * reordered packets.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_remove_retired_connection_id(ngtcp2_conn *conn,
+                                             ngtcp2_duration pto,
+                                             ngtcp2_tstamp ts) {
+  ngtcp2_duration timeout = pto;
+  ngtcp2_scid *scid;
+  int rv;
+
+  for (; !ngtcp2_pq_empty(&conn->scid.used);) {
+    scid = ngtcp2_struct_of(ngtcp2_pq_top(&conn->scid.used), ngtcp2_scid, pe);
+
+    if (!ngtcp2_tstamp_elapsed(scid->retired_ts, timeout, ts)) {
+      break;
+    }
+
+    assert(scid->flags & NGTCP2_SCID_FLAG_RETIRED);
+
+    rv = conn_call_remove_connection_id(conn, &scid->cid);
+    if (rv != 0) {
+      return rv;
+    }
+
+    ngtcp2_ksl_remove(&conn->scid.set, NULL, &scid->cid);
+    ngtcp2_pq_pop(&conn->scid.used);
+    ngtcp2_mem_free(conn->mem, scid);
+
+    assert(conn->scid.num_retired);
+    --conn->scid.num_retired;
+  }
+
+  rv = ngtcp2_dcidtr_remove_stale_retired_dcid(&conn->dcid.dtr, timeout, ts,
+                                               dcidtr_on_deactivate, conn);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_min_pktlen returns the minimum length of packet this endpoint
+ * sends.  It may underestimate the length because this does not take
+ * into account header protection sample.
+ */
+static size_t conn_min_pktlen(ngtcp2_conn *conn) {
+  return conn->oscid.datalen + NGTCP2_MIN_PKT_EXPANDLEN;
+}
+
+/*
+ * conn_handle_unconfirmed_key_update_from_remote deals with key
+ * update which has not been confirmed yet and initiated by the remote
+ * endpoint.
+ *
+ * If key update was initiated by the remote endpoint, acknowledging a
+ * packet encrypted with the new key completes key update procedure.
+ */
+static void conn_handle_unconfirmed_key_update_from_remote(ngtcp2_conn *conn,
+                                                           int64_t largest_ack,
+                                                           ngtcp2_tstamp ts) {
+  if (!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) ||
+      (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR) ||
+      largest_ack < conn->pktns.crypto.rx.ckm->pkt_num) {
+    return;
+  }
+
+  conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED;
+  conn->crypto.key_update.confirmed_ts = ts;
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CRY, "key update confirmed");
+}
+
+static uint64_t conn_tx_strmq_first_cycle(ngtcp2_conn *conn);
+
+/*
+ * strm_should_send_stream_data_blocked returns nonzero if
+ * STREAM_DATA_BLOCKED frame should be sent to |strm|.
+ */
+static int strm_should_send_stream_data_blocked(ngtcp2_strm *strm) {
+  return strm->tx.offset == strm->tx.max_offset &&
+         strm->tx.last_blocked_offset != strm->tx.max_offset;
+}
+
+/*
+ * conn_should_send_data_blocked returns nonzero if DATA_BLOCKED frame
+ * should be sent.
+ */
+static int conn_should_send_data_blocked(ngtcp2_conn *conn) {
+  return conn->tx.offset == conn->tx.max_offset &&
+         conn->tx.last_blocked_offset != conn->tx.max_offset;
+}
+
+/*
+ * conn_reset_ppe_pending clears NGTCP2_CONN_FLAG_PPE_PENDING flag and
+ * nullifies conn->pkt.
+ */
+static void conn_reset_ppe_pending(ngtcp2_conn *conn) {
+  conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_PPE_PENDING;
+
+  memset(&conn->pkt, 0, sizeof(conn->pkt));
+}
+
+/*
+ * conn_write_pkt writes a protected packet in the buffer pointed by
+ * |dest| whose length if |destlen|.  |dgram_offset| is the offset in
+ * UDP datagram payload where this QUIC packet is positioned at.
+ * |type| specifies the type of packet.  It can be NGTCP2_PKT_1RTT or
+ * NGTCP2_PKT_0RTT.
+ *
+ * This function can send new stream data.  In order to send stream
+ * data, specify the underlying stream and parameters to
+ * |vmsg|->stream.  If |vmsg|->stream.fin is set to nonzero, it
+ * signals that the given data is the final portion of the stream.
+ * |vmsg|->stream.data vector of length |vmsg|->stream.datacnt
+ * specifies stream data to send.  The number of bytes sent to the
+ * stream is assigned to *|vmsg|->stream.pdatalen.  If 0 length STREAM
+ * data is sent, 0 is assigned to it.  The caller should initialize
+ * *|vmsg|->stream.pdatalen to -1.
+ *
+ * If |require_padding| is nonzero, padding bytes are added to occupy
+ * the remaining packet payload.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ * NGTCP2_ERR_STREAM_DATA_BLOCKED
+ *     Stream data could not be written because of flow control.
+ */
+static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
+                                   uint8_t *dest, size_t destlen,
+                                   size_t dgram_offset, ngtcp2_vmsg *vmsg,
+                                   uint8_t type, uint8_t flags,
+                                   ngtcp2_tstamp ts) {
+  int rv = 0;
+  ngtcp2_crypto_cc *cc = &conn->pkt.cc;
+  ngtcp2_ppe *ppe = &conn->pkt.ppe;
+  ngtcp2_pkt_hd *hd = &conn->pkt.hd;
+  ngtcp2_max_frame mfr;
+  ngtcp2_frame *ackfr = NULL, lfr;
+  ngtcp2_ssize nwrite;
+  ngtcp2_frame_chain **pfrc, *nfrc, *frc;
+  ngtcp2_rtb_entry *ent;
+  ngtcp2_strm *strm;
+  int pkt_empty = 1;
+  uint64_t ndatalen = 0;
+  int send_stream = 0;
+  int stream_blocked = 0;
+  int send_datagram = 0;
+  ngtcp2_pktns *pktns = &conn->pktns;
+  size_t left;
+  uint64_t datalen = 0;
+  ngtcp2_vec data[NGTCP2_MAX_STREAM_DATACNT];
+  size_t datacnt;
+  uint16_t rtb_entry_flags = NGTCP2_RTB_ENTRY_FLAG_NONE;
+  int hd_logged = 0;
+  ngtcp2_path_challenge_entry *pcent;
+  uint8_t hd_flags = NGTCP2_PKT_FLAG_NONE;
+  int require_padding = (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) != 0;
+  int write_more = (flags & NGTCP2_WRITE_PKT_FLAG_MORE) != 0;
+  int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0;
+  size_t min_pktlen = conn_min_pktlen(conn);
+  int min_padded = 0;
+  int padded = 0;
+  ngtcp2_cc_pkt cc_pkt;
+  uint64_t crypto_offset;
+  uint64_t stream_offset;
+  ngtcp2_ssize num_reclaimed;
+  int fin;
+  uint64_t target_max_data;
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  uint64_t delta;
+  const ngtcp2_cid *scid = NULL;
+  int keep_alive_expired = 0;
+  uint32_t version = 0;
+
+  /* Return 0 if destlen is less than minimum packet length which can
+     trigger Stateless Reset */
+  if (destlen < min_pktlen) {
+    return 0;
+  }
+
+  if (vmsg) {
+    switch (vmsg->type) {
+    case NGTCP2_VMSG_TYPE_STREAM:
+      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) {
+        send_stream = 1;
+      } else {
+        stream_blocked = 1;
+      }
+      break;
+    case NGTCP2_VMSG_TYPE_DATAGRAM:
+      datalen = ngtcp2_vec_len(vmsg->datagram.data, vmsg->datagram.datacnt);
+      send_datagram = 1;
+      break;
+    default:
+      break;
+    }
+  }
+
+  if (!ppe_pending) {
+    switch (type) {
+    case NGTCP2_PKT_1RTT:
+      hd_flags = conn_pkt_flags_short(conn);
+      scid = NULL;
+      cc->aead = pktns->crypto.ctx.aead;
+      cc->hp = pktns->crypto.ctx.hp;
+      cc->ckm = pktns->crypto.tx.ckm;
+      cc->hp_ctx = pktns->crypto.tx.hp_ctx;
+
+      assert(conn->negotiated_version);
+
+      version = conn->negotiated_version;
+
+      /* transport parameter is only valid after handshake completion
+         which means we don't know how many connection ID that remote
+         peer can accept before handshake completion.  Because server
+         can use remote transport parameters sending stream data in
+         0.5 RTT, it is also allowed to use remote transport
+         parameters here.  */
+      if (conn->oscid.datalen &&
+          (conn->server || conn_is_tls_handshake_completed(conn))) {
+        rv = conn_enqueue_new_connection_id(conn);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+
+      break;
+    case NGTCP2_PKT_0RTT:
+      assert(!conn->server);
+      if (!conn->early.ckm) {
+        return 0;
+      }
+      hd_flags = conn_pkt_flags_long(conn);
+      scid = &conn->oscid;
+      cc->aead = conn->early.ctx.aead;
+      cc->hp = conn->early.ctx.hp;
+      cc->ckm = conn->early.ckm;
+      cc->hp_ctx = conn->early.hp_ctx;
+      version = conn->client_chosen_version;
+      break;
+    default:
+      /* Unreachable */
+      ngtcp2_unreachable();
+    }
+
+    cc->encrypt = conn->callbacks.encrypt;
+    cc->hp_mask = conn->callbacks.hp_mask;
+
+    if (conn_should_send_max_data(conn)) {
+      rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+      if (rv != 0) {
+        return rv;
+      }
+
+      if (conn->local.settings.max_window &&
+          conn->tx.last_max_data_ts != UINT64_MAX &&
+          ts - conn->tx.last_max_data_ts <
+            NGTCP2_FLOW_WINDOW_RTT_FACTOR * cstat->smoothed_rtt &&
+          conn->local.settings.max_window > conn->rx.window) {
+        target_max_data = NGTCP2_FLOW_WINDOW_SCALING_FACTOR * conn->rx.window;
+        if (target_max_data > conn->local.settings.max_window) {
+          target_max_data = conn->local.settings.max_window;
+        }
+
+        delta = target_max_data - conn->rx.window;
+        if (conn->rx.unsent_max_offset + delta > NGTCP2_MAX_VARINT) {
+          delta = NGTCP2_MAX_VARINT - conn->rx.unsent_max_offset;
+        }
+
+        conn->rx.window = target_max_data;
+      } else {
+        delta = 0;
+      }
+
+      conn->tx.last_max_data_ts = ts;
+
+      nfrc->fr.type = NGTCP2_FRAME_MAX_DATA;
+      nfrc->fr.max_data.max_data = conn->rx.unsent_max_offset + delta;
+      nfrc->next = pktns->tx.frq;
+      pktns->tx.frq = nfrc;
+
+      conn->rx.max_offset = conn->rx.unsent_max_offset =
+        nfrc->fr.max_data.max_data;
+    }
+
+    rv = conn_handle_skip_pkt(conn, pktns, ts);
+    if (rv != 0) {
+      return rv;
+    }
+
+    ngtcp2_pkt_hd_init(hd, hd_flags, type, &conn->dcid.current.cid, scid,
+                       pktns->tx.last_pkt_num + 1,
+                       pktns_select_pkt_numlen(pktns), version);
+
+    ngtcp2_ppe_init(ppe, dest, destlen, dgram_offset, cc);
+
+    rv = ngtcp2_ppe_encode_hd(ppe, hd);
+    if (rv != 0) {
+      assert(NGTCP2_ERR_NOBUF == rv);
+      return 0;
+    }
+
+    if (!ngtcp2_ppe_ensure_hp_sample(ppe)) {
+      return 0;
+    }
+
+    if (ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb)) {
+      pcent = ngtcp2_ringbuf_get(&conn->rx.path_challenge.rb, 0);
+
+      /* PATH_RESPONSE is bound to the path that the corresponding
+         PATH_CHALLENGE is received. */
+      if (ngtcp2_path_eq(&conn->dcid.current.ps.path, &pcent->ps.path)) {
+        lfr.type = NGTCP2_FRAME_PATH_RESPONSE;
+        memcpy(lfr.path_response.data, pcent->data,
+               sizeof(lfr.path_response.data));
+
+        rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr);
+        if (rv != 0) {
+          assert(NGTCP2_ERR_NOBUF == rv);
+        } else {
+          ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb);
+
+          pkt_empty = 0;
+          rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING;
+          require_padding = require_padding || !conn->server ||
+                            destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE;
+          /* We don't retransmit PATH_RESPONSE. */
+
+          /* Include PING to make a packet non-probing as per
+             https://datatracker.ietf.org/doc/html/rfc9000#section-9.3.3
+
+             An endpoint that receives a PATH_CHALLENGE on an active
+             path SHOULD send a non-probing packet in response. */
+          lfr.type = NGTCP2_FRAME_PING;
+          rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr);
+          if (rv != 0) {
+            assert(NGTCP2_ERR_NOBUF == rv);
+          }
+        }
+      }
+    }
+
+    ackfr = ngtcp2_acktr_create_ack_frame(
+      &pktns->acktr, &mfr.fr, type, ts, conn_compute_ack_delay(conn),
+      conn->local.transport_params.ack_delay_exponent);
+    if (ackfr) {
+      rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, ackfr);
+      if (rv != 0) {
+        assert(NGTCP2_ERR_NOBUF == rv);
+      } else {
+        ngtcp2_acktr_commit_ack(&pktns->acktr);
+        ngtcp2_acktr_add_ack(&pktns->acktr, hd->pkt_num,
+                             ackfr->ack.largest_ack);
+        assert(NGTCP2_PKT_1RTT == type);
+        conn_handle_unconfirmed_key_update_from_remote(
+          conn, ackfr->ack.largest_ack, ts);
+        pkt_empty = 0;
+      }
+    }
+
+  build_pkt:
+    for (pfrc = &pktns->tx.frq; *pfrc;) {
+      if ((*pfrc)->binder &&
+          ((*pfrc)->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK)) {
+        frc = *pfrc;
+        *pfrc = (*pfrc)->next;
+        ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+        continue;
+      }
+
+      switch ((*pfrc)->fr.type) {
+      case NGTCP2_FRAME_RESET_STREAM:
+        strm =
+          ngtcp2_conn_find_stream(conn, (*pfrc)->fr.reset_stream.stream_id);
+        if (strm == NULL ||
+            !ngtcp2_strm_require_retransmit_reset_stream(strm)) {
+          frc = *pfrc;
+          *pfrc = (*pfrc)->next;
+          ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+          continue;
+        }
+        break;
+      case NGTCP2_FRAME_STOP_SENDING:
+        strm =
+          ngtcp2_conn_find_stream(conn, (*pfrc)->fr.stop_sending.stream_id);
+        if (strm == NULL ||
+            !ngtcp2_strm_require_retransmit_stop_sending(strm)) {
+          frc = *pfrc;
+          *pfrc = (*pfrc)->next;
+          ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+          continue;
+        }
+        break;
+      case NGTCP2_FRAME_STREAM:
+        ngtcp2_unreachable();
+      case NGTCP2_FRAME_MAX_STREAMS_BIDI:
+        if ((*pfrc)->fr.max_streams.max_streams <
+            conn->remote.bidi.max_streams) {
+          frc = *pfrc;
+          *pfrc = (*pfrc)->next;
+          ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+          continue;
+        }
+        break;
+      case NGTCP2_FRAME_MAX_STREAMS_UNI:
+        if ((*pfrc)->fr.max_streams.max_streams <
+            conn->remote.uni.max_streams) {
+          frc = *pfrc;
+          *pfrc = (*pfrc)->next;
+          ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+          continue;
+        }
+        break;
+      case NGTCP2_FRAME_MAX_STREAM_DATA:
+        strm =
+          ngtcp2_conn_find_stream(conn, (*pfrc)->fr.max_stream_data.stream_id);
+        if (strm == NULL || !ngtcp2_strm_require_retransmit_max_stream_data(
+                              strm, &(*pfrc)->fr.max_stream_data)) {
+          frc = *pfrc;
+          *pfrc = (*pfrc)->next;
+          ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+          continue;
+        }
+        break;
+      case NGTCP2_FRAME_MAX_DATA:
+        if ((*pfrc)->fr.max_data.max_data < conn->rx.max_offset) {
+          frc = *pfrc;
+          *pfrc = (*pfrc)->next;
+          ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+          continue;
+        }
+        break;
+      case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
+        strm = ngtcp2_conn_find_stream(
+          conn, (*pfrc)->fr.stream_data_blocked.stream_id);
+        if (strm == NULL || !ngtcp2_strm_require_retransmit_stream_data_blocked(
+                              strm, &(*pfrc)->fr.stream_data_blocked)) {
+          frc = *pfrc;
+          *pfrc = (*pfrc)->next;
+          ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+          continue;
+        }
+        break;
+      case NGTCP2_FRAME_DATA_BLOCKED:
+        if ((*pfrc)->fr.data_blocked.offset != conn->tx.max_offset) {
+          frc = *pfrc;
+          *pfrc = (*pfrc)->next;
+          ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+          continue;
+        }
+        break;
+      case NGTCP2_FRAME_CRYPTO:
+        ngtcp2_unreachable();
+      }
+
+      rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->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;
+    }
+
+    if (*pfrc == NULL) {
+      for (; !ngtcp2_strm_streamfrq_empty(&pktns->crypto.strm);) {
+        left = ngtcp2_ppe_left(ppe);
+
+        crypto_offset =
+          ngtcp2_strm_streamfrq_unacked_offset(&pktns->crypto.strm);
+        if (crypto_offset == (uint64_t)-1) {
+          ngtcp2_strm_streamfrq_clear(&pktns->crypto.strm);
+          break;
+        }
+
+        left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left);
+
+        if (left == (size_t)-1) {
+          break;
+        }
+
+        rv = ngtcp2_strm_streamfrq_pop(&pktns->crypto.strm, &nfrc, left);
+        if (rv != 0) {
+          assert(ngtcp2_err_is_fatal(rv));
+          return rv;
+        }
+
+        if (nfrc == NULL) {
+          break;
+        }
+
+        rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
+        if (rv != 0) {
+          ngtcp2_unreachable();
+        }
+
+        *pfrc = nfrc;
+        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;
+      }
+    }
+
+    if (*pfrc == NULL) {
+      for (; !ngtcp2_pq_empty(&conn->tx.strmq);) {
+        strm = ngtcp2_conn_tx_strmq_top(conn);
+
+        if (strm->flags & NGTCP2_STRM_FLAG_SEND_RESET_STREAM) {
+          rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+          if (rv != 0) {
+            return rv;
+          }
+
+          nfrc->fr.type = NGTCP2_FRAME_RESET_STREAM;
+          nfrc->fr.reset_stream.stream_id = strm->stream_id;
+          nfrc->fr.reset_stream.app_error_code =
+            strm->tx.reset_stream_app_error_code;
+          nfrc->fr.reset_stream.final_size = strm->tx.offset;
+          *pfrc = nfrc;
+
+          strm->flags &= ~NGTCP2_STRM_FLAG_SEND_RESET_STREAM;
+
+          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;
+        }
+
+        if (strm->flags & NGTCP2_STRM_FLAG_SEND_STOP_SENDING) {
+          if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
+              ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) {
+            strm->flags &= ~NGTCP2_STRM_FLAG_SEND_STOP_SENDING;
+          } else {
+            rv = conn_call_stream_stop_sending(
+              conn, strm->stream_id, strm->tx.stop_sending_app_error_code,
+              strm->stream_user_data);
+            if (rv != 0) {
+              assert(ngtcp2_err_is_fatal(rv));
+
+              return rv;
+            }
+
+            rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+            if (rv != 0) {
+              return rv;
+            }
+
+            nfrc->fr.type = NGTCP2_FRAME_STOP_SENDING;
+            nfrc->fr.stop_sending.stream_id = strm->stream_id;
+            nfrc->fr.stop_sending.app_error_code =
+              strm->tx.stop_sending_app_error_code;
+            *pfrc = nfrc;
+
+            strm->flags &= ~NGTCP2_STRM_FLAG_SEND_STOP_SENDING;
+
+            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;
+          }
+        }
+
+        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;
+          }
+
+          nfrc->fr.type = NGTCP2_FRAME_STREAM_DATA_BLOCKED;
+          nfrc->fr.stream_data_blocked.stream_id = strm->stream_id;
+          nfrc->fr.stream_data_blocked.offset = 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;
+        }
+
+        if (!(strm->flags &
+              (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING)) &&
+            conn_should_send_max_stream_data(conn, strm)) {
+          rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+          if (rv != 0) {
+            assert(ngtcp2_err_is_fatal(rv));
+            return rv;
+          }
+
+          if (conn->local.settings.max_stream_window &&
+              strm->tx.last_max_stream_data_ts != UINT64_MAX &&
+              ts - strm->tx.last_max_stream_data_ts <
+                NGTCP2_FLOW_WINDOW_RTT_FACTOR * cstat->smoothed_rtt &&
+              conn->local.settings.max_stream_window > strm->rx.window) {
+            target_max_data =
+              NGTCP2_FLOW_WINDOW_SCALING_FACTOR * strm->rx.window;
+            if (target_max_data > conn->local.settings.max_stream_window) {
+              target_max_data = conn->local.settings.max_stream_window;
+            }
+
+            delta = target_max_data - strm->rx.window;
+            if (strm->rx.unsent_max_offset + delta > NGTCP2_MAX_VARINT) {
+              delta = NGTCP2_MAX_VARINT - strm->rx.unsent_max_offset;
+            }
+
+            strm->rx.window = target_max_data;
+          } else {
+            delta = 0;
+          }
+
+          strm->tx.last_max_stream_data_ts = ts;
+
+          nfrc->fr.type = NGTCP2_FRAME_MAX_STREAM_DATA;
+          nfrc->fr.max_stream_data.stream_id = strm->stream_id;
+          nfrc->fr.max_stream_data.max_stream_data =
+            strm->rx.unsent_max_offset + delta;
+          *pfrc = nfrc;
+
+          strm->rx.max_offset = strm->rx.unsent_max_offset =
+            nfrc->fr.max_stream_data.max_stream_data;
+
+          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;
+        }
+
+        if (ngtcp2_strm_streamfrq_empty(strm)) {
+          ngtcp2_conn_tx_strmq_pop(conn);
+          continue;
+        }
+
+        stream_offset = ngtcp2_strm_streamfrq_unacked_offset(strm);
+        if (stream_offset == (uint64_t)-1) {
+          ngtcp2_strm_streamfrq_clear(strm);
+          ngtcp2_conn_tx_strmq_pop(conn);
+          continue;
+        }
+
+        left = ngtcp2_ppe_left(ppe);
+
+        left = ngtcp2_pkt_stream_max_datalen(strm->stream_id, stream_offset,
+                                             left, left);
+
+        if (left == (size_t)-1) {
+          break;
+        }
+
+        rv = ngtcp2_strm_streamfrq_pop(strm, &nfrc, left);
+        if (rv != 0) {
+          assert(ngtcp2_err_is_fatal(rv));
+          return rv;
+        }
+
+        if (nfrc == NULL) {
+          /* TODO Why? */
+          break;
+        }
+
+        rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
+        if (rv != 0) {
+          ngtcp2_unreachable();
+        }
+
+        *pfrc = nfrc;
+        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;
+
+        if (ngtcp2_strm_streamfrq_empty(strm)) {
+          ngtcp2_conn_tx_strmq_pop(conn);
+          continue;
+        }
+
+        ngtcp2_conn_tx_strmq_pop(conn);
+        ++strm->cycle;
+        rv = ngtcp2_conn_tx_strmq_push(conn, strm);
+        if (rv != 0) {
+          assert(ngtcp2_err_is_fatal(rv));
+          return rv;
+        }
+      }
+    }
+
+    /* 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) {
+      rv = conn_call_extend_max_remote_streams_bidi(
+        conn, conn->remote.bidi.unsent_max_streams);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        return rv;
+      }
+
+      rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        return rv;
+      }
+      nfrc->fr.type = NGTCP2_FRAME_MAX_STREAMS_BIDI;
+      nfrc->fr.max_streams.max_streams = conn->remote.bidi.unsent_max_streams;
+      *pfrc = nfrc;
+
+      conn->remote.bidi.max_streams = conn->remote.bidi.unsent_max_streams;
+
+      rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr);
+      if (rv != 0) {
+        assert(NGTCP2_ERR_NOBUF == rv);
+      } else {
+        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 (*pfrc == NULL &&
+        conn->remote.uni.unsent_max_streams > conn->remote.uni.max_streams) {
+      rv = conn_call_extend_max_remote_streams_uni(
+        conn, conn->remote.uni.unsent_max_streams);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        return rv;
+      }
+
+      rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        return rv;
+      }
+      nfrc->fr.type = NGTCP2_FRAME_MAX_STREAMS_UNI;
+      nfrc->fr.max_streams.max_streams = conn->remote.uni.unsent_max_streams;
+      *pfrc = nfrc;
+
+      conn->remote.uni.max_streams = conn->remote.uni.unsent_max_streams;
+
+      rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr);
+      if (rv != 0) {
+        assert(NGTCP2_ERR_NOBUF == rv);
+      } else {
+        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 (pktns->tx.frq == NULL && !send_stream && !send_datagram &&
+        !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) &&
+        pktns->rtb.num_retransmittable && pktns->rtb.probe_pkt_left) {
+      num_reclaimed = ngtcp2_rtb_reclaim_on_pto(&pktns->rtb, conn, pktns, 1);
+      if (num_reclaimed < 0) {
+        return num_reclaimed;
+      }
+      if (num_reclaimed) {
+        goto build_pkt;
+      }
+
+      /* 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;
+        }
+      }
+    }
+  } else {
+    pfrc = conn->pkt.pfrc;
+    rtb_entry_flags |= conn->pkt.rtb_entry_flags;
+    pkt_empty = conn->pkt.pkt_empty;
+    hd_logged = conn->pkt.hd_logged;
+    require_padding = conn->pkt.require_padding;
+  }
+
+  left = ngtcp2_ppe_left(ppe);
+
+  if (*pfrc == NULL && send_stream && ngtcp2_pq_empty(&conn->tx.strmq) &&
+      (ndatalen = ngtcp2_pkt_stream_max_datalen(
+         vmsg->stream.strm->stream_id, vmsg->stream.strm->tx.offset, ndatalen,
+         left)) != (size_t)-1 &&
+      (ndatalen || datalen == 0)) {
+    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);
+
+    assert((datacnt == 0 && datalen == 0) || (datacnt && datalen));
+
+    rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
+      &nfrc, datacnt, &conn->frc_objalloc, conn->mem);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      return rv;
+    }
+
+    nfrc->fr.stream.type = NGTCP2_FRAME_STREAM;
+    nfrc->fr.stream.flags = 0;
+    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);
+
+    fin = (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_FIN) &&
+          ndatalen == datalen;
+    nfrc->fr.stream.fin = (uint8_t)fin;
+
+    rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
+    if (rv != 0) {
+      ngtcp2_unreachable();
+    }
+
+    *pfrc = nfrc;
+    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;
+
+    vmsg->stream.strm->tx.offset += ndatalen;
+    conn->tx.offset += ndatalen;
+    vmsg->stream.strm->flags |= NGTCP2_STRM_FLAG_ANY_SENT;
+
+    if (fin) {
+      ngtcp2_strm_shutdown(vmsg->stream.strm, NGTCP2_STRM_FLAG_SHUT_WR);
+    }
+
+    if (vmsg->stream.pdatalen) {
+      *vmsg->stream.pdatalen = (ngtcp2_ssize)ndatalen;
+    }
+  } else {
+    send_stream = 0;
+  }
+
+  if (vmsg && vmsg->type == NGTCP2_VMSG_TYPE_STREAM &&
+      ((stream_blocked && *pfrc == NULL) ||
+       (send_stream &&
+        !(vmsg->stream.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) {
+        assert(ngtcp2_err_is_fatal(rv));
+
+        return rv;
+      }
+
+      nfrc->fr.type = NGTCP2_FRAME_DATA_BLOCKED;
+      nfrc->fr.data_blocked.offset = conn->tx.offset;
+
+      rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
+      if (rv != 0) {
+        assert(NGTCP2_ERR_NOBUF == rv);
+
+        /* We cannot add nfrc to pktns->tx.frq here. */
+        ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem);
+      } else {
+        *pfrc = nfrc;
+        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;
+
+        conn->tx.last_blocked_offset = conn->tx.max_offset;
+      }
+    }
+
+    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) {
+        assert(ngtcp2_err_is_fatal(rv));
+
+        return rv;
+      }
+
+      nfrc->fr.type = NGTCP2_FRAME_STREAM_DATA_BLOCKED;
+      nfrc->fr.stream_data_blocked.stream_id = strm->stream_id;
+      nfrc->fr.stream_data_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);
+
+        /* 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 = conn_tx_strmq_first_cycle(conn);
+          rv = ngtcp2_conn_tx_strmq_push(conn, strm);
+          if (rv != 0) {
+            return rv;
+          }
+        }
+      } else {
+        *pfrc = nfrc;
+        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;
+
+        strm->tx.last_blocked_offset = strm->tx.max_offset;
+      }
+    }
+  }
+
+  if (*pfrc == NULL && send_datagram &&
+      left >= ngtcp2_pkt_datagram_framelen((size_t)datalen)) {
+    if (conn->callbacks.ack_datagram || conn->callbacks.lost_datagram) {
+      rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        return rv;
+      }
+
+      nfrc->fr.datagram.type = NGTCP2_FRAME_DATAGRAM_LEN;
+      nfrc->fr.datagram.dgram_id = vmsg->datagram.dgram_id;
+      nfrc->fr.datagram.datacnt = vmsg->datagram.datacnt;
+      nfrc->fr.datagram.data = (ngtcp2_vec *)vmsg->datagram.data;
+
+      rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr);
+      assert(rv == 0);
+
+      /* Because DATAGRAM will not be retransmitted, we do not use
+         data anymore.  Just nullify it.  The only reason to keep
+         track a frame is keep dgram_id to pass it to
+         ngtcp2_ack_datagram or ngtcp2_lost_datagram callbacks. */
+      nfrc->fr.datagram.datacnt = 0;
+      nfrc->fr.datagram.data = NULL;
+
+      *pfrc = nfrc;
+      pfrc = &(*pfrc)->next;
+    } else {
+      lfr.datagram.type = NGTCP2_FRAME_DATAGRAM_LEN;
+      lfr.datagram.datacnt = vmsg->datagram.datacnt;
+      lfr.datagram.data = (ngtcp2_vec *)vmsg->datagram.data;
+
+      rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr);
+      assert(rv == 0);
+    }
+
+    pkt_empty = 0;
+    rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING |
+                       NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
+                       NGTCP2_RTB_ENTRY_FLAG_DATAGRAM;
+
+    if (vmsg->datagram.paccepted) {
+      *vmsg->datagram.paccepted = 1;
+    }
+  } else {
+    send_datagram = 0;
+  }
+
+  if (pkt_empty) {
+    if (*pfrc == NULL && rv == 0 && stream_blocked &&
+        (write_more || !require_padding) &&
+        ngtcp2_conn_get_max_data_left(conn)) {
+      if (write_more) {
+        conn->pkt.pfrc = pfrc;
+        conn->pkt.pkt_empty = pkt_empty;
+        conn->pkt.rtb_entry_flags = rtb_entry_flags;
+        conn->pkt.hd_logged = hd_logged;
+        conn->pkt.require_padding = require_padding;
+        conn->flags |= NGTCP2_CONN_FLAG_PPE_PENDING;
+      }
+
+      return NGTCP2_ERR_STREAM_DATA_BLOCKED;
+    }
+
+    keep_alive_expired =
+      type == NGTCP2_PKT_1RTT && conn_keep_alive_expired(conn, ts);
+
+    if (conn->pktns.rtb.probe_pkt_left == 0 && !keep_alive_expired &&
+        !require_padding) {
+      conn_reset_ppe_pending(conn);
+
+      return 0;
+    }
+  } else if (write_more) {
+    conn->pkt.pfrc = pfrc;
+    conn->pkt.pkt_empty = pkt_empty;
+    conn->pkt.rtb_entry_flags = rtb_entry_flags;
+    conn->pkt.hd_logged = hd_logged;
+    conn->pkt.require_padding = require_padding;
+    conn->flags |= NGTCP2_CONN_FLAG_PPE_PENDING;
+
+    assert(vmsg);
+
+    switch (vmsg->type) {
+    case NGTCP2_VMSG_TYPE_STREAM:
+      if (send_stream) {
+        if (ngtcp2_ppe_left(ppe)) {
+          return NGTCP2_ERR_WRITE_MORE;
+        }
+        break;
+      }
+
+      if (*pfrc == NULL && ngtcp2_conn_get_max_data_left(conn) &&
+          stream_blocked) {
+        return NGTCP2_ERR_STREAM_DATA_BLOCKED;
+      }
+      break;
+    case NGTCP2_VMSG_TYPE_DATAGRAM:
+      if (send_datagram && ngtcp2_ppe_left(ppe)) {
+        return NGTCP2_ERR_WRITE_MORE;
+      }
+      /* If DATAGRAM cannot be written due to insufficient space,
+         continue to create a packet with the hope that application
+         calls ngtcp2_conn_writev_datagram again. */
+      break;
+    default:
+      ngtcp2_unreachable();
+    }
+  }
+
+  if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+    if (ngtcp2_tstamp_elapsed(pktns->tx.non_ack_pkt_start_ts,
+                              cstat->smoothed_rtt, ts) ||
+        keep_alive_expired || conn->pktns.rtb.probe_pkt_left) {
+      lfr.type = NGTCP2_FRAME_PING;
+
+      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. */
+      } else {
+        rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING;
+        if (conn->pktns.rtb.probe_pkt_left) {
+          rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_PROBE;
+        } else {
+          rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING;
+        }
+        pktns->tx.non_ack_pkt_start_ts = UINT64_MAX;
+        pkt_empty = 0;
+      }
+    } else if (pktns->tx.non_ack_pkt_start_ts == UINT64_MAX) {
+      pktns->tx.non_ack_pkt_start_ts = ts;
+    }
+  } else {
+    pktns->tx.non_ack_pkt_start_ts = UINT64_MAX;
+  }
+
+  /* TODO Push STREAM frame back to ngtcp2_strm if there is an error
+     before ngtcp2_rtb_entry is safely created and added. */
+  if ((flags & (NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY)) &&
+      (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+    lfr.padding.len = ngtcp2_ppe_padding_size(ppe, destlen);
+  } else if (require_padding) {
+    lfr.padding.len = ngtcp2_ppe_dgram_padding(ppe);
+  } else {
+    lfr.padding.len = ngtcp2_ppe_padding_size(ppe, min_pktlen);
+    min_padded = 1;
+  }
+
+  if (lfr.padding.len) {
+    if (!min_padded ||
+        (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+      padded = 1;
+    }
+    lfr.type = NGTCP2_FRAME_PADDING;
+    ngtcp2_log_tx_fr(&conn->log, hd, &lfr);
+    ngtcp2_qlog_write_frame(&conn->qlog, &lfr);
+  }
+
+  nwrite = ngtcp2_ppe_final(ppe, NULL);
+  if (nwrite < 0) {
+    assert(ngtcp2_err_is_fatal((int)nwrite));
+    return nwrite;
+  }
+
+  ++cc->ckm->use_count;
+
+  ngtcp2_qlog_pkt_sent_end(&conn->qlog, hd, (size_t)nwrite);
+
+  /* TODO ack-eliciting vs needs-tracking */
+  /* probe packet needs tracking but it does not need ACK, could be lost. */
+  if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) {
+    if (pi) {
+      conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, hd, ts);
+    }
+
+    rv =
+      ngtcp2_rtb_entry_objalloc_new(&ent, hd, NULL, ts, (size_t)nwrite,
+                                    rtb_entry_flags, &conn->rtb_entry_objalloc);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      return rv;
+    }
+
+    if (*pfrc != pktns->tx.frq) {
+      ent->frc = pktns->tx.frq;
+      pktns->tx.frq = *pfrc;
+      *pfrc = NULL;
+    }
+
+    if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) &&
+        pktns->rtb.num_ack_eliciting == 0 && conn->cc.event) {
+      conn->cc.event(&conn->cc, &conn->cstat, NGTCP2_CC_EVENT_TYPE_TX_START,
+                     ts);
+    }
+
+    rv = conn_on_pkt_sent(conn, pktns, ent);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_rtb_entry_objalloc_del(ent, &conn->rtb_entry_objalloc,
+                                    &conn->frc_objalloc, conn->mem);
+      return rv;
+    }
+
+    if (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
+      if (conn->cc.on_pkt_sent) {
+        conn->cc.on_pkt_sent(
+          &conn->cc, &conn->cstat,
+          ngtcp2_cc_pkt_init(&cc_pkt, hd->pkt_num, (size_t)nwrite,
+                             NGTCP2_PKTNS_ID_APPLICATION, ts, ent->rst.lost,
+                             ent->rst.tx_in_flight, ent->rst.is_app_limited));
+      }
+
+      if (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE) {
+        conn_restart_timer_on_write(conn, ts);
+      }
+    }
+  } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) {
+    conn_handle_tx_ecn(conn, pi, NULL, pktns, hd, ts);
+  }
+
+  conn_reset_ppe_pending(conn);
+
+  if (pktns->rtb.probe_pkt_left &&
+      (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+    --pktns->rtb.probe_pkt_left;
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "probe pkt size=%td",
+                    nwrite);
+  }
+
+  conn_update_keep_alive_last_ts(conn, ts);
+
+  conn->dcid.current.bytes_sent += (uint64_t)nwrite;
+
+  conn->tx.pacing.pktlen += (size_t)nwrite;
+
+  ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat);
+
+  ++pktns->tx.last_pkt_num;
+
+  return nwrite;
+}
+
+ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt(
+  ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen,
+  uint8_t type, uint8_t flags, const ngtcp2_cid *dcid, ngtcp2_frame *fr,
+  uint16_t rtb_entry_flags, const ngtcp2_path *path, ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_ppe ppe;
+  ngtcp2_pkt_hd hd;
+  ngtcp2_frame lfr;
+  ngtcp2_ssize nwrite;
+  ngtcp2_crypto_cc cc;
+  ngtcp2_pktns *pktns;
+  uint8_t hd_flags;
+  ngtcp2_rtb_entry *rtbent;
+  int padded = 0;
+  const ngtcp2_cid *scid;
+  uint32_t version;
+
+  switch (type) {
+  case NGTCP2_PKT_INITIAL:
+    pktns = conn->in_pktns;
+    hd_flags = conn_pkt_flags_long(conn);
+    scid = &conn->oscid;
+    version = conn->negotiated_version ? conn->negotiated_version
+                                       : conn->client_chosen_version;
+    if (version == conn->client_chosen_version) {
+      cc.ckm = pktns->crypto.tx.ckm;
+      cc.hp_ctx = pktns->crypto.tx.hp_ctx;
+    } else {
+      assert(version == conn->vneg.version);
+
+      cc.ckm = conn->vneg.tx.ckm;
+      cc.hp_ctx = conn->vneg.tx.hp_ctx;
+    }
+    break;
+  case NGTCP2_PKT_HANDSHAKE:
+    pktns = conn->hs_pktns;
+    hd_flags = conn_pkt_flags_long(conn);
+    scid = &conn->oscid;
+    version = conn->negotiated_version;
+    cc.ckm = pktns->crypto.tx.ckm;
+    cc.hp_ctx = pktns->crypto.tx.hp_ctx;
+    break;
+  case NGTCP2_PKT_1RTT:
+    pktns = &conn->pktns;
+    hd_flags = conn_pkt_flags_short(conn);
+    scid = NULL;
+    version = conn->negotiated_version;
+    cc.ckm = pktns->crypto.tx.ckm;
+    cc.hp_ctx = pktns->crypto.tx.hp_ctx;
+
+    rv = conn_handle_skip_pkt(conn, pktns, ts);
+    if (rv != 0) {
+      return rv;
+    }
+
+    break;
+  default:
+    /* We don't support 0-RTT packet in this function. */
+    ngtcp2_unreachable();
+  }
+
+  cc.aead = pktns->crypto.ctx.aead;
+  cc.hp = pktns->crypto.ctx.hp;
+  cc.encrypt = conn->callbacks.encrypt;
+  cc.hp_mask = conn->callbacks.hp_mask;
+
+  ngtcp2_pkt_hd_init(&hd, hd_flags, type, dcid, scid,
+                     pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns),
+                     version);
+
+  ngtcp2_ppe_init(&ppe, dest, destlen, 0, &cc);
+
+  rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
+  if (rv != 0) {
+    assert(NGTCP2_ERR_NOBUF == rv);
+    return 0;
+  }
+
+  if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) {
+    return 0;
+  }
+
+  ngtcp2_log_tx_pkt_hd(&conn->log, &hd);
+  ngtcp2_qlog_pkt_sent_start(&conn->qlog);
+
+  rv = conn_ppe_write_frame(conn, &ppe, &hd, fr);
+  if (rv != 0) {
+    assert(NGTCP2_ERR_NOBUF == rv);
+    return 0;
+  }
+
+  lfr.type = NGTCP2_FRAME_PADDING;
+  if (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL) {
+    lfr.padding.len = ngtcp2_ppe_dgram_padding_size(&ppe, destlen);
+  } else if (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) {
+    lfr.padding.len = ngtcp2_ppe_dgram_padding(&ppe);
+  } else {
+    switch (fr->type) {
+    case NGTCP2_FRAME_PATH_CHALLENGE:
+    case NGTCP2_FRAME_PATH_RESPONSE:
+      if (!conn->server || destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE) {
+        lfr.padding.len = ngtcp2_ppe_dgram_padding(&ppe);
+      } else {
+        lfr.padding.len = 0;
+      }
+      break;
+    default:
+      lfr.padding.len = ngtcp2_ppe_padding_size(&ppe, conn_min_pktlen(conn));
+    }
+  }
+  if (lfr.padding.len) {
+    padded = 1;
+    ngtcp2_log_tx_fr(&conn->log, &hd, &lfr);
+    ngtcp2_qlog_write_frame(&conn->qlog, &lfr);
+  }
+
+  nwrite = ngtcp2_ppe_final(&ppe, NULL);
+  if (nwrite < 0) {
+    return nwrite;
+  }
+
+  if (type == NGTCP2_PKT_1RTT) {
+    ++cc.ckm->use_count;
+  }
+
+  ngtcp2_qlog_pkt_sent_end(&conn->qlog, &hd, (size_t)nwrite);
+
+  /* Do this when we are sure that there is no error. */
+  switch (fr->type) {
+  case NGTCP2_FRAME_ACK:
+  case NGTCP2_FRAME_ACK_ECN:
+    ngtcp2_acktr_commit_ack(&pktns->acktr);
+    ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, fr->ack.largest_ack);
+    if (type == NGTCP2_PKT_1RTT) {
+      conn_handle_unconfirmed_key_update_from_remote(conn, fr->ack.largest_ack,
+                                                     ts);
+    }
+
+    if (!(flags & (NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL |
+                   NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING))) {
+      padded = 0;
+    }
+
+    break;
+  }
+
+  if (((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) &&
+      (!path || ngtcp2_path_eq(&conn->dcid.current.ps.path, path))) {
+    if (pi && (conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE ||
+               !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE))) {
+      conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, &hd, ts);
+    }
+
+    rv =
+      ngtcp2_rtb_entry_objalloc_new(&rtbent, &hd, NULL, ts, (size_t)nwrite,
+                                    rtb_entry_flags, &conn->rtb_entry_objalloc);
+    if (rv != 0) {
+      return rv;
+    }
+
+    rv = conn_on_pkt_sent(conn, pktns, rtbent);
+    if (rv != 0) {
+      ngtcp2_rtb_entry_objalloc_del(rtbent, &conn->rtb_entry_objalloc,
+                                    &conn->frc_objalloc, conn->mem);
+      return rv;
+    }
+
+    if (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
+      if (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE) {
+        conn_restart_timer_on_write(conn, ts);
+      }
+
+      if (pktns->rtb.probe_pkt_left && path &&
+          ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) {
+        --pktns->rtb.probe_pkt_left;
+
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "probe pkt size=%td",
+                        nwrite);
+      }
+    }
+  } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) {
+    conn_handle_tx_ecn(conn, pi, NULL, pktns, &hd, ts);
+  }
+
+  if (path && ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) {
+    conn_update_keep_alive_last_ts(conn, ts);
+  }
+
+  if (!padded) {
+    switch (fr->type) {
+    case NGTCP2_FRAME_ACK:
+    case NGTCP2_FRAME_ACK_ECN:
+      break;
+    default:
+      conn->tx.pacing.pktlen += (size_t)nwrite;
+    }
+  } else {
+    conn->tx.pacing.pktlen += (size_t)nwrite;
+  }
+
+  ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat);
+
+  ++pktns->tx.last_pkt_num;
+
+  return nwrite;
+}
+
+/*
+ * conn_process_early_rtb makes any pending 0RTT packet 1RTT packet.
+ */
+static void conn_process_early_rtb(ngtcp2_conn *conn) {
+  ngtcp2_rtb_entry *ent;
+  ngtcp2_rtb *rtb = &conn->pktns.rtb;
+  ngtcp2_ksl_it it;
+
+  for (it = ngtcp2_rtb_head(rtb); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    ent = ngtcp2_ksl_it_get(&it);
+
+    if ((ent->hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) == 0 ||
+        ent->hd.type != NGTCP2_PKT_0RTT) {
+      continue;
+    }
+
+    /*  0-RTT packet is retransmitted as a 1RTT packet. */
+    ent->hd.flags &= (uint8_t)~NGTCP2_PKT_FLAG_LONG_FORM;
+    ent->hd.type = NGTCP2_PKT_1RTT;
+  }
+}
+
+/*
+ * conn_handshake_remnants_left returns nonzero if there may be
+ * handshake packets the local endpoint has to send, including new
+ * packets and lost ones.
+ */
+static int conn_handshake_remnants_left(ngtcp2_conn *conn) {
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+
+  return !conn_is_tls_handshake_completed(conn) ||
+         (in_pktns && (in_pktns->rtb.num_pto_eliciting ||
+                       !ngtcp2_strm_streamfrq_empty(&in_pktns->crypto.strm))) ||
+         (hs_pktns && (hs_pktns->rtb.num_pto_eliciting ||
+                       !ngtcp2_strm_streamfrq_empty(&hs_pktns->crypto.strm)));
+}
+
+/*
+ * conn_enqueue_retire_connection_id enqueues RETIRE_CONNECTION_ID
+ * frame with |seq|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_enqueue_retire_connection_id(ngtcp2_conn *conn, uint64_t seq) {
+  ngtcp2_pktns *pktns = &conn->pktns;
+  ngtcp2_frame_chain *nfrc;
+  int rv;
+
+  rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nfrc->fr.type = NGTCP2_FRAME_RETIRE_CONNECTION_ID;
+  nfrc->fr.retire_connection_id.seq = seq;
+  nfrc->next = pktns->tx.frq;
+  pktns->tx.frq = nfrc;
+
+  return 0;
+}
+
+static int dcidtr_on_retire(const ngtcp2_dcid *dcid, void *user_data) {
+  return conn_enqueue_retire_connection_id(user_data, dcid->seq);
+}
+
+/*
+ * conn_retire_active_dcid retires the activated |dcid|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+static int conn_retire_active_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid,
+                                   ngtcp2_tstamp ts) {
+  int rv;
+
+  assert(dcid->cid.datalen);
+
+  rv = ngtcp2_dcidtr_retire_active_dcid(&conn->dcid.dtr, dcid, ts,
+                                        dcidtr_on_deactivate, conn);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return conn_enqueue_retire_connection_id(conn, dcid->seq);
+}
+
+/*
+ * conn_bind_dcid stores the DCID to |*pdcid| bound to |path|.  If
+ * such DCID is not found, bind the new DCID to |path| and stores it
+ * to |*pdcid|.  If a remote endpoint uses zero-length connection ID,
+ * the pointer to conn->dcid.current is assigned to |*pdcid|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CONN_ID_BLOCKED
+ *     No unused DCID is available
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+static int conn_bind_dcid(ngtcp2_conn *conn, ngtcp2_dcid **pdcid,
+                          const ngtcp2_path *path, ngtcp2_tstamp ts) {
+  assert(!ngtcp2_path_eq(&conn->dcid.current.ps.path, path));
+  assert(!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path));
+  assert(!conn->pv || !(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) ||
+         !ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path));
+
+  *pdcid = ngtcp2_dcidtr_find_bound_dcid(&conn->dcid.dtr, path);
+  if (*pdcid) {
+    return 0;
+  }
+
+  if (conn->dcid.current.cid.datalen == 0) {
+    *pdcid = ngtcp2_dcidtr_bind_zerolen_dcid(&conn->dcid.dtr, path);
+
+    return 0;
+  }
+
+  if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) {
+    return NGTCP2_ERR_CONN_ID_BLOCKED;
+  }
+
+  return ngtcp2_dcidtr_bind_dcid(&conn->dcid.dtr, pdcid, path, ts,
+                                 dcidtr_on_retire, conn);
+}
+
+static int conn_start_pmtud(ngtcp2_conn *conn) {
+  int rv;
+  size_t hard_max_udp_payload_size;
+
+  assert(!conn->local.settings.no_pmtud);
+  assert(!conn->pmtud);
+  assert(conn_is_tls_handshake_completed(conn));
+  assert(conn->remote.transport_params);
+  assert(conn->remote.transport_params->max_udp_payload_size >=
+         NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+
+  hard_max_udp_payload_size = (size_t)ngtcp2_min_uint64(
+    conn->remote.transport_params->max_udp_payload_size,
+    (uint64_t)conn->local.settings.max_tx_udp_payload_size);
+
+  rv =
+    ngtcp2_pmtud_new(&conn->pmtud, conn->dcid.current.max_udp_payload_size,
+                     hard_max_udp_payload_size, conn->pktns.tx.last_pkt_num + 1,
+                     conn->local.settings.pmtud_probes,
+                     conn->local.settings.pmtud_probeslen, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (ngtcp2_pmtud_finished(conn->pmtud)) {
+    ngtcp2_conn_stop_pmtud(conn);
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_start_pmtud(ngtcp2_conn *conn) {
+  return conn_start_pmtud(conn);
+}
+
+void ngtcp2_conn_stop_pmtud(ngtcp2_conn *conn) {
+  if (!conn->pmtud) {
+    return;
+  }
+
+  ngtcp2_pmtud_del(conn->pmtud);
+
+  conn->pmtud = NULL;
+}
+
+static ngtcp2_ssize conn_write_pmtud_probe(ngtcp2_conn *conn,
+                                           ngtcp2_pkt_info *pi, uint8_t *dest,
+                                           size_t destlen, ngtcp2_tstamp ts) {
+  size_t probelen;
+  ngtcp2_ssize nwrite;
+  ngtcp2_frame lfr;
+
+  assert(conn->pmtud);
+  assert(!ngtcp2_pmtud_finished(conn->pmtud));
+
+  if (!ngtcp2_pmtud_require_probe(conn->pmtud)) {
+    return 0;
+  }
+
+  probelen = ngtcp2_pmtud_probelen(conn->pmtud);
+  if (probelen > destlen) {
+    return 0;
+  }
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                  "sending PMTUD probe packet len=%zu", probelen);
+
+  lfr.type = NGTCP2_FRAME_PING;
+
+  nwrite = ngtcp2_conn_write_single_frame_pkt(
+    conn, pi, dest, probelen, NGTCP2_PKT_1RTT,
+    NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL, &conn->dcid.current.cid, &lfr,
+    NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING |
+      NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE,
+    NULL, ts);
+  if (nwrite < 0) {
+    return nwrite;
+  }
+
+  assert(nwrite);
+
+  ngtcp2_pmtud_probe_sent(conn->pmtud, conn_compute_pto(conn, &conn->pktns),
+                          ts);
+
+  return nwrite;
+}
+
+/*
+ * conn_stop_pv stops the path validation which is currently running.
+ * This function does nothing if no path validation is currently being
+ * performed.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+static int conn_stop_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  int rv = 0;
+  ngtcp2_pv *pv = conn->pv;
+
+  if (pv == NULL) {
+    return 0;
+  }
+
+  if (pv->dcid.cid.datalen && pv->dcid.seq != conn->dcid.current.seq) {
+    rv = conn_retire_active_dcid(conn, &pv->dcid, ts);
+    if (rv != 0) {
+      goto fin;
+    }
+  }
+
+  if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+      pv->fallback_dcid.cid.datalen &&
+      pv->fallback_dcid.seq != conn->dcid.current.seq &&
+      pv->fallback_dcid.seq != pv->dcid.seq) {
+    rv = conn_retire_active_dcid(conn, &pv->fallback_dcid, ts);
+    if (rv != 0) {
+      goto fin;
+    }
+  }
+
+fin:
+  ngtcp2_pv_del(pv);
+  conn->pv = NULL;
+
+  return rv;
+}
+
+/*
+ * conn_abort_pv aborts the current path validation and frees
+ * resources allocated for it.  This function assumes that conn->pv is
+ * not NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_abort_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  ngtcp2_pv *pv = conn->pv;
+  int rv;
+
+  assert(pv);
+
+  if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) {
+    rv = conn_call_path_validation(conn, pv,
+                                   NGTCP2_PATH_VALIDATION_RESULT_ABORTED);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return conn_stop_pv(conn, ts);
+}
+
+static size_t conn_shape_udp_payload(ngtcp2_conn *conn, const ngtcp2_dcid *dcid,
+                                     size_t payloadlen) {
+  if (conn->remote.transport_params &&
+      conn->remote.transport_params->max_udp_payload_size) {
+    assert(conn->remote.transport_params->max_udp_payload_size >=
+           NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+
+    payloadlen = (size_t)ngtcp2_min_uint64(
+      (uint64_t)payloadlen,
+      conn->remote.transport_params->max_udp_payload_size);
+  }
+
+  payloadlen =
+    ngtcp2_min_size(payloadlen, conn->local.settings.max_tx_udp_payload_size);
+
+  if (conn->local.settings.no_tx_udp_payload_size_shaping) {
+    return payloadlen;
+  }
+
+  return ngtcp2_min_size(payloadlen, dcid->max_udp_payload_size);
+}
+
+static void conn_reset_congestion_state(ngtcp2_conn *conn, ngtcp2_tstamp ts);
+
+/*
+ * conn_on_path_validation_failed is called when path validation
+ * fails.  This function may delete |pv|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_on_path_validation_failed(ngtcp2_conn *conn, ngtcp2_pv *pv,
+                                          ngtcp2_tstamp ts) {
+  int rv;
+
+  if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) {
+    rv = conn_call_path_validation(conn, pv,
+                                   NGTCP2_PATH_VALIDATION_RESULT_FAILURE);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) {
+    ngtcp2_dcid_copy(&conn->dcid.current, &pv->fallback_dcid);
+    conn_reset_congestion_state(conn, ts);
+  }
+
+  return conn_stop_pv(conn, ts);
+}
+
+/*
+ * conn_write_path_challenge writes a packet which includes
+ * PATH_CHALLENGE frame into |dest| of length |destlen|.
+ *
+ * This function returns the number of bytes written to |dest|, or one
+ * of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static ngtcp2_ssize conn_write_path_challenge(ngtcp2_conn *conn,
+                                              ngtcp2_path *path,
+                                              ngtcp2_pkt_info *pi,
+                                              uint8_t *dest, size_t destlen,
+                                              ngtcp2_tstamp ts) {
+  ngtcp2_ssize nwrite;
+  ngtcp2_tstamp expiry;
+  ngtcp2_pv *pv = conn->pv;
+  ngtcp2_frame lfr;
+  ngtcp2_duration timeout, initial_pto;
+  uint8_t flags;
+  uint64_t tx_left;
+  int rv;
+
+  if (ngtcp2_pv_validation_timed_out(pv, ts)) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV,
+                    "path validation was timed out");
+    rv = conn_on_path_validation_failed(conn, pv, ts);
+    if (rv != 0) {
+      return rv;
+    }
+
+    /* We might set path to the one which we just failed validate.
+       Set it to the current path here. */
+    if (path) {
+      ngtcp2_path_copy(path, &conn->dcid.current.ps.path);
+    }
+
+    return 0;
+  }
+
+  ngtcp2_pv_handle_entry_expiry(pv, ts);
+
+  if (!ngtcp2_pv_should_send_probe(pv)) {
+    return 0;
+  }
+
+  rv = conn_call_get_path_challenge_data(conn, lfr.path_challenge.data);
+  if (rv != 0) {
+    return rv;
+  }
+
+  lfr.type = NGTCP2_FRAME_PATH_CHALLENGE;
+
+  initial_pto = conn_compute_initial_pto(conn, &conn->pktns);
+  timeout = conn_compute_pto(conn, &conn->pktns);
+  timeout = ngtcp2_max_uint64(timeout, initial_pto);
+  expiry = ts + timeout * (1ULL << pv->round);
+
+  destlen = ngtcp2_min_size(destlen, NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+
+  if (conn->server) {
+    if (!(pv->dcid.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) {
+      tx_left = conn_server_tx_left(conn, &pv->dcid);
+      destlen = (size_t)ngtcp2_min_uint64((uint64_t)destlen, tx_left);
+      if (destlen == 0) {
+        return 0;
+      }
+    }
+
+    if (destlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) {
+      flags = NGTCP2_PV_ENTRY_FLAG_UNDERSIZED;
+    } else {
+      flags = NGTCP2_PV_ENTRY_FLAG_NONE;
+    }
+  } else {
+    flags = NGTCP2_PV_ENTRY_FLAG_NONE;
+  }
+
+  ngtcp2_pv_add_entry(pv, lfr.path_challenge.data, expiry, flags, ts);
+
+  nwrite = ngtcp2_conn_write_single_frame_pkt(
+    conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE,
+    &pv->dcid.cid, &lfr,
+    NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING,
+    &pv->dcid.ps.path, ts);
+  if (nwrite <= 0) {
+    return nwrite;
+  }
+
+  if (path) {
+    ngtcp2_path_copy(path, &pv->dcid.ps.path);
+  }
+
+  if (ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)) {
+    conn->dcid.current.bytes_sent += (uint64_t)nwrite;
+  } else {
+    pv->dcid.bytes_sent += (uint64_t)nwrite;
+  }
+
+  return nwrite;
+}
+
+/*
+ * conn_write_path_response writes a packet which includes
+ * PATH_RESPONSE frame into |dest| of length |destlen|.
+ *
+ * This function returns the number of bytes written to |dest|, or one
+ * of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn,
+                                             ngtcp2_path *path,
+                                             ngtcp2_pkt_info *pi, uint8_t *dest,
+                                             size_t destlen, ngtcp2_tstamp ts) {
+  ngtcp2_pv *pv = conn->pv;
+  ngtcp2_path_challenge_entry *pcent = NULL;
+  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);) {
+    pcent = ngtcp2_ringbuf_get(&conn->rx.path_challenge.rb, 0);
+
+    if (ngtcp2_path_eq(&conn->dcid.current.ps.path, &pcent->ps.path)) {
+      /* Send PATH_RESPONSE from conn_write_pkt. */
+      return 0;
+    }
+
+    if (pv) {
+      if (ngtcp2_path_eq(&pv->dcid.ps.path, &pcent->ps.path)) {
+        dcid = &pv->dcid;
+        break;
+      }
+      if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+          ngtcp2_path_eq(&pv->fallback_dcid.ps.path, &pcent->ps.path)) {
+        dcid = &pv->fallback_dcid;
+        break;
+      }
+    }
+
+    if (conn->server) {
+      break;
+    }
+
+    /* Client does not expect to respond to path validation against
+       unknown path */
+    ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb);
+    pcent = NULL;
+  }
+
+  if (pcent == NULL) {
+    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_size(destlen, NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+
+  if (conn->server && !(dcid->flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) {
+    tx_left = conn_server_tx_left(conn, dcid);
+    destlen = (size_t)ngtcp2_min_uint64((uint64_t)destlen, tx_left);
+    if (destlen == 0) {
+      return 0;
+    }
+  }
+
+  lfr.type = NGTCP2_FRAME_PATH_RESPONSE;
+  memcpy(lfr.path_response.data, pcent->data, sizeof(lfr.path_response.data));
+
+  nwrite = ngtcp2_conn_write_single_frame_pkt(
+    conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE,
+    &dcid->cid, &lfr, NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING, &pcent->ps.path, ts);
+  if (nwrite <= 0) {
+    return nwrite;
+  }
+
+  if (path) {
+    ngtcp2_path_copy(path, &pcent->ps.path);
+  }
+
+  ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb);
+
+  dcid->bytes_sent += (uint64_t)nwrite;
+
+  return nwrite;
+}
+
+ngtcp2_ssize ngtcp2_conn_write_pkt_versioned(ngtcp2_conn *conn,
+                                             ngtcp2_path *path,
+                                             int pkt_info_version,
+                                             ngtcp2_pkt_info *pi, uint8_t *dest,
+                                             size_t destlen, ngtcp2_tstamp ts) {
+  return ngtcp2_conn_writev_stream_versioned(
+    conn, path, pkt_info_version, pi, dest, destlen,
+    /* pdatalen = */ NULL, NGTCP2_WRITE_STREAM_FLAG_NONE,
+    /* stream_id = */ -1,
+    /* datav = */ NULL, /* datavcnt = */ 0, ts);
+}
+
+/*
+ * conn_on_version_negotiation is called when Version Negotiation
+ * packet is received.  The function decodes the data in the buffer
+ * pointed by |payload| whose length is |payloadlen| as Version
+ * Negotiation packet payload.  The packet header is given in |hd|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Packet payload is badly formatted.
+ */
+static int conn_on_version_negotiation(ngtcp2_conn *conn,
+                                       const ngtcp2_pkt_hd *hd,
+                                       const uint8_t *payload,
+                                       size_t payloadlen) {
+  uint32_t sv[16];
+  uint32_t *p;
+  int rv = 0;
+  size_t nsv;
+  size_t i;
+
+  if (payloadlen % sizeof(uint32_t)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  /* Version Negotiation packet is ignored if client has reacted upon
+     Version Negotiation packet. */
+  if (conn->local.settings.original_version != conn->client_chosen_version) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (payloadlen > sizeof(sv)) {
+    p = ngtcp2_mem_malloc(conn->mem, payloadlen);
+    if (p == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+  } else {
+    p = sv;
+  }
+
+  nsv = ngtcp2_pkt_decode_version_negotiation(p, payload, payloadlen);
+
+  ngtcp2_log_rx_vn(&conn->log, hd, p, nsv);
+
+  ngtcp2_qlog_version_negotiation_pkt_received(&conn->qlog, hd, p, nsv);
+
+  if (!ngtcp2_is_reserved_version(conn->local.settings.original_version)) {
+    for (i = 0; i < nsv; ++i) {
+      if (p[i] == conn->local.settings.original_version) {
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                        "ignore Version Negotiation because it contains the "
+                        "original version");
+
+        rv = NGTCP2_ERR_INVALID_ARGUMENT;
+        goto fin;
+      }
+    }
+  }
+
+  rv = conn_call_recv_version_negotiation(conn, hd, p, nsv);
+  if (rv != 0) {
+    goto fin;
+  }
+
+fin:
+  if (p != sv) {
+    ngtcp2_mem_free(conn->mem, p);
+  }
+
+  return rv;
+}
+
+static uint64_t conn_tx_strmq_first_cycle(ngtcp2_conn *conn) {
+  ngtcp2_strm *strm;
+
+  if (ngtcp2_pq_empty(&conn->tx.strmq)) {
+    return 0;
+  }
+
+  strm = ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe);
+  return strm->cycle;
+}
+
+uint64_t ngtcp2_conn_tx_strmq_first_cycle(ngtcp2_conn *conn) {
+  ngtcp2_strm *strm;
+
+  if (ngtcp2_pq_empty(&conn->tx.strmq)) {
+    return 0;
+  }
+
+  strm = ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe);
+  return strm->cycle;
+}
+
+/*
+ * conn_on_retry is called when Retry packet is received.  The
+ * function decodes the data in the buffer pointed by |pkt| whose
+ * length is |pktlen| as Retry packet.  The length of long packet
+ * header is given in |hdpktlen|.  |pkt| includes packet header.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Packet payload is badly formatted.
+ * NGTCP2_ERR_PROTO
+ *     ODCID does not match; or Token is empty.
+ */
+static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
+                         size_t hdpktlen, const uint8_t *pkt, size_t pktlen,
+                         ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_pkt_retry retry;
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_rtb *rtb = &conn->pktns.rtb;
+  ngtcp2_rtb *in_rtb;
+  uint8_t cidbuf[sizeof(retry.odcid.data) * 2 + 1];
+  uint8_t *token;
+
+  if (!in_pktns || conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) {
+    return 0;
+  }
+
+  in_rtb = &in_pktns->rtb;
+
+  rv = ngtcp2_pkt_decode_retry(&retry, pkt + hdpktlen, pktlen - hdpktlen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  retry.odcid = conn->dcid.current.cid;
+
+  rv = ngtcp2_pkt_verify_retry_tag(
+    conn->client_chosen_version, &retry, pkt, pktlen, conn->callbacks.encrypt,
+    &conn->crypto.retry_aead, &conn->crypto.retry_aead_ctx);
+  if (rv != 0) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "unable to verify Retry packet integrity");
+    return rv;
+  }
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, "odcid=0x%s",
+                  (const char *)ngtcp2_encode_hex(cidbuf, retry.odcid.data,
+                                                  retry.odcid.datalen));
+
+  if (retry.tokenlen == 0) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  if (ngtcp2_cid_eq(&conn->dcid.current.cid, &hd->scid)) {
+    return 0;
+  }
+
+  ngtcp2_qlog_retry_pkt_received(&conn->qlog, hd, &retry);
+
+  /* DCID must be updated before invoking callback because client
+     generates new initial keys there. */
+  conn->dcid.current.cid = hd->scid;
+  conn->retry_scid = hd->scid;
+
+  conn->flags |= NGTCP2_CONN_FLAG_RECV_RETRY;
+
+  rv = conn_call_recv_retry(conn, hd);
+  if (rv != 0) {
+    return rv;
+  }
+
+  conn->state = NGTCP2_CS_CLIENT_INITIAL;
+
+  /* Just freeing memory is dangerous because we might free twice. */
+
+  rv = ngtcp2_rtb_reclaim_on_retry(rtb, conn, &conn->pktns, &conn->cstat);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = ngtcp2_rtb_reclaim_on_retry(in_rtb, conn, in_pktns, &conn->cstat);
+  if (rv != 0) {
+    return rv;
+  }
+
+  ngtcp2_mem_free(conn->mem, (uint8_t *)conn->local.settings.token);
+  conn->local.settings.token = NULL;
+  conn->local.settings.tokenlen = 0;
+
+  token = ngtcp2_mem_malloc(conn->mem, retry.tokenlen);
+  if (token == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_cpymem(token, retry.token, retry.tokenlen);
+
+  conn->local.settings.token = token;
+  conn->local.settings.tokenlen = retry.tokenlen;
+
+  reset_conn_stat_recovery(&conn->cstat);
+  conn_reset_congestion_state(conn, ts);
+  conn_reset_ecn_validation_state(conn);
+
+  return 0;
+}
+
+int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                                ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) {
+  return ngtcp2_rtb_detect_lost_pkt(&pktns->rtb, conn, pktns, cstat, ts);
+}
+
+/*
+ * conn_recv_ack processes received ACK frame |fr|.  |pkt_ts| is the
+ * timestamp when packet is received.  |ts| should be the current
+ * time.  Usually they are the same, but for buffered packets,
+ * |pkt_ts| would be earlier than |ts|.  This function needs to be
+ * called after |fr| is validated by ngtcp2_pkt_validate_ack.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_PROTO
+ *     |fr| acknowledges a packet this endpoint has not sent.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User callback failed.
+ */
+static int conn_recv_ack(ngtcp2_conn *conn, ngtcp2_pktns *pktns, ngtcp2_ack *fr,
+                         ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) {
+  ngtcp2_ssize num_acked;
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+
+  if (pktns->tx.last_pkt_num < fr->largest_ack) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  ngtcp2_acktr_recv_ack(&pktns->acktr, fr);
+
+  num_acked =
+    ngtcp2_rtb_recv_ack(&pktns->rtb, fr, &conn->cstat, conn, pktns, pkt_ts, ts);
+  if (num_acked < 0) {
+    return (int)num_acked;
+  }
+
+  if (num_acked == 0) {
+    return 0;
+  }
+
+  pktns->rtb.probe_pkt_left = 0;
+
+  if (cstat->pto_count &&
+      (conn->server || (conn->flags & NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED))) {
+    /* Reset PTO count but no less than 2 to avoid frequent probe
+       packet transmission. */
+    cstat->pto_count = ngtcp2_min_size(cstat->pto_count, 2);
+  }
+
+  ngtcp2_conn_set_loss_detection_timer(conn, ts);
+
+  return 0;
+}
+
+/*
+ * conn_assign_recved_ack_delay_unscaled assigns
+ * fr->ack_delay_unscaled.
+ */
+static void assign_recved_ack_delay_unscaled(ngtcp2_ack *fr,
+                                             uint64_t ack_delay_exponent) {
+  const ngtcp2_tstamp max_ack_delay = ((1 << 14) - 1) * NGTCP2_MILLISECONDS;
+  uint64_t exp = (1ULL << ack_delay_exponent) * NGTCP2_MICROSECONDS;
+
+  if (fr->ack_delay > max_ack_delay / exp) {
+    fr->ack_delay_unscaled = max_ack_delay;
+    return;
+  }
+
+  fr->ack_delay_unscaled = fr->ack_delay * exp;
+}
+
+/*
+ * conn_recv_max_stream_data processes received MAX_STREAM_DATA frame
+ * |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_STREAM_STATE
+ *     Stream ID indicates that it is a local stream, and the local
+ *     endpoint has not initiated it; or stream is peer initiated
+ *     unidirectional stream.
+ * NGTCP2_ERR_STREAM_LIMIT
+ *     Stream ID exceeds allowed limit.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_recv_max_stream_data(ngtcp2_conn *conn,
+                                     const ngtcp2_max_stream_data *fr) {
+  ngtcp2_strm *strm;
+  ngtcp2_idtr *idtr;
+  int local_stream = conn_local_stream(conn, fr->stream_id);
+  int bidi = bidi_stream(fr->stream_id);
+  int rv;
+
+  if (bidi) {
+    if (local_stream) {
+      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+        return NGTCP2_ERR_STREAM_STATE;
+      }
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
+      return NGTCP2_ERR_STREAM_LIMIT;
+    }
+
+    idtr = &conn->remote.bidi.idtr;
+  } else {
+    if (!local_stream || conn->local.uni.next_stream_id <= fr->stream_id) {
+      return NGTCP2_ERR_STREAM_STATE;
+    }
+
+    idtr = &conn->remote.uni.idtr;
+  }
+
+  strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
+  if (strm == NULL) {
+    if (local_stream) {
+      /* Stream has been closed. */
+      return 0;
+    }
+
+    rv = ngtcp2_idtr_open(idtr, fr->stream_id);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      assert(rv == NGTCP2_ERR_STREAM_IN_USE);
+      /* Stream has been closed. */
+      return 0;
+    }
+
+    strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc);
+    if (strm == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+    rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL);
+    if (rv != 0) {
+      ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
+      return rv;
+    }
+
+    rv = conn_call_stream_open(conn, strm);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (strm->tx.max_offset < fr->max_stream_data) {
+    strm->tx.max_offset = fr->max_stream_data;
+
+    /* Don't call callback if stream is half-closed local */
+    if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) {
+      return 0;
+    }
+
+    rv = conn_call_extend_max_stream_data(conn, strm, fr->stream_id,
+                                          fr->max_stream_data);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * 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_uint64(conn->tx.max_offset, fr->max_data);
+}
+
+/*
+ * should_buffer_1rtt_pkt returns nonzero if 1RTT packet |pkt| of
+ * length |pktlen| should be buffered.
+ */
+static int should_buffer_1rtt_pkt(const uint8_t *pkt, size_t pktlen) {
+  /* A packet starting with 21 bytes zeros are most likely padding
+     bytes. */
+  return pktlen >= NGTCP2_MIN_QUIC_PKTLEN &&
+         (pkt[0] != 0 || memcmp(pkt, pkt + 1, NGTCP2_MIN_QUIC_PKTLEN - 1) != 0);
+}
+
+/*
+ * conn_buffer_pkt buffers |pkt| of length |pktlen|, chaining it from
+ * |*ppc|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_buffer_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                           const ngtcp2_path *path, const ngtcp2_pkt_info *pi,
+                           const uint8_t *pkt, size_t pktlen, size_t dgramlen,
+                           ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_pkt_chain **ppc = &pktns->rx.buffed_pkts, *pc;
+  size_t i;
+  for (i = 0; *ppc && i < NGTCP2_MAX_NUM_BUFFED_RX_PKTS;
+       ppc = &(*ppc)->next, ++i)
+    ;
+
+  if (i == NGTCP2_MAX_NUM_BUFFED_RX_PKTS) {
+    return 0;
+  }
+
+  rv =
+    ngtcp2_pkt_chain_new(&pc, path, pi, pkt, pktlen, dgramlen, ts, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  *ppc = pc;
+
+  return 0;
+}
+
+static int ensure_decrypt_buffer(ngtcp2_vec *vec, size_t n, size_t initial,
+                                 const ngtcp2_mem *mem) {
+  uint8_t *nbuf;
+  size_t len;
+
+  if (vec->len >= n) {
+    return 0;
+  }
+
+  len = vec->len == 0 ? initial : vec->len * 2;
+  for (; len < n; len *= 2)
+    ;
+  nbuf = ngtcp2_mem_realloc(mem, vec->base, len);
+  if (nbuf == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+  vec->base = nbuf;
+  vec->len = len;
+
+  return 0;
+}
+
+/*
+ * conn_ensure_decrypt_hp_buffer ensures that
+ * conn->crypto.decrypt_hp_buf has at least |n| bytes space.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_ensure_decrypt_hp_buffer(ngtcp2_conn *conn, size_t n) {
+  return ensure_decrypt_buffer(&conn->crypto.decrypt_hp_buf, n, 256, conn->mem);
+}
+
+/*
+ * conn_ensure_decrypt_buffer ensures that conn->crypto.decrypt_buf
+ * has at least |n| bytes space.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_ensure_decrypt_buffer(ngtcp2_conn *conn, size_t n) {
+  return ensure_decrypt_buffer(&conn->crypto.decrypt_buf, n, 2048, conn->mem);
+}
+
+/*
+ * decrypt_pkt decrypts the data pointed by |payload| whose length is
+ * |payloadlen|, and writes plaintext data to the buffer pointed by
+ * |dest|.  The buffer pointed by |aad| is the Additional
+ * Authenticated Data, and its length is |aadlen|.  |pkt_num| is used
+ * to create a nonce.  |ckm| is the cryptographic key, and iv to use.
+ * |decrypt| is a callback function which actually decrypts a packet.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User callback failed.
+ * NGTCP2_ERR_DECRYPT
+ *     Failed to decrypt a packet.
+ */
+static ngtcp2_ssize decrypt_pkt(uint8_t *dest, const ngtcp2_crypto_aead *aead,
+                                const uint8_t *payload, size_t payloadlen,
+                                const uint8_t *aad, size_t aadlen,
+                                int64_t pkt_num, ngtcp2_crypto_km *ckm,
+                                ngtcp2_decrypt decrypt) {
+  /* TODO nonce is limited to 64 bytes. */
+  uint8_t nonce[64];
+  int rv;
+
+  assert(sizeof(nonce) >= ckm->iv.len);
+
+  ngtcp2_crypto_create_nonce(nonce, ckm->iv.base, ckm->iv.len, pkt_num);
+
+  rv = decrypt(dest, aead, &ckm->aead_ctx, payload, payloadlen, nonce,
+               ckm->iv.len, aad, aadlen);
+
+  if (rv != 0) {
+    if (rv == NGTCP2_ERR_DECRYPT) {
+      return rv;
+    }
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  assert(payloadlen >= aead->max_overhead);
+
+  return (ngtcp2_ssize)(payloadlen - aead->max_overhead);
+}
+
+/*
+ * decrypt_hp decryptes packet header.  The packet number starts at
+ * |pkt| + |pkt_num_offset|.  The entire plaintext QUIC packet header
+ * will be written to the buffer pointed by |dest| whose capacity is
+ * |destlen|.
+ *
+ * This function returns the number of bytes written to |dest|, or one
+ * of the following negative error codes:
+ *
+ * NGTCP2_ERR_PROTO
+ *     Packet is badly formatted
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed; or it does not return
+ *     expected result.
+ */
+static ngtcp2_ssize
+decrypt_hp(ngtcp2_pkt_hd *hd, uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+           const uint8_t *pkt, size_t pktlen, size_t pkt_num_offset,
+           const ngtcp2_crypto_cipher_ctx *hp_ctx, ngtcp2_hp_mask hp_mask) {
+  size_t sample_offset;
+  uint8_t *p = dest;
+  uint8_t mask[NGTCP2_HP_SAMPLELEN];
+  size_t i;
+  int rv;
+
+  assert(hp_mask);
+
+  if (pkt_num_offset + 4 + NGTCP2_HP_SAMPLELEN > pktlen) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  p = ngtcp2_cpymem(p, pkt, pkt_num_offset);
+
+  sample_offset = pkt_num_offset + 4;
+
+  rv = hp_mask(mask, hp, hp_ctx, pkt + sample_offset);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) {
+    dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x0f));
+  } else {
+    dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x1f));
+    if (dest[0] & NGTCP2_SHORT_KEY_PHASE_BIT) {
+      hd->flags |= NGTCP2_PKT_FLAG_KEY_PHASE;
+    }
+  }
+
+  hd->pkt_numlen = (size_t)((dest[0] & NGTCP2_PKT_NUMLEN_MASK) + 1);
+
+  for (i = 0; i < 4; ++i) {
+    *p++ = *(pkt + pkt_num_offset + i) ^ mask[i + 1];
+  }
+
+  p -= 4;
+  hd->pkt_num = ngtcp2_get_pkt_num(p, hd->pkt_numlen);
+  p += hd->pkt_numlen;
+
+  return p - dest;
+}
+
+/*
+ * conn_emit_pending_crypto_data delivers pending stream data to the
+ * application due to packet reordering.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User callback failed
+ * NGTCP2_ERR_CRYPTO
+ *     TLS backend reported error
+ */
+static int
+conn_emit_pending_crypto_data(ngtcp2_conn *conn,
+                              ngtcp2_encryption_level encryption_level,
+                              ngtcp2_strm *strm, uint64_t rx_offset) {
+  size_t datalen;
+  const uint8_t *data;
+  int rv;
+  uint64_t offset;
+
+  if (!strm->rx.rob) {
+    return 0;
+  }
+
+  for (;;) {
+    datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset);
+    if (datalen == 0) {
+      assert(rx_offset == ngtcp2_strm_rx_offset(strm));
+      return 0;
+    }
+
+    offset = rx_offset;
+    rx_offset += datalen;
+
+    rv =
+      conn_call_recv_crypto_data(conn, encryption_level, offset, data, datalen);
+    if (rv != 0) {
+      return rv;
+    }
+
+    ngtcp2_rob_pop(strm->rx.rob, rx_offset - datalen, datalen);
+  }
+}
+
+/*
+ * conn_recv_connection_close is called when CONNECTION_CLOSE or
+ * APPLICATION_CLOSE frame is received.
+ */
+static int conn_recv_connection_close(ngtcp2_conn *conn,
+                                      ngtcp2_connection_close *fr) {
+  ngtcp2_ccerr *ccerr = &conn->rx.ccerr;
+
+  conn->state = NGTCP2_CS_DRAINING;
+  if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) {
+    ccerr->type = NGTCP2_CCERR_TYPE_TRANSPORT;
+  } else {
+    ccerr->type = NGTCP2_CCERR_TYPE_APPLICATION;
+  }
+  ccerr->error_code = fr->error_code;
+  ccerr->frame_type = fr->frame_type;
+
+  if (!fr->reasonlen) {
+    ccerr->reasonlen = 0;
+
+    return 0;
+  }
+
+  if (ccerr->reason == NULL) {
+    ccerr->reason = ngtcp2_mem_malloc(conn->mem, NGTCP2_CCERR_MAX_REASONLEN);
+    if (ccerr->reason == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+  }
+
+  ccerr->reasonlen = ngtcp2_min_size(fr->reasonlen, NGTCP2_CCERR_MAX_REASONLEN);
+  ngtcp2_cpymem((uint8_t *)ccerr->reason, fr->reason, ccerr->reasonlen);
+
+  return 0;
+}
+
+static void conn_recv_path_challenge(ngtcp2_conn *conn, const ngtcp2_path *path,
+                                     ngtcp2_path_challenge *fr) {
+  ngtcp2_path_challenge_entry *ent;
+
+  /* client only responds to PATH_CHALLENGE from the current path or
+     path which client is migrating to. */
+  if (!conn->server && !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) &&
+      (!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path))) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                    "discard PATH_CHALLENGE from the path which is not current "
+                    "or endpoint is migrating to");
+    return;
+  }
+
+  ent = ngtcp2_ringbuf_push_front(&conn->rx.path_challenge.rb);
+  ngtcp2_path_challenge_entry_init(ent, path, fr->data);
+}
+
+/*
+ * conn_reset_congestion_state resets congestion state.
+ */
+static void conn_reset_congestion_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  conn_reset_conn_stat_cc(conn, &conn->cstat);
+
+  if (conn->cc.reset) {
+    conn->cc.reset(&conn->cc, &conn->cstat, ts);
+  }
+
+  if (conn->hs_pktns) {
+    ngtcp2_rtb_reset_cc_state(&conn->hs_pktns->rtb,
+                              conn->hs_pktns->tx.last_pkt_num + 1);
+  }
+  ngtcp2_rtb_reset_cc_state(&conn->pktns.rtb, conn->pktns.tx.last_pkt_num + 1);
+  ngtcp2_rst_reset(&conn->rst);
+
+  conn->tx.pacing.next_ts = UINT64_MAX;
+  conn->tx.pacing.compensation = 0;
+}
+
+/*
+ * conn_server_preferred_addr_migration returns nonzero if
+ * |local_addr| equals to one of the preferred addresses.
+ */
+static int conn_server_preferred_addr_migration(const ngtcp2_conn *conn,
+                                                const ngtcp2_addr *local_addr) {
+  const ngtcp2_preferred_addr *paddr;
+
+  assert(conn->server);
+
+  if (!conn->local.transport_params.preferred_addr_present) {
+    return 0;
+  }
+
+  paddr = &conn->local.transport_params.preferred_addr;
+
+  switch (local_addr->addr->sa_family) {
+  case NGTCP2_AF_INET:
+    if (!paddr->ipv4_present) {
+      return 0;
+    }
+
+    return ngtcp2_sockaddr_eq((const ngtcp2_sockaddr *)&paddr->ipv4,
+                              local_addr->addr);
+  case NGTCP2_AF_INET6:
+    if (!paddr->ipv6_present) {
+      return 0;
+    }
+
+    return ngtcp2_sockaddr_eq((const ngtcp2_sockaddr *)&paddr->ipv6,
+                              local_addr->addr);
+  }
+
+  return 0;
+}
+
+static int conn_recv_path_response(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
+                                   ngtcp2_path_response *fr, ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_pv *pv = conn->pv, *npv = NULL;
+  uint8_t ent_flags;
+
+  if (!pv) {
+    return 0;
+  }
+
+  rv = ngtcp2_pv_validate(pv, &ent_flags, fr->data);
+  if (rv != 0) {
+    assert(!ngtcp2_err_is_fatal(rv));
+
+    return 0;
+  }
+
+  if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) {
+    if (pv->dcid.seq != conn->dcid.current.seq ||
+        !ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)) {
+      if (conn->dcid.current.cid.datalen &&
+          pv->dcid.seq != conn->dcid.current.seq) {
+        rv = conn_retire_active_dcid(conn, &conn->dcid.current, ts);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+
+      if (conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) {
+        ngtcp2_conn_add_path_history(conn, &conn->dcid.current, ts);
+      }
+
+      ngtcp2_dcid_copy(&conn->dcid.current, &pv->dcid);
+
+      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));
+
+    conn->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED;
+
+    if (!conn->local.settings.no_pmtud) {
+      ngtcp2_conn_stop_pmtud(conn);
+
+      if (!(ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED)) {
+        rv = conn_start_pmtud(conn);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+    }
+
+    if (!(ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED)) {
+      rv = conn_call_path_validation(conn, pv,
+                                     NGTCP2_PATH_VALIDATION_RESULT_SUCCESS);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+  }
+
+  if (ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED) {
+    assert(conn->server);
+
+    /* Validate path again */
+    rv = ngtcp2_pv_new(&npv, &pv->dcid, conn_compute_pv_timeout(conn),
+                       NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem);
+    if (rv != 0) {
+      return rv;
+    }
+
+    npv->dcid.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED;
+
+    if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) {
+      ngtcp2_pv_set_fallback(npv, &pv->fallback_dcid, pv->fallback_pto);
+    }
+  } else if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) {
+    rv = ngtcp2_pv_new(&npv, &pv->fallback_dcid,
+                       conn_compute_pv_timeout_pto(conn, pv->fallback_pto),
+                       NGTCP2_PV_FLAG_DONT_CARE, &conn->log, conn->mem);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) {
+    /* Unset the flag bit so that conn_stop_pv does not retire
+       DCID. */
+    pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_PRESENT;
+  }
+
+  rv = conn_stop_pv(conn, ts);
+  if (rv != 0) {
+    ngtcp2_pv_del(npv);
+    return rv;
+  }
+
+  conn->pv = npv;
+
+  return 0;
+}
+
+/*
+ * pktns_pkt_num_is_duplicate returns nonzero if |pkt_num| is
+ * duplicated packet number.
+ */
+static int pktns_pkt_num_is_duplicate(ngtcp2_pktns *pktns, int64_t pkt_num) {
+  return ngtcp2_gaptr_is_pushed(&pktns->rx.pngap, (uint64_t)pkt_num, 1);
+}
+
+/*
+ * pktns_commit_recv_pkt_num marks packet number |pkt_num| as
+ * received.  It stores |pkt_num| and its reception timestamp |ts| in
+ * order to send its ACK.  It also increase ECN counts from |pi|.
+ * |require_ack| is nonzero if the received packet is ack-eliciting.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_PROTO
+ *     Same packet number has already been added.
+ */
+static int pktns_commit_recv_pkt_num(ngtcp2_pktns *pktns, int64_t pkt_num,
+                                     const ngtcp2_pkt_info *pi, int require_ack,
+                                     ngtcp2_tstamp ts) {
+  ngtcp2_acktr *acktr = &pktns->acktr;
+  ngtcp2_range r;
+  int rv;
+
+  rv = ngtcp2_gaptr_push(&pktns->rx.pngap, (uint64_t)pkt_num, 1);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (ngtcp2_ksl_len(&pktns->rx.pngap.gap) > 256) {
+    ngtcp2_gaptr_drop_first_gap(&pktns->rx.pngap);
+  }
+
+  if (require_ack) {
+    if (pktns->rx.max_ack_eliciting_pkt_num != -1) {
+      if (pkt_num < pktns->rx.max_ack_eliciting_pkt_num) {
+        ngtcp2_acktr_immediate_ack(&pktns->acktr);
+      } else if (pkt_num != pktns->rx.max_ack_eliciting_pkt_num + 1) {
+        r = ngtcp2_gaptr_get_first_gap_after(
+          &pktns->rx.pngap, (uint64_t)pktns->rx.max_ack_eliciting_pkt_num);
+
+        if (r.begin < (uint64_t)pkt_num) {
+          ngtcp2_acktr_immediate_ack(&pktns->acktr);
+        }
+      }
+    }
+
+    if (pktns->rx.max_ack_eliciting_pkt_num < pkt_num) {
+      pktns->rx.max_ack_eliciting_pkt_num = pkt_num;
+    }
+  }
+
+  ngtcp2_acktr_increase_ecn_counts(acktr, pi);
+
+  rv = ngtcp2_acktr_add(acktr, pkt_num, require_ack, ts);
+  if (rv != 0) {
+    assert(rv != NGTCP2_ERR_INVALID_ARGUMENT);
+    return rv;
+  }
+
+  return 0;
+}
+
+/*
+ * verify_token verifies |hd| contains |token| in its token field.  It
+ * returns 0 if it succeeds, or NGTCP2_ERR_PROTO.
+ */
+static int verify_token(const uint8_t *token, size_t tokenlen,
+                        const ngtcp2_pkt_hd *hd) {
+  if (tokenlen == hd->tokenlen && ngtcp2_cmemeq(token, hd->token, tokenlen)) {
+    return 0;
+  }
+  return NGTCP2_ERR_PROTO;
+}
+
+/*
+ * vneg_available_versions_includes returns nonzero if
+ * |available_versions| of length |available_versionslen| includes
+ * |version|.  |available_versions| is the wire image of
+ * available_versions field of version_information transport
+ * parameter, and each version is encoded in network byte order.
+ */
+static int vneg_available_versions_includes(const uint8_t *available_versions,
+                                            size_t available_versionslen,
+                                            uint32_t version) {
+  size_t i;
+  uint32_t v;
+
+  assert(!(available_versionslen & 0x3));
+
+  if (available_versionslen == 0) {
+    return 0;
+  }
+
+  for (i = 0; i < available_versionslen; i += sizeof(uint32_t)) {
+    available_versions = ngtcp2_get_uint32be(&v, available_versions);
+
+    if (version == v) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * conn_verify_fixed_bit verifies that fixed bit in |hd| is
+ * acceptable.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Clearing fixed bit is not permitted.
+ */
+static int conn_verify_fixed_bit(ngtcp2_conn *conn, ngtcp2_pkt_hd *hd) {
+  if (!(hd->flags & NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR)) {
+    return 0;
+  }
+
+  if (conn->server) {
+    switch (hd->type) {
+    case NGTCP2_PKT_INITIAL:
+    case NGTCP2_PKT_0RTT:
+    case NGTCP2_PKT_HANDSHAKE:
+      /* RFC 9287 requires that a token from NEW_TOKEN. */
+      if (!(conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) &&
+          (conn->local.settings.token_type != NGTCP2_TOKEN_TYPE_NEW_TOKEN ||
+           !conn->local.settings.tokenlen)) {
+        return NGTCP2_ERR_INVALID_ARGUMENT;
+      }
+
+      break;
+    }
+  }
+
+  /* TODO we have no information that we enabled grease_quic_bit in
+     the previous connection. */
+  if (!conn->local.transport_params.grease_quic_bit) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  return 0;
+}
+
+static int conn_recv_crypto(ngtcp2_conn *conn,
+                            ngtcp2_encryption_level encryption_level,
+                            ngtcp2_strm *strm, const ngtcp2_stream *fr);
+
+static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
+                                  const ngtcp2_pkt_info *pi, const uint8_t *pkt,
+                                  size_t pktlen, size_t dgramlen,
+                                  ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts);
+
+static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn,
+                                               ngtcp2_pktns *pktns,
+                                               ngtcp2_tstamp ts);
+
+/*
+ * conn_recv_handshake_pkt processes received packet |pkt| whose
+ * length is |pktlen| during handshake period.  The buffer pointed by
+ * |pkt| might contain multiple packets.  This function only processes
+ * one packet.  |pkt_ts| is the timestamp when packet is received.
+ * |ts| should be the current time.  Usually they are the same, but
+ * for buffered packets, |pkt_ts| would be earlier than |ts|.
+ *
+ * This function returns the number of bytes it reads if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_RECV_VERSION_NEGOTIATION
+ *     Version Negotiation packet is received.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ * NGTCP2_ERR_DISCARD_PKT
+ *     Packet was discarded because plain text header was malformed;
+ *     or its payload could not be decrypted.
+ * NGTCP2_ERR_FRAME_FORMAT
+ *     Frame is badly formatted
+ * NGTCP2_ERR_ACK_FRAME
+ *     ACK frame is malformed.
+ * NGTCP2_ERR_CRYPTO
+ *     TLS stack reported error.
+ * NGTCP2_ERR_PROTO
+ *     Generic QUIC protocol error.
+ *
+ * In addition to the above error codes, error codes returned from
+ * conn_recv_pkt are also returned.
+ */
+static ngtcp2_ssize
+conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
+                        const ngtcp2_pkt_info *pi, const uint8_t *pkt,
+                        size_t pktlen, size_t dgramlen, ngtcp2_tstamp pkt_ts,
+                        ngtcp2_tstamp ts) {
+  ngtcp2_ssize nread;
+  ngtcp2_pkt_hd hd;
+  ngtcp2_max_frame mfr;
+  ngtcp2_frame *fr = &mfr.fr;
+  int rv;
+  int require_ack = 0;
+  size_t hdpktlen;
+  const uint8_t *payload;
+  size_t payloadlen;
+  ngtcp2_ssize nwrite;
+  ngtcp2_crypto_aead *aead;
+  ngtcp2_crypto_cipher *hp;
+  ngtcp2_crypto_km *ckm;
+  ngtcp2_crypto_cipher_ctx *hp_ctx;
+  ngtcp2_hp_mask hp_mask;
+  ngtcp2_decrypt decrypt;
+  ngtcp2_pktns *pktns;
+  ngtcp2_strm *crypto;
+  ngtcp2_encryption_level encryption_level;
+  int invalid_reserved_bits = 0;
+  size_t num_ack_processed = 0;
+
+  if (!(pkt[0] & NGTCP2_HEADER_FORM_BIT)) {
+    /* Ignore 1RTT packet unless server's first Handshake packet has
+       been transmitted. */
+    if (conn->state == NGTCP2_CS_SERVER_INITIAL ||
+        !should_buffer_1rtt_pkt(pkt, pktlen)) {
+      return (ngtcp2_ssize)pktlen;
+    }
+
+    if (conn->pktns.crypto.rx.ckm) {
+      return 0;
+    }
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                    "buffering 1RTT packet len=%zu", pktlen);
+
+    rv =
+      conn_buffer_pkt(conn, &conn->pktns, path, pi, pkt, pktlen, dgramlen, ts);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      return rv;
+    }
+    return (ngtcp2_ssize)pktlen;
+  }
+
+  nread = ngtcp2_pkt_decode_hd_long(&hd, pkt, pktlen);
+  if (nread < 0) {
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  switch (hd.type) {
+  case NGTCP2_PKT_VERSION_NEGOTIATION:
+    hdpktlen = (size_t)nread;
+
+    ngtcp2_log_rx_pkt_hd(&conn->log, &hd);
+
+    if (conn->server) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    /* Receiving Version Negotiation packet after getting Handshake
+       packet from server is invalid. */
+    if (conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (!ngtcp2_cid_eq(&conn->oscid, &hd.dcid)) {
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "packet was ignored because of mismatched DCID");
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (!ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) {
+      /* Just discard invalid Version Negotiation packet */
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "packet was ignored because of mismatched SCID");
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+    rv =
+      conn_on_version_negotiation(conn, &hd, pkt + hdpktlen, pktlen - hdpktlen);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+    return NGTCP2_ERR_RECV_VERSION_NEGOTIATION;
+  case NGTCP2_PKT_RETRY:
+    hdpktlen = (size_t)nread;
+
+    ngtcp2_log_rx_pkt_hd(&conn->log, &hd);
+
+    if (conn->server) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (conn_verify_fixed_bit(conn, &hd) != 0) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    /* Receiving Retry packet after getting Initial packet from server
+       is invalid. */
+    if (conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (conn->client_chosen_version != hd.version) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    rv = conn_on_retry(conn, &hd, hdpktlen, pkt, pktlen, ts);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+    return (ngtcp2_ssize)pktlen;
+  }
+
+  if (pktlen < (size_t)nread + hd.len) {
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  pktlen = (size_t)nread + hd.len;
+
+  if (!ngtcp2_is_supported_version(hd.version)) {
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  if (conn->server) {
+    if (hd.version != conn->client_chosen_version &&
+        (!conn->negotiated_version || hd.version != conn->negotiated_version)) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+  } else if (hd.version != conn->client_chosen_version &&
+             conn->negotiated_version &&
+             hd.version != conn->negotiated_version) {
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  if (conn_verify_fixed_bit(conn, &hd) != 0) {
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  /* Quoted from spec: if subsequent packets of those types include a
+     different Source Connection ID, they MUST be discarded. */
+  if ((conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) &&
+      !ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) {
+    ngtcp2_log_rx_pkt_hd(&conn->log, &hd);
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "packet was ignored because of mismatched SCID");
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  switch (hd.type) {
+  case NGTCP2_PKT_0RTT:
+    if (!conn->server) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (hd.version != conn->client_chosen_version) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) {
+      if (conn->early.ckm) {
+        ngtcp2_ssize nread2;
+        /* TODO Avoid to parse header twice. */
+        nread2 =
+          conn_recv_pkt(conn, path, pi, pkt, pktlen, dgramlen, pkt_ts, ts);
+        if (nread2 < 0) {
+          return nread2;
+        }
+      }
+
+      /* Discard 0-RTT packet if we don't have a key to decrypt it. */
+      return (ngtcp2_ssize)pktlen;
+    }
+
+    /* Buffer re-ordered 0-RTT packet. */
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                    "buffering 0-RTT packet len=%zu", pktlen);
+
+    rv = conn_buffer_pkt(conn, conn->in_pktns, path, pi, pkt, pktlen, dgramlen,
+                         ts);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      return rv;
+    }
+
+    return (ngtcp2_ssize)pktlen;
+  case NGTCP2_PKT_INITIAL:
+    if (!conn->in_pktns) {
+      ngtcp2_log_info(
+        &conn->log, NGTCP2_LOG_EVENT_PKT,
+        "Initial packet is discarded because keys have been discarded");
+      return (ngtcp2_ssize)pktlen;
+    }
+
+    assert(conn->in_pktns);
+
+    if (conn->server) {
+      if (dgramlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) {
+        ngtcp2_log_info(
+          &conn->log, NGTCP2_LOG_EVENT_PKT,
+          "Initial packet was ignored because it is included in UDP datagram "
+          "less than %zu bytes: %zu bytes",
+          NGTCP2_MAX_UDP_PAYLOAD_SIZE, dgramlen);
+        return NGTCP2_ERR_DISCARD_PKT;
+      }
+      if (conn->local.settings.tokenlen) {
+        rv = verify_token(conn->local.settings.token,
+                          conn->local.settings.tokenlen, &hd);
+        if (rv != 0) {
+          ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                          "packet was ignored because token is invalid");
+          return NGTCP2_ERR_DISCARD_PKT;
+        }
+      }
+      if ((conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) == 0) {
+        /* Set rcid here so that it is available to callback.  If this
+           packet is discarded later in this function and no packet is
+           processed in this connection attempt so far, connection
+           will be dropped. */
+        conn->rcid = hd.dcid;
+
+        rv = conn_call_recv_client_initial(conn, &hd.dcid);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+    } else {
+      if (hd.tokenlen != 0) {
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                        "packet was ignored because token is not empty");
+        return NGTCP2_ERR_DISCARD_PKT;
+      }
+
+      if (hd.version != conn->client_chosen_version &&
+          !conn->negotiated_version && conn->vneg.version != hd.version) {
+        if (!vneg_available_versions_includes(conn->vneg.available_versions,
+                                              conn->vneg.available_versionslen,
+                                              hd.version)) {
+          return NGTCP2_ERR_DISCARD_PKT;
+        }
+
+        /* 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->rcid);
+        if (rv != 0) {
+          return rv;
+        }
+
+        assert(conn->vneg.version == hd.version);
+      }
+    }
+
+    pktns = conn->in_pktns;
+    crypto = &pktns->crypto.strm;
+    encryption_level = NGTCP2_ENCRYPTION_LEVEL_INITIAL;
+
+    if (hd.version == conn->client_chosen_version) {
+      ckm = pktns->crypto.rx.ckm;
+      hp_ctx = &pktns->crypto.rx.hp_ctx;
+    } else {
+      assert(conn->vneg.version == hd.version);
+
+      ckm = conn->vneg.rx.ckm;
+      hp_ctx = &conn->vneg.rx.hp_ctx;
+    }
+
+    break;
+  case NGTCP2_PKT_HANDSHAKE:
+    if (hd.version != conn->negotiated_version) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (!conn->hs_pktns->crypto.rx.ckm) {
+      if (conn->server) {
+        ngtcp2_log_info(
+          &conn->log, NGTCP2_LOG_EVENT_PKT,
+          "Handshake packet at this point is unexpected and discarded");
+        return (ngtcp2_ssize)pktlen;
+      }
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                      "buffering Handshake packet len=%zu", pktlen);
+
+      rv = conn_buffer_pkt(conn, conn->hs_pktns, path, pi, pkt, pktlen,
+                           dgramlen, ts);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        return rv;
+      }
+      return (ngtcp2_ssize)pktlen;
+    }
+
+    pktns = conn->hs_pktns;
+    crypto = &pktns->crypto.strm;
+    encryption_level = NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE;
+    ckm = pktns->crypto.rx.ckm;
+    hp_ctx = &pktns->crypto.rx.hp_ctx;
+
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  hp_mask = conn->callbacks.hp_mask;
+  decrypt = conn->callbacks.decrypt;
+  aead = &pktns->crypto.ctx.aead;
+  hp = &pktns->crypto.ctx.hp;
+
+  assert(ckm);
+  assert(hp_mask);
+  assert(decrypt);
+
+  rv = conn_ensure_decrypt_hp_buffer(conn, (size_t)nread + 4);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nwrite = decrypt_hp(&hd, conn->crypto.decrypt_hp_buf.base, hp, pkt, pktlen,
+                      (size_t)nread, hp_ctx, hp_mask);
+  if (nwrite < 0) {
+    if (ngtcp2_err_is_fatal((int)nwrite)) {
+      return nwrite;
+    }
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "could not decrypt packet number");
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  hdpktlen = (size_t)nwrite;
+  payload = pkt + hdpktlen;
+  payloadlen = hd.len - hd.pkt_numlen;
+
+  hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->acktr.max_pkt_num, hd.pkt_num,
+                                         hd.pkt_numlen);
+  if (hd.pkt_num > NGTCP2_MAX_PKT_NUM) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num);
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  ngtcp2_log_rx_pkt_hd(&conn->log, &hd);
+
+  rv = ngtcp2_pkt_verify_reserved_bits(conn->crypto.decrypt_hp_buf.base[0]);
+  if (rv != 0) {
+    invalid_reserved_bits = 1;
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "packet has incorrect reserved bits");
+
+    /* Will return error after decrypting payload */
+  }
+
+  if (pktns_pkt_num_is_duplicate(pktns, hd.pkt_num)) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "packet was discarded because of duplicated packet number");
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  rv = conn_ensure_decrypt_buffer(conn, payloadlen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nwrite = decrypt_pkt(conn->crypto.decrypt_buf.base, aead, payload, payloadlen,
+                       conn->crypto.decrypt_hp_buf.base, hdpktlen, hd.pkt_num,
+                       ckm, decrypt);
+  if (nwrite < 0) {
+    if (ngtcp2_err_is_fatal((int)nwrite)) {
+      return nwrite;
+    }
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "could not decrypt packet payload");
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  if (invalid_reserved_bits) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  if (!conn->server && hd.version != conn->client_chosen_version &&
+      !conn->negotiated_version) {
+    conn->negotiated_version = hd.version;
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                    "the negotiated version is 0x%08x",
+                    conn->negotiated_version);
+  }
+
+  payload = conn->crypto.decrypt_buf.base;
+  payloadlen = (size_t)nwrite;
+
+  switch (hd.type) {
+  case NGTCP2_PKT_INITIAL:
+    if (!conn->server ||
+        ((conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED) &&
+         !ngtcp2_cid_eq(&conn->rcid, &hd.dcid))) {
+      rv = conn_verify_dcid(conn, NULL, &hd);
+      if (rv != 0) {
+        if (ngtcp2_err_is_fatal(rv)) {
+          return rv;
+        }
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                        "packet was ignored because of mismatched DCID");
+        return NGTCP2_ERR_DISCARD_PKT;
+      }
+    }
+    break;
+  case NGTCP2_PKT_HANDSHAKE:
+    rv = conn_verify_dcid(conn, NULL, &hd);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "packet was ignored because of mismatched DCID");
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  if (payloadlen == 0) {
+    /* QUIC packet must contain at least one frame */
+    if (hd.type == NGTCP2_PKT_INITIAL) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+    return NGTCP2_ERR_PROTO;
+  }
+
+  if (hd.type == NGTCP2_PKT_INITIAL &&
+      !(conn->flags & NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED)) {
+    conn->flags |= NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED;
+    if (!conn->server) {
+      conn->dcid.current.cid = hd.scid;
+    }
+  }
+
+  ngtcp2_qlog_pkt_received_start(&conn->qlog);
+
+  for (; payloadlen;) {
+    nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen);
+    if (nread < 0) {
+      return nread;
+    }
+
+    payload += nread;
+    payloadlen -= (size_t)nread;
+
+    switch (fr->type) {
+    case NGTCP2_FRAME_ACK:
+    case NGTCP2_FRAME_ACK_ECN:
+      fr->ack.ack_delay = 0;
+      fr->ack.ack_delay_unscaled = 0;
+
+      rv =
+        ngtcp2_pkt_validate_ack(&fr->ack, conn->local.settings.initial_pkt_num);
+      if (rv != 0) {
+        return rv;
+      }
+
+      break;
+    }
+
+    ngtcp2_log_rx_fr(&conn->log, &hd, fr);
+
+    switch (fr->type) {
+    case NGTCP2_FRAME_ACK:
+    case NGTCP2_FRAME_ACK_ECN:
+      if (num_ack_processed >= NGTCP2_MAX_ACK_PER_PKT) {
+        break;
+      }
+      if (!conn->server && hd.type == NGTCP2_PKT_HANDSHAKE) {
+        conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED;
+      }
+      rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts);
+      if (rv != 0) {
+        return rv;
+      }
+      ++num_ack_processed;
+      break;
+    case NGTCP2_FRAME_PADDING:
+      break;
+    case NGTCP2_FRAME_CRYPTO:
+      if (!conn->server && !conn->negotiated_version &&
+          ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt)) {
+        conn->negotiated_version = hd.version;
+
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                        "the negotiated version is 0x%08x",
+                        conn->negotiated_version);
+      }
+
+      rv = conn_recv_crypto(conn, encryption_level, crypto, &fr->stream);
+      if (rv != 0) {
+        return rv;
+      }
+      require_ack = 1;
+      break;
+    case NGTCP2_FRAME_CONNECTION_CLOSE:
+      rv = conn_recv_connection_close(conn, &fr->connection_close);
+      if (rv != 0) {
+        return rv;
+      }
+      break;
+    case NGTCP2_FRAME_PING:
+      require_ack = 1;
+      break;
+    default:
+      return NGTCP2_ERR_PROTO;
+    }
+
+    ngtcp2_qlog_write_frame(&conn->qlog, fr);
+  }
+
+  if (hd.type == NGTCP2_PKT_HANDSHAKE) {
+    /* Successful processing of Handshake packet from a remote
+       endpoint validates its source address. */
+    conn->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED;
+  }
+
+  ngtcp2_qlog_pkt_received_end(&conn->qlog, &hd, pktlen);
+
+  rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, pi, require_ack, pkt_ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* Initial and Handshake are always acknowledged without delay.  No
+     need to call ngtcp2_acktr_immediate_ack(). */
+
+  conn_restart_timer_on_read(conn, ts);
+
+  ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat);
+
+  return conn->state == NGTCP2_CS_DRAINING ? NGTCP2_ERR_DRAINING
+                                           : (ngtcp2_ssize)pktlen;
+}
+
+static int is_unrecoverable_error(int liberr) {
+  switch (liberr) {
+  case NGTCP2_ERR_CRYPTO:
+  case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM:
+  case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM:
+  case NGTCP2_ERR_TRANSPORT_PARAM:
+  case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE:
+    return 1;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_recv_handshake_cpkt processes compound packet during
+ * handshake.  The buffer pointed by |pkt| might contain multiple
+ * packets.  The 1RTT packet must be the last one because it does not
+ * have payload length field.
+ *
+ * This function returns the same error code returned by
+ * conn_recv_handshake_pkt.
+ */
+static ngtcp2_ssize conn_recv_handshake_cpkt(ngtcp2_conn *conn,
+                                             const ngtcp2_path *path,
+                                             const ngtcp2_pkt_info *pi,
+                                             const uint8_t *pkt, size_t pktlen,
+                                             ngtcp2_tstamp ts) {
+  ngtcp2_ssize nread;
+  size_t dgramlen = pktlen;
+  const uint8_t *origpkt = pkt;
+  uint32_t version;
+
+  if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) {
+    conn->dcid.current.bytes_recv += dgramlen;
+  }
+
+  while (pktlen) {
+    nread =
+      conn_recv_handshake_pkt(conn, path, pi, pkt, pktlen, dgramlen, ts, ts);
+    if (nread < 0) {
+      if (ngtcp2_err_is_fatal((int)nread)) {
+        return nread;
+      }
+
+      if (nread == NGTCP2_ERR_DRAINING) {
+        return NGTCP2_ERR_DRAINING;
+      }
+
+      if ((pkt[0] & NGTCP2_HEADER_FORM_BIT) && pktlen > 4) {
+        /* Not a Version Negotiation packet */
+        ngtcp2_get_uint32be(&version, &pkt[1]);
+        if (ngtcp2_pkt_get_type_long(version, pkt[0]) == NGTCP2_PKT_INITIAL) {
+          if (conn->server) {
+            if (is_unrecoverable_error((int)nread)) {
+              /* If server gets crypto error from TLS stack, it is
+                 unrecoverable, therefore drop connection. */
+              return nread;
+            }
+
+            /* If server discards first Initial, then drop connection
+               state.  This is because SCID in packet might be corrupted
+               and the current connection state might wrongly discard
+               valid packet and prevent the handshake from
+               completing. */
+            if (conn->in_pktns && conn->in_pktns->acktr.max_pkt_num == -1) {
+              return NGTCP2_ERR_DROP_CONN;
+            }
+
+            return (ngtcp2_ssize)dgramlen;
+          }
+          /* client */
+          if (is_unrecoverable_error((int)nread)) {
+            /* If client gets crypto error from TLS stack, it is
+               unrecoverable, therefore drop connection. */
+            return nread;
+          }
+          return (ngtcp2_ssize)dgramlen;
+        }
+      }
+
+      if (nread == NGTCP2_ERR_DISCARD_PKT) {
+        return (ngtcp2_ssize)dgramlen;
+      }
+
+      return nread;
+    }
+
+    if (nread == 0) {
+      assert(!(pkt[0] & NGTCP2_HEADER_FORM_BIT));
+      return pkt - origpkt;
+    }
+
+    assert(pktlen >= (size_t)nread);
+    pkt += nread;
+    pktlen -= (size_t)nread;
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "read packet %td left %zu", nread, pktlen);
+  }
+
+  return (ngtcp2_ssize)dgramlen;
+}
+
+int ngtcp2_conn_init_stream(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                            int64_t stream_id, void *stream_user_data) {
+  int rv;
+  uint64_t max_rx_offset;
+  uint64_t max_tx_offset;
+  int local_stream = conn_local_stream(conn, stream_id);
+
+  assert(conn->remote.transport_params);
+
+  if (bidi_stream(stream_id)) {
+    if (local_stream) {
+      max_rx_offset =
+        conn->local.transport_params.initial_max_stream_data_bidi_local;
+      max_tx_offset =
+        conn->remote.transport_params->initial_max_stream_data_bidi_remote;
+    } else {
+      max_rx_offset =
+        conn->local.transport_params.initial_max_stream_data_bidi_remote;
+      max_tx_offset =
+        conn->remote.transport_params->initial_max_stream_data_bidi_local;
+    }
+  } else if (local_stream) {
+    max_rx_offset = 0;
+    max_tx_offset = conn->remote.transport_params->initial_max_stream_data_uni;
+  } else {
+    max_rx_offset = conn->local.transport_params.initial_max_stream_data_uni;
+    max_tx_offset = 0;
+  }
+
+  ngtcp2_strm_init(strm, stream_id, NGTCP2_STRM_FLAG_NONE, max_rx_offset,
+                   max_tx_offset, stream_user_data, &conn->frc_objalloc,
+                   conn->mem);
+
+  rv =
+    ngtcp2_map_insert(&conn->strms, (ngtcp2_map_key_type)strm->stream_id, strm);
+  if (rv != 0) {
+    assert(rv != NGTCP2_ERR_INVALID_ARGUMENT);
+    goto fail;
+  }
+
+  return 0;
+
+fail:
+  ngtcp2_strm_free(strm);
+  return rv;
+}
+
+/*
+ * conn_emit_pending_stream_data passes buffered ordered stream data
+ * to the application.  |rx_offset| is the first offset to deliver to
+ * the application.  This function assumes that the data up to
+ * |rx_offset| has been delivered already.  This function only passes
+ * the ordered data without any gap.  If there is a gap, it stops
+ * providing the data to the application, and returns.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User callback failed.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                                         uint64_t rx_offset) {
+  size_t datalen;
+  const uint8_t *data;
+  int rv;
+  uint64_t offset;
+  uint32_t sdflags;
+  int handshake_completed = conn_is_tls_handshake_completed(conn);
+
+  if (!strm->rx.rob) {
+    return 0;
+  }
+
+  for (;;) {
+    /* Stop calling callback if application has called
+       ngtcp2_conn_shutdown_stream_read() inside the callback.
+       Because it doubly counts connection window. */
+    if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) {
+      return 0;
+    }
+
+    datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset);
+    if (datalen == 0) {
+      assert(rx_offset == ngtcp2_strm_rx_offset(strm));
+      return 0;
+    }
+
+    offset = rx_offset;
+    rx_offset += datalen;
+
+    sdflags = NGTCP2_STREAM_DATA_FLAG_NONE;
+    if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
+        rx_offset == strm->rx.last_offset) {
+      sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN;
+    }
+    if (!handshake_completed) {
+      sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT;
+    }
+
+    rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, datalen);
+    if (rv != 0) {
+      return rv;
+    }
+
+    /* ngtcp2_conn_shutdown_stream_read from a callback will free
+       strm->rx.rob. */
+    if (!strm->rx.rob) {
+      return 0;
+    }
+
+    ngtcp2_rob_pop(strm->rx.rob, rx_offset - datalen, datalen);
+  }
+}
+
+/*
+ * conn_recv_crypto is called when CRYPTO frame |fr| is received.
+ * |rx_offset_base| is the offset in the entire TLS handshake stream.
+ * fr->offset specifies the offset in each encryption level.
+ * |max_rx_offset| is, if it is nonzero, the maximum offset in the
+ * entire TLS handshake stream that |fr| can carry.
+ * |encryption_level| is the encryption level where this data is
+ * received.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_PROTO
+ *     CRYPTO frame has invalid offset.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CRYPTO
+ *     TLS stack reported error.
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     The end offset exceeds the maximum value.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_recv_crypto(ngtcp2_conn *conn,
+                            ngtcp2_encryption_level encryption_level,
+                            ngtcp2_strm *crypto, const ngtcp2_stream *fr) {
+  uint64_t fr_end_offset;
+  uint64_t rx_offset;
+  int rv;
+
+  if (fr->datacnt == 0) {
+    return 0;
+  }
+
+  fr_end_offset = fr->offset + fr->data[0].len;
+
+  if (NGTCP2_MAX_VARINT < fr_end_offset) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  rx_offset = ngtcp2_strm_rx_offset(crypto);
+
+  if (fr_end_offset <= rx_offset) {
+    if (conn->server &&
+        !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT) &&
+        encryption_level == NGTCP2_ENCRYPTION_LEVEL_INITIAL) {
+      /* https://datatracker.ietf.org/doc/html/rfc9002#section-6.2.3:
+         Speeding Up Handshake Completion
+
+         When a server receives an Initial packet containing duplicate
+         CRYPTO data, it can assume the client did not receive all of
+         the server's CRYPTO data sent in Initial packets, or the
+         client's estimated RTT is too small.  ...  To speed up
+         handshake completion under these conditions, an endpoint MAY
+         send a packet containing unacknowledged CRYPTO data earlier
+         than the PTO expiry, subject to address validation limits;
+         ... */
+      conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT;
+      conn->in_pktns->rtb.probe_pkt_left = 1;
+      conn->hs_pktns->rtb.probe_pkt_left = 1;
+    }
+    return 0;
+  }
+
+  crypto->rx.last_offset =
+    ngtcp2_max_uint64(crypto->rx.last_offset, fr_end_offset);
+
+  /* TODO Before dispatching incoming data to TLS stack, make sure
+     that previous data in previous encryption level has been
+     completely sent to TLS stack.  Usually, if data is left, it is an
+     error because key is generated after consuming all data in the
+     previous encryption level. */
+  if (fr->offset <= rx_offset) {
+    size_t ncut = (size_t)(rx_offset - fr->offset);
+    const uint8_t *data = fr->data[0].base + ncut;
+    size_t datalen = fr->data[0].len - ncut;
+    uint64_t offset = rx_offset;
+
+    rx_offset += datalen;
+    ngtcp2_strm_update_rx_offset(crypto, rx_offset);
+
+    rv =
+      conn_call_recv_crypto_data(conn, encryption_level, offset, data, datalen);
+    if (rv != 0) {
+      return rv;
+    }
+
+    rv =
+      conn_emit_pending_crypto_data(conn, encryption_level, crypto, rx_offset);
+    if (rv != 0) {
+      return rv;
+    }
+
+    return 0;
+  }
+
+  if (fr_end_offset - rx_offset > NGTCP2_MAX_REORDERED_CRYPTO_DATA) {
+    return NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED;
+  }
+
+  return ngtcp2_strm_recv_reordering(crypto, fr->data[0].base, fr->data[0].len,
+                                     fr->offset);
+}
+
+/*
+ * conn_max_data_violated returns nonzero if receiving |datalen|
+ * violates connection flow control on local endpoint.
+ */
+static int conn_max_data_violated(ngtcp2_conn *conn, uint64_t datalen) {
+  return conn->rx.max_offset - conn->rx.offset < datalen;
+}
+
+/*
+ * conn_recv_stream is called when STREAM frame |fr| is received.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_STREAM_STATE
+ *     STREAM frame is received for a local stream which is not
+ *     initiated; or STREAM frame is received for a local
+ *     unidirectional stream
+ * NGTCP2_ERR_STREAM_LIMIT
+ *     STREAM frame has remote stream ID which is strictly greater
+ *     than the allowed limit.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ * NGTCP2_ERR_FLOW_CONTROL
+ *     Flow control limit is violated; or the end offset of stream
+ *     data is beyond the NGTCP2_MAX_VARINT.
+ * NGTCP2_ERR_FINAL_SIZE
+ *     STREAM frame has strictly larger end offset than it is
+ *     permitted.
+ */
+static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) {
+  int rv;
+  ngtcp2_strm *strm;
+  ngtcp2_idtr *idtr;
+  uint64_t rx_offset, fr_end_offset;
+  int local_stream;
+  int bidi;
+  uint64_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt);
+  uint32_t sdflags = NGTCP2_STREAM_DATA_FLAG_NONE;
+
+  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) {
+        return NGTCP2_ERR_STREAM_STATE;
+      }
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
+      return NGTCP2_ERR_STREAM_LIMIT;
+    }
+
+    idtr = &conn->remote.bidi.idtr;
+  } else {
+    if (local_stream) {
+      return NGTCP2_ERR_STREAM_STATE;
+    }
+    if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
+      return NGTCP2_ERR_STREAM_LIMIT;
+    }
+
+    idtr = &conn->remote.uni.idtr;
+  }
+
+  if (NGTCP2_MAX_VARINT - datalen < fr->offset) {
+    return NGTCP2_ERR_FLOW_CONTROL;
+  }
+
+  strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
+  if (strm == NULL) {
+    if (local_stream) {
+      /* TODO The stream has been closed.  This should be responded
+         with RESET_STREAM, or simply ignored. */
+      return 0;
+    }
+
+    rv = ngtcp2_idtr_open(idtr, fr->stream_id);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      assert(rv == NGTCP2_ERR_STREAM_IN_USE);
+      /* TODO The stream has been closed.  This should be responded
+         with RESET_STREAM, or simply ignored. */
+      return 0;
+    }
+
+    strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc);
+    if (strm == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+
+    rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL);
+    if (rv != 0) {
+      ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
+      return rv;
+    }
+
+    if (!bidi) {
+      ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_WR);
+      strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED;
+    }
+
+    rv = conn_call_stream_open(conn, strm);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  fr_end_offset = fr->offset + datalen;
+
+  if (strm->rx.max_offset < fr_end_offset) {
+    return NGTCP2_ERR_FLOW_CONTROL;
+  }
+
+  if (strm->rx.last_offset < fr_end_offset) {
+    uint64_t len = fr_end_offset - strm->rx.last_offset;
+
+    if (conn_max_data_violated(conn, len)) {
+      return NGTCP2_ERR_FLOW_CONTROL;
+    }
+
+    conn->rx.offset += len;
+
+    if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) {
+      ngtcp2_conn_extend_max_offset(conn, len);
+    }
+  }
+
+  rx_offset = ngtcp2_strm_rx_offset(strm);
+
+  if (fr->fin) {
+    if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) {
+      if (strm->rx.last_offset != fr_end_offset) {
+        return NGTCP2_ERR_FINAL_SIZE;
+      }
+
+      if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) {
+        return 0;
+      }
+
+      if (rx_offset == fr_end_offset) {
+        return 0;
+      }
+    } else if (strm->rx.last_offset > fr_end_offset) {
+      return NGTCP2_ERR_FINAL_SIZE;
+    } else {
+      strm->rx.last_offset = fr_end_offset;
+
+      ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_RD);
+    }
+  } else {
+    if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
+        strm->rx.last_offset < fr_end_offset) {
+      return NGTCP2_ERR_FINAL_SIZE;
+    }
+
+    strm->rx.last_offset =
+      ngtcp2_max_uint64(strm->rx.last_offset, fr_end_offset);
+
+    if (fr_end_offset <= rx_offset) {
+      return 0;
+    }
+
+    if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) {
+      return 0;
+    }
+  }
+
+  if (fr->offset <= rx_offset) {
+    size_t ncut = (size_t)(rx_offset - fr->offset);
+    uint64_t offset = rx_offset;
+    const uint8_t *data;
+    int fin;
+
+    if (fr->datacnt) {
+      data = fr->data[0].base + ncut;
+      datalen -= ncut;
+
+      rx_offset += datalen;
+      ngtcp2_strm_update_rx_offset(strm, rx_offset);
+    } else {
+      data = NULL;
+      datalen = 0;
+    }
+
+    if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) {
+      return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm);
+    }
+
+    fin = (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) &&
+          rx_offset == strm->rx.last_offset;
+
+    if (fin || datalen) {
+      if (fin) {
+        sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN;
+      }
+      if (!conn_is_tls_handshake_completed(conn)) {
+        sdflags |= NGTCP2_STREAM_DATA_FLAG_0RTT;
+      }
+      rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data,
+                                      (size_t)datalen);
+      if (rv != 0) {
+        return rv;
+      }
+
+      rv = conn_emit_pending_stream_data(conn, strm, rx_offset);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+  } else if (fr->datacnt && !(strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING)) {
+    rv = ngtcp2_strm_recv_reordering(strm, fr->data[0].base, fr->data[0].len,
+                                     fr->offset);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+  return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm);
+}
+
+/*
+ * conn_reset_stream adds RESET_STREAM frame to the transmission
+ * queue.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_reset_stream(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                             uint64_t app_error_code) {
+  strm->flags |= NGTCP2_STRM_FLAG_SEND_RESET_STREAM;
+  strm->tx.reset_stream_app_error_code = app_error_code;
+
+  if (ngtcp2_strm_is_tx_queued(strm)) {
+    return 0;
+  }
+
+  strm->cycle = conn_tx_strmq_first_cycle(conn);
+
+  return ngtcp2_conn_tx_strmq_push(conn, strm);
+}
+
+/*
+ * conn_stop_sending adds STOP_SENDING frame to the transmission
+ * queue.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_stop_sending(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                             uint64_t app_error_code) {
+  strm->flags |= NGTCP2_STRM_FLAG_SEND_STOP_SENDING;
+  strm->tx.stop_sending_app_error_code = app_error_code;
+
+  if (ngtcp2_strm_is_tx_queued(strm)) {
+    return 0;
+  }
+
+  strm->cycle = conn_tx_strmq_first_cycle(conn);
+
+  return ngtcp2_conn_tx_strmq_push(conn, strm);
+}
+
+/*
+ * handle_max_remote_streams_extension extends
+ * |*punsent_max_remote_streams| by |n| if a condition allows it.
+ */
+static void
+handle_max_remote_streams_extension(uint64_t *punsent_max_remote_streams,
+                                    size_t n) {
+  if (
+#if SIZE_MAX == UINT64_MAX
+    NGTCP2_MAX_STREAMS < n ||
+#endif /* SIZE_MAX == UINT64_MAX */
+    *punsent_max_remote_streams > (uint64_t)(NGTCP2_MAX_STREAMS - n)) {
+    *punsent_max_remote_streams = NGTCP2_MAX_STREAMS;
+  } else {
+    *punsent_max_remote_streams += n;
+  }
+}
+
+/*
+ * conn_recv_reset_stream is called when RESET_STREAM |fr| is
+ * received.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_STREAM_STATE
+ *     RESET_STREAM frame is received to the local stream which is not
+ *     initiated.
+ * 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
+ *     User-defined callback function failed.
+ * NGTCP2_ERR_FLOW_CONTROL
+ *     Flow control limit is violated; or the final size is beyond the
+ *     NGTCP2_MAX_VARINT.
+ * NGTCP2_ERR_FINAL_SIZE
+ *     The final offset is strictly larger than it is permitted.
+ */
+static int conn_recv_reset_stream(ngtcp2_conn *conn,
+                                  const ngtcp2_reset_stream *fr) {
+  ngtcp2_strm *strm;
+  int local_stream = conn_local_stream(conn, fr->stream_id);
+  int bidi = bidi_stream(fr->stream_id);
+  uint64_t datalen;
+  ngtcp2_idtr *idtr;
+  int rv;
+
+  /* TODO share this piece of code */
+  if (bidi) {
+    if (local_stream) {
+      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+        return NGTCP2_ERR_STREAM_STATE;
+      }
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
+      return NGTCP2_ERR_STREAM_LIMIT;
+    }
+
+    idtr = &conn->remote.bidi.idtr;
+  } else {
+    if (local_stream) {
+      return NGTCP2_ERR_PROTO;
+    }
+    if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
+      return NGTCP2_ERR_STREAM_LIMIT;
+    }
+
+    idtr = &conn->remote.uni.idtr;
+  }
+
+  if (NGTCP2_MAX_VARINT < fr->final_size) {
+    return NGTCP2_ERR_FLOW_CONTROL;
+  }
+
+  strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
+  if (strm == NULL) {
+    if (local_stream) {
+      return 0;
+    }
+
+    rv = ngtcp2_idtr_open(idtr, fr->stream_id);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      assert(rv == NGTCP2_ERR_STREAM_IN_USE);
+      return 0;
+    }
+
+    if (conn_initial_stream_rx_offset(conn, fr->stream_id) < fr->final_size ||
+        conn_max_data_violated(conn, fr->final_size)) {
+      return NGTCP2_ERR_FLOW_CONTROL;
+    }
+
+    /* Stream is reset before we create ngtcp2_strm object. */
+    strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc);
+    if (strm == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+    rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL);
+    if (rv != 0) {
+      ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
+      return rv;
+    }
+
+    rv = conn_call_stream_open(conn, strm);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD)) {
+    if (strm->rx.last_offset != fr->final_size) {
+      return NGTCP2_ERR_FINAL_SIZE;
+    }
+  } else if (strm->rx.last_offset > fr->final_size) {
+    return NGTCP2_ERR_FINAL_SIZE;
+  }
+
+  if (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) {
+    return 0;
+  }
+
+  if (strm->rx.max_offset < fr->final_size) {
+    return NGTCP2_ERR_FLOW_CONTROL;
+  }
+
+  datalen = fr->final_size - strm->rx.last_offset;
+
+  if (conn_max_data_violated(conn, datalen)) {
+    return NGTCP2_ERR_FLOW_CONTROL;
+  }
+
+  rv = conn_call_stream_reset(conn, fr->stream_id, fr->final_size,
+                              fr->app_error_code, strm->stream_user_data);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* Extend connection flow control window for the amount of data
+     which are not passed to application. */
+  if (!(strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING)) {
+    ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset -
+                                          ngtcp2_strm_rx_offset(strm));
+  }
+
+  conn->rx.offset += datalen;
+  ngtcp2_conn_extend_max_offset(conn, datalen);
+
+  strm->rx.last_offset = fr->final_size;
+  strm->flags |=
+    NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_RESET_STREAM_RECVED;
+
+  ngtcp2_strm_set_app_error_code(strm, fr->app_error_code);
+
+  return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm);
+}
+
+/*
+ * conn_recv_stop_sending is called when STOP_SENDING |fr| is received.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_STREAM_STATE
+ *     STOP_SENDING frame is received for a local stream which is not
+ *     initiated; or STOP_SENDING frame is received for a local
+ *     unidirectional stream.
+ * NGTCP2_ERR_STREAM_LIMIT
+ *     STOP_SENDING frame has remote stream ID which is strictly
+ *     greater than the allowed limit.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_recv_stop_sending(ngtcp2_conn *conn,
+                                  const ngtcp2_stop_sending *fr) {
+  int rv;
+  ngtcp2_strm *strm;
+  ngtcp2_idtr *idtr;
+  int local_stream = conn_local_stream(conn, fr->stream_id);
+  int bidi = bidi_stream(fr->stream_id);
+
+  if (bidi) {
+    if (local_stream) {
+      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+        return NGTCP2_ERR_STREAM_STATE;
+      }
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
+      return NGTCP2_ERR_STREAM_LIMIT;
+    }
+
+    idtr = &conn->remote.bidi.idtr;
+  } else {
+    if (!local_stream || conn->local.uni.next_stream_id <= fr->stream_id) {
+      return NGTCP2_ERR_STREAM_STATE;
+    }
+
+    idtr = &conn->remote.uni.idtr;
+  }
+
+  strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
+  if (strm == NULL) {
+    if (local_stream) {
+      return 0;
+    }
+    rv = ngtcp2_idtr_open(idtr, fr->stream_id);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      assert(rv == NGTCP2_ERR_STREAM_IN_USE);
+      return 0;
+    }
+
+    /* STOP_SENDING frame is received before we create ngtcp2_strm
+       object. */
+    strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc);
+    if (strm == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+    rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL);
+    if (rv != 0) {
+      ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
+      return rv;
+    }
+
+    rv = conn_call_stream_open(conn, strm);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING_RECVED) {
+    return 0;
+  }
+
+  ngtcp2_strm_set_app_error_code(strm, fr->app_error_code);
+
+  /* No RESET_STREAM is required if we have sent FIN and all data have
+     been acknowledged. */
+  if (!ngtcp2_strm_is_all_tx_data_fin_acked(strm) &&
+      !(strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM)) {
+    strm->flags |= NGTCP2_STRM_FLAG_RESET_STREAM;
+
+    rv = conn_reset_stream(conn, strm, fr->app_error_code);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  strm->flags |=
+    NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_STOP_SENDING_RECVED;
+
+  ngtcp2_strm_streamfrq_clear(strm);
+
+  return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm);
+}
+
+/*
+ * check_stateless_reset returns nonzero if Stateless Reset |sr|
+ * coming via |path| is valid against |dcid|.
+ */
+static int check_stateless_reset(const ngtcp2_dcid *dcid,
+                                 const ngtcp2_path *path,
+                                 const ngtcp2_pkt_stateless_reset *sr) {
+  return ngtcp2_dcid_verify_stateless_reset_token(
+           dcid, path, sr->stateless_reset_token) == 0;
+}
+
+/*
+ * conn_on_stateless_reset decodes Stateless Reset from the buffer
+ * pointed by |payload| whose length is |payloadlen|.  |payload|
+ * should start after first byte of packet.
+ *
+ * If Stateless Reset is decoded, and the Stateless Reset Token is
+ * validated, the connection is closed.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Could not decode Stateless Reset; or Stateless Reset Token does
+ *     not match; or No stateless reset token is available.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User callback failed.
+ */
+static int conn_on_stateless_reset(ngtcp2_conn *conn, const ngtcp2_path *path,
+                                   const uint8_t *payload, size_t payloadlen) {
+  int rv;
+  ngtcp2_pv *pv = conn->pv;
+  ngtcp2_pkt_stateless_reset sr;
+
+  rv = ngtcp2_pkt_decode_stateless_reset(&sr, payload, payloadlen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (!check_stateless_reset(&conn->dcid.current, path, &sr) &&
+      (!pv || (!check_stateless_reset(&pv->dcid, path, &sr) &&
+               (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) ||
+                !check_stateless_reset(&pv->fallback_dcid, path, &sr))))) {
+    rv = ngtcp2_dcidtr_verify_stateless_reset(&conn->dcid.dtr, path,
+                                              sr.stateless_reset_token);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  conn->state = NGTCP2_CS_DRAINING;
+
+  ngtcp2_log_rx_sr(&conn->log, &sr);
+
+  ngtcp2_qlog_stateless_reset_pkt_received(&conn->qlog, &sr);
+
+  return conn_call_recv_stateless_reset(conn, &sr);
+}
+
+/*
+ * conn_recv_max_streams processes the incoming MAX_STREAMS frame
+ * |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User callback failed.
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     The maximum streams field exceeds the maximum value.
+ */
+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_uint64(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);
+    }
+    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);
+  }
+  return 0;
+}
+
+/*
+ * conn_recv_new_connection_id processes the incoming
+ * NEW_CONNECTION_ID frame |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_PROTO
+ *     |fr| has the duplicated sequence number with different CID or
+ *     token; 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;
+  }
+
+  rv = ngtcp2_dcid_verify_uniqueness(&conn->dcid.current, fr->seq, &fr->cid,
+                                     fr->stateless_reset_token);
+  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->stateless_reset_token);
+    if (rv != 0) {
+      return rv;
+    }
+    if (ngtcp2_cid_eq(&pv->dcid.cid, &fr->cid)) {
+      found = 1;
+    }
+  }
+
+  rv = ngtcp2_dcidtr_verify_token_uniqueness(
+    &conn->dcid.dtr, &found, fr->seq, &fr->cid, fr->stateless_reset_token);
+  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(
+      &conn->dcid.dtr, fr->retire_prior_to, dcidtr_on_retire, conn);
+    if (rv != 0) {
+      return rv;
+    }
+  } else if (fr->seq < conn->dcid.retire_prior_to) {
+    /* If packets are reordered, we might have retire_prior_to which
+       is larger than fr->seq.
+
+       A malicious peer might send crafted NEW_CONNECTION_ID to force
+       local endpoint to create lots of RETIRE_CONNECTION_ID frames.
+       For example, a peer might send seq = 50000 and retire_prior_to
+       = 50000.  Then send NEW_CONNECTION_ID frames with seq <
+       50000. */
+    if (ngtcp2_dcidtr_check_retired_seq_tracked(&conn->dcid.dtr, fr->seq)) {
+      return 0;
+    }
+
+    rv = ngtcp2_dcidtr_track_retired_seq(&conn->dcid.dtr, fr->seq);
+    if (rv != 0) {
+      return rv;
+    }
+
+    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) {
+    ++extra_dcid;
+  }
+  if (pv) {
+    if (pv->dcid.seq != conn->dcid.current.seq &&
+        pv->dcid.seq >= conn->dcid.retire_prior_to) {
+      ++extra_dcid;
+    }
+    if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+        pv->fallback_dcid.seq != conn->dcid.current.seq &&
+        pv->fallback_dcid.seq >= conn->dcid.retire_prior_to) {
+      ++extra_dcid;
+    }
+  }
+
+  if (conn->local.transport_params.active_connection_id_limit <=
+      len + extra_dcid) {
+    return NGTCP2_ERR_CONNECTION_ID_LIMIT;
+  }
+
+  if (len >= NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "too many connection ID");
+    return 0;
+  }
+
+  ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, fr->seq, &fr->cid,
+                            fr->stateless_reset_token);
+
+  return 0;
+}
+
+/*
+ * conn_post_process_recv_new_connection_id handles retirement request
+ * of active DCIDs.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_post_process_recv_new_connection_id(ngtcp2_conn *conn,
+                                                    ngtcp2_tstamp ts) {
+  ngtcp2_pv *pv = conn->pv;
+  ngtcp2_dcid dcid;
+  int rv;
+
+  if (conn->dcid.current.seq < conn->dcid.retire_prior_to) {
+    if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) {
+      return 0;
+    }
+
+    rv = conn_retire_active_dcid(conn, &conn->dcid.current, ts);
+    if (rv != 0) {
+      return rv;
+    }
+
+    ngtcp2_dcidtr_pop_unused_cid_token(&conn->dcid.dtr, &dcid);
+
+    if (pv) {
+      if (conn->dcid.current.seq == pv->dcid.seq) {
+        ngtcp2_dcid_copy_cid_token(&pv->dcid, &dcid);
+      }
+      if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+          conn->dcid.current.seq == pv->fallback_dcid.seq) {
+        ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, &dcid);
+      }
+    }
+
+    ngtcp2_dcid_copy_cid_token(&conn->dcid.current, &dcid);
+
+    rv = conn_call_activate_dcid(conn, &conn->dcid.current);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (pv) {
+    if (pv->dcid.seq < conn->dcid.retire_prior_to) {
+      if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) {
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV,
+                        "path migration is aborted because connection ID is"
+                        "retired and no unused connection ID is available");
+
+        return conn_abort_pv(conn, ts);
+      }
+
+      rv = conn_retire_active_dcid(conn, &pv->dcid, ts);
+      if (rv != 0) {
+        return rv;
+      }
+
+      ngtcp2_dcidtr_pop_unused_cid_token(&conn->dcid.dtr, &dcid);
+
+      if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+          pv->dcid.seq == pv->fallback_dcid.seq) {
+        ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, &dcid);
+      }
+
+      ngtcp2_dcid_copy_cid_token(&pv->dcid, &dcid);
+
+      rv = conn_call_activate_dcid(conn, &pv->dcid);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+    if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+        pv->fallback_dcid.seq < conn->dcid.retire_prior_to) {
+      if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) {
+        /* Now we have no fallback dcid. */
+        return conn_abort_pv(conn, ts);
+      }
+
+      rv = conn_retire_active_dcid(conn, &pv->fallback_dcid, ts);
+      if (rv != 0) {
+        return rv;
+      }
+
+      ngtcp2_dcidtr_pop_unused_cid_token(&conn->dcid.dtr, &dcid);
+      ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, &dcid);
+
+      rv = conn_call_activate_dcid(conn, &pv->fallback_dcid);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * conn_recv_retire_connection_id processes the incoming
+ * RETIRE_CONNECTION_ID frame |fr|.  |hd| is a packet header which
+ * |fr| is included.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_PROTO
+ *     SCID is zero-length.
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Attempt to retire CID which is used as DCID to send this frame.
+ */
+static int conn_recv_retire_connection_id(ngtcp2_conn *conn,
+                                          const ngtcp2_pkt_hd *hd,
+                                          const ngtcp2_retire_connection_id *fr,
+                                          ngtcp2_tstamp ts) {
+  ngtcp2_ksl_it it;
+  ngtcp2_scid *scid;
+
+  if (conn->oscid.datalen == 0 || conn->scid.last_seq < fr->seq) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  for (it = ngtcp2_ksl_begin(&conn->scid.set); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    scid = ngtcp2_ksl_it_get(&it);
+    if (scid->seq == fr->seq) {
+      if (ngtcp2_cid_eq(&scid->cid, &hd->dcid)) {
+        return NGTCP2_ERR_PROTO;
+      }
+
+      if (!(scid->flags & NGTCP2_SCID_FLAG_RETIRED)) {
+        scid->flags |= NGTCP2_SCID_FLAG_RETIRED;
+        ++conn->scid.num_retired;
+      }
+
+      if (scid->pe.index != NGTCP2_PQ_BAD_INDEX) {
+        ngtcp2_pq_remove(&conn->scid.used, &scid->pe);
+        scid->pe.index = NGTCP2_PQ_BAD_INDEX;
+      }
+
+      scid->retired_ts = ts;
+
+      return ngtcp2_pq_push(&conn->scid.used, &scid->pe);
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * conn_recv_new_token processes the incoming NEW_TOKEN frame |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Token is empty
+ * NGTCP2_ERR_PROTO:
+ *     Server received NEW_TOKEN.
+ */
+static int conn_recv_new_token(ngtcp2_conn *conn, const ngtcp2_new_token *fr) {
+  if (conn->server) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  if (fr->tokenlen == 0) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  return conn_call_recv_new_token(conn, fr->token, fr->tokenlen);
+}
+
+/*
+ * conn_recv_streams_blocked_bidi processes the incoming
+ * STREAMS_BLOCKED (0x16).
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     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;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_recv_streams_blocked_uni processes the incoming
+ * STREAMS_BLOCKED (0x17).
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     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;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_recv_stream_data_blocked processes the incoming
+ * STREAM_DATA_BLOCKED frame |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_STREAM_STATE
+ *     STREAM_DATA_BLOCKED is received for a local stream which is not
+ *     initiated; or it is received for a local unidirectional stream.
+ * NGTCP2_ERR_STREAM_LIMIT
+ *     STREAM_DATA_BLOCKED has remote stream ID which is strictly
+ *     greater than the allowed limit.
+ * NGTCP2_ERR_FLOW_CONTROL
+ *     STREAM_DATA_BLOCKED frame violates flow control limit.
+ * NGTCP2_ERR_FINAL_SIZE
+ *     The offset is strictly larger than it is permitted.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_recv_stream_data_blocked(ngtcp2_conn *conn,
+                                         ngtcp2_stream_data_blocked *fr) {
+  int rv;
+  ngtcp2_strm *strm;
+  ngtcp2_idtr *idtr;
+  int local_stream = conn_local_stream(conn, fr->stream_id);
+  int bidi = bidi_stream(fr->stream_id);
+  uint64_t datalen;
+
+  if (bidi) {
+    if (local_stream) {
+      if (conn->local.bidi.next_stream_id <= fr->stream_id) {
+        return NGTCP2_ERR_STREAM_STATE;
+      }
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
+      return NGTCP2_ERR_STREAM_LIMIT;
+    }
+
+    idtr = &conn->remote.bidi.idtr;
+  } else {
+    if (local_stream) {
+      return NGTCP2_ERR_STREAM_STATE;
+    }
+    if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
+      return NGTCP2_ERR_STREAM_LIMIT;
+    }
+
+    idtr = &conn->remote.uni.idtr;
+  }
+
+  strm = ngtcp2_conn_find_stream(conn, fr->stream_id);
+  if (strm == NULL) {
+    if (local_stream) {
+      return 0;
+    }
+
+    rv = ngtcp2_idtr_open(idtr, fr->stream_id);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      assert(rv == NGTCP2_ERR_STREAM_IN_USE);
+      return 0;
+    }
+
+    /* Frame is received before we create ngtcp2_strm object. */
+    strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc);
+    if (strm == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+    rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL);
+    if (rv != 0) {
+      ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
+      return rv;
+    }
+
+    if (!bidi) {
+      ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_WR);
+      strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED;
+    }
+
+    rv = conn_call_stream_open(conn, strm);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (strm->rx.max_offset < fr->offset) {
+    return NGTCP2_ERR_FLOW_CONTROL;
+  }
+
+  if (fr->offset <= strm->rx.last_offset) {
+    return 0;
+  }
+
+  if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) {
+    return NGTCP2_ERR_FINAL_SIZE;
+  }
+
+  datalen = fr->offset - strm->rx.last_offset;
+  if (datalen) {
+    if (conn_max_data_violated(conn, datalen)) {
+      return NGTCP2_ERR_FLOW_CONTROL;
+    }
+
+    conn->rx.offset += datalen;
+
+    if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) {
+      ngtcp2_conn_extend_max_offset(conn, datalen);
+    }
+  }
+
+  strm->rx.last_offset = fr->offset;
+
+  return 0;
+}
+
+/*
+ * conn_recv_data_blocked processes the incoming DATA_BLOCKED frame
+ * |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_FLOW_CONTROL
+ *     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) {
+    return NGTCP2_ERR_FLOW_CONTROL;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_select_preferred_addr asks a client application to select a
+ * server address from preferred addresses received from server.  If a
+ * client chooses the address, path validation will start.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_select_preferred_addr(ngtcp2_conn *conn) {
+  ngtcp2_path_storage ps;
+  int rv;
+  ngtcp2_pv *pv;
+  ngtcp2_dcid dcid;
+
+  if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) {
+    return 0;
+  }
+
+  ngtcp2_path_storage_zero(&ps);
+  ngtcp2_addr_copy(&ps.path.local, &conn->dcid.current.ps.path.local);
+
+  rv = conn_call_select_preferred_addr(conn, &ps.path);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (ngtcp2_addr_empty(&ps.path.remote) ||
+      ngtcp2_addr_eq(&conn->dcid.current.ps.path.remote, &ps.path.remote)) {
+    return 0;
+  }
+
+  assert(conn->pv == NULL);
+
+  ngtcp2_dcidtr_pop_unused(&conn->dcid.dtr, &dcid);
+  ngtcp2_dcid_set_path(&dcid, &ps.path);
+
+  rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout(conn),
+                     NGTCP2_PV_FLAG_PREFERRED_ADDR, &conn->log, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  conn->pv = pv;
+
+  return conn_call_activate_dcid(conn, &pv->dcid);
+}
+
+/*
+ * conn_recv_handshake_done processes the incoming HANDSHAKE_DONE
+ * frame |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_PROTO
+ *     Server received HANDSHAKE_DONE frame.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_recv_handshake_done(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  int rv;
+
+  if (conn->server) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  if (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) {
+    return 0;
+  }
+
+  conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED |
+                 NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED;
+
+  conn->handshake_confirmed_ts = ts;
+
+  ngtcp2_conn_discard_handshake_state(conn, ts);
+
+  assert(conn->remote.transport_params);
+
+  if (conn->remote.transport_params->preferred_addr_present) {
+    rv = conn_select_preferred_addr(conn);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  rv = conn_call_handshake_confirmed(conn);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* Re-arm loss detection timer after handshake has been
+     confirmed. */
+  ngtcp2_conn_set_loss_detection_timer(conn, ts);
+
+  return 0;
+}
+
+/*
+ * conn_recv_datagram processes the incoming DATAGRAM frame |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+static int conn_recv_datagram(ngtcp2_conn *conn, ngtcp2_datagram *fr) {
+  assert(conn->local.transport_params.max_datagram_frame_size);
+
+  return conn_call_recv_datagram(conn, fr);
+}
+
+/*
+ * conn_key_phase_changed returns nonzero if |hd| indicates that the
+ * key phase has unexpected value.
+ */
+static int conn_key_phase_changed(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd) {
+  ngtcp2_pktns *pktns = &conn->pktns;
+
+  return !(pktns->crypto.rx.ckm->flags & NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE) ^
+         !(hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE);
+}
+
+static int conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts);
+
+/*
+ * conn_prepare_key_update installs new updated keys.
+ */
+static int conn_prepare_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_tstamp confirmed_ts = conn->crypto.key_update.confirmed_ts;
+  ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns);
+  ngtcp2_pktns *pktns = &conn->pktns;
+  ngtcp2_crypto_km *rx_ckm = pktns->crypto.rx.ckm;
+  ngtcp2_crypto_km *tx_ckm = pktns->crypto.tx.ckm;
+  ngtcp2_crypto_km *new_rx_ckm, *new_tx_ckm;
+  ngtcp2_crypto_aead_ctx rx_aead_ctx = {0}, tx_aead_ctx = {0};
+  size_t secretlen, ivlen;
+
+  if ((conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) &&
+      tx_ckm->use_count >= pktns->crypto.ctx.max_encryption &&
+      conn_initiate_key_update(conn, ts) != 0) {
+    return NGTCP2_ERR_AEAD_LIMIT_REACHED;
+  }
+
+  if ((conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) ||
+      ngtcp2_tstamp_not_elapsed(confirmed_ts, pto, ts)) {
+    return 0;
+  }
+
+  if (conn->crypto.key_update.new_rx_ckm ||
+      conn->crypto.key_update.new_tx_ckm) {
+    assert(conn->crypto.key_update.new_rx_ckm);
+    assert(conn->crypto.key_update.new_tx_ckm);
+    return 0;
+  }
+
+  secretlen = rx_ckm->secret.len;
+  ivlen = rx_ckm->iv.len;
+
+  rv = ngtcp2_crypto_km_nocopy_new(&conn->crypto.key_update.new_rx_ckm,
+                                   secretlen, ivlen, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = ngtcp2_crypto_km_nocopy_new(&conn->crypto.key_update.new_tx_ckm,
+                                   secretlen, ivlen, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  new_rx_ckm = conn->crypto.key_update.new_rx_ckm;
+  new_tx_ckm = conn->crypto.key_update.new_tx_ckm;
+
+  rv = conn_call_update_key(
+    conn, new_rx_ckm->secret.base, new_tx_ckm->secret.base, &rx_aead_ctx,
+    new_rx_ckm->iv.base, &tx_aead_ctx, new_tx_ckm->iv.base, rx_ckm->secret.base,
+    tx_ckm->secret.base, secretlen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  new_rx_ckm->aead_ctx = rx_aead_ctx;
+  new_tx_ckm->aead_ctx = tx_aead_ctx;
+
+  if (!(rx_ckm->flags & NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE)) {
+    new_rx_ckm->flags |= NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE;
+    new_tx_ckm->flags |= NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE;
+  }
+
+  if (conn->crypto.key_update.old_rx_ckm) {
+    conn_call_delete_crypto_aead_ctx(
+      conn, &conn->crypto.key_update.old_rx_ckm->aead_ctx);
+    ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem);
+    conn->crypto.key_update.old_rx_ckm = NULL;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_rotate_keys rotates keys.  The current key moves to old key,
+ * and new key moves to the current key.  If the local endpoint
+ * initiated this key update, pass nonzero as |initiator|.
+ */
+static void conn_rotate_keys(ngtcp2_conn *conn, int64_t pkt_num,
+                             int initiator) {
+  ngtcp2_pktns *pktns = &conn->pktns;
+
+  assert(conn->crypto.key_update.new_rx_ckm);
+  assert(conn->crypto.key_update.new_tx_ckm);
+  assert(!conn->crypto.key_update.old_rx_ckm);
+  assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING));
+
+  conn->crypto.key_update.old_rx_ckm = pktns->crypto.rx.ckm;
+
+  pktns->crypto.rx.ckm = conn->crypto.key_update.new_rx_ckm;
+  conn->crypto.key_update.new_rx_ckm = NULL;
+  pktns->crypto.rx.ckm->pkt_num = pkt_num;
+
+  assert(pktns->crypto.tx.ckm);
+
+  conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx);
+  ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem);
+
+  pktns->crypto.tx.ckm = conn->crypto.key_update.new_tx_ckm;
+  conn->crypto.key_update.new_tx_ckm = NULL;
+  pktns->crypto.tx.ckm->pkt_num = pktns->tx.last_pkt_num + 1;
+
+  conn->flags |= NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED;
+  if (initiator) {
+    conn->flags |= NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR;
+  }
+}
+
+/*
+ * conn_path_validation_in_progress returns nonzero if path validation
+ * against |path| is underway.
+ */
+static int conn_path_validation_in_progress(ngtcp2_conn *conn,
+                                            const ngtcp2_path *path) {
+  ngtcp2_pv *pv = conn->pv;
+
+  return pv && ngtcp2_path_eq(&pv->dcid.ps.path, path);
+}
+
+/*
+ * 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.
+ *
+ * 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_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,
+                                                 ngtcp2_tstamp ts) {
+  ngtcp2_dcid dcid;
+  ngtcp2_pv *pv = NULL;
+  int rv;
+  ngtcp2_duration pto;
+  int require_new_cid;
+  int local_addr_eq;
+  int pref_addr_migration;
+  uint32_t remote_addr_cmp;
+  const ngtcp2_path_history_entry *validated_path;
+
+  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);
+    }
+
+    return 0;
+  }
+
+  remote_addr_cmp =
+    ngtcp2_addr_cmp(&conn->dcid.current.ps.path.remote, &path->remote);
+  local_addr_eq =
+    ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local);
+  pref_addr_migration =
+    !local_addr_eq && conn_server_preferred_addr_migration(conn, &path->local);
+
+  /*
+   * When to change DCID?  RFC 9002 section 9.5 says:
+   *
+   * An endpoint MUST NOT reuse a connection ID when sending from more
+   * than one local address -- for example, when initiating connection
+   * migration as described in Section 9.2 or when probing a new
+   * network path as described in Section 9.1.
+   *
+   * Similarly, an endpoint MUST NOT reuse a connection ID when
+   * sending to more than one destination address.  Due to network
+   * changes outside the control of its peer, an endpoint might
+   * receive packets from a new source address with the same
+   * Destination Connection ID field value, in which case it MAY
+   * continue to use the current connection ID with the new remote
+   * address while still sending from the same local address.
+   */
+  require_new_cid = conn->dcid.current.cid.datalen &&
+                    ((new_cid_used && remote_addr_cmp) || !local_addr_eq);
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                  "non-probing packet was received from new remote address");
+
+  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");
+
+    require_new_cid = 0;
+
+    if (dcid.cid.datalen) {
+      rv = conn_call_activate_dcid(conn, &dcid);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+  } else {
+    if (require_new_cid) {
+      if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) {
+        return NGTCP2_ERR_CONN_ID_BLOCKED;
+      }
+
+      ngtcp2_dcidtr_pop_unused(&conn->dcid.dtr, &dcid);
+
+      rv = conn_call_activate_dcid(conn, &dcid);
+      if (rv != 0) {
+        return rv;
+      }
+    } else {
+      /* Use the current DCID if a remote endpoint does not change
+         DCID. */
+      ngtcp2_dcid_copy(&dcid, &conn->dcid.current);
+      dcid.bytes_sent = 0;
+      dcid.bytes_recv = 0;
+      dcid.flags &= (uint8_t)~NGTCP2_DCID_FLAG_PATH_VALIDATED;
+    }
+
+    ngtcp2_dcid_set_path(&dcid, path);
+  }
+
+  dcid.bytes_recv += dgramlen;
+
+  validated_path = ngtcp2_conn_find_path_history(conn, path, ts);
+  if (!validated_path) {
+    pto = conn_compute_pto(conn, &conn->pktns);
+
+    rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout_pto(conn, pto),
+                       NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem);
+    if (rv != 0) {
+      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) {
+      ngtcp2_pv_set_fallback(pv, &conn->dcid.current, pto);
+    }
+  }
+
+  if (!pref_addr_migration || validated_path) {
+    if (conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) {
+      ngtcp2_conn_add_path_history(conn, &conn->dcid.current, ts);
+    }
+
+    ngtcp2_dcid_copy(&conn->dcid.current, &dcid);
+
+    if (!local_addr_eq || (remote_addr_cmp & (NGTCP2_ADDR_CMP_FLAG_ADDR |
+                                              NGTCP2_ADDR_CMP_FLAG_FAMILY))) {
+      conn_reset_congestion_state(conn, ts);
+    }
+
+    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 (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;
+    }
+  }
+
+  conn->pv = pv;
+
+  return 0;
+}
+
+/*
+ * conn_recv_pkt_from_new_path is called when a 1RTT packet is
+ * received from new path (not current path).  This packet would be a
+ * packet which only contains probing frame, or reordered packet, or a
+ * path is being validated.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CONN_ID_BLOCKED
+ *     No unused DCID is available
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+static int conn_recv_pkt_from_new_path(ngtcp2_conn *conn,
+                                       const ngtcp2_path *path, size_t dgramlen,
+                                       int path_challenge_recved,
+                                       ngtcp2_tstamp ts) {
+  ngtcp2_pv *pv = conn->pv;
+  ngtcp2_dcid *bound_dcid;
+  int rv;
+
+  if (pv) {
+    if (ngtcp2_path_eq(&pv->dcid.ps.path, path)) {
+      pv->dcid.bytes_recv += dgramlen;
+      return 0;
+    }
+
+    if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+        ngtcp2_path_eq(&pv->fallback_dcid.ps.path, path)) {
+      pv->fallback_dcid.bytes_recv += dgramlen;
+      return 0;
+    }
+  }
+
+  if (!path_challenge_recved) {
+    return 0;
+  }
+
+  rv = conn_bind_dcid(conn, &bound_dcid, path, ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  bound_dcid->bytes_recv += dgramlen;
+
+  return 0;
+}
+
+/*
+ * conn_recv_delayed_handshake_pkt processes the received Handshake
+ * packet which is received after handshake completed.  This function
+ * does the minimal job, and its purpose is send acknowledgement of
+ * this packet to the peer.  We assume that hd->type ==
+ * NGTCP2_PKT_HANDSHAKE.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Frame is badly formatted; or frame type is unknown.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_DISCARD_PKT
+ *     Packet was discarded.
+ * NGTCP2_ERR_ACK_FRAME
+ *     ACK frame is malformed.
+ * NGTCP2_ERR_PROTO
+ *     Frame that is not allowed in Handshake packet is received.
+ */
+static int
+conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi,
+                                const ngtcp2_pkt_hd *hd, size_t pktlen,
+                                const uint8_t *payload, size_t payloadlen,
+                                ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) {
+  ngtcp2_ssize nread;
+  ngtcp2_max_frame mfr;
+  ngtcp2_frame *fr = &mfr.fr;
+  int rv;
+  int require_ack = 0;
+  ngtcp2_pktns *pktns;
+  size_t num_ack_processed = 0;
+
+  assert(hd->type == NGTCP2_PKT_HANDSHAKE);
+
+  pktns = conn->hs_pktns;
+
+  if (payloadlen == 0) {
+    /* QUIC packet must contain at least one frame */
+    return NGTCP2_ERR_PROTO;
+  }
+
+  ngtcp2_qlog_pkt_received_start(&conn->qlog);
+
+  for (; payloadlen;) {
+    nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen);
+    if (nread < 0) {
+      return (int)nread;
+    }
+
+    payload += nread;
+    payloadlen -= (size_t)nread;
+
+    switch (fr->type) {
+    case NGTCP2_FRAME_ACK:
+    case NGTCP2_FRAME_ACK_ECN:
+      fr->ack.ack_delay = 0;
+      fr->ack.ack_delay_unscaled = 0;
+
+      rv =
+        ngtcp2_pkt_validate_ack(&fr->ack, conn->local.settings.initial_pkt_num);
+      if (rv != 0) {
+        return rv;
+      }
+
+      break;
+    }
+
+    ngtcp2_log_rx_fr(&conn->log, hd, fr);
+
+    switch (fr->type) {
+    case NGTCP2_FRAME_ACK:
+    case NGTCP2_FRAME_ACK_ECN:
+      if (num_ack_processed >= NGTCP2_MAX_ACK_PER_PKT) {
+        break;
+      }
+      if (!conn->server) {
+        conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED;
+      }
+      rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts);
+      if (rv != 0) {
+        return rv;
+      }
+      ++num_ack_processed;
+      break;
+    case NGTCP2_FRAME_PADDING:
+      break;
+    case NGTCP2_FRAME_CONNECTION_CLOSE:
+      rv = conn_recv_connection_close(conn, &fr->connection_close);
+      if (rv != 0) {
+        return rv;
+      }
+      break;
+    case NGTCP2_FRAME_CRYPTO:
+    case NGTCP2_FRAME_PING:
+      require_ack = 1;
+      break;
+    default:
+      return NGTCP2_ERR_PROTO;
+    }
+
+    ngtcp2_qlog_write_frame(&conn->qlog, fr);
+  }
+
+  ngtcp2_qlog_pkt_received_end(&conn->qlog, hd, pktlen);
+
+  rv = pktns_commit_recv_pkt_num(pktns, hd->pkt_num, pi, require_ack, pkt_ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* Initial and Handshake are always acknowledged without delay.  No
+     need to call ngtcp2_acktr_immediate_ack(). */
+
+  conn_restart_timer_on_read(conn, ts);
+
+  ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat);
+
+  return 0;
+}
+
+/*
+ * conn_recv_pkt processes a packet contained in the buffer pointed by
+ * |pkt| of length |pktlen|.  |pkt| may contain multiple QUIC packets.
+ * This function only processes the first packet.  |pkt_ts| is the
+ * timestamp when packet is received.  |ts| should be the current
+ * time.  Usually they are the same, but for buffered packets,
+ * |pkt_ts| would be earlier than |ts|.
+ *
+ * This function returns the number of bytes processed if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_DISCARD_PKT
+ *     Packet was discarded because plain text header was malformed;
+ *     or its payload could not be decrypted.
+ * NGTCP2_ERR_PROTO
+ *     Packet is badly formatted; or 0RTT packet contains other than
+ *     PADDING or STREAM frames; or other QUIC protocol violation is
+ *     found.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Frame is badly formatted; or frame type is unknown.
+ * NGTCP2_ERR_ACK_FRAME
+ *     ACK frame is malformed.
+ * NGTCP2_ERR_STREAM_STATE
+ *     Frame is received to the local stream which is not initiated.
+ * NGTCP2_ERR_STREAM_LIMIT
+ *     Frame has remote stream ID which is strictly greater than the
+ *     allowed limit.
+ * NGTCP2_ERR_FLOW_CONTROL
+ *     Flow control limit is violated.
+ * NGTCP2_ERR_FINAL_SIZE
+ *     Frame has strictly larger end offset than it is permitted.
+ */
+static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path,
+                                  const ngtcp2_pkt_info *pi, const uint8_t *pkt,
+                                  size_t pktlen, size_t dgramlen,
+                                  ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) {
+  ngtcp2_pkt_hd hd;
+  int rv = 0;
+  size_t hdpktlen;
+  const uint8_t *payload;
+  size_t payloadlen;
+  ngtcp2_ssize nread, nwrite;
+  ngtcp2_max_frame mfr;
+  ngtcp2_frame *fr = &mfr.fr;
+  int require_ack = 0;
+  ngtcp2_crypto_aead *aead;
+  ngtcp2_crypto_cipher *hp;
+  ngtcp2_crypto_km *ckm;
+  ngtcp2_crypto_cipher_ctx *hp_ctx;
+  ngtcp2_hp_mask hp_mask;
+  ngtcp2_decrypt decrypt;
+  ngtcp2_pktns *pktns;
+  int non_probing_pkt = 0;
+  int key_phase_bit_changed = 0;
+  int force_decrypt_failure = 0;
+  int recv_ncid = 0;
+  int new_cid_used = 0;
+  int path_challenge_recved = 0;
+  size_t num_ack_processed = 0;
+
+  if (pkt[0] & NGTCP2_HEADER_FORM_BIT) {
+    nread = ngtcp2_pkt_decode_hd_long(&hd, pkt, pktlen);
+    if (nread < 0) {
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "could not decode long header");
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (pktlen < (size_t)nread + hd.len) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    assert(conn->negotiated_version);
+
+    if (hd.version != conn->client_chosen_version &&
+        hd.version != conn->negotiated_version) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (conn_verify_fixed_bit(conn, &hd) != 0) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    pktlen = (size_t)nread + hd.len;
+
+    /* Quoted from spec: if subsequent packets of those types include
+       a different Source Connection ID, they MUST be discarded. */
+    if (!ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) {
+      ngtcp2_log_rx_pkt_hd(&conn->log, &hd);
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "packet was ignored because of mismatched SCID");
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    switch (hd.type) {
+    case NGTCP2_PKT_INITIAL:
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "delayed Initial packet was discarded");
+      return (ngtcp2_ssize)pktlen;
+    case NGTCP2_PKT_HANDSHAKE:
+      if (hd.version != conn->negotiated_version) {
+        return NGTCP2_ERR_DISCARD_PKT;
+      }
+
+      if (!conn->hs_pktns) {
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                        "delayed Handshake packet was discarded");
+        return (ngtcp2_ssize)pktlen;
+      }
+
+      pktns = conn->hs_pktns;
+      aead = &pktns->crypto.ctx.aead;
+      hp = &pktns->crypto.ctx.hp;
+      ckm = pktns->crypto.rx.ckm;
+      hp_ctx = &pktns->crypto.rx.hp_ctx;
+      hp_mask = conn->callbacks.hp_mask;
+      decrypt = conn->callbacks.decrypt;
+      break;
+    case NGTCP2_PKT_0RTT:
+      if (!conn->server || hd.version != conn->client_chosen_version) {
+        return NGTCP2_ERR_DISCARD_PKT;
+      }
+
+      if (!conn->early.ckm) {
+        return (ngtcp2_ssize)pktlen;
+      }
+
+      pktns = &conn->pktns;
+      aead = &conn->early.ctx.aead;
+      hp = &conn->early.ctx.hp;
+      ckm = conn->early.ckm;
+      hp_ctx = &conn->early.hp_ctx;
+      hp_mask = conn->callbacks.hp_mask;
+      decrypt = conn->callbacks.decrypt;
+      break;
+    default:
+      ngtcp2_log_rx_pkt_hd(&conn->log, &hd);
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "packet type 0x%02x was ignored", hd.type);
+      return (ngtcp2_ssize)pktlen;
+    }
+  } else {
+    nread = ngtcp2_pkt_decode_hd_short(&hd, pkt, pktlen, conn->oscid.datalen);
+    if (nread < 0) {
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "could not decode short header");
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    if (conn_verify_fixed_bit(conn, &hd) != 0) {
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    pktns = &conn->pktns;
+    aead = &pktns->crypto.ctx.aead;
+    hp = &pktns->crypto.ctx.hp;
+    ckm = pktns->crypto.rx.ckm;
+    hp_ctx = &pktns->crypto.rx.hp_ctx;
+    hp_mask = conn->callbacks.hp_mask;
+    decrypt = conn->callbacks.decrypt;
+  }
+
+  rv = conn_ensure_decrypt_hp_buffer(conn, (size_t)nread + 4);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nwrite = decrypt_hp(&hd, conn->crypto.decrypt_hp_buf.base, hp, pkt, pktlen,
+                      (size_t)nread, hp_ctx, hp_mask);
+  if (nwrite < 0) {
+    if (ngtcp2_err_is_fatal((int)nwrite)) {
+      return nwrite;
+    }
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "could not decrypt packet number");
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  hdpktlen = (size_t)nwrite;
+  payload = pkt + hdpktlen;
+  payloadlen = pktlen - hdpktlen;
+
+  hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->acktr.max_pkt_num, hd.pkt_num,
+                                         hd.pkt_numlen);
+  if (hd.pkt_num > NGTCP2_MAX_PKT_NUM) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num);
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  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_info(
+        &conn->log, NGTCP2_LOG_EVENT_PKT,
+        "pkt=%" PRId64
+        " is discarded because it was received on handshake local "
+        "address after preferred address migration",
+        hd.pkt_num);
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    key_phase_bit_changed = conn_key_phase_changed(conn, &hd);
+  }
+
+  rv = conn_ensure_decrypt_buffer(conn, payloadlen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (key_phase_bit_changed) {
+    assert(hd.type == NGTCP2_PKT_1RTT);
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, "unexpected KEY_PHASE");
+
+    if (ckm->pkt_num > hd.pkt_num) {
+      if (conn->crypto.key_update.old_rx_ckm) {
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                        "decrypting with old key");
+        ckm = conn->crypto.key_update.old_rx_ckm;
+      } else {
+        force_decrypt_failure = 1;
+      }
+    } else if (pktns->acktr.max_pkt_num < hd.pkt_num) {
+      assert(ckm->pkt_num < hd.pkt_num);
+      if (!conn->crypto.key_update.new_rx_ckm) {
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                        "new key is not available");
+        force_decrypt_failure = 1;
+      } else {
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                        "decrypting with new key");
+        ckm = conn->crypto.key_update.new_rx_ckm;
+      }
+    } else {
+      force_decrypt_failure = 1;
+    }
+  }
+
+  nwrite = decrypt_pkt(conn->crypto.decrypt_buf.base, aead, payload, payloadlen,
+                       conn->crypto.decrypt_hp_buf.base, hdpktlen, hd.pkt_num,
+                       ckm, decrypt);
+
+  if (force_decrypt_failure) {
+    nwrite = NGTCP2_ERR_DECRYPT;
+  }
+
+  if (nwrite < 0) {
+    if (ngtcp2_err_is_fatal((int)nwrite)) {
+      return nwrite;
+    }
+
+    assert(NGTCP2_ERR_DECRYPT == nwrite);
+
+    if (hd.type == NGTCP2_PKT_1RTT &&
+        ++conn->crypto.decryption_failure_count >=
+          pktns->crypto.ctx.max_decryption_failure) {
+      return NGTCP2_ERR_AEAD_LIMIT_REACHED;
+    }
+
+    if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) {
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "could not decrypt packet payload");
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "could not decrypt packet payload");
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  rv = ngtcp2_pkt_verify_reserved_bits(conn->crypto.decrypt_hp_buf.base[0]);
+  if (rv != 0) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "packet has incorrect reserved bits");
+
+    return NGTCP2_ERR_PROTO;
+  }
+
+  if (pktns_pkt_num_is_duplicate(pktns, hd.pkt_num)) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "packet was discarded because of duplicated packet number");
+    return NGTCP2_ERR_DISCARD_PKT;
+  }
+
+  payload = conn->crypto.decrypt_buf.base;
+  payloadlen = (size_t)nwrite;
+
+  if (payloadlen == 0) {
+    /* QUIC packet must contain at least one frame */
+    return NGTCP2_ERR_PROTO;
+  }
+
+  if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) {
+    switch (hd.type) {
+    case NGTCP2_PKT_HANDSHAKE:
+      rv = conn_verify_dcid(conn, NULL, &hd);
+      if (rv != 0) {
+        if (ngtcp2_err_is_fatal(rv)) {
+          return rv;
+        }
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                        "packet was ignored because of mismatched DCID");
+        return NGTCP2_ERR_DISCARD_PKT;
+      }
+
+      rv = conn_recv_delayed_handshake_pkt(conn, pi, &hd, pktlen, payload,
+                                           payloadlen, pkt_ts, ts);
+      if (rv < 0) {
+        return (ngtcp2_ssize)rv;
+      }
+
+      return (ngtcp2_ssize)pktlen;
+    case NGTCP2_PKT_0RTT:
+      if (!ngtcp2_cid_eq(&conn->rcid, &hd.dcid)) {
+        rv = conn_verify_dcid(conn, NULL, &hd);
+        if (rv != 0) {
+          if (ngtcp2_err_is_fatal(rv)) {
+            return rv;
+          }
+          ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                          "packet was ignored because of mismatched DCID");
+          return NGTCP2_ERR_DISCARD_PKT;
+        }
+      }
+      break;
+    default:
+      /* Unreachable */
+      ngtcp2_unreachable();
+    }
+  } else {
+    rv = conn_verify_dcid(conn, &new_cid_used, &hd);
+    if (rv != 0) {
+      if (ngtcp2_err_is_fatal(rv)) {
+        return rv;
+      }
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                      "packet was ignored because of mismatched DCID");
+      return NGTCP2_ERR_DISCARD_PKT;
+    }
+  }
+
+  ngtcp2_qlog_pkt_received_start(&conn->qlog);
+
+  for (; payloadlen;) {
+    nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen);
+    if (nread < 0) {
+      return nread;
+    }
+
+    payload += nread;
+    payloadlen -= (size_t)nread;
+
+    switch (fr->type) {
+    case NGTCP2_FRAME_ACK:
+    case NGTCP2_FRAME_ACK_ECN:
+      if ((hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) &&
+          hd.type == NGTCP2_PKT_0RTT) {
+        return NGTCP2_ERR_PROTO;
+      }
+      assert(conn->remote.transport_params);
+      assign_recved_ack_delay_unscaled(
+        &fr->ack, conn->remote.transport_params->ack_delay_exponent);
+
+      rv =
+        ngtcp2_pkt_validate_ack(&fr->ack, conn->local.settings.initial_pkt_num);
+      if (rv != 0) {
+        return rv;
+      }
+
+      break;
+    }
+
+    ngtcp2_log_rx_fr(&conn->log, &hd, fr);
+
+    if (hd.type == NGTCP2_PKT_0RTT) {
+      switch (fr->type) {
+      case NGTCP2_FRAME_PADDING:
+      case NGTCP2_FRAME_PING:
+      case NGTCP2_FRAME_RESET_STREAM:
+      case NGTCP2_FRAME_STOP_SENDING:
+      case NGTCP2_FRAME_STREAM:
+      case NGTCP2_FRAME_MAX_DATA:
+      case NGTCP2_FRAME_MAX_STREAM_DATA:
+      case NGTCP2_FRAME_MAX_STREAMS_BIDI:
+      case NGTCP2_FRAME_MAX_STREAMS_UNI:
+      case NGTCP2_FRAME_DATA_BLOCKED:
+      case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
+      case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI:
+      case NGTCP2_FRAME_STREAMS_BLOCKED_UNI:
+      case NGTCP2_FRAME_NEW_CONNECTION_ID:
+      case NGTCP2_FRAME_PATH_CHALLENGE:
+      case NGTCP2_FRAME_CONNECTION_CLOSE:
+      case NGTCP2_FRAME_CONNECTION_CLOSE_APP:
+      case NGTCP2_FRAME_DATAGRAM:
+      case NGTCP2_FRAME_DATAGRAM_LEN:
+        break;
+      default:
+        return NGTCP2_ERR_PROTO;
+      }
+    }
+
+    switch (fr->type) {
+    case NGTCP2_FRAME_ACK:
+    case NGTCP2_FRAME_ACK_ECN:
+    case NGTCP2_FRAME_PADDING:
+    case NGTCP2_FRAME_CONNECTION_CLOSE:
+    case NGTCP2_FRAME_CONNECTION_CLOSE_APP:
+      break;
+    default:
+      require_ack = 1;
+    }
+
+    switch (fr->type) {
+    case NGTCP2_FRAME_ACK:
+    case NGTCP2_FRAME_ACK_ECN:
+      if (num_ack_processed >= NGTCP2_MAX_ACK_PER_PKT) {
+        break;
+      }
+      if (!conn->server) {
+        conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED;
+      }
+      rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      ++num_ack_processed;
+      break;
+    case NGTCP2_FRAME_STREAM:
+      rv = conn_recv_stream(conn, &fr->stream);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_CRYPTO:
+      rv = conn_recv_crypto(conn, NGTCP2_ENCRYPTION_LEVEL_1RTT,
+                            &pktns->crypto.strm, &fr->stream);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_RESET_STREAM:
+      rv = conn_recv_reset_stream(conn, &fr->reset_stream);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_STOP_SENDING:
+      rv = conn_recv_stop_sending(conn, &fr->stop_sending);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_MAX_STREAM_DATA:
+      rv = conn_recv_max_stream_data(conn, &fr->max_stream_data);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_MAX_DATA:
+      conn_recv_max_data(conn, &fr->max_data);
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_MAX_STREAMS_BIDI:
+    case NGTCP2_FRAME_MAX_STREAMS_UNI:
+      rv = conn_recv_max_streams(conn, &fr->max_streams);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_CONNECTION_CLOSE:
+    case NGTCP2_FRAME_CONNECTION_CLOSE_APP:
+      rv = conn_recv_connection_close(conn, &fr->connection_close);
+      if (rv != 0) {
+        return rv;
+      }
+      break;
+    case NGTCP2_FRAME_PING:
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_PATH_CHALLENGE:
+      conn_recv_path_challenge(conn, path, &fr->path_challenge);
+      path_challenge_recved = 1;
+      break;
+    case NGTCP2_FRAME_PATH_RESPONSE:
+      rv = conn_recv_path_response(conn, &hd, &fr->path_response, ts);
+      if (rv != 0) {
+        return rv;
+      }
+      break;
+    case NGTCP2_FRAME_NEW_CONNECTION_ID:
+      rv = conn_recv_new_connection_id(conn, &fr->new_connection_id);
+      if (rv != 0) {
+        return rv;
+      }
+      recv_ncid = 1;
+      break;
+    case NGTCP2_FRAME_RETIRE_CONNECTION_ID:
+      rv = conn_recv_retire_connection_id(conn, &hd, &fr->retire_connection_id,
+                                          ts);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_NEW_TOKEN:
+      rv = conn_recv_new_token(conn, &fr->new_token);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_HANDSHAKE_DONE:
+      rv = conn_recv_handshake_done(conn, ts);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI:
+      rv = conn_recv_streams_blocked_bidi(conn, &fr->streams_blocked);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_STREAMS_BLOCKED_UNI:
+      rv = conn_recv_streams_blocked_uni(conn, &fr->streams_blocked);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
+      rv = conn_recv_stream_data_blocked(conn, &fr->stream_data_blocked);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_DATA_BLOCKED:
+      rv = conn_recv_data_blocked(conn, &fr->data_blocked);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    case NGTCP2_FRAME_DATAGRAM:
+    case NGTCP2_FRAME_DATAGRAM_LEN:
+      if ((uint64_t)nread >
+          conn->local.transport_params.max_datagram_frame_size) {
+        return NGTCP2_ERR_PROTO;
+      }
+      rv = conn_recv_datagram(conn, &fr->datagram);
+      if (rv != 0) {
+        return rv;
+      }
+      non_probing_pkt = 1;
+      break;
+    }
+
+    ngtcp2_qlog_write_frame(&conn->qlog, fr);
+  }
+
+  ngtcp2_qlog_pkt_received_end(&conn->qlog, &hd, pktlen);
+
+  if (recv_ncid) {
+    rv = conn_post_process_recv_new_connection_id(conn, ts);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (conn->server && hd.type == NGTCP2_PKT_1RTT &&
+      !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);
+      }
+    } 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 (hd.type == NGTCP2_PKT_1RTT) {
+    if (ckm == conn->crypto.key_update.new_rx_ckm) {
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "rotate keys");
+      conn_rotate_keys(conn, hd.pkt_num, /* initiator = */ 0);
+    } else if (ckm->pkt_num > hd.pkt_num) {
+      ckm->pkt_num = hd.pkt_num;
+    }
+
+    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)) {
+      conn_update_keep_alive_last_ts(conn, ts);
+    }
+  }
+
+  rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, pi, require_ack, pkt_ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (require_ack &&
+      (++pktns->acktr.rx_npkt >= conn->local.settings.ack_thresh ||
+       (pi->ecn & NGTCP2_ECN_MASK) == NGTCP2_ECN_CE)) {
+    ngtcp2_acktr_immediate_ack(&pktns->acktr);
+  }
+
+  conn_restart_timer_on_read(conn, ts);
+
+  ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat);
+
+  return conn->state == NGTCP2_CS_DRAINING ? NGTCP2_ERR_DRAINING
+                                           : (ngtcp2_ssize)pktlen;
+}
+
+/*
+ * conn_process_buffered_protected_pkt processes buffered 0RTT or 1RTT
+ * packets.
+ *
+ * This function returns 0 if it succeeds, or the same negative error
+ * codes from conn_recv_pkt.
+ */
+static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn,
+                                               ngtcp2_pktns *pktns,
+                                               ngtcp2_tstamp ts) {
+  ngtcp2_ssize nread;
+  ngtcp2_pkt_chain **ppc, *next;
+  int rv;
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                  "processing buffered protected packet");
+
+  for (ppc = &pktns->rx.buffed_pkts; *ppc;) {
+    next = (*ppc)->next;
+    nread = conn_recv_pkt(conn, &(*ppc)->path.path, &(*ppc)->pi, (*ppc)->pkt,
+                          (*ppc)->pktlen, (*ppc)->dgramlen, (*ppc)->ts, ts);
+    if (nread < 0 && !ngtcp2_err_is_fatal((int)nread) &&
+        nread != NGTCP2_ERR_DRAINING) {
+      /* TODO We don't know this is the first QUIC packet in a
+         datagram. */
+      rv = conn_on_stateless_reset(conn, &(*ppc)->path.path, (*ppc)->pkt,
+                                   (*ppc)->pktlen);
+      if (rv == 0) {
+        ngtcp2_pkt_chain_del(*ppc, conn->mem);
+        *ppc = next;
+        return NGTCP2_ERR_DRAINING;
+      }
+    }
+
+    ngtcp2_pkt_chain_del(*ppc, conn->mem);
+    *ppc = next;
+    if (nread < 0) {
+      if (nread == NGTCP2_ERR_DISCARD_PKT) {
+        continue;
+      }
+      return (int)nread;
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * conn_process_buffered_handshake_pkt processes buffered Handshake
+ * packets.
+ *
+ * This function returns 0 if it succeeds, or the same negative error
+ * codes from conn_recv_handshake_pkt.
+ */
+static int conn_process_buffered_handshake_pkt(ngtcp2_conn *conn,
+                                               ngtcp2_tstamp ts) {
+  ngtcp2_pktns *pktns = conn->hs_pktns;
+  ngtcp2_ssize nread;
+  ngtcp2_pkt_chain **ppc, *next;
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                  "processing buffered handshake packet");
+
+  for (ppc = &pktns->rx.buffed_pkts; *ppc;) {
+    next = (*ppc)->next;
+    nread = conn_recv_handshake_pkt(conn, &(*ppc)->path.path, &(*ppc)->pi,
+                                    (*ppc)->pkt, (*ppc)->pktlen,
+                                    (*ppc)->dgramlen, (*ppc)->ts, ts);
+    ngtcp2_pkt_chain_del(*ppc, conn->mem);
+    *ppc = next;
+    if (nread < 0) {
+      if (nread == NGTCP2_ERR_DISCARD_PKT) {
+        continue;
+      }
+      return (int)nread;
+    }
+  }
+
+  return 0;
+}
+
+static void conn_sync_stream_id_limit(ngtcp2_conn *conn) {
+  ngtcp2_transport_params *params = conn->remote.transport_params;
+
+  assert(params);
+
+  conn->local.bidi.max_streams = params->initial_max_streams_bidi;
+  conn->local.uni.max_streams = params->initial_max_streams_uni;
+}
+
+static int strm_set_max_offset(void *data, void *ptr) {
+  ngtcp2_conn *conn = ptr;
+  ngtcp2_transport_params *params = conn->remote.transport_params;
+  ngtcp2_strm *strm = data;
+  uint64_t max_offset;
+  int rv;
+
+  assert(params);
+
+  if (!conn_local_stream(conn, strm->stream_id)) {
+    return 0;
+  }
+
+  if (bidi_stream(strm->stream_id)) {
+    max_offset = params->initial_max_stream_data_bidi_remote;
+  } else {
+    max_offset = params->initial_max_stream_data_uni;
+  }
+
+  if (strm->tx.max_offset < max_offset) {
+    strm->tx.max_offset = max_offset;
+
+    /* Don't call callback if stream is half-closed local */
+    if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) {
+      return 0;
+    }
+
+    rv = conn_call_extend_max_stream_data(conn, strm, strm->stream_id,
+                                          strm->tx.max_offset);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+static int conn_sync_stream_data_limit(ngtcp2_conn *conn) {
+  return ngtcp2_map_each(&conn->strms, strm_set_max_offset, conn);
+}
+
+/*
+ * conn_handshake_completed is called once cryptographic handshake has
+ * completed.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User callback failed.
+ */
+static int conn_handshake_completed(ngtcp2_conn *conn) {
+  int rv;
+
+  conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED;
+
+  rv = conn_call_handshake_completed(conn);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (conn->local.bidi.max_streams > 0) {
+    rv = conn_call_extend_max_local_streams_bidi(conn,
+                                                 conn->local.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 (rv != 0) {
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * conn_recv_cpkt processes compound packet after handshake.  The
+ * buffer pointed by |pkt| might contain multiple packets.  The 1RTT
+ * packet must be the last one because it does not have payload length
+ * field.
+ *
+ * This function returns 0 if it succeeds, or the same negative error
+ * codes from conn_recv_pkt except for NGTCP2_ERR_DISCARD_PKT.
+ */
+static int conn_recv_cpkt(ngtcp2_conn *conn, const ngtcp2_path *path,
+                          const ngtcp2_pkt_info *pi, const uint8_t *pkt,
+                          size_t pktlen, ngtcp2_tstamp ts) {
+  ngtcp2_ssize nread;
+  int rv;
+  const uint8_t *origpkt = pkt;
+  size_t dgramlen = pktlen;
+
+  if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) {
+    conn->dcid.current.bytes_recv += dgramlen;
+  }
+
+  if (conn->server && conn->local.transport_params.disable_active_migration &&
+      !ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local) &&
+      !conn_server_preferred_addr_migration(conn, &path->local)) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "packet is discarded because active migration is disabled");
+
+    return 0;
+  }
+
+  while (pktlen) {
+    nread = conn_recv_pkt(conn, path, pi, pkt, pktlen, dgramlen, ts, ts);
+    if (nread < 0) {
+      if (ngtcp2_err_is_fatal((int)nread)) {
+        return (int)nread;
+      }
+
+      if (nread == NGTCP2_ERR_DRAINING) {
+        return NGTCP2_ERR_DRAINING;
+      }
+
+      if (origpkt == pkt) {
+        rv = conn_on_stateless_reset(conn, path, origpkt, dgramlen);
+        if (rv == 0) {
+          return NGTCP2_ERR_DRAINING;
+        }
+      }
+      if (nread == NGTCP2_ERR_DISCARD_PKT) {
+        return 0;
+      }
+      return (int)nread;
+    }
+
+    assert(pktlen >= (size_t)nread);
+    pkt += nread;
+    pktlen -= (size_t)nread;
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT,
+                    "read packet %td left %zu", nread, pktlen);
+  }
+
+  return 0;
+}
+
+/*
+ * conn_enqueue_handshake_done enqueues HANDSHAKE_DONE frame for
+ * transmission.
+ */
+static int conn_enqueue_handshake_done(ngtcp2_conn *conn) {
+  ngtcp2_pktns *pktns = &conn->pktns;
+  ngtcp2_frame_chain *nfrc;
+  int rv;
+
+  assert(conn->server);
+
+  rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nfrc->fr.type = NGTCP2_FRAME_HANDSHAKE_DONE;
+  nfrc->next = pktns->tx.frq;
+  pktns->tx.frq = nfrc;
+
+  return 0;
+}
+
+/**
+ * @function
+ *
+ * `conn_read_handshake` performs QUIC cryptographic handshake by
+ * reading given data.  |pkt| points to the buffer to read and
+ * |pktlen| is the length of the buffer.  |path| is the network path.
+ *
+ * This function returns the number of bytes processed.  Unless the
+ * last packet is 1RTT packet and an application decryption key has
+ * been installed, it returns |pktlen| if it succeeds.  If it finds
+ * 1RTT packet and an application decryption key has been installed,
+ * it returns the number of bytes just before 1RTT packet begins.
+ *
+ * This function returns the number of bytes processed if it succeeds,
+ * or one of the following negative error codes: (TBD).
+ */
+static ngtcp2_ssize conn_read_handshake(ngtcp2_conn *conn,
+                                        const ngtcp2_path *path,
+                                        const ngtcp2_pkt_info *pi,
+                                        const uint8_t *pkt, size_t pktlen,
+                                        ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_ssize nread;
+
+  switch (conn->state) {
+  case NGTCP2_CS_CLIENT_INITIAL:
+    /* TODO Better to log something when we ignore input */
+    return (ngtcp2_ssize)pktlen;
+  case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE:
+    nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts);
+    if (nread < 0) {
+      return nread;
+    }
+
+    if (conn->state == NGTCP2_CS_CLIENT_INITIAL) {
+      /* Retry packet was received */
+      return (ngtcp2_ssize)pktlen;
+    }
+
+    assert(conn->hs_pktns);
+
+    if (conn->hs_pktns->crypto.rx.ckm && conn->in_pktns) {
+      rv = conn_process_buffered_handshake_pkt(conn, ts);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    if (conn_is_tls_handshake_completed(conn) &&
+        !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) {
+      rv = conn_handshake_completed(conn);
+      if (rv != 0) {
+        return rv;
+      }
+
+      rv = conn_process_buffered_protected_pkt(conn, &conn->pktns, ts);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    return nread;
+  case NGTCP2_CS_SERVER_INITIAL:
+    nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts);
+    if (nread < 0) {
+      return nread;
+    }
+
+    /*
+     * Client Hello might not fit into single Initial packet (e.g.,
+     * resuming session with client authentication).  If we get Client
+     * Initial which does not increase offset or it is 0RTT packet
+     * buffered, perform address validation in order to buffer
+     * validated data only.
+     */
+    if (ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm) == 0) {
+      if (conn->in_pktns->crypto.strm.rx.rob &&
+          ngtcp2_rob_data_buffered(conn->in_pktns->crypto.strm.rx.rob)) {
+        /* Address has been validated with token */
+        if (conn->local.settings.tokenlen) {
+          return nread;
+        }
+        return NGTCP2_ERR_RETRY;
+      }
+      /* If CRYPTO frame is not processed, just drop connection. */
+      return NGTCP2_ERR_DROP_CONN;
+    }
+
+    /* Process re-ordered 0-RTT packets which arrived before Initial
+       packet. */
+    if (conn->early.ckm) {
+      assert(conn->in_pktns);
+
+      rv = conn_process_buffered_protected_pkt(conn, conn->in_pktns, ts);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    return nread;
+  case NGTCP2_CS_SERVER_WAIT_HANDSHAKE:
+    nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts);
+    if (nread < 0) {
+      return nread;
+    }
+
+    if (conn->hs_pktns->crypto.rx.ckm) {
+      rv = conn_process_buffered_handshake_pkt(conn, ts);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    if (conn->hs_pktns->acktr.max_pkt_num != -1) {
+      ngtcp2_conn_discard_initial_state(conn, ts);
+    }
+
+    if (!conn_is_tls_handshake_completed(conn)) {
+      /* If server hits amplification limit, it cancels loss detection
+         timer.  If server receives a packet from client, the limit is
+         increased and server can send more.  If server has
+         ack-eliciting Initial or Handshake packets, it should resend
+         it if timer fired but timer is not armed in this case.  So
+         instead of resending Initial/Handshake packets, if server has
+         1RTT data to send, it might send them and then might hit
+         amplification limit again until it hits stream data limit.
+         Initial/Handshake data is not resent.  In order to avoid this
+         situation, try to arm loss detection and check the expiry
+         here so that on next write call, we can resend
+         Initial/Handshake first. */
+      if (conn->cstat.loss_detection_timer == UINT64_MAX) {
+        ngtcp2_conn_set_loss_detection_timer(conn, ts);
+        if (ngtcp2_conn_loss_detection_expiry(conn) <= ts) {
+          rv = ngtcp2_conn_on_loss_detection_timer(conn, ts);
+          if (rv != 0) {
+            return rv;
+          }
+        }
+      }
+
+      if ((size_t)nread < pktlen) {
+        /* We have 1RTT packet and application rx key, but the
+           handshake has not completed yet. */
+        ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                        "buffering 1RTT packet len=%zu",
+                        pktlen - (size_t)nread);
+
+        rv = conn_buffer_pkt(conn, &conn->pktns, path, pi, pkt + nread,
+                             pktlen - (size_t)nread, pktlen, ts);
+        if (rv != 0) {
+          assert(ngtcp2_err_is_fatal(rv));
+          return rv;
+        }
+
+        return (ngtcp2_ssize)pktlen;
+      }
+
+      return nread;
+    }
+
+    if (!(conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED)) {
+      return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM;
+    }
+
+    rv = conn_handshake_completed(conn);
+    if (rv != 0) {
+      return rv;
+    }
+    conn->state = NGTCP2_CS_POST_HANDSHAKE;
+
+    rv = conn_call_activate_dcid(conn, &conn->dcid.current);
+    if (rv != 0) {
+      return rv;
+    }
+
+    rv = conn_process_buffered_protected_pkt(conn, &conn->pktns, ts);
+    if (rv != 0) {
+      return rv;
+    }
+
+    ngtcp2_conn_discard_handshake_state(conn, ts);
+
+    rv = conn_enqueue_handshake_done(conn);
+    if (rv != 0) {
+      return rv;
+    }
+
+    if (!conn->local.settings.no_pmtud) {
+      rv = conn_start_pmtud(conn);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    conn->handshake_confirmed_ts = ts;
+
+    /* Re-arm loss detection timer here after handshake has been
+       confirmed. */
+    ngtcp2_conn_set_loss_detection_timer(conn, ts);
+
+    return nread;
+  case NGTCP2_CS_CLOSING:
+    return NGTCP2_ERR_CLOSING;
+  case NGTCP2_CS_DRAINING:
+    return NGTCP2_ERR_DRAINING;
+  default:
+    return (ngtcp2_ssize)pktlen;
+  }
+}
+
+int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path,
+                                   int pkt_info_version,
+                                   const ngtcp2_pkt_info *pi,
+                                   const uint8_t *pkt, size_t pktlen,
+                                   ngtcp2_tstamp ts) {
+  int rv = 0;
+  ngtcp2_ssize nread = 0;
+  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_info(&conn->log, NGTCP2_LOG_EVENT_CON, "recv packet len=%zu",
+                  pktlen);
+
+  if (pktlen == 0) {
+    return 0;
+  }
+
+  /* client does not expect a packet from unknown path. */
+  if (!conn->server && !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) &&
+      (!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)) &&
+      !ngtcp2_dcidtr_check_path_retired(&conn->dcid.dtr, path)) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                    "ignore packet from unknown path");
+    return 0;
+  }
+
+  if (!pi) {
+    pi = &zero_pi;
+  }
+
+  switch (conn->state) {
+  case NGTCP2_CS_CLIENT_INITIAL:
+  case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE:
+    nread = conn_read_handshake(conn, path, pi, pkt, pktlen, ts);
+    if (nread < 0) {
+      return (int)nread;
+    }
+
+    if ((size_t)nread == pktlen) {
+      return 0;
+    }
+
+    assert(conn->pktns.crypto.rx.ckm);
+
+    pkt += nread;
+    pktlen -= (size_t)nread;
+
+    break;
+  case NGTCP2_CS_SERVER_INITIAL:
+  case NGTCP2_CS_SERVER_WAIT_HANDSHAKE:
+    if (!ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) {
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                      "ignore packet from unknown path during handshake");
+
+      if (conn->state == NGTCP2_CS_SERVER_INITIAL &&
+          ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm) == 0 &&
+          (!conn->in_pktns->crypto.strm.rx.rob ||
+           !ngtcp2_rob_data_buffered(conn->in_pktns->crypto.strm.rx.rob))) {
+        return NGTCP2_ERR_DROP_CONN;
+      }
+
+      return 0;
+    }
+
+    nread = conn_read_handshake(conn, path, pi, pkt, pktlen, ts);
+    if (nread < 0) {
+      return (int)nread;
+    }
+
+    if ((size_t)nread == pktlen) {
+      return 0;
+    }
+
+    assert(conn->pktns.crypto.rx.ckm);
+
+    pkt += nread;
+    pktlen -= (size_t)nread;
+
+    break;
+  case NGTCP2_CS_CLOSING:
+    return NGTCP2_ERR_CLOSING;
+  case NGTCP2_CS_DRAINING:
+    return NGTCP2_ERR_DRAINING;
+  case NGTCP2_CS_POST_HANDSHAKE:
+    rv = conn_prepare_key_update(conn, ts);
+    if (rv != 0) {
+      return rv;
+    }
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  return conn_recv_cpkt(conn, path, pi, pkt, pktlen, ts);
+}
+
+/*
+ * conn_check_pkt_num_exhausted returns nonzero if packet number is
+ * exhausted in at least one of packet number space.
+ */
+static int conn_check_pkt_num_exhausted(ngtcp2_conn *conn) {
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+
+  return (in_pktns && in_pktns->tx.last_pkt_num == NGTCP2_MAX_PKT_NUM) ||
+         (hs_pktns && hs_pktns->tx.last_pkt_num == NGTCP2_MAX_PKT_NUM) ||
+         conn->pktns.tx.last_pkt_num == NGTCP2_MAX_PKT_NUM;
+}
+
+/*
+ * conn_retransmit_retry_early retransmits 0RTT packet after Retry is
+ * received from server.
+ */
+static ngtcp2_ssize
+conn_retransmit_retry_early(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
+                            uint8_t *dest, size_t destlen, size_t dgram_offset,
+                            uint8_t flags, ngtcp2_tstamp ts) {
+  return conn_write_pkt(conn, pi, dest, destlen, dgram_offset, NULL,
+                        NGTCP2_PKT_0RTT, flags, ts);
+}
+
+/*
+ * conn_handshake_probe_left returns nonzero if there are probe
+ * packets to be sent for Initial or Handshake packet number space
+ * left.
+ */
+static int conn_handshake_probe_left(ngtcp2_conn *conn) {
+  return (conn->in_pktns && conn->in_pktns->rtb.probe_pkt_left) ||
+         conn->hs_pktns->rtb.probe_pkt_left;
+}
+
+/*
+ * conn_validate_early_transport_params_limits validates that the
+ * limits in transport parameters remembered by client for early data
+ * are not reduced.  This function is only used by client and should
+ * only be called when early data is accepted by server.
+ */
+static int conn_validate_early_transport_params_limits(ngtcp2_conn *conn) {
+  const ngtcp2_transport_params *params = conn->remote.transport_params;
+
+  assert(!conn->server);
+  assert(params);
+
+  if (conn->early.transport_params.active_connection_id_limit >
+        params->active_connection_id_limit ||
+      conn->early.transport_params.initial_max_data >
+        params->initial_max_data ||
+      conn->early.transport_params.initial_max_stream_data_bidi_local >
+        params->initial_max_stream_data_bidi_local ||
+      conn->early.transport_params.initial_max_stream_data_bidi_remote >
+        params->initial_max_stream_data_bidi_remote ||
+      conn->early.transport_params.initial_max_stream_data_uni >
+        params->initial_max_stream_data_uni ||
+      conn->early.transport_params.initial_max_streams_bidi >
+        params->initial_max_streams_bidi ||
+      conn->early.transport_params.initial_max_streams_uni >
+        params->initial_max_streams_uni ||
+      conn->early.transport_params.max_datagram_frame_size >
+        params->max_datagram_frame_size) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  return 0;
+}
+
+/*
+ * conn_write_handshake writes QUIC handshake packets to the buffer
+ * pointed by |dest| of length |destlen|.  |write_datalen| specifies
+ * the expected length of 0RTT packet payload.  Specify 0 to
+ * |write_datalen| if there is no such data.
+ *
+ * This function returns the number of bytes written to the buffer, or
+ * one of the following negative error codes:
+ *
+ * NGTCP2_ERR_PKT_NUM_EXHAUSTED
+ *     Packet number is exhausted.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM
+ *     Required transport parameter is missing.
+ * NGTCP2_CS_CLOSING
+ *     Connection is in closing state.
+ * NGTCP2_CS_DRAINING
+ *     Connection is in draining state.
+ *
+ * In addition to the above negative error codes, the same error codes
+ * from conn_recv_pkt may also be returned.
+ */
+static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
+                                         uint8_t *dest, size_t destlen,
+                                         uint8_t wflags, uint64_t write_datalen,
+                                         ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_ssize res = 0, nwrite = 0, early_spktlen = 0;
+  size_t origlen = destlen;
+  uint64_t pending_early_datalen;
+  ngtcp2_preferred_addr *paddr;
+
+  switch (conn->state) {
+  case NGTCP2_CS_CLIENT_INITIAL:
+    pending_early_datalen = conn_retry_early_payloadlen(conn);
+    if (pending_early_datalen) {
+      write_datalen = pending_early_datalen;
+    }
+
+    if (!(conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY)) {
+      nwrite =
+        conn_write_client_initial(conn, pi, dest, destlen, write_datalen, ts);
+      if (nwrite <= 0) {
+        return nwrite;
+      }
+    } else {
+      nwrite =
+        conn_write_handshake_pkt(conn, pi, dest, destlen, 0, NGTCP2_PKT_INITIAL,
+                                 NGTCP2_WRITE_PKT_FLAG_NONE, write_datalen, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+    }
+
+    if (pending_early_datalen) {
+      early_spktlen = conn_retransmit_retry_early(
+        conn, pi, dest + nwrite, destlen - (size_t)nwrite, (size_t)nwrite,
+        wflags | (nwrite ? NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING
+                         : NGTCP2_WRITE_PKT_FLAG_NONE),
+        ts);
+
+      if (early_spktlen < 0) {
+        assert(ngtcp2_err_is_fatal((int)early_spktlen));
+        return early_spktlen;
+      }
+    }
+
+    conn->state = NGTCP2_CS_CLIENT_WAIT_HANDSHAKE;
+
+    res = nwrite + early_spktlen;
+
+    return res;
+  case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE:
+    pending_early_datalen = 0;
+
+    if (!conn_handshake_probe_left(conn) && conn_cwnd_is_zero(conn)) {
+      destlen = 0;
+    } else {
+      if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) {
+        pending_early_datalen = conn_retry_early_payloadlen(conn);
+        if (pending_early_datalen) {
+          write_datalen = pending_early_datalen;
+        }
+      }
+
+      nwrite =
+        conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+
+      res += nwrite;
+      dest += nwrite;
+      destlen -= (size_t)nwrite;
+    }
+
+    if (!conn_is_tls_handshake_completed(conn)) {
+      if (pending_early_datalen &&
+          !(conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED)) {
+        nwrite = conn_retransmit_retry_early(
+          conn, pi, dest, destlen, (size_t)res,
+          wflags | ((nwrite &&
+                     ngtcp2_pkt_get_type_long(
+                       conn->negotiated_version ? conn->negotiated_version
+                                                : conn->client_chosen_version,
+                       *(dest - nwrite)) == NGTCP2_PKT_INITIAL)
+                      ? NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING
+                      : NGTCP2_WRITE_PKT_FLAG_NONE),
+          ts);
+        if (nwrite < 0) {
+          return nwrite;
+        }
+
+        res += nwrite;
+      }
+
+      if (res == 0) {
+        nwrite = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts);
+        if (nwrite < 0) {
+          return nwrite;
+        }
+        res = nwrite;
+      }
+
+      return res;
+    }
+
+    if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) {
+      return res;
+    }
+
+    if (!(conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED)) {
+      return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM;
+    }
+
+    if ((conn->flags & NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED) &&
+        !(conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED)) {
+      rv = conn_validate_early_transport_params_limits(conn);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    /* Server might increase stream data limits.  Extend it if we have
+       streams created for early data. */
+    rv = conn_sync_stream_data_limit(conn);
+    if (rv != 0) {
+      return rv;
+    }
+
+    conn->state = NGTCP2_CS_POST_HANDSHAKE;
+
+    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;
+      ngtcp2_dcidtr_push_unused(&conn->dcid.dtr, 1, &paddr->cid,
+                                paddr->stateless_reset_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) {
+      assert(conn->dcid.current.seq == 0);
+      assert(!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT));
+      ngtcp2_dcid_set_token(
+        &conn->dcid.current,
+        conn->remote.transport_params->stateless_reset_token);
+    }
+
+    rv = conn_call_activate_dcid(conn, &conn->dcid.current);
+    if (rv != 0) {
+      return rv;
+    }
+
+    conn_process_early_rtb(conn);
+
+    if (!conn->local.settings.no_pmtud) {
+      rv = conn_start_pmtud(conn);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    return res;
+  case NGTCP2_CS_SERVER_INITIAL:
+    nwrite =
+      conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts);
+    if (nwrite < 0) {
+      return nwrite;
+    }
+
+    if (nwrite) {
+      conn->state = NGTCP2_CS_SERVER_WAIT_HANDSHAKE;
+    }
+
+    return nwrite;
+  case NGTCP2_CS_SERVER_WAIT_HANDSHAKE:
+    if (conn_handshake_probe_left(conn) || !conn_cwnd_is_zero(conn)) {
+      nwrite =
+        conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+
+      res += nwrite;
+      dest += nwrite;
+      destlen -= (size_t)nwrite;
+    }
+
+    if (res == 0) {
+      nwrite = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+
+      res += nwrite;
+      dest += nwrite;
+      origlen -= (size_t)nwrite;
+    }
+
+    return res;
+  case NGTCP2_CS_CLOSING:
+    return NGTCP2_ERR_CLOSING;
+  case NGTCP2_CS_DRAINING:
+    return NGTCP2_ERR_DRAINING;
+  default:
+    return 0;
+  }
+}
+
+/**
+ * @function
+ *
+ * `conn_client_write_handshake` writes client side handshake data and
+ * 0RTT packet.
+ *
+ * In order to send STREAM data in 0RTT packet, specify
+ * |vmsg|->stream.  |vmsg|->stream.strm, |vmsg|->stream.fin,
+ * |vmsg|->stream.data, and |vmsg|->stream.datacnt are stream to which
+ * 0-RTT data is sent, whether it is a last data chunk in this stream,
+ * a vector of 0-RTT data, and its number of elements respectively.
+ * The amount of 0RTT data sent is assigned to
+ * *|vmsg|->stream.pdatalen.  If no data is sent, -1 is assigned.
+ * Note that 0 length STREAM frame is allowed in QUIC, so 0 might be
+ * assigned to *|vmsg|->stream.pdatalen.
+ *
+ * This function returns 0 if it cannot write any frame because buffer
+ * is too small, or packet is congestion limited.  Application should
+ * keep reading and wait for congestion window to grow.
+ *
+ * This function returns the number of bytes written to the buffer
+ * pointed by |dest| if it succeeds, or one of the following negative
+ * error codes: (TBD).
+ */
+static ngtcp2_ssize
+conn_client_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
+                            uint8_t *dest, size_t destlen, uint8_t wflags,
+                            ngtcp2_vmsg *vmsg, ngtcp2_tstamp ts) {
+  int send_stream = 0;
+  int send_datagram = 0;
+  ngtcp2_ssize spktlen, early_spktlen;
+  uint64_t datalen;
+  uint64_t write_datalen = 0;
+  int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0;
+  uint32_t version;
+
+  assert(!conn->server);
+
+  /* conn->early.ckm might be created in the first call of
+     conn_handshake().  Check it later. */
+  if (vmsg) {
+    switch (vmsg->type) {
+    case NGTCP2_VMSG_TYPE_STREAM:
+      datalen = ngtcp2_vec_len(vmsg->stream.data, vmsg->stream.datacnt);
+      send_stream = conn_retry_early_payloadlen(conn) == 0;
+      if (send_stream) {
+        write_datalen = ngtcp2_min_uint64(datalen + NGTCP2_STREAM_OVERHEAD,
+                                          NGTCP2_MIN_COALESCED_PAYLOADLEN);
+
+        if (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_MORE) {
+          wflags |= NGTCP2_WRITE_PKT_FLAG_MORE;
+        }
+      } else {
+        vmsg = NULL;
+      }
+      break;
+    case NGTCP2_VMSG_TYPE_DATAGRAM:
+      datalen = ngtcp2_vec_len(vmsg->datagram.data, vmsg->datagram.datacnt);
+      send_datagram = conn_retry_early_payloadlen(conn) == 0;
+      if (send_datagram) {
+        write_datalen = datalen + NGTCP2_DATAGRAM_OVERHEAD;
+
+        if (vmsg->datagram.flags & NGTCP2_WRITE_DATAGRAM_FLAG_MORE) {
+          wflags |= NGTCP2_WRITE_PKT_FLAG_MORE;
+        }
+      } else {
+        vmsg = NULL;
+      }
+      break;
+    }
+  }
+
+  if (!ppe_pending) {
+    spktlen =
+      conn_write_handshake(conn, pi, dest, destlen, wflags, write_datalen, ts);
+
+    if (spktlen < 0) {
+      return spktlen;
+    }
+
+    if ((conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) ||
+        !conn->early.ckm || (!send_stream && !send_datagram)) {
+      return spktlen;
+    }
+
+    /* If spktlen > 0, we are making a compound packet.  If Initial
+       packet is written, we have to pad bytes to 0-RTT packet. */
+    version = conn->negotiated_version ? conn->negotiated_version
+                                       : conn->client_chosen_version;
+    if (spktlen > 0 &&
+        ngtcp2_pkt_get_type_long(version, dest[0]) == NGTCP2_PKT_INITIAL) {
+      wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING;
+    }
+  } else {
+    assert(!conn->pktns.crypto.rx.ckm);
+    assert(!conn->pktns.crypto.tx.ckm);
+    assert(conn->early.ckm);
+
+    spktlen = conn->pkt.hs_spktlen;
+  }
+
+  dest += spktlen;
+  destlen -= (size_t)spktlen;
+
+  if (conn_cwnd_is_zero(conn)) {
+    return spktlen;
+  }
+
+  early_spktlen = conn_write_pkt(conn, pi, dest, destlen, (size_t)spktlen, vmsg,
+                                 NGTCP2_PKT_0RTT, wflags, ts);
+  if (early_spktlen < 0) {
+    switch (early_spktlen) {
+    case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+      if (!(wflags & NGTCP2_WRITE_PKT_FLAG_MORE)) {
+        if (spktlen) {
+          return spktlen;
+        }
+
+        break;
+      }
+      /* fall through */
+    case NGTCP2_ERR_WRITE_MORE:
+      conn->pkt.hs_spktlen = spktlen;
+      break;
+    }
+    return early_spktlen;
+  }
+
+  return spktlen + early_spktlen;
+}
+
+void ngtcp2_conn_tls_handshake_completed(ngtcp2_conn *conn) {
+  conn->flags |= NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED;
+  if (conn->server) {
+    conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED;
+  }
+}
+
+int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn) {
+  return conn_is_tls_handshake_completed(conn) &&
+         (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED);
+}
+
+int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt, size_t pktlen) {
+  ngtcp2_ssize nread;
+  ngtcp2_pkt_hd hd, *p;
+
+  if (dest) {
+    p = dest;
+  } else {
+    p = &hd;
+  }
+
+  if (pktlen == 0 || (pkt[0] & NGTCP2_HEADER_FORM_BIT) == 0) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  nread = ngtcp2_pkt_decode_hd_long(p, pkt, pktlen);
+  if (nread < 0) {
+    return (int)nread;
+  }
+
+  switch (p->type) {
+  case NGTCP2_PKT_INITIAL:
+    break;
+  case NGTCP2_PKT_0RTT:
+    /* 0-RTT packet may arrive before Initial packet due to
+       re-ordering.  ngtcp2 does not buffer 0RTT packet unless the
+       very first Initial packet is received or token is received.
+       Previously, we returned NGTCP2_ERR_RETRY here, so that client
+       can resend 0RTT data.  But it incurs 1RTT already and
+       diminishes the value of 0RTT.  Therefore, we just discard the
+       packet here for now. */
+  default:
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (pktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE ||
+      (p->tokenlen == 0 && p->dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_install_initial_key(
+  ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *rx_aead_ctx,
+  const uint8_t *rx_iv, const ngtcp2_crypto_cipher_ctx *rx_hp_ctx,
+  const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv,
+  const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen) {
+  ngtcp2_pktns *pktns = conn->in_pktns;
+  int rv;
+
+  assert(ivlen >= 8);
+  assert(pktns);
+
+  conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.rx.hp_ctx);
+  pktns->crypto.rx.hp_ctx.native_handle = NULL;
+
+  if (pktns->crypto.rx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.rx.ckm->aead_ctx);
+    ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem);
+    pktns->crypto.rx.ckm = NULL;
+  }
+
+  conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.tx.hp_ctx);
+  pktns->crypto.tx.hp_ctx.native_handle = NULL;
+
+  if (pktns->crypto.tx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx);
+    ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem);
+    pktns->crypto.tx.ckm = NULL;
+  }
+
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, NULL, rx_iv, ivlen,
+                            conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, NULL, tx_iv, ivlen,
+                            conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* Take owner ship after we are sure that no failure occurs, so that
+     caller can delete these contexts on failure. */
+  pktns->crypto.rx.ckm->aead_ctx = *rx_aead_ctx;
+  pktns->crypto.rx.hp_ctx = *rx_hp_ctx;
+  pktns->crypto.tx.ckm->aead_ctx = *tx_aead_ctx;
+  pktns->crypto.tx.hp_ctx = *tx_hp_ctx;
+
+  return 0;
+}
+
+int ngtcp2_conn_install_vneg_initial_key(
+  ngtcp2_conn *conn, uint32_t version,
+  const ngtcp2_crypto_aead_ctx *rx_aead_ctx, const uint8_t *rx_iv,
+  const ngtcp2_crypto_cipher_ctx *rx_hp_ctx,
+  const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv,
+  const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen) {
+  int rv;
+
+  assert(ivlen >= 8);
+
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.rx.hp_ctx);
+  conn->vneg.rx.hp_ctx.native_handle = NULL;
+
+  if (conn->vneg.rx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.rx.ckm->aead_ctx);
+    ngtcp2_crypto_km_del(conn->vneg.rx.ckm, conn->mem);
+    conn->vneg.rx.ckm = NULL;
+  }
+
+  conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.tx.hp_ctx);
+  conn->vneg.tx.hp_ctx.native_handle = NULL;
+
+  if (conn->vneg.tx.ckm) {
+    conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.tx.ckm->aead_ctx);
+    ngtcp2_crypto_km_del(conn->vneg.tx.ckm, conn->mem);
+    conn->vneg.tx.ckm = NULL;
+  }
+
+  rv = ngtcp2_crypto_km_new(&conn->vneg.rx.ckm, NULL, 0, NULL, rx_iv, ivlen,
+                            conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = ngtcp2_crypto_km_new(&conn->vneg.tx.ckm, NULL, 0, NULL, tx_iv, ivlen,
+                            conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  /* Take owner ship after we are sure that no failure occurs, so that
+     caller can delete these contexts on failure. */
+  conn->vneg.rx.ckm->aead_ctx = *rx_aead_ctx;
+  conn->vneg.rx.hp_ctx = *rx_hp_ctx;
+  conn->vneg.tx.ckm->aead_ctx = *tx_aead_ctx;
+  conn->vneg.tx.hp_ctx = *tx_hp_ctx;
+  conn->vneg.version = version;
+
+  return 0;
+}
+
+int ngtcp2_conn_install_rx_handshake_key(
+  ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv,
+  size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx) {
+  ngtcp2_pktns *pktns = conn->hs_pktns;
+  int rv;
+
+  assert(ivlen >= 8);
+  assert(pktns);
+  assert(!pktns->crypto.rx.hp_ctx.native_handle);
+  assert(!pktns->crypto.rx.ckm);
+
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, aead_ctx, iv, ivlen,
+                            conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  pktns->crypto.rx.hp_ctx = *hp_ctx;
+
+  rv = conn_call_recv_rx_key(conn, NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE);
+  if (rv != 0) {
+    ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem);
+    pktns->crypto.rx.ckm = NULL;
+
+    memset(&pktns->crypto.rx.hp_ctx, 0, sizeof(pktns->crypto.rx.hp_ctx));
+
+    return rv;
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_install_tx_handshake_key(
+  ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv,
+  size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx) {
+  ngtcp2_pktns *pktns = conn->hs_pktns;
+  int rv;
+
+  assert(ivlen >= 8);
+  assert(pktns);
+  assert(!pktns->crypto.tx.hp_ctx.native_handle);
+  assert(!pktns->crypto.tx.ckm);
+
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, aead_ctx, iv, ivlen,
+                            conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  pktns->crypto.tx.hp_ctx = *hp_ctx;
+
+  if (conn->server) {
+    rv = ngtcp2_conn_commit_local_transport_params(conn);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  rv = conn_call_recv_tx_key(conn, NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE);
+  if (rv != 0) {
+    ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem);
+    pktns->crypto.tx.ckm = NULL;
+
+    memset(&pktns->crypto.tx.hp_ctx, 0, sizeof(pktns->crypto.tx.hp_ctx));
+
+    return rv;
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_install_0rtt_key(ngtcp2_conn *conn,
+                                 const ngtcp2_crypto_aead_ctx *aead_ctx,
+                                 const uint8_t *iv, size_t ivlen,
+                                 const ngtcp2_crypto_cipher_ctx *hp_ctx) {
+  int rv;
+
+  assert(ivlen >= 8);
+  assert(!conn->early.hp_ctx.native_handle);
+  assert(!conn->early.ckm);
+
+  rv = ngtcp2_crypto_km_new(&conn->early.ckm, NULL, 0, aead_ctx, iv, ivlen,
+                            conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  conn->early.hp_ctx = *hp_ctx;
+
+  conn->flags |= NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED;
+
+  if (conn->server) {
+    rv = conn_call_recv_rx_key(conn, NGTCP2_ENCRYPTION_LEVEL_0RTT);
+  } else {
+    rv = conn_call_recv_tx_key(conn, NGTCP2_ENCRYPTION_LEVEL_0RTT);
+  }
+  if (rv != 0) {
+    ngtcp2_crypto_km_del(conn->early.ckm, conn->mem);
+    conn->early.ckm = NULL;
+
+    memset(&conn->early.hp_ctx, 0, sizeof(conn->early.hp_ctx));
+
+    return rv;
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret,
+                               size_t secretlen,
+                               const ngtcp2_crypto_aead_ctx *aead_ctx,
+                               const uint8_t *iv, size_t ivlen,
+                               const ngtcp2_crypto_cipher_ctx *hp_ctx) {
+  ngtcp2_pktns *pktns = &conn->pktns;
+  int rv;
+
+  assert(ivlen >= 8);
+  assert(!pktns->crypto.rx.hp_ctx.native_handle);
+  assert(!pktns->crypto.rx.ckm);
+
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, secret, secretlen, aead_ctx,
+                            iv, ivlen, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  pktns->crypto.rx.hp_ctx = *hp_ctx;
+
+  if (!conn->server) {
+    if (conn->remote.pending_transport_params) {
+      ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem);
+
+      conn->remote.transport_params = conn->remote.pending_transport_params;
+      conn->remote.pending_transport_params = NULL;
+      conn_sync_stream_id_limit(conn);
+      conn->tx.max_offset = conn->remote.transport_params->initial_max_data;
+    }
+
+    if (conn->early.ckm) {
+      conn_discard_early_key(conn);
+    }
+  }
+
+  rv = conn_call_recv_rx_key(conn, NGTCP2_ENCRYPTION_LEVEL_1RTT);
+  if (rv != 0) {
+    ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem);
+    pktns->crypto.rx.ckm = NULL;
+
+    memset(&pktns->crypto.rx.hp_ctx, 0, sizeof(pktns->crypto.rx.hp_ctx));
+
+    return rv;
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_install_tx_key(ngtcp2_conn *conn, const uint8_t *secret,
+                               size_t secretlen,
+                               const ngtcp2_crypto_aead_ctx *aead_ctx,
+                               const uint8_t *iv, size_t ivlen,
+                               const ngtcp2_crypto_cipher_ctx *hp_ctx) {
+  ngtcp2_pktns *pktns = &conn->pktns;
+  int rv;
+
+  assert(ivlen >= 8);
+  assert(!pktns->crypto.tx.hp_ctx.native_handle);
+  assert(!pktns->crypto.tx.ckm);
+
+  rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, secret, secretlen, aead_ctx,
+                            iv, ivlen, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  pktns->crypto.tx.hp_ctx = *hp_ctx;
+
+  if (conn->server) {
+    if (conn->remote.pending_transport_params) {
+      ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem);
+
+      conn->remote.transport_params = conn->remote.pending_transport_params;
+      conn->remote.pending_transport_params = NULL;
+      conn_sync_stream_id_limit(conn);
+      conn->tx.max_offset = conn->remote.transport_params->initial_max_data;
+    }
+  } else if (conn->early.ckm) {
+    conn_discard_early_key(conn);
+  }
+
+  rv = conn_call_recv_tx_key(conn, NGTCP2_ENCRYPTION_LEVEL_1RTT);
+  if (rv != 0) {
+    ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem);
+    pktns->crypto.tx.ckm = NULL;
+
+    memset(&pktns->crypto.tx.hp_ctx, 0, sizeof(pktns->crypto.tx.hp_ctx));
+
+    return rv;
+  }
+
+  return 0;
+}
+
+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 = conn_compute_pto(conn, &conn->pktns);
+
+  assert(conn->state == NGTCP2_CS_POST_HANDSHAKE);
+
+  if (!(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 ||
+      ngtcp2_tstamp_not_elapsed(confirmed_ts, 3 * pto, ts)) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  conn_rotate_keys(conn, NGTCP2_MAX_PKT_NUM, /* initiator = */ 1);
+
+  return 0;
+}
+
+int ngtcp2_conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  conn_update_timestamp(conn, ts);
+
+  return conn_initiate_key_update(conn, ts);
+}
+
+ngtcp2_tstamp ngtcp2_conn_loss_detection_expiry(ngtcp2_conn *conn) {
+  return conn->cstat.loss_detection_timer;
+}
+
+ngtcp2_tstamp ngtcp2_conn_internal_expiry(ngtcp2_conn *conn) {
+  ngtcp2_tstamp res = UINT64_MAX, ts;
+  ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns);
+  ngtcp2_scid *scid;
+
+  if (conn->pv) {
+    res = ngtcp2_pv_next_expiry(conn->pv);
+  }
+
+  if (conn->pmtud) {
+    res = ngtcp2_min_uint64(res, conn->pmtud->expiry);
+  }
+
+  if (!ngtcp2_pq_empty(&conn->scid.used)) {
+    scid = ngtcp2_struct_of(ngtcp2_pq_top(&conn->scid.used), ngtcp2_scid, pe);
+    if (scid->retired_ts != UINT64_MAX) {
+      res = ngtcp2_min_uint64(res, scid->retired_ts + pto);
+    }
+  }
+
+  ts = ngtcp2_dcidtr_earliest_retired_ts(&conn->dcid.dtr);
+  if (ts != UINT64_MAX) {
+    res = ngtcp2_min_uint64(res, ts + pto);
+  }
+
+  if (conn->dcid.current.cid.datalen) {
+    ts = ngtcp2_dcidtr_earliest_bound_ts(&conn->dcid.dtr);
+    if (ts != UINT64_MAX) {
+      res = ngtcp2_min_uint64(res, ts + 3 * pto);
+    }
+  }
+
+  if (conn->server && conn->early.ckm &&
+      conn->early.discard_started_ts != UINT64_MAX) {
+    res = ngtcp2_min_uint64(res, conn->early.discard_started_ts + 3 * pto);
+  }
+
+  return res;
+}
+
+ngtcp2_tstamp ngtcp2_conn_ack_delay_expiry(ngtcp2_conn *conn) {
+  ngtcp2_acktr *acktr = &conn->pktns.acktr;
+
+  if (!(acktr->flags & NGTCP2_ACKTR_FLAG_CANCEL_TIMER) &&
+      acktr->first_unacked_ts != UINT64_MAX) {
+    return acktr->first_unacked_ts + conn_compute_ack_delay(conn);
+  }
+  return UINT64_MAX;
+}
+
+static ngtcp2_tstamp conn_handshake_expiry(ngtcp2_conn *conn) {
+  if (conn_is_tls_handshake_completed(conn) ||
+      conn->local.settings.handshake_timeout == UINT64_MAX ||
+      conn->local.settings.initial_ts >=
+        UINT64_MAX - conn->local.settings.handshake_timeout) {
+    return UINT64_MAX;
+  }
+
+  return conn->local.settings.initial_ts +
+         conn->local.settings.handshake_timeout;
+}
+
+ngtcp2_tstamp ngtcp2_conn_get_expiry(ngtcp2_conn *conn) {
+  ngtcp2_tstamp res = ngtcp2_min_uint64(ngtcp2_conn_loss_detection_expiry(conn),
+                                        ngtcp2_conn_ack_delay_expiry(conn));
+  res = ngtcp2_min_uint64(res, ngtcp2_conn_internal_expiry(conn));
+  res = ngtcp2_min_uint64(res, ngtcp2_conn_lost_pkt_expiry(conn));
+  res = ngtcp2_min_uint64(res, conn_keep_alive_expiry(conn));
+  res = ngtcp2_min_uint64(res, conn_handshake_expiry(conn));
+  res = ngtcp2_min_uint64(res, ngtcp2_conn_get_idle_expiry(conn));
+  return ngtcp2_min_uint64(res, conn->tx.pacing.next_ts);
+}
+
+int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_duration pto;
+
+  conn_update_timestamp(conn, ts);
+
+  pto = 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;
+  }
+
+  ngtcp2_conn_cancel_expired_ack_delay_timer(conn, ts);
+
+  conn_cancel_expired_keep_alive_timer(conn, ts);
+
+  conn_cancel_expired_pkt_tx_timer(conn, ts);
+
+  ngtcp2_conn_remove_lost_pkt(conn, ts);
+
+  if (conn->pv) {
+    ngtcp2_pv_cancel_expired_timer(conn->pv, ts);
+  }
+
+  if (conn->pmtud) {
+    ngtcp2_pmtud_handle_expiry(conn->pmtud, ts);
+    if (ngtcp2_pmtud_finished(conn->pmtud)) {
+      ngtcp2_conn_stop_pmtud(conn);
+    }
+  }
+
+  if (ngtcp2_conn_loss_detection_expiry(conn) <= ts) {
+    rv = ngtcp2_conn_on_loss_detection_timer(conn, ts);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (conn->dcid.current.cid.datalen) {
+    rv = ngtcp2_dcidtr_retire_stale_bound_dcid(&conn->dcid.dtr, 3 * pto, ts,
+                                               dcidtr_on_retire, conn);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  rv = conn_remove_retired_connection_id(conn, pto, ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (conn->server && conn->early.ckm &&
+      ngtcp2_tstamp_elapsed(conn->early.discard_started_ts, 3 * pto, ts)) {
+    conn_discard_early_key(conn);
+  }
+
+  if (!conn_is_tls_handshake_completed(conn) &&
+      ngtcp2_tstamp_elapsed(conn->local.settings.initial_ts,
+                            conn->local.settings.handshake_timeout, ts)) {
+    return NGTCP2_ERR_HANDSHAKE_TIMEOUT;
+  }
+
+  return 0;
+}
+
+static void acktr_cancel_expired_ack_delay_timer(ngtcp2_acktr *acktr,
+                                                 ngtcp2_duration max_ack_delay,
+                                                 ngtcp2_tstamp ts) {
+  if (!(acktr->flags & NGTCP2_ACKTR_FLAG_CANCEL_TIMER) &&
+      ngtcp2_tstamp_elapsed(acktr->first_unacked_ts, max_ack_delay, ts)) {
+    acktr->flags |= NGTCP2_ACKTR_FLAG_CANCEL_TIMER;
+  }
+}
+
+void ngtcp2_conn_cancel_expired_ack_delay_timer(ngtcp2_conn *conn,
+                                                ngtcp2_tstamp ts) {
+  ngtcp2_duration ack_delay = conn_compute_ack_delay(conn);
+
+  if (conn->in_pktns) {
+    acktr_cancel_expired_ack_delay_timer(&conn->in_pktns->acktr, 0, ts);
+  }
+  if (conn->hs_pktns) {
+    acktr_cancel_expired_ack_delay_timer(&conn->hs_pktns->acktr, 0, ts);
+  }
+  acktr_cancel_expired_ack_delay_timer(&conn->pktns.acktr, ack_delay, ts);
+}
+
+ngtcp2_tstamp ngtcp2_conn_lost_pkt_expiry(ngtcp2_conn *conn) {
+  ngtcp2_tstamp res = UINT64_MAX, ts;
+
+  if (conn->in_pktns) {
+    ts = ngtcp2_rtb_lost_pkt_ts(&conn->in_pktns->rtb);
+    if (ts != UINT64_MAX) {
+      ts += conn_compute_pto(conn, conn->in_pktns) * 3;
+      res = ngtcp2_min_uint64(res, ts);
+    }
+  }
+
+  if (conn->hs_pktns) {
+    ts = ngtcp2_rtb_lost_pkt_ts(&conn->hs_pktns->rtb);
+    if (ts != UINT64_MAX) {
+      ts += conn_compute_pto(conn, conn->hs_pktns) * 3;
+      res = ngtcp2_min_uint64(res, ts);
+    }
+  }
+
+  ts = ngtcp2_rtb_lost_pkt_ts(&conn->pktns.rtb);
+  if (ts != UINT64_MAX) {
+    ts += conn_compute_pto(conn, &conn->pktns) * 3;
+    res = ngtcp2_min_uint64(res, ts);
+  }
+
+  return res;
+}
+
+void ngtcp2_conn_remove_lost_pkt(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  ngtcp2_duration timeout;
+
+  if (conn->in_pktns) {
+    timeout = conn_compute_pto(conn, conn->in_pktns) * 3;
+    ngtcp2_rtb_remove_expired_lost_pkt(&conn->in_pktns->rtb, timeout, ts);
+  }
+  if (conn->hs_pktns) {
+    timeout = conn_compute_pto(conn, conn->hs_pktns) * 3;
+    ngtcp2_rtb_remove_expired_lost_pkt(&conn->hs_pktns->rtb, timeout, ts);
+  }
+  timeout = conn_compute_pto(conn, &conn->pktns) * 3;
+  ngtcp2_rtb_remove_expired_lost_pkt(&conn->pktns.rtb, timeout, ts);
+}
+
+/*
+ * select_preferred_version selects the most preferred version.
+ * |fallback_version| is chosen if no preference is made, or
+ * |preferred_versions| does not include any of |chosen_version| or
+ * |available_versions|.  |chosen_version| is treated as an extra
+ * other version.
+ */
+static uint32_t select_preferred_version(const uint32_t *preferred_versions,
+                                         size_t preferred_versionslen,
+                                         uint32_t chosen_version,
+                                         const uint8_t *available_versions,
+                                         size_t available_versionslen,
+                                         uint32_t fallback_version) {
+  size_t i, j;
+  const uint8_t *p;
+  uint32_t v;
+
+  if (!preferred_versionslen ||
+      (!available_versionslen && chosen_version == fallback_version)) {
+    return fallback_version;
+  }
+
+  for (i = 0; i < preferred_versionslen; ++i) {
+    if (preferred_versions[i] == chosen_version) {
+      return chosen_version;
+    }
+    for (j = 0, p = available_versions; j < available_versionslen;
+         j += sizeof(uint32_t)) {
+      p = ngtcp2_get_uint32be(&v, p);
+
+      if (preferred_versions[i] == v) {
+        return v;
+      }
+    }
+  }
+
+  return fallback_version;
+}
+
+/*
+ * conn_client_validate_transport_params validates |params| as client.
+ * |params| must be sent with Encrypted Extensions.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_TRANSPORT_PARAM
+ *     params contains preferred address but server chose zero-length
+ *     connection ID.
+ * NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE
+ *     Validation against version negotiation parameters failed.
+ */
+static int
+conn_client_validate_transport_params(ngtcp2_conn *conn,
+                                      const ngtcp2_transport_params *params) {
+  if (!params->original_dcid_present) {
+    return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM;
+  }
+
+  if (!ngtcp2_cid_eq(&conn->rcid, &params->original_dcid)) {
+    return NGTCP2_ERR_TRANSPORT_PARAM;
+  }
+
+  if (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) {
+    if (!params->retry_scid_present) {
+      return NGTCP2_ERR_TRANSPORT_PARAM;
+    }
+    if (!ngtcp2_cid_eq(&conn->retry_scid, &params->retry_scid)) {
+      return NGTCP2_ERR_TRANSPORT_PARAM;
+    }
+  } else if (params->retry_scid_present) {
+    return NGTCP2_ERR_TRANSPORT_PARAM;
+  }
+
+  if (params->preferred_addr_present && conn->dcid.current.cid.datalen == 0) {
+    return NGTCP2_ERR_TRANSPORT_PARAM;
+  }
+
+  if (params->version_info_present) {
+    if (conn->negotiated_version != params->version_info.chosen_version) {
+      return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE;
+    }
+
+    assert(vneg_available_versions_includes(conn->vneg.available_versions,
+                                            conn->vneg.available_versionslen,
+                                            conn->negotiated_version));
+  } else if (conn->client_chosen_version != conn->negotiated_version) {
+    return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE;
+  }
+
+  /* When client reacted upon Version Negotiation */
+  if (conn->local.settings.original_version != conn->client_chosen_version) {
+    if (!params->version_info_present) {
+      assert(conn->client_chosen_version == conn->negotiated_version);
+
+      /* QUIC v1 is treated specially.  If version_info is missing, no
+         further validation is necessary.  See
+         https://datatracker.ietf.org/doc/html/rfc9368#section-8
+       */
+      if (conn->client_chosen_version == NGTCP2_PROTO_VER_V1) {
+        return 0;
+      }
+
+      return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE;
+    }
+
+    /* Server choose original version after Version Negotiation.  RFC
+       9368 does not say this particular case, but this smells like
+       misbehaved server because server should accept original_version
+       in the original connection. */
+    if (conn->local.settings.original_version ==
+        params->version_info.chosen_version) {
+      return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE;
+    }
+
+    /* Check version downgrade on incompatible version negotiation. */
+    if (params->version_info.available_versionslen == 0) {
+      return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE;
+    }
+
+    if (conn->client_chosen_version !=
+        select_preferred_version(conn->vneg.preferred_versions,
+                                 conn->vneg.preferred_versionslen,
+                                 params->version_info.chosen_version,
+                                 params->version_info.available_versions,
+                                 params->version_info.available_versionslen,
+                                 /* fallback_version = */ 0)) {
+      return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE;
+    }
+  }
+
+  return 0;
+}
+
+uint32_t
+ngtcp2_conn_server_negotiate_version(ngtcp2_conn *conn,
+                                     const ngtcp2_version_info *version_info) {
+  assert(conn->server);
+  assert(conn->client_chosen_version == version_info->chosen_version);
+
+  return select_preferred_version(
+    conn->vneg.preferred_versions, conn->vneg.preferred_versionslen,
+    version_info->chosen_version, version_info->available_versions,
+    version_info->available_versionslen, version_info->chosen_version);
+}
+
+int ngtcp2_conn_set_remote_transport_params(
+  ngtcp2_conn *conn, const ngtcp2_transport_params *params) {
+  int rv;
+
+  /* We expect this function is called once per QUIC connection, but
+     GnuTLS server seems to call TLS extension callback twice if it
+     sends HelloRetryRequest.  In practice, same QUIC transport
+     parameters are sent in the 2nd client flight, just returning 0
+     would cause no harm. */
+  if (conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED) {
+    return 0;
+  }
+
+  if (!params->initial_scid_present) {
+    return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM;
+  }
+
+  /* Assume that ngtcp2_transport_params_decode sets default value if
+     active_connection_id_limit is omitted. */
+  if (params->active_connection_id_limit <
+      NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) {
+    return NGTCP2_ERR_TRANSPORT_PARAM;
+  }
+
+  /* We assume that conn->dcid.current.cid is still the initial one.
+     This requires that transport parameter must be fed into
+     ngtcp2_conn as early as possible. */
+  if (!ngtcp2_cid_eq(&conn->dcid.current.cid, &params->initial_scid)) {
+    return NGTCP2_ERR_TRANSPORT_PARAM;
+  }
+
+  if (params->max_udp_payload_size < NGTCP2_MAX_UDP_PAYLOAD_SIZE) {
+    return NGTCP2_ERR_TRANSPORT_PARAM;
+  }
+
+  if (conn->server) {
+    if (params->original_dcid_present ||
+        params->stateless_reset_token_present ||
+        params->preferred_addr_present || params->retry_scid_present) {
+      return NGTCP2_ERR_TRANSPORT_PARAM;
+    }
+
+    if (params->version_info_present) {
+      if (!vneg_available_versions_includes(
+            params->version_info.available_versions,
+            params->version_info.available_versionslen,
+            params->version_info.chosen_version)) {
+        return NGTCP2_ERR_TRANSPORT_PARAM;
+      }
+
+      if (params->version_info.chosen_version != conn->client_chosen_version) {
+        return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE;
+      }
+
+      conn->negotiated_version =
+        ngtcp2_conn_server_negotiate_version(conn, &params->version_info);
+      if (conn->negotiated_version != conn->client_chosen_version) {
+        rv = conn_call_version_negotiation(conn, conn->negotiated_version,
+                                           &conn->rcid);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+    } else {
+      conn->negotiated_version = conn->client_chosen_version;
+    }
+
+    conn->local.transport_params.version_info.chosen_version =
+      conn->negotiated_version;
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                    "the negotiated version is 0x%08x",
+                    conn->negotiated_version);
+  } else {
+    rv = conn_client_validate_transport_params(conn, params);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  ngtcp2_log_remote_tp(&conn->log, params);
+
+  ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, params, conn->server,
+                                              NGTCP2_QLOG_SIDE_REMOTE);
+
+  if ((conn->server && conn->pktns.crypto.tx.ckm) ||
+      (!conn->server && conn->pktns.crypto.rx.ckm)) {
+    ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem);
+    conn->remote.transport_params = NULL;
+
+    rv = ngtcp2_transport_params_copy_new(&conn->remote.transport_params,
+                                          params, conn->mem);
+    if (rv != 0) {
+      return rv;
+    }
+    conn_sync_stream_id_limit(conn);
+    conn->tx.max_offset = conn->remote.transport_params->initial_max_data;
+  } else {
+    assert(!conn->remote.pending_transport_params);
+
+    rv = ngtcp2_transport_params_copy_new(
+      &conn->remote.pending_transport_params, params, conn->mem);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  conn->flags |= NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED;
+
+  return 0;
+}
+
+int ngtcp2_conn_decode_and_set_remote_transport_params(ngtcp2_conn *conn,
+                                                       const uint8_t *data,
+                                                       size_t datalen) {
+  ngtcp2_transport_params params;
+  int rv;
+
+  rv = ngtcp2_transport_params_decode(&params, data, datalen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return ngtcp2_conn_set_remote_transport_params(conn, &params);
+}
+
+const ngtcp2_transport_params *
+ngtcp2_conn_get_remote_transport_params(ngtcp2_conn *conn) {
+  if (conn->remote.pending_transport_params) {
+    return conn->remote.pending_transport_params;
+  }
+
+  return conn->remote.transport_params;
+}
+
+ngtcp2_ssize ngtcp2_conn_encode_0rtt_transport_params(ngtcp2_conn *conn,
+                                                      uint8_t *dest,
+                                                      size_t destlen) {
+  ngtcp2_transport_params params, *src;
+
+  if (conn->server) {
+    src = &conn->local.transport_params;
+  } else {
+    assert(conn->remote.transport_params);
+
+    src = conn->remote.transport_params;
+  }
+
+  ngtcp2_transport_params_default(&params);
+
+  params.initial_max_streams_bidi = src->initial_max_streams_bidi;
+  params.initial_max_streams_uni = src->initial_max_streams_uni;
+  params.initial_max_stream_data_bidi_local =
+    src->initial_max_stream_data_bidi_local;
+  params.initial_max_stream_data_bidi_remote =
+    src->initial_max_stream_data_bidi_remote;
+  params.initial_max_stream_data_uni = src->initial_max_stream_data_uni;
+  params.initial_max_data = src->initial_max_data;
+  params.active_connection_id_limit = src->active_connection_id_limit;
+  params.max_datagram_frame_size = src->max_datagram_frame_size;
+
+  if (conn->server) {
+    params.max_idle_timeout = src->max_idle_timeout;
+    params.max_udp_payload_size = src->max_udp_payload_size;
+    params.disable_active_migration = src->disable_active_migration;
+  }
+
+  return ngtcp2_transport_params_encode(dest, destlen, &params);
+}
+
+int ngtcp2_conn_decode_and_set_0rtt_transport_params(ngtcp2_conn *conn,
+                                                     const uint8_t *data,
+                                                     size_t datalen) {
+  ngtcp2_transport_params params;
+  int rv;
+
+  rv = ngtcp2_transport_params_decode(&params, data, datalen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return ngtcp2_conn_set_0rtt_remote_transport_params(conn, &params);
+}
+
+int ngtcp2_conn_set_0rtt_remote_transport_params(
+  ngtcp2_conn *conn, const ngtcp2_transport_params *params) {
+  ngtcp2_transport_params *p;
+
+  assert(!conn->server);
+  assert(!conn->remote.transport_params);
+
+  /* Assume that all pointer fields in p are NULL */
+  p = ngtcp2_mem_calloc(conn->mem, 1, sizeof(*p));
+  if (p == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  conn->remote.transport_params = p;
+
+  ngtcp2_transport_params_default(conn->remote.transport_params);
+
+  p->initial_max_streams_bidi = params->initial_max_streams_bidi;
+  p->initial_max_streams_uni = params->initial_max_streams_uni;
+  p->initial_max_stream_data_bidi_local =
+    params->initial_max_stream_data_bidi_local;
+  p->initial_max_stream_data_bidi_remote =
+    params->initial_max_stream_data_bidi_remote;
+  p->initial_max_stream_data_uni = params->initial_max_stream_data_uni;
+  p->initial_max_data = params->initial_max_data;
+  /* we might hit garbage, then set the sane default. */
+  p->active_connection_id_limit =
+    ngtcp2_max_uint64(NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT,
+                      params->active_connection_id_limit);
+  p->max_datagram_frame_size = params->max_datagram_frame_size;
+
+  /* we might hit garbage, then set the sane default. */
+  if (params->max_udp_payload_size) {
+    p->max_udp_payload_size = ngtcp2_max_uint64(NGTCP2_MAX_UDP_PAYLOAD_SIZE,
+                                                params->max_udp_payload_size);
+  }
+
+  /* These parameters are treated specially.  If server accepts early
+     data, it must not set values for these parameters that are
+     smaller than these remembered values. */
+  conn->early.transport_params.initial_max_streams_bidi =
+    params->initial_max_streams_bidi;
+  conn->early.transport_params.initial_max_streams_uni =
+    params->initial_max_streams_uni;
+  conn->early.transport_params.initial_max_stream_data_bidi_local =
+    params->initial_max_stream_data_bidi_local;
+  conn->early.transport_params.initial_max_stream_data_bidi_remote =
+    params->initial_max_stream_data_bidi_remote;
+  conn->early.transport_params.initial_max_stream_data_uni =
+    params->initial_max_stream_data_uni;
+  conn->early.transport_params.initial_max_data = params->initial_max_data;
+  conn->early.transport_params.active_connection_id_limit =
+    params->active_connection_id_limit;
+  conn->early.transport_params.max_datagram_frame_size =
+    params->max_datagram_frame_size;
+
+  conn_sync_stream_id_limit(conn);
+
+  conn->tx.max_offset = p->initial_max_data;
+
+  ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, p, conn->server,
+                                              NGTCP2_QLOG_SIDE_REMOTE);
+
+  return 0;
+}
+
+int ngtcp2_conn_set_local_transport_params_versioned(
+  ngtcp2_conn *conn, int transport_params_version,
+  const ngtcp2_transport_params *params) {
+  ngtcp2_transport_params paramsbuf;
+
+  params = ngtcp2_transport_params_convert_to_latest(
+    &paramsbuf, transport_params_version, params);
+
+  assert(conn->server);
+  assert(params->active_connection_id_limit >=
+         NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT);
+  assert(params->active_connection_id_limit <=
+         NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE);
+
+  if (conn->hs_pktns == NULL || conn->hs_pktns->crypto.tx.ckm) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  conn_set_local_transport_params(conn, params);
+
+  return 0;
+}
+
+int ngtcp2_conn_commit_local_transport_params(ngtcp2_conn *conn) {
+  const ngtcp2_mem *mem = conn->mem;
+  ngtcp2_transport_params *params = &conn->local.transport_params;
+  ngtcp2_scid *scident;
+  int rv;
+
+  assert(1 == ngtcp2_ksl_len(&conn->scid.set));
+
+  params->initial_scid = conn->oscid;
+  params->initial_scid_present = 1;
+
+  if (conn->oscid.datalen == 0) {
+    params->preferred_addr_present = 0;
+  }
+
+  if (conn->server && params->preferred_addr_present) {
+    scident = ngtcp2_mem_malloc(mem, sizeof(*scident));
+    if (scident == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+
+    ngtcp2_scid_init(scident, 1, &params->preferred_addr.cid);
+
+    rv = ngtcp2_ksl_insert(&conn->scid.set, NULL, &scident->cid, scident);
+    if (rv != 0) {
+      ngtcp2_mem_free(mem, scident);
+      return rv;
+    }
+
+    conn->scid.last_seq = 1;
+  }
+
+  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->flags |= NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED;
+
+  ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, params, conn->server,
+                                              NGTCP2_QLOG_SIDE_LOCAL);
+
+  return 0;
+}
+
+const ngtcp2_transport_params *
+ngtcp2_conn_get_local_transport_params(ngtcp2_conn *conn) {
+  return &conn->local.transport_params;
+}
+
+ngtcp2_ssize ngtcp2_conn_encode_local_transport_params(ngtcp2_conn *conn,
+                                                       uint8_t *dest,
+                                                       size_t destlen) {
+  return ngtcp2_transport_params_encode(dest, destlen,
+                                        &conn->local.transport_params);
+}
+
+int ngtcp2_conn_open_bidi_stream(ngtcp2_conn *conn, int64_t *pstream_id,
+                                 void *stream_user_data) {
+  int rv;
+  ngtcp2_strm *strm;
+
+  if (ngtcp2_conn_get_streams_bidi_left(conn) == 0) {
+    return NGTCP2_ERR_STREAM_ID_BLOCKED;
+  }
+
+  strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc);
+  if (strm == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  rv = ngtcp2_conn_init_stream(conn, strm, conn->local.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;
+
+  return 0;
+}
+
+int ngtcp2_conn_open_uni_stream(ngtcp2_conn *conn, int64_t *pstream_id,
+                                void *stream_user_data) {
+  int rv;
+  ngtcp2_strm *strm;
+
+  if (ngtcp2_conn_get_streams_uni_left(conn) == 0) {
+    return NGTCP2_ERR_STREAM_ID_BLOCKED;
+  }
+
+  strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc);
+  if (strm == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  rv = ngtcp2_conn_init_stream(conn, strm, conn->local.uni.next_stream_id,
+                               stream_user_data);
+  if (rv != 0) {
+    ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
+    return rv;
+  }
+  ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_RD);
+
+  *pstream_id = conn->local.uni.next_stream_id;
+  conn->local.uni.next_stream_id += 4;
+
+  return 0;
+}
+
+ngtcp2_strm *ngtcp2_conn_find_stream(ngtcp2_conn *conn, int64_t stream_id) {
+  return ngtcp2_map_find(&conn->strms, (uint64_t)stream_id);
+}
+
+ngtcp2_ssize ngtcp2_conn_write_stream_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  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);
+}
+
+static ngtcp2_ssize
+conn_write_vmsg_wrapper(ngtcp2_conn *conn, ngtcp2_path *path,
+                        int pkt_info_version, ngtcp2_pkt_info *pi,
+                        uint8_t *dest, size_t destlen, uint8_t wflags,
+                        ngtcp2_vmsg *vmsg, ngtcp2_tstamp ts) {
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  ngtcp2_ssize nwrite;
+
+  nwrite = ngtcp2_conn_write_vmsg(conn, path, pkt_info_version, pi, dest,
+                                  destlen, wflags, vmsg, ts);
+  if (nwrite < 0) {
+    return nwrite;
+  }
+
+  if (cstat->bytes_in_flight >= cstat->cwnd) {
+    conn->rst.is_cwnd_limited = 1;
+  } else if ((cstat->cwnd >= cstat->ssthresh ||
+              cstat->bytes_in_flight * 2 < cstat->cwnd) &&
+             nwrite == 0 && conn_pacing_pkt_tx_allowed(conn, ts) &&
+             (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) {
+    conn->rst.app_limited = conn->rst.delivered + cstat->bytes_in_flight;
+
+    if (conn->rst.app_limited == 0) {
+      conn->rst.app_limited = cstat->max_tx_udp_payload_size;
+    }
+  }
+
+  return nwrite;
+}
+
+ngtcp2_ssize ngtcp2_conn_writev_stream_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen,
+  uint32_t flags, int64_t stream_id, const ngtcp2_vec *datav, size_t datavcnt,
+  ngtcp2_tstamp ts) {
+  ngtcp2_vmsg vmsg, *pvmsg;
+  ngtcp2_strm *strm;
+  int64_t datalen;
+  uint8_t wflags;
+
+  if (pdatalen) {
+    *pdatalen = -1;
+  }
+
+  if (stream_id != -1) {
+    strm = ngtcp2_conn_find_stream(conn, stream_id);
+    if (strm == NULL) {
+      return NGTCP2_ERR_STREAM_NOT_FOUND;
+    }
+
+    if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) {
+      return NGTCP2_ERR_STREAM_SHUT_WR;
+    }
+
+    datalen = ngtcp2_vec_len_varint(datav, datavcnt);
+    if (datalen == -1) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    if (datalen == 0 && !(flags & NGTCP2_WRITE_STREAM_FLAG_FIN) &&
+        (strm->flags & NGTCP2_STRM_FLAG_ANY_SENT)) {
+      pvmsg = NULL;
+    } else {
+      if ((uint64_t)datalen > NGTCP2_MAX_VARINT - strm->tx.offset ||
+          (uint64_t)datalen > NGTCP2_MAX_VARINT - conn->tx.offset) {
+        return NGTCP2_ERR_INVALID_ARGUMENT;
+      }
+
+      vmsg.type = NGTCP2_VMSG_TYPE_STREAM;
+      vmsg.stream.strm = strm;
+      vmsg.stream.flags = flags;
+      vmsg.stream.data = datav;
+      vmsg.stream.datacnt = datavcnt;
+      vmsg.stream.pdatalen = pdatalen;
+
+      pvmsg = &vmsg;
+    }
+  } else {
+    pvmsg = NULL;
+  }
+
+  if (flags & NGTCP2_WRITE_STREAM_FLAG_PADDING) {
+    wflags = NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY;
+  } else {
+    wflags = NGTCP2_WRITE_PKT_FLAG_NONE;
+  }
+
+  return conn_write_vmsg_wrapper(conn, path, pkt_info_version, pi, dest,
+                                 destlen, wflags, pvmsg, ts);
+}
+
+ngtcp2_ssize ngtcp2_conn_write_datagram_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted,
+  uint32_t flags, uint64_t dgram_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_datagram_versioned(conn, path, pkt_info_version, pi,
+                                               dest, destlen, paccepted, flags,
+                                               dgram_id, v, datacnt, ts);
+}
+
+ngtcp2_ssize ngtcp2_conn_writev_datagram_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted,
+  uint32_t flags, uint64_t dgram_id, const ngtcp2_vec *datav, size_t datavcnt,
+  ngtcp2_tstamp ts) {
+  ngtcp2_vmsg vmsg;
+  int64_t datalen;
+  uint8_t wflags;
+
+  if (paccepted) {
+    *paccepted = 0;
+  }
+
+  if (conn->remote.transport_params == NULL ||
+      conn->remote.transport_params->max_datagram_frame_size == 0) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  datalen = ngtcp2_vec_len_varint(datav, datavcnt);
+  if (datalen == -1
+#if SIZE_MAX < UINT64_MAX
+      || (uint64_t)datalen > SIZE_MAX
+#endif /* SIZE_MAX < UINT64_MAX */
+  ) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (conn->remote.transport_params->max_datagram_frame_size <
+      ngtcp2_pkt_datagram_framelen((size_t)datalen)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  vmsg.type = NGTCP2_VMSG_TYPE_DATAGRAM;
+  vmsg.datagram.dgram_id = dgram_id;
+  vmsg.datagram.flags = flags;
+  vmsg.datagram.data = datav;
+  vmsg.datagram.datacnt = datavcnt;
+  vmsg.datagram.paccepted = paccepted;
+
+  if (flags & NGTCP2_WRITE_DATAGRAM_FLAG_PADDING) {
+    wflags = NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY;
+  } else {
+    wflags = NGTCP2_WRITE_PKT_FLAG_NONE;
+  }
+
+  return conn_write_vmsg_wrapper(conn, path, pkt_info_version, pi, dest,
+                                 destlen, wflags, &vmsg, ts);
+}
+
+ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path,
+                                    int pkt_info_version, ngtcp2_pkt_info *pi,
+                                    uint8_t *dest, size_t destlen,
+                                    uint8_t wflags, ngtcp2_vmsg *vmsg,
+                                    ngtcp2_tstamp ts) {
+  ngtcp2_ssize nwrite;
+  size_t origlen;
+  size_t origdestlen = destlen;
+  int rv;
+  int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0;
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  ngtcp2_ssize res = 0;
+  uint64_t server_tx_left;
+  int64_t prev_in_pkt_num = -1;
+  ngtcp2_ksl_it it;
+  ngtcp2_rtb_entry *rtbent;
+  (void)pkt_info_version;
+
+  conn_update_timestamp(conn, ts);
+
+  if (path) {
+    ngtcp2_path_copy(path, &conn->dcid.current.ps.path);
+  }
+
+  origlen = destlen =
+    conn_shape_udp_payload(conn, &conn->dcid.current, destlen);
+
+  if (!ppe_pending && pi) {
+    pi->ecn = NGTCP2_ECN_NOT_ECT;
+  }
+
+  switch (conn->state) {
+  case NGTCP2_CS_CLIENT_INITIAL:
+  case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE:
+    if (!conn_pacing_pkt_tx_allowed(conn, ts)) {
+      assert(!ppe_pending);
+
+      return conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts);
+    }
+
+    nwrite =
+      conn_client_write_handshake(conn, pi, dest, destlen, wflags, vmsg, ts);
+    /* We might be unable to write a packet because of depletion of
+       congestion window budget, perhaps due to packet loss that
+       shrinks the window drastically. */
+    if (nwrite <= 0) {
+      return nwrite;
+    }
+    if (conn->state != NGTCP2_CS_POST_HANDSHAKE) {
+      return nwrite;
+    }
+
+    assert(nwrite);
+    assert(dest[0] & NGTCP2_HEADER_FORM_BIT);
+    assert(conn->negotiated_version);
+
+    if (nwrite < NGTCP2_MAX_UDP_PAYLOAD_SIZE &&
+        ngtcp2_pkt_get_type_long(conn->negotiated_version, dest[0]) ==
+          NGTCP2_PKT_INITIAL) {
+      wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING;
+    }
+
+    res = nwrite;
+    dest += nwrite;
+    destlen -= (size_t)nwrite;
+    /* Break here so that we can coalesces 1RTT packet. */
+    break;
+  case NGTCP2_CS_SERVER_INITIAL:
+  case NGTCP2_CS_SERVER_WAIT_HANDSHAKE:
+    if (!conn_pacing_pkt_tx_allowed(conn, ts)) {
+      assert(!ppe_pending);
+
+      if (!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) {
+        server_tx_left = conn_server_tx_left(conn, &conn->dcid.current);
+        if (server_tx_left == 0) {
+          return 0;
+        }
+
+        origlen = (size_t)ngtcp2_min_uint64((uint64_t)origlen, server_tx_left);
+      }
+
+      return conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts);
+    }
+
+    if (!ppe_pending) {
+      if (!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) {
+        server_tx_left = conn_server_tx_left(conn, &conn->dcid.current);
+        if (server_tx_left == 0) {
+          if (cstat->loss_detection_timer != UINT64_MAX) {
+            ngtcp2_log_info(
+              &conn->log, NGTCP2_LOG_EVENT_LDC,
+              "loss detection timer canceled due to amplification limit");
+            ngtcp2_conn_cancel_loss_detection_timer(conn);
+          }
+
+          return 0;
+        }
+
+        destlen = (size_t)ngtcp2_min_uint64((uint64_t)destlen, server_tx_left);
+      }
+
+      if (conn->in_pktns) {
+        it = ngtcp2_rtb_head(&conn->in_pktns->rtb);
+        if (!ngtcp2_ksl_it_end(&it)) {
+          rtbent = ngtcp2_ksl_it_get(&it);
+          prev_in_pkt_num = rtbent->hd.pkt_num;
+        }
+      }
+
+      nwrite = conn_write_handshake(conn, pi, dest, destlen, wflags,
+                                    /* write_datalen = */ 0, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+
+      res = nwrite;
+      dest += nwrite;
+      destlen -= (size_t)nwrite;
+
+      if (res < NGTCP2_MAX_UDP_PAYLOAD_SIZE && conn->in_pktns && nwrite > 0) {
+        it = ngtcp2_rtb_head(&conn->in_pktns->rtb);
+        if (!ngtcp2_ksl_it_end(&it)) {
+          rtbent = ngtcp2_ksl_it_get(&it);
+          if (rtbent->hd.pkt_num != prev_in_pkt_num &&
+              (rtbent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+            wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING;
+          }
+        }
+      }
+    }
+    if (conn->pktns.crypto.tx.ckm == NULL) {
+      return res;
+    }
+    break;
+  case NGTCP2_CS_POST_HANDSHAKE:
+    if (!conn_pacing_pkt_tx_allowed(conn, ts)) {
+      assert(!ppe_pending);
+
+      if (conn->server &&
+          !(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) {
+        server_tx_left = conn_server_tx_left(conn, &conn->dcid.current);
+        if (server_tx_left == 0) {
+          return 0;
+        }
+
+        origlen = (size_t)ngtcp2_min_uint64((uint64_t)origlen, server_tx_left);
+      }
+
+      return conn_write_ack_pkt(conn, pi, dest, origlen, NGTCP2_PKT_1RTT, ts);
+    }
+
+    break;
+  case NGTCP2_CS_CLOSING:
+    return NGTCP2_ERR_CLOSING;
+  case NGTCP2_CS_DRAINING:
+    return NGTCP2_ERR_DRAINING;
+  default:
+    return 0;
+  }
+
+  assert(conn->pktns.crypto.tx.ckm);
+
+  if (conn_check_pkt_num_exhausted(conn)) {
+    return NGTCP2_ERR_PKT_NUM_EXHAUSTED;
+  }
+
+  if (vmsg) {
+    switch (vmsg->type) {
+    case NGTCP2_VMSG_TYPE_STREAM:
+      if (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_MORE) {
+        wflags |= NGTCP2_WRITE_PKT_FLAG_MORE;
+      }
+      break;
+    case NGTCP2_VMSG_TYPE_DATAGRAM:
+      if (vmsg->datagram.flags & NGTCP2_WRITE_DATAGRAM_FLAG_MORE) {
+        wflags |= NGTCP2_WRITE_PKT_FLAG_MORE;
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+  if (ppe_pending) {
+    res = conn->pkt.hs_spktlen;
+    /* dest and destlen have already been adjusted in ppe in the first
+       run.  They are adjusted for probe packet later. */
+    nwrite = conn_write_pkt(conn, pi, dest, destlen, (size_t)res, vmsg,
+                            NGTCP2_PKT_1RTT, wflags, ts);
+    goto fin;
+  } else {
+    conn->pkt.require_padding =
+      (wflags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING);
+
+    if (conn->state == NGTCP2_CS_POST_HANDSHAKE) {
+      rv = conn_prepare_key_update(conn, ts);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    if (!conn->pktns.rtb.probe_pkt_left && conn_cwnd_is_zero(conn)) {
+      destlen = 0;
+    } else {
+      if (res == 0) {
+        nwrite =
+          conn_write_path_response(conn, path, pi, dest, origdestlen, ts);
+        if (nwrite) {
+          goto fin;
+        }
+
+        if (conn->pv) {
+          nwrite =
+            conn_write_path_challenge(conn, path, pi, dest, origdestlen, ts);
+          if (nwrite) {
+            goto fin;
+          }
+        }
+
+        if (conn->pmtud &&
+            (conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) &&
+            (!conn->hs_pktns ||
+             ngtcp2_strm_streamfrq_empty(&conn->hs_pktns->crypto.strm))) {
+          nwrite = conn_write_pmtud_probe(conn, pi, dest, origdestlen, ts);
+          if (nwrite) {
+            goto fin;
+          }
+        }
+      }
+    }
+
+    if (conn->server &&
+        !(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) {
+      server_tx_left = conn_server_tx_left(conn, &conn->dcid.current);
+      origlen = (size_t)ngtcp2_min_uint64((uint64_t)origlen, server_tx_left);
+      destlen = (size_t)ngtcp2_min_uint64((uint64_t)destlen, server_tx_left);
+
+      if (server_tx_left == 0 &&
+          conn->cstat.loss_detection_timer != UINT64_MAX) {
+        ngtcp2_log_info(
+          &conn->log, NGTCP2_LOG_EVENT_LDC,
+          "loss detection timer canceled due to amplification limit");
+        ngtcp2_conn_cancel_loss_detection_timer(conn);
+      }
+    }
+  }
+
+  if (res == 0) {
+    if (conn_handshake_remnants_left(conn)) {
+      if (conn_handshake_probe_left(conn) ||
+          /* Allow exceeding CWND if an Handshake packet needs to be
+             sent in order to avoid dead lock.  In some situation,
+             typically for client, 1 RTT packets may occupy in-flight
+             bytes (e.g., some large requests and PMTUD), and
+             Handshake packet loss shrinks CWND, and we may get in the
+             situation that we are unable to send Handshake packet. */
+          (conn->hs_pktns->rtb.num_pto_eliciting == 0 &&
+           !ngtcp2_strm_streamfrq_empty(&conn->hs_pktns->crypto.strm))) {
+        destlen = origlen;
+      }
+      nwrite = conn_write_handshake_pkts(conn, pi, dest, destlen,
+                                         /* write_datalen = */ 0, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+      if (nwrite > 0) {
+        res = nwrite;
+        dest += nwrite;
+        destlen -= (size_t)nwrite;
+      } else if (destlen == 0) {
+        res = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts);
+        if (res) {
+          return res;
+        }
+      }
+    }
+  }
+
+  if (conn->pktns.rtb.probe_pkt_left) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                    "transmit probe pkt left=%zu",
+                    conn->pktns.rtb.probe_pkt_left);
+
+    nwrite = conn_write_pkt(conn, pi, dest, destlen, (size_t)res, vmsg,
+                            NGTCP2_PKT_1RTT, wflags, ts);
+
+    goto fin;
+  }
+
+  nwrite = conn_write_pkt(conn, pi, dest, destlen, (size_t)res, vmsg,
+                          NGTCP2_PKT_1RTT, wflags, ts);
+  if (nwrite) {
+    assert(nwrite != NGTCP2_ERR_NOBUF);
+    goto fin;
+  }
+
+  if (res == 0) {
+    nwrite = conn_write_ack_pkt(conn, pi, dest, origlen, NGTCP2_PKT_1RTT, ts);
+  }
+
+fin:
+  if (nwrite >= 0) {
+    res += nwrite;
+    return res;
+  }
+
+  switch (nwrite) {
+  case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+    if (!(wflags & NGTCP2_WRITE_PKT_FLAG_MORE)) {
+      if (res) {
+        return res;
+      }
+
+      break;
+    }
+    /* fall through */
+  case NGTCP2_ERR_WRITE_MORE:
+    conn->pkt.hs_spktlen = res;
+    break;
+  }
+
+  return nwrite;
+}
+
+static ngtcp2_ssize
+conn_write_connection_close(ngtcp2_conn *conn, ngtcp2_pkt_info *pi,
+                            uint8_t *dest, size_t destlen, uint8_t pkt_type,
+                            uint64_t error_code, const uint8_t *reason,
+                            size_t reasonlen, ngtcp2_tstamp ts) {
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+  ngtcp2_ssize res = 0, nwrite;
+  ngtcp2_frame fr;
+  uint8_t flags = NGTCP2_WRITE_PKT_FLAG_NONE;
+
+  fr.type = NGTCP2_FRAME_CONNECTION_CLOSE;
+  fr.connection_close.error_code = error_code;
+  fr.connection_close.frame_type = 0;
+  fr.connection_close.reasonlen = reasonlen;
+  fr.connection_close.reason = (uint8_t *)reason;
+
+  if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) &&
+      pkt_type != NGTCP2_PKT_INITIAL) {
+    if (in_pktns && conn->server) {
+      nwrite = ngtcp2_conn_write_single_frame_pkt(
+        conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, NGTCP2_WRITE_PKT_FLAG_NONE,
+        &conn->dcid.current.cid, &fr, NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+
+      dest += nwrite;
+      destlen -= (size_t)nwrite;
+      res += nwrite;
+    }
+
+    if (pkt_type != NGTCP2_PKT_HANDSHAKE && hs_pktns &&
+        hs_pktns->crypto.tx.ckm) {
+      nwrite = ngtcp2_conn_write_single_frame_pkt(
+        conn, pi, dest, destlen, NGTCP2_PKT_HANDSHAKE,
+        NGTCP2_WRITE_PKT_FLAG_NONE, &conn->dcid.current.cid, &fr,
+        NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts);
+      if (nwrite < 0) {
+        return nwrite;
+      }
+
+      dest += nwrite;
+      destlen -= (size_t)nwrite;
+      res += nwrite;
+    }
+  }
+
+  if (!conn->server && pkt_type == NGTCP2_PKT_INITIAL) {
+    flags = NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING;
+  }
+
+  nwrite = ngtcp2_conn_write_single_frame_pkt(
+    conn, pi, dest, destlen, pkt_type, flags, &conn->dcid.current.cid, &fr,
+    NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts);
+
+  if (nwrite < 0) {
+    return nwrite;
+  }
+
+  res += nwrite;
+
+  if (res == 0) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  return res;
+}
+
+ngtcp2_ssize ngtcp2_conn_write_connection_close_pkt(
+  ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest,
+  size_t destlen, uint64_t error_code, const uint8_t *reason, size_t reasonlen,
+  ngtcp2_tstamp ts) {
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+  uint8_t pkt_type;
+  ngtcp2_ssize nwrite;
+  uint64_t server_tx_left;
+
+  if (conn_check_pkt_num_exhausted(conn)) {
+    return NGTCP2_ERR_PKT_NUM_EXHAUSTED;
+  }
+
+  switch (conn->state) {
+  case NGTCP2_CS_CLIENT_INITIAL:
+    return NGTCP2_ERR_INVALID_STATE;
+  case NGTCP2_CS_CLOSING:
+  case NGTCP2_CS_DRAINING:
+    return 0;
+  default:
+    break;
+  }
+
+  if (path) {
+    ngtcp2_path_copy(path, &conn->dcid.current.ps.path);
+  }
+
+  destlen = conn_shape_udp_payload(conn, &conn->dcid.current, destlen);
+
+  if (pi) {
+    pi->ecn = NGTCP2_ECN_NOT_ECT;
+  }
+
+  if (conn->server) {
+    server_tx_left = conn_server_tx_left(conn, &conn->dcid.current);
+    destlen = (size_t)ngtcp2_min_uint64((uint64_t)destlen, server_tx_left);
+  }
+
+  if (conn->state == NGTCP2_CS_POST_HANDSHAKE ||
+      (conn->server && conn->pktns.crypto.tx.ckm)) {
+    pkt_type = NGTCP2_PKT_1RTT;
+  } else if (hs_pktns && hs_pktns->crypto.tx.ckm) {
+    pkt_type = NGTCP2_PKT_HANDSHAKE;
+  } else if (in_pktns && in_pktns->crypto.tx.ckm) {
+    pkt_type = NGTCP2_PKT_INITIAL;
+  } else {
+    /* This branch is taken if server has not read any Initial packet
+       from client. */
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  nwrite = conn_write_connection_close(conn, pi, dest, destlen, pkt_type,
+                                       error_code, reason, reasonlen, ts);
+  if (nwrite < 0) {
+    return nwrite;
+  }
+
+  conn->state = NGTCP2_CS_CLOSING;
+
+  return nwrite;
+}
+
+ngtcp2_ssize ngtcp2_conn_write_application_close_pkt(
+  ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest,
+  size_t destlen, uint64_t app_error_code, const uint8_t *reason,
+  size_t reasonlen, ngtcp2_tstamp ts) {
+  ngtcp2_ssize nwrite;
+  ngtcp2_ssize res = 0;
+  ngtcp2_frame fr;
+  uint64_t server_tx_left;
+
+  if (conn_check_pkt_num_exhausted(conn)) {
+    return NGTCP2_ERR_PKT_NUM_EXHAUSTED;
+  }
+
+  switch (conn->state) {
+  case NGTCP2_CS_CLIENT_INITIAL:
+    return NGTCP2_ERR_INVALID_STATE;
+  case NGTCP2_CS_CLOSING:
+  case NGTCP2_CS_DRAINING:
+    return 0;
+  default:
+    break;
+  }
+
+  if (path) {
+    ngtcp2_path_copy(path, &conn->dcid.current.ps.path);
+  }
+
+  destlen = conn_shape_udp_payload(conn, &conn->dcid.current, destlen);
+
+  if (pi) {
+    pi->ecn = NGTCP2_ECN_NOT_ECT;
+  }
+
+  if (conn->server) {
+    server_tx_left = conn_server_tx_left(conn, &conn->dcid.current);
+    destlen = (size_t)ngtcp2_min_uint64((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;
+    }
+    res = nwrite;
+    dest += nwrite;
+    destlen -= (size_t)nwrite;
+  }
+
+  if (conn->state != NGTCP2_CS_POST_HANDSHAKE &&
+      (!conn->server || !conn->pktns.crypto.tx.ckm)) {
+    return res;
+  }
+
+  assert(conn->pktns.crypto.tx.ckm);
+
+  fr.type = NGTCP2_FRAME_CONNECTION_CLOSE_APP;
+  fr.connection_close.error_code = app_error_code;
+  fr.connection_close.frame_type = 0;
+  fr.connection_close.reasonlen = reasonlen;
+  fr.connection_close.reason = (uint8_t *)reason;
+
+  nwrite = ngtcp2_conn_write_single_frame_pkt(
+    conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE,
+    &conn->dcid.current.cid, &fr, NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts);
+
+  if (nwrite < 0) {
+    return nwrite;
+  }
+
+  res += nwrite;
+
+  if (res == 0) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  conn->state = NGTCP2_CS_CLOSING;
+
+  return res;
+}
+
+static void ccerr_init(ngtcp2_ccerr *ccerr, ngtcp2_ccerr_type type,
+                       uint64_t error_code, const uint8_t *reason,
+                       size_t reasonlen) {
+  ccerr->type = type;
+  ccerr->error_code = error_code;
+  ccerr->frame_type = 0;
+  ccerr->reason = (uint8_t *)reason;
+  ccerr->reasonlen = reasonlen;
+}
+
+void ngtcp2_ccerr_default(ngtcp2_ccerr *ccerr) {
+  ccerr_init(ccerr, NGTCP2_CCERR_TYPE_TRANSPORT, NGTCP2_NO_ERROR, NULL, 0);
+}
+
+void ngtcp2_ccerr_set_transport_error(ngtcp2_ccerr *ccerr, uint64_t error_code,
+                                      const uint8_t *reason, size_t reasonlen) {
+  ccerr_init(ccerr, NGTCP2_CCERR_TYPE_TRANSPORT, error_code, reason, reasonlen);
+}
+
+void ngtcp2_ccerr_set_liberr(ngtcp2_ccerr *ccerr, int liberr,
+                             const uint8_t *reason, size_t reasonlen) {
+  switch (liberr) {
+  case NGTCP2_ERR_RECV_VERSION_NEGOTIATION:
+    ccerr_init(ccerr, NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION, NGTCP2_NO_ERROR,
+               reason, reasonlen);
+
+    return;
+  case NGTCP2_ERR_IDLE_CLOSE:
+    ccerr_init(ccerr, NGTCP2_CCERR_TYPE_IDLE_CLOSE, NGTCP2_NO_ERROR, reason,
+               reasonlen);
+
+    return;
+  case NGTCP2_ERR_DROP_CONN:
+    ccerr_init(ccerr, NGTCP2_CCERR_TYPE_DROP_CONN, NGTCP2_NO_ERROR, reason,
+               reasonlen);
+
+    return;
+  case NGTCP2_ERR_RETRY:
+    ccerr_init(ccerr, NGTCP2_CCERR_TYPE_RETRY, NGTCP2_NO_ERROR, reason,
+               reasonlen);
+
+    return;
+  };
+
+  ngtcp2_ccerr_set_transport_error(
+    ccerr, ngtcp2_err_infer_quic_transport_error_code(liberr), reason,
+    reasonlen);
+}
+
+void ngtcp2_ccerr_set_tls_alert(ngtcp2_ccerr *ccerr, uint8_t tls_alert,
+                                const uint8_t *reason, size_t reasonlen) {
+  ngtcp2_ccerr_set_transport_error(ccerr, NGTCP2_CRYPTO_ERROR | tls_alert,
+                                   reason, reasonlen);
+}
+
+void ngtcp2_ccerr_set_application_error(ngtcp2_ccerr *ccerr,
+                                        uint64_t error_code,
+                                        const uint8_t *reason,
+                                        size_t reasonlen) {
+  ccerr_init(ccerr, NGTCP2_CCERR_TYPE_APPLICATION, error_code, reason,
+             reasonlen);
+}
+
+ngtcp2_ssize ngtcp2_conn_write_connection_close_versioned(
+  ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version,
+  ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, const ngtcp2_ccerr *ccerr,
+  ngtcp2_tstamp ts) {
+  (void)pkt_info_version;
+
+  conn_update_timestamp(conn, ts);
+
+  switch (ccerr->type) {
+  case NGTCP2_CCERR_TYPE_TRANSPORT:
+    return ngtcp2_conn_write_connection_close_pkt(
+      conn, path, pi, dest, destlen, ccerr->error_code, ccerr->reason,
+      ccerr->reasonlen, ts);
+  case NGTCP2_CCERR_TYPE_APPLICATION:
+    return ngtcp2_conn_write_application_close_pkt(
+      conn, path, pi, dest, destlen, ccerr->error_code, ccerr->reason,
+      ccerr->reasonlen, ts);
+  default:
+    return 0;
+  }
+}
+
+int ngtcp2_conn_in_closing_period(ngtcp2_conn *conn) {
+  return conn->state == NGTCP2_CS_CLOSING;
+}
+
+int ngtcp2_conn_in_draining_period(ngtcp2_conn *conn) {
+  return conn->state == NGTCP2_CS_DRAINING;
+}
+
+int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm) {
+  int rv;
+
+  rv = conn_call_stream_close(conn, strm);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = ngtcp2_map_remove(&conn->strms, (ngtcp2_map_key_type)strm->stream_id);
+  if (rv != 0) {
+    assert(rv != NGTCP2_ERR_INVALID_ARGUMENT);
+    return rv;
+  }
+
+  if (ngtcp2_strm_is_tx_queued(strm)) {
+    ngtcp2_pq_remove(&conn->tx.strmq, &strm->pe);
+  }
+
+  ngtcp2_strm_free(strm);
+  ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm);
+
+  return 0;
+}
+
+int ngtcp2_conn_close_stream_if_shut_rdwr(ngtcp2_conn *conn,
+                                          ngtcp2_strm *strm) {
+  if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RDWR) ==
+        NGTCP2_STRM_FLAG_SHUT_RDWR &&
+      ((strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED) ||
+       ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) &&
+      (((strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM) &&
+        (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_ACKED)) ||
+       ngtcp2_strm_is_all_tx_data_fin_acked(strm))) {
+    return ngtcp2_conn_close_stream(conn, strm);
+  }
+  return 0;
+}
+
+/*
+ * conn_shutdown_stream_write closes send stream with error code
+ * |app_error_code|.  RESET_STREAM frame is scheduled.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_shutdown_stream_write(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                                      uint64_t app_error_code) {
+  ngtcp2_strm_set_app_error_code(strm, app_error_code);
+
+  if ((strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM) ||
+      ngtcp2_strm_is_all_tx_data_fin_acked(strm)) {
+    return 0;
+  }
+
+  /* Set this flag so that we don't accidentally send DATA to this
+     stream. */
+  strm->flags |= NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_RESET_STREAM;
+
+  ngtcp2_strm_streamfrq_clear(strm);
+
+  return conn_reset_stream(conn, strm, app_error_code);
+}
+
+/*
+ * conn_shutdown_stream_read closes read stream with error code
+ * |app_error_code|.  STOP_SENDING frame is scheduled.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int conn_shutdown_stream_read(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                                     uint64_t app_error_code) {
+  ngtcp2_strm_set_app_error_code(strm, app_error_code);
+
+  if (strm->flags &
+      (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) {
+    return 0;
+  }
+
+  /* Extend connection flow control window for the amount of data
+     which are not passed to application. */
+  if (!(strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM_RECVED)) {
+    ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset -
+                                          ngtcp2_strm_rx_offset(strm));
+  }
+
+  strm->flags |= NGTCP2_STRM_FLAG_STOP_SENDING;
+
+  ngtcp2_strm_discard_reordered_data(strm);
+
+  return conn_stop_sending(conn, strm, app_error_code);
+}
+
+int ngtcp2_conn_shutdown_stream(ngtcp2_conn *conn, uint32_t flags,
+                                int64_t stream_id, uint64_t app_error_code) {
+  int rv;
+  ngtcp2_strm *strm;
+  (void)flags;
+
+  strm = ngtcp2_conn_find_stream(conn, stream_id);
+  if (strm == NULL) {
+    return 0;
+  }
+
+  if (bidi_stream(stream_id) || !conn_local_stream(conn, stream_id)) {
+    rv = conn_shutdown_stream_read(conn, strm, app_error_code);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (bidi_stream(stream_id) || conn_local_stream(conn, stream_id)) {
+    rv = conn_shutdown_stream_write(conn, strm, app_error_code);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_shutdown_stream_write(ngtcp2_conn *conn, uint32_t flags,
+                                      int64_t stream_id,
+                                      uint64_t app_error_code) {
+  ngtcp2_strm *strm;
+  (void)flags;
+
+  if (!bidi_stream(stream_id) && !conn_local_stream(conn, stream_id)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  strm = ngtcp2_conn_find_stream(conn, stream_id);
+  if (strm == NULL) {
+    return 0;
+  }
+
+  return conn_shutdown_stream_write(conn, strm, app_error_code);
+}
+
+int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, uint32_t flags,
+                                     int64_t stream_id,
+                                     uint64_t app_error_code) {
+  ngtcp2_strm *strm;
+  (void)flags;
+
+  if (!bidi_stream(stream_id) && conn_local_stream(conn, stream_id)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  strm = ngtcp2_conn_find_stream(conn, stream_id);
+  if (strm == NULL) {
+    return 0;
+  }
+
+  return conn_shutdown_stream_read(conn, strm, app_error_code);
+}
+
+/*
+ * conn_extend_max_stream_offset extends stream level flow control
+ * window by |datalen| of the stream denoted by |strm|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+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;
+  } else {
+    strm->rx.unsent_max_offset += datalen;
+  }
+
+  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 = conn_tx_strmq_first_cycle(conn);
+    return ngtcp2_conn_tx_strmq_push(conn, strm);
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn, int64_t stream_id,
+                                         uint64_t datalen) {
+  ngtcp2_strm *strm;
+
+  if (!bidi_stream(stream_id) && conn_local_stream(conn, stream_id)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  strm = ngtcp2_conn_find_stream(conn, stream_id);
+  if (strm == NULL) {
+    return 0;
+  }
+
+  return conn_extend_max_stream_offset(conn, strm, datalen);
+}
+
+void ngtcp2_conn_extend_max_offset(ngtcp2_conn *conn, uint64_t datalen) {
+  if (NGTCP2_MAX_VARINT < datalen ||
+      conn->rx.unsent_max_offset > NGTCP2_MAX_VARINT - datalen) {
+    conn->rx.unsent_max_offset = NGTCP2_MAX_VARINT;
+    return;
+  }
+
+  conn->rx.unsent_max_offset += 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);
+}
+
+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);
+}
+
+const ngtcp2_cid *ngtcp2_conn_get_dcid(ngtcp2_conn *conn) {
+  return &conn->dcid.current.cid;
+}
+
+const ngtcp2_cid *ngtcp2_conn_get_client_initial_dcid(ngtcp2_conn *conn) {
+  return &conn->rcid;
+}
+
+uint32_t ngtcp2_conn_get_client_chosen_version(ngtcp2_conn *conn) {
+  return conn->client_chosen_version;
+}
+
+uint32_t ngtcp2_conn_get_negotiated_version(ngtcp2_conn *conn) {
+  return conn->negotiated_version;
+}
+
+static int delete_strms_pq_each(void *data, void *ptr) {
+  ngtcp2_conn *conn = ptr;
+  ngtcp2_strm *s = data;
+
+  if (ngtcp2_strm_is_tx_queued(s)) {
+    ngtcp2_pq_remove(&conn->tx.strmq, &s->pe);
+  }
+
+  ngtcp2_strm_free(s);
+  ngtcp2_objalloc_strm_release(&conn->strm_objalloc, s);
+
+  return 0;
+}
+
+/*
+ * conn_discard_early_data_state discards any connection states which
+ * are altered by any operations during early data transfer.
+ */
+static void conn_discard_early_data_state(ngtcp2_conn *conn) {
+  ngtcp2_frame_chain **pfrc, *frc;
+
+  ngtcp2_rtb_remove_early_data(&conn->pktns.rtb, &conn->cstat);
+
+  ngtcp2_map_each(&conn->strms, delete_strms_pq_each, conn);
+  ngtcp2_map_clear(&conn->strms);
+
+  conn->tx.offset = 0;
+  conn->tx.last_blocked_offset = UINT64_MAX;
+
+  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->local.transport_params.initial_max_streams_bidi;
+
+  conn->remote.uni.unsent_max_streams = conn->remote.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;
+  }
+
+  for (pfrc = &conn->pktns.tx.frq; *pfrc;) {
+    frc = *pfrc;
+    *pfrc = (*pfrc)->next;
+    ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+  }
+}
+
+int ngtcp2_conn_tls_early_data_rejected(ngtcp2_conn *conn) {
+  if (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) {
+    return 0;
+  }
+
+  conn->flags |= NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED;
+
+  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);
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_get_tls_early_data_rejected(ngtcp2_conn *conn) {
+  return (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) != 0;
+}
+
+int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt,
+                           ngtcp2_duration ack_delay, ngtcp2_tstamp ts) {
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+
+  assert(rtt > 0);
+
+  if (cstat->min_rtt == UINT64_MAX) {
+    cstat->latest_rtt = rtt;
+    cstat->min_rtt = rtt;
+    cstat->smoothed_rtt = rtt;
+    cstat->rttvar = rtt / 2;
+    cstat->first_rtt_sample_ts = ts;
+  } else {
+    if (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) {
+      assert(conn->remote.transport_params);
+
+      ack_delay = ngtcp2_min_uint64(
+        ack_delay, conn->remote.transport_params->max_ack_delay);
+    } else if (ack_delay > 0 && rtt >= cstat->min_rtt &&
+               rtt < cstat->min_rtt + ack_delay) {
+      /* Ignore RTT sample if adjusting ack_delay causes the sample
+         less than min_rtt before handshake confirmation. */
+      ngtcp2_log_info(
+        &conn->log, NGTCP2_LOG_EVENT_LDC,
+        "ignore rtt sample because ack_delay is too large latest_rtt=%" PRIu64
+        " min_rtt=%" PRIu64 " ack_delay=%" PRIu64,
+        rtt / NGTCP2_MILLISECONDS, cstat->min_rtt / NGTCP2_MILLISECONDS,
+        ack_delay / NGTCP2_MILLISECONDS);
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    cstat->latest_rtt = rtt;
+    cstat->min_rtt = ngtcp2_min_uint64(cstat->min_rtt, rtt);
+
+    if (rtt >= cstat->min_rtt + ack_delay) {
+      rtt -= ack_delay;
+    }
+
+    cstat->rttvar = (cstat->rttvar * 3 + (cstat->smoothed_rtt < rtt
+                                            ? rtt - cstat->smoothed_rtt
+                                            : cstat->smoothed_rtt - rtt)) /
+                    4;
+    cstat->smoothed_rtt = (cstat->smoothed_rtt * 7 + rtt) / 8;
+  }
+
+  ngtcp2_log_info(
+    &conn->log, NGTCP2_LOG_EVENT_LDC,
+    "latest_rtt=%" PRIu64 " min_rtt=%" PRIu64 " smoothed_rtt=%" PRIu64
+    " rttvar=%" PRIu64 " ack_delay=%" PRIu64,
+    cstat->latest_rtt / NGTCP2_MILLISECONDS,
+    cstat->min_rtt / NGTCP2_MILLISECONDS,
+    cstat->smoothed_rtt / NGTCP2_MILLISECONDS,
+    cstat->rttvar / NGTCP2_MILLISECONDS, ack_delay / NGTCP2_MILLISECONDS);
+
+  return 0;
+}
+
+void ngtcp2_conn_get_conn_info_versioned(ngtcp2_conn *conn,
+                                         int conn_info_version,
+                                         ngtcp2_conn_info *cinfo) {
+  const ngtcp2_conn_stat *cstat = &conn->cstat;
+  (void)conn_info_version;
+
+  cinfo->latest_rtt = cstat->latest_rtt;
+  cinfo->min_rtt = cstat->min_rtt;
+  cinfo->smoothed_rtt = cstat->smoothed_rtt;
+  cinfo->rttvar = cstat->rttvar;
+  cinfo->cwnd = cstat->cwnd;
+  cinfo->ssthresh = cstat->ssthresh;
+  cinfo->bytes_in_flight = cstat->bytes_in_flight;
+}
+
+static void conn_get_loss_time_and_pktns(ngtcp2_conn *conn,
+                                         ngtcp2_tstamp *ploss_time,
+                                         ngtcp2_pktns **ppktns) {
+  ngtcp2_pktns *const ns[] = {conn->hs_pktns, &conn->pktns};
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  ngtcp2_duration *loss_time = cstat->loss_time + 1;
+  ngtcp2_tstamp earliest_loss_time = cstat->loss_time[NGTCP2_PKTNS_ID_INITIAL];
+  ngtcp2_pktns *pktns = conn->in_pktns;
+  size_t i;
+
+  for (i = 0; i < ngtcp2_arraylen(ns); ++i) {
+    if (ns[i] == NULL || loss_time[i] >= earliest_loss_time) {
+      continue;
+    }
+
+    earliest_loss_time = loss_time[i];
+    pktns = ns[i];
+  }
+
+  if (ploss_time) {
+    *ploss_time = earliest_loss_time;
+  }
+  if (ppktns) {
+    *ppktns = pktns;
+  }
+}
+
+static ngtcp2_tstamp conn_get_earliest_pto_expiry(ngtcp2_conn *conn,
+                                                  ngtcp2_tstamp ts) {
+  ngtcp2_pktns *const ns[] = {conn->in_pktns, conn->hs_pktns, &conn->pktns};
+  size_t i;
+  ngtcp2_tstamp earliest_ts = UINT64_MAX, t;
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  ngtcp2_tstamp *times = cstat->last_tx_pkt_ts;
+  ngtcp2_duration duration =
+    compute_pto(cstat->smoothed_rtt, cstat->rttvar, /* max_ack_delay = */ 0) *
+    (1ULL << cstat->pto_count);
+
+  for (i = NGTCP2_PKTNS_ID_INITIAL; i < NGTCP2_PKTNS_ID_MAX; ++i) {
+    if (ns[i] == NULL || ns[i]->rtb.num_pto_eliciting == 0 ||
+        (times[i] == UINT64_MAX ||
+         (i == NGTCP2_PKTNS_ID_APPLICATION &&
+          !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) {
+      continue;
+    }
+
+    t = times[i] + duration;
+
+    if (i == NGTCP2_PKTNS_ID_APPLICATION) {
+      assert(conn->remote.transport_params);
+      t += conn->remote.transport_params->max_ack_delay *
+           (1ULL << cstat->pto_count);
+    }
+
+    if (t < earliest_ts) {
+      earliest_ts = t;
+    }
+  }
+
+  if (earliest_ts == UINT64_MAX) {
+    return ts + duration;
+  }
+
+  return earliest_ts;
+}
+
+void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  ngtcp2_duration timeout;
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+  ngtcp2_pktns *pktns = &conn->pktns;
+  ngtcp2_tstamp earliest_loss_time;
+
+  conn_get_loss_time_and_pktns(conn, &earliest_loss_time, NULL);
+
+  if (earliest_loss_time != UINT64_MAX) {
+    cstat->loss_detection_timer = earliest_loss_time;
+
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC,
+                    "loss_detection_timer=%" PRIu64 " nonzero crypto loss time",
+                    cstat->loss_detection_timer);
+    return;
+  }
+
+  if ((!in_pktns || in_pktns->rtb.num_pto_eliciting == 0) &&
+      (!hs_pktns || hs_pktns->rtb.num_pto_eliciting == 0) &&
+      (pktns->rtb.num_pto_eliciting == 0 ||
+       !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) &&
+      (conn->server ||
+       (conn->flags & (NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED |
+                       NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) {
+    if (cstat->loss_detection_timer != UINT64_MAX) {
+      ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC,
+                      "loss detection timer canceled");
+      ngtcp2_conn_cancel_loss_detection_timer(conn);
+    }
+    return;
+  }
+
+  cstat->loss_detection_timer = conn_get_earliest_pto_expiry(conn, ts);
+
+  timeout =
+    cstat->loss_detection_timer > ts ? cstat->loss_detection_timer - ts : 0;
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC,
+                  "loss_detection_timer=%" PRIu64 " timeout=%" PRIu64,
+                  cstat->loss_detection_timer, timeout / NGTCP2_MILLISECONDS);
+}
+
+void ngtcp2_conn_cancel_loss_detection_timer(ngtcp2_conn *conn) {
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+
+  cstat->loss_detection_timer = UINT64_MAX;
+  cstat->pto_count = 0;
+}
+
+int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  ngtcp2_conn_stat *cstat = &conn->cstat;
+  int rv;
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+  ngtcp2_tstamp earliest_loss_time;
+  ngtcp2_pktns *loss_pktns = NULL;
+
+  switch (conn->state) {
+  case NGTCP2_CS_CLOSING:
+  case NGTCP2_CS_DRAINING:
+    ngtcp2_conn_cancel_loss_detection_timer(conn);
+    return 0;
+  default:
+    break;
+  }
+
+  if (cstat->loss_detection_timer == UINT64_MAX) {
+    return 0;
+  }
+
+  conn_get_loss_time_and_pktns(conn, &earliest_loss_time, &loss_pktns);
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC,
+                  "loss detection timer fired");
+
+  if (earliest_loss_time != UINT64_MAX) {
+    assert(loss_pktns);
+
+    rv = ngtcp2_conn_detect_lost_pkt(conn, loss_pktns, cstat, ts);
+    if (rv != 0) {
+      return rv;
+    }
+    ngtcp2_conn_set_loss_detection_timer(conn, ts);
+    return 0;
+  }
+
+  if (!conn->server && !conn_is_tls_handshake_completed(conn)) {
+    if (hs_pktns->crypto.tx.ckm) {
+      hs_pktns->rtb.probe_pkt_left = 1;
+    } else {
+      in_pktns->rtb.probe_pkt_left = 1;
+    }
+  } else {
+    if (in_pktns && in_pktns->rtb.num_pto_eliciting) {
+      in_pktns->rtb.probe_pkt_left = 1;
+
+      assert(hs_pktns);
+
+      if (conn->server && hs_pktns->rtb.num_pto_eliciting) {
+        /* let server coalesce packets */
+        hs_pktns->rtb.probe_pkt_left = 1;
+      }
+    } else if (hs_pktns && hs_pktns->rtb.num_pto_eliciting) {
+      hs_pktns->rtb.probe_pkt_left = 2;
+    } else {
+      conn->pktns.rtb.probe_pkt_left = 2;
+    }
+  }
+
+  ++cstat->pto_count;
+
+  ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_LDC, "pto_count=%zu",
+                  cstat->pto_count);
+
+  ngtcp2_conn_set_loss_detection_timer(conn, ts);
+
+  return 0;
+}
+
+static int conn_buffer_crypto_data(ngtcp2_conn *conn, const uint8_t **pdata,
+                                   ngtcp2_pktns *pktns, const uint8_t *data,
+                                   size_t datalen) {
+  int rv;
+  ngtcp2_buf_chain **pbufchain = &pktns->crypto.tx.data;
+
+  if (*pbufchain) {
+    for (; (*pbufchain)->next; pbufchain = &(*pbufchain)->next)
+      ;
+
+    if (ngtcp2_buf_left(&(*pbufchain)->buf) < datalen) {
+      pbufchain = &(*pbufchain)->next;
+    }
+  }
+
+  if (!*pbufchain) {
+    rv = ngtcp2_buf_chain_new(pbufchain, ngtcp2_max_size(1024, datalen),
+                              conn->mem);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  *pdata = (*pbufchain)->buf.last;
+  (*pbufchain)->buf.last = ngtcp2_cpymem((*pbufchain)->buf.last, data, datalen);
+
+  return 0;
+}
+
+int ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn,
+                                   ngtcp2_encryption_level encryption_level,
+                                   const uint8_t *data, const size_t datalen) {
+  ngtcp2_pktns *pktns;
+  ngtcp2_frame_chain *frc;
+  ngtcp2_stream *fr;
+  int rv;
+
+  if (datalen == 0) {
+    return 0;
+  }
+
+  switch (encryption_level) {
+  case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
+    assert(conn->in_pktns);
+    pktns = conn->in_pktns;
+    break;
+  case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
+    assert(conn->hs_pktns);
+    pktns = conn->hs_pktns;
+    break;
+  case NGTCP2_ENCRYPTION_LEVEL_1RTT:
+    pktns = &conn->pktns;
+    break;
+  default:
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  rv = conn_buffer_crypto_data(conn, &data, pktns, data, datalen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = ngtcp2_frame_chain_objalloc_new(&frc, &conn->frc_objalloc);
+  if (rv != 0) {
+    return rv;
+  }
+
+  fr = &frc->fr.stream;
+
+  fr->type = NGTCP2_FRAME_CRYPTO;
+  fr->flags = 0;
+  fr->fin = 0;
+  fr->stream_id = 0;
+  fr->offset = pktns->crypto.tx.offset;
+  fr->datacnt = 1;
+  fr->data[0].len = datalen;
+  fr->data[0].base = (uint8_t *)data;
+
+  rv = ngtcp2_strm_streamfrq_push(&pktns->crypto.strm, frc);
+  if (rv != 0) {
+    ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem);
+    return rv;
+  }
+
+  pktns->crypto.strm.tx.offset += datalen;
+  pktns->crypto.tx.offset += datalen;
+
+  return 0;
+}
+
+int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn, const uint8_t *token,
+                                 size_t tokenlen) {
+  int rv;
+  ngtcp2_frame_chain *nfrc;
+
+  assert(conn->server);
+  assert(token);
+  assert(tokenlen);
+
+  rv = ngtcp2_frame_chain_new_token_objalloc_new(
+    &nfrc, token, tokenlen, &conn->frc_objalloc, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nfrc->next = conn->pktns.tx.frq;
+  conn->pktns.tx.frq = nfrc;
+
+  return 0;
+}
+
+ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn) {
+  assert(!ngtcp2_pq_empty(&conn->tx.strmq));
+  return ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe);
+}
+
+void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn) {
+  ngtcp2_strm *strm = ngtcp2_conn_tx_strmq_top(conn);
+  assert(strm);
+  ngtcp2_pq_pop(&conn->tx.strmq);
+  strm->pe.index = NGTCP2_PQ_BAD_INDEX;
+}
+
+int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm) {
+  return ngtcp2_pq_push(&conn->tx.strmq, &strm->pe);
+}
+
+static int conn_has_uncommitted_preferred_addr_cid(ngtcp2_conn *conn) {
+  return conn->server &&
+         !(conn->flags & NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED) &&
+         conn->oscid.datalen &&
+         conn->local.transport_params.preferred_addr_present;
+}
+
+size_t ngtcp2_conn_get_scid(ngtcp2_conn *conn, ngtcp2_cid *dest) {
+  ngtcp2_cid *origdest = dest;
+  ngtcp2_ksl_it it;
+  ngtcp2_scid *scid;
+
+  if (dest == NULL) {
+    return ngtcp2_ksl_len(&conn->scid.set) +
+           (size_t)conn_has_uncommitted_preferred_addr_cid(conn);
+  }
+
+  for (it = ngtcp2_ksl_begin(&conn->scid.set); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    scid = ngtcp2_ksl_it_get(&it);
+    *dest++ = scid->cid;
+  }
+
+  if (conn_has_uncommitted_preferred_addr_cid(conn)) {
+    *dest++ = conn->local.transport_params.preferred_addr.cid;
+  }
+
+  return (size_t)(dest - origdest);
+}
+
+static size_t conn_get_num_active_dcid(ngtcp2_conn *conn) {
+  size_t n = 1; /* for conn->dcid.current */
+  ngtcp2_pv *pv = conn->pv;
+
+  if (conn->dcid.current.cid.datalen == 0) {
+    return n;
+  }
+
+  if (pv) {
+    if (pv->dcid.seq != conn->dcid.current.seq) {
+      ++n;
+    }
+    if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+        pv->fallback_dcid.seq != conn->dcid.current.seq &&
+        pv->fallback_dcid.seq != pv->dcid.seq) {
+      ++n;
+    }
+  }
+
+  n += ngtcp2_dcidtr_retired_len(&conn->dcid.dtr);
+
+  return n;
+}
+
+static void copy_dcid_to_cid_token(ngtcp2_cid_token *dest,
+                                   const ngtcp2_dcid *src) {
+  dest->seq = src->seq;
+  dest->cid = src->cid;
+  ngtcp2_path_storage_init2(&dest->ps, &src->ps.path);
+  if ((dest->token_present =
+         (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) != 0)) {
+    memcpy(dest->token, src->token, NGTCP2_STATELESS_RESET_TOKENLEN);
+  }
+}
+
+size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, ngtcp2_cid_token *dest) {
+  ngtcp2_pv *pv = conn->pv;
+  ngtcp2_cid_token *orig = dest;
+  ngtcp2_dcid *dcid;
+  size_t len, i;
+
+  if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED)) {
+    return 0;
+  }
+
+  if (dest == NULL) {
+    return conn_get_num_active_dcid(conn);
+  }
+
+  copy_dcid_to_cid_token(dest, &conn->dcid.current);
+  ++dest;
+
+  if (conn->dcid.current.cid.datalen == 0) {
+    return 1;
+  }
+
+  if (pv) {
+    if (pv->dcid.seq != conn->dcid.current.seq) {
+      copy_dcid_to_cid_token(dest, &pv->dcid);
+      ++dest;
+    }
+    if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_PRESENT) &&
+        pv->fallback_dcid.seq != conn->dcid.current.seq &&
+        pv->fallback_dcid.seq != pv->dcid.seq) {
+      copy_dcid_to_cid_token(dest, &pv->fallback_dcid);
+      ++dest;
+    }
+  }
+
+  len = ngtcp2_ringbuf_len(&conn->dcid.dtr.retired.rb);
+  for (i = 0; i < len; ++i) {
+    dcid = ngtcp2_ringbuf_get(&conn->dcid.dtr.retired.rb, i);
+    copy_dcid_to_cid_token(dest, dcid);
+    ++dest;
+  }
+
+  return (size_t)(dest - orig);
+}
+
+void ngtcp2_conn_set_local_addr(ngtcp2_conn *conn, const ngtcp2_addr *addr) {
+  ngtcp2_addr *dest = &conn->dcid.current.ps.path.local;
+
+  assert(addr->addrlen <=
+         (ngtcp2_socklen)sizeof(conn->dcid.current.ps.local_addrbuf));
+  ngtcp2_addr_copy(dest, addr);
+}
+
+void ngtcp2_conn_set_path_user_data(ngtcp2_conn *conn, void *path_user_data) {
+  conn->dcid.current.ps.path.user_data = path_user_data;
+}
+
+const ngtcp2_path *ngtcp2_conn_get_path(ngtcp2_conn *conn) {
+  return &conn->dcid.current.ps.path;
+}
+
+size_t ngtcp2_conn_get_max_tx_udp_payload_size(ngtcp2_conn *conn) {
+  return conn->local.settings.max_tx_udp_payload_size;
+}
+
+size_t ngtcp2_conn_get_path_max_tx_udp_payload_size(ngtcp2_conn *conn) {
+  if (conn->local.settings.no_tx_udp_payload_size_shaping) {
+    return ngtcp2_conn_get_max_tx_udp_payload_size(conn);
+  }
+
+  return conn->dcid.current.max_udp_payload_size;
+}
+
+static int conn_initiate_migration_precheck(ngtcp2_conn *conn,
+                                            const ngtcp2_addr *local_addr) {
+  if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) ||
+      conn->remote.transport_params->disable_active_migration ||
+      conn->dcid.current.cid.datalen == 0 ||
+      (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_PREFERRED_ADDR))) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  if (ngtcp2_dcidtr_unused_empty(&conn->dcid.dtr)) {
+    return NGTCP2_ERR_CONN_ID_BLOCKED;
+  }
+
+  if (ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, local_addr)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  return 0;
+}
+
+int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn,
+                                             const ngtcp2_path *path,
+                                             ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_dcid dcid;
+  ngtcp2_pv *pv;
+  const ngtcp2_path_history_entry *validated_path;
+
+  assert(!conn->server);
+
+  conn_update_timestamp(conn, ts);
+
+  rv = conn_initiate_migration_precheck(conn, &path->local);
+  if (rv != 0) {
+    return rv;
+  }
+
+  ngtcp2_conn_stop_pmtud(conn);
+
+  if (conn->pv) {
+    rv = conn_abort_pv(conn, ts);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  rv = conn_retire_active_dcid(conn, &conn->dcid.current, ts);
+  if (rv != 0) {
+    return rv;
+  }
+
+  ngtcp2_dcidtr_pop_unused(&conn->dcid.dtr, &dcid);
+  ngtcp2_dcid_set_path(&dcid, path);
+
+  if (conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) {
+    ngtcp2_conn_add_path_history(conn, &conn->dcid.current, ts);
+  }
+
+  ngtcp2_dcid_copy(&conn->dcid.current, &dcid);
+
+  conn_reset_congestion_state(conn, ts);
+  conn_reset_ecn_validation_state(conn);
+
+  validated_path = ngtcp2_conn_find_path_history(conn, path, ts);
+  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;
+      }
+    }
+  } else {
+    /* TODO It might be better to add a new flag which indicates that
+       a connection should be closed if this path validation failed.
+       The current design allows an application to continue, by
+       migrating into yet another path. */
+    rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout(conn),
+                       NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem);
+    if (rv != 0) {
+      return rv;
+    }
+
+    conn->pv = pv;
+  }
+
+  return conn_call_activate_dcid(conn, &conn->dcid.current);
+}
+
+int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, const ngtcp2_path *path,
+                                   ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_dcid dcid;
+  ngtcp2_pv *pv;
+
+  assert(!conn->server);
+
+  if (ngtcp2_conn_find_path_history(conn, path, ts)) {
+    return ngtcp2_conn_initiate_immediate_migration(conn, path, ts);
+  }
+
+  conn_update_timestamp(conn, ts);
+
+  rv = conn_initiate_migration_precheck(conn, &path->local);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (conn->pv) {
+    rv = conn_abort_pv(conn, ts);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  ngtcp2_dcidtr_pop_unused(&conn->dcid.dtr, &dcid);
+  ngtcp2_dcid_set_path(&dcid, path);
+
+  rv = ngtcp2_pv_new(&pv, &dcid, conn_compute_pv_timeout(conn),
+                     NGTCP2_PV_FLAG_NONE, &conn->log, conn->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  conn->pv = pv;
+
+  return conn_call_activate_dcid(conn, &pv->dcid);
+}
+
+uint64_t ngtcp2_conn_get_max_data_left(ngtcp2_conn *conn) {
+  return conn->tx.max_offset - conn->tx.offset;
+}
+
+uint64_t ngtcp2_conn_get_max_stream_data_left(ngtcp2_conn *conn,
+                                              int64_t stream_id) {
+  ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id);
+
+  if (strm == NULL) {
+    return 0;
+  }
+
+  return strm->tx.max_offset - strm->tx.offset;
+}
+
+uint64_t ngtcp2_conn_get_streams_bidi_left(ngtcp2_conn *conn) {
+  uint64_t n = ngtcp2_ord_stream_id(conn->local.bidi.next_stream_id);
+
+  return n > conn->local.bidi.max_streams
+           ? 0
+           : conn->local.bidi.max_streams - n + 1;
+}
+
+uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn) {
+  uint64_t n = ngtcp2_ord_stream_id(conn->local.uni.next_stream_id);
+
+  return n > conn->local.uni.max_streams ? 0
+                                         : conn->local.uni.max_streams - n + 1;
+}
+
+uint64_t ngtcp2_conn_get_cwnd_left(ngtcp2_conn *conn) {
+  uint64_t bytes_in_flight = conn->cstat.bytes_in_flight;
+  uint64_t cwnd = conn->cstat.cwnd;
+
+  if (cwnd > bytes_in_flight) {
+    return cwnd - bytes_in_flight;
+  }
+
+  return 0;
+}
+
+ngtcp2_tstamp ngtcp2_conn_get_idle_expiry(ngtcp2_conn *conn) {
+  ngtcp2_duration trpto;
+  ngtcp2_duration idle_timeout;
+
+  /* TODO Remote max_idle_timeout becomes effective after handshake
+     completion. */
+
+  if (!conn_is_tls_handshake_completed(conn) ||
+      conn->remote.transport_params->max_idle_timeout == 0 ||
+      (conn->local.transport_params.max_idle_timeout &&
+       conn->local.transport_params.max_idle_timeout <
+         conn->remote.transport_params->max_idle_timeout)) {
+    idle_timeout = conn->local.transport_params.max_idle_timeout;
+  } else {
+    idle_timeout = conn->remote.transport_params->max_idle_timeout;
+  }
+
+  if (idle_timeout == 0) {
+    return UINT64_MAX;
+  }
+
+  trpto = 3 * conn_compute_pto(conn, conn_is_tls_handshake_completed(conn)
+                                       ? &conn->pktns
+                                       : conn->hs_pktns);
+
+  idle_timeout = ngtcp2_max_uint64(idle_timeout, trpto);
+
+  if (conn->idle_ts >= UINT64_MAX - idle_timeout) {
+    return UINT64_MAX;
+  }
+
+  return conn->idle_ts + idle_timeout;
+}
+
+ngtcp2_duration ngtcp2_conn_get_pto(ngtcp2_conn *conn) {
+  return conn_compute_pto(conn, conn_is_tls_handshake_completed(conn)
+                                  ? &conn->pktns
+                                  : conn->hs_pktns);
+}
+
+void ngtcp2_conn_set_initial_crypto_ctx(ngtcp2_conn *conn,
+                                        const ngtcp2_crypto_ctx *ctx) {
+  assert(conn->in_pktns);
+  conn->in_pktns->crypto.ctx = *ctx;
+}
+
+const ngtcp2_crypto_ctx *ngtcp2_conn_get_initial_crypto_ctx(ngtcp2_conn *conn) {
+  assert(conn->in_pktns);
+  return &conn->in_pktns->crypto.ctx;
+}
+
+void ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn,
+                                const ngtcp2_crypto_aead *aead,
+                                const ngtcp2_crypto_aead_ctx *aead_ctx) {
+  assert(!conn->crypto.retry_aead_ctx.native_handle);
+
+  conn->crypto.retry_aead = *aead;
+  conn->crypto.retry_aead_ctx = *aead_ctx;
+}
+
+void ngtcp2_conn_set_crypto_ctx(ngtcp2_conn *conn,
+                                const ngtcp2_crypto_ctx *ctx) {
+  assert(conn->hs_pktns);
+  conn->hs_pktns->crypto.ctx = *ctx;
+  conn->pktns.crypto.ctx = *ctx;
+}
+
+const ngtcp2_crypto_ctx *ngtcp2_conn_get_crypto_ctx(ngtcp2_conn *conn) {
+  return &conn->pktns.crypto.ctx;
+}
+
+void ngtcp2_conn_set_0rtt_crypto_ctx(ngtcp2_conn *conn,
+                                     const ngtcp2_crypto_ctx *ctx) {
+  conn->early.ctx = *ctx;
+}
+
+const ngtcp2_crypto_ctx *ngtcp2_conn_get_0rtt_crypto_ctx(ngtcp2_conn *conn) {
+  return &conn->early.ctx;
+}
+
+void *ngtcp2_conn_get_tls_native_handle(ngtcp2_conn *conn) {
+  return conn->crypto.tls_native_handle;
+}
+
+void ngtcp2_conn_set_tls_native_handle(ngtcp2_conn *conn,
+                                       void *tls_native_handle) {
+  conn->crypto.tls_native_handle = tls_native_handle;
+}
+
+const ngtcp2_ccerr *ngtcp2_conn_get_ccerr(ngtcp2_conn *conn) {
+  return &conn->rx.ccerr;
+}
+
+void ngtcp2_conn_set_tls_error(ngtcp2_conn *conn, int liberr) {
+  conn->crypto.tls_error = liberr;
+}
+
+int ngtcp2_conn_get_tls_error(ngtcp2_conn *conn) {
+  return conn->crypto.tls_error;
+}
+
+void ngtcp2_conn_set_tls_alert(ngtcp2_conn *conn, uint8_t alert) {
+  conn->crypto.tls_alert = alert;
+}
+
+uint8_t ngtcp2_conn_get_tls_alert(ngtcp2_conn *conn) {
+  return conn->crypto.tls_alert;
+}
+
+int ngtcp2_conn_is_local_stream(ngtcp2_conn *conn, int64_t stream_id) {
+  return conn_local_stream(conn, stream_id);
+}
+
+int ngtcp2_conn_is_server(ngtcp2_conn *conn) { return conn->server; }
+
+int ngtcp2_conn_after_retry(ngtcp2_conn *conn) {
+  return (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) != 0;
+}
+
+int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn, int64_t stream_id,
+                                     void *stream_user_data) {
+  ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id);
+
+  if (strm == NULL) {
+    return NGTCP2_ERR_STREAM_NOT_FOUND;
+  }
+
+  strm->stream_user_data = stream_user_data;
+
+  return 0;
+}
+
+void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, ngtcp2_tstamp ts) {
+  uint64_t pacing_interval_m;
+  ngtcp2_duration wait, d;
+
+  conn_update_timestamp(conn, ts);
+
+  if (conn->tx.pacing.pktlen == 0) {
+    return;
+  }
+
+  if (conn->cstat.pacing_interval_m) {
+    pacing_interval_m = conn->cstat.pacing_interval_m;
+  } else {
+    /* 1.25 is the under-utilization avoidance factor described in
+       https://datatracker.ietf.org/doc/html/rfc9002#section-7.7 */
+    pacing_interval_m = ((conn->cstat.first_rtt_sample_ts == UINT64_MAX
+                            ? NGTCP2_MILLISECONDS
+                            : conn->cstat.smoothed_rtt)
+                         << 10) *
+                        100 / 125 / conn->cstat.cwnd;
+    pacing_interval_m = ngtcp2_max_uint64(pacing_interval_m, 1);
+  }
+
+  wait = (ngtcp2_duration)((conn->tx.pacing.pktlen * pacing_interval_m) >> 10);
+
+  d = ngtcp2_min_uint64(wait / 2, conn->tx.pacing.compensation);
+  wait -= d;
+  conn->tx.pacing.compensation -= d;
+
+  conn->tx.pacing.next_ts = ts + wait;
+  conn->tx.pacing.pktlen = 0;
+}
+
+size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn) {
+  return conn->cstat.send_quantum;
+}
+
+size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, int64_t stream_id) {
+  ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id);
+
+  if (strm == NULL) {
+    return 0;
+  }
+
+  return strm->tx.loss_count;
+}
+
+void ngtcp2_conn_add_path_history(ngtcp2_conn *conn, const ngtcp2_dcid *dcid,
+                                  ngtcp2_tstamp ts) {
+  ngtcp2_path_history_entry *ent;
+
+  ent = ngtcp2_ringbuf_push_front(&conn->path_history.rb);
+  ngtcp2_path_storage_init2(&ent->ps, &dcid->ps.path);
+  ent->max_udp_payload_size = dcid->max_udp_payload_size;
+  ent->ts = ts;
+}
+
+const ngtcp2_path_history_entry *
+ngtcp2_conn_find_path_history(ngtcp2_conn *conn, const ngtcp2_path *path,
+                              ngtcp2_tstamp ts) {
+  ngtcp2_ringbuf *rb = &conn->path_history.rb;
+  size_t i, len = ngtcp2_ringbuf_len(rb);
+  ngtcp2_path_history_entry *ent;
+
+  for (i = 0; i < len; ++i) {
+    ent = ngtcp2_ringbuf_get(rb, i);
+    if (ngtcp2_tstamp_elapsed(ent->ts, 10 * NGTCP2_MINUTES, ts)) {
+      return NULL;
+    }
+
+    if (ngtcp2_path_eq(path, &ent->ps.path)) {
+      return ent;
+    }
+  }
+
+  return NULL;
+}
+
+void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent,
+                                      const ngtcp2_path *path,
+                                      const uint8_t *data) {
+  ngtcp2_path_storage_init2(&pcent->ps, path);
+  memcpy(pcent->data, data, sizeof(pcent->data));
+}
+
+/* The functions prefixed with ngtcp2_pkt_ are usually put inside
+   ngtcp2_pkt.c.  This function uses encryption construct and uses
+   test data defined only in ngtcp2_conn_test.c, so it is written
+   here. */
+ngtcp2_ssize ngtcp2_pkt_write_connection_close(
+  uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid,
+  const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason,
+  size_t reasonlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead,
+  const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv,
+  ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp,
+  const ngtcp2_crypto_cipher_ctx *hp_ctx) {
+  ngtcp2_pkt_hd hd;
+  ngtcp2_crypto_km ckm;
+  ngtcp2_crypto_cc cc;
+  ngtcp2_ppe ppe;
+  ngtcp2_frame fr = {0};
+  int rv;
+
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_INITIAL, dcid,
+                     scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version);
+
+  ngtcp2_vec_init(&ckm.secret, NULL, 0);
+  ngtcp2_vec_init(&ckm.iv, iv, 12);
+  ckm.aead_ctx = *aead_ctx;
+  ckm.pkt_num = 0;
+  ckm.flags = NGTCP2_CRYPTO_KM_FLAG_NONE;
+
+  cc.aead = *aead;
+  cc.hp = *hp;
+  cc.ckm = &ckm;
+  cc.hp_ctx = *hp_ctx;
+  cc.encrypt = encrypt;
+  cc.hp_mask = hp_mask;
+
+  ngtcp2_ppe_init(&ppe, dest, destlen, 0, &cc);
+
+  rv = ngtcp2_ppe_encode_hd(&ppe, &hd);
+  if (rv != 0) {
+    assert(NGTCP2_ERR_NOBUF == rv);
+    return rv;
+  }
+
+  if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  fr.type = NGTCP2_FRAME_CONNECTION_CLOSE;
+  fr.connection_close.error_code = error_code;
+  fr.connection_close.frame_type = 0;
+  fr.connection_close.reasonlen = reasonlen;
+  fr.connection_close.reason = (uint8_t *)reason;
+
+  rv = ngtcp2_ppe_encode_frame(&ppe, &fr);
+  if (rv != 0) {
+    assert(NGTCP2_ERR_NOBUF == rv);
+    return rv;
+  }
+
+  return ngtcp2_ppe_final(&ppe, NULL);
+}
+
+int ngtcp2_is_bidi_stream(int64_t stream_id) { return bidi_stream(stream_id); }
+
+uint32_t ngtcp2_select_version(const uint32_t *preferred_versions,
+                               size_t preferred_versionslen,
+                               const uint32_t *offered_versions,
+                               size_t offered_versionslen) {
+  size_t i, j;
+
+  if (!preferred_versionslen || !offered_versionslen) {
+    return 0;
+  }
+
+  for (i = 0; i < preferred_versionslen; ++i) {
+    assert(ngtcp2_is_supported_version(preferred_versions[i]));
+
+    for (j = 0; j < offered_versionslen; ++j) {
+      if (preferred_versions[i] == offered_versions[j]) {
+        return preferred_versions[i];
+      }
+    }
+  }
+
+  return 0;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_conn.h b/third_party/ngtcp2/lib/ngtcp2_conn.h
new file mode 100644 (file)
index 0000000..5979d39
--- /dev/null
@@ -0,0 +1,1121 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CONN_H
+#define NGTCP2_CONN_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+#include "ngtcp2_crypto.h"
+#include "ngtcp2_acktr.h"
+#include "ngtcp2_rtb.h"
+#include "ngtcp2_strm.h"
+#include "ngtcp2_idtr.h"
+#include "ngtcp2_str.h"
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_log.h"
+#include "ngtcp2_pq.h"
+#include "ngtcp2_cc.h"
+#include "ngtcp2_bbr.h"
+#include "ngtcp2_pv.h"
+#include "ngtcp2_pmtud.h"
+#include "ngtcp2_cid.h"
+#include "ngtcp2_buf.h"
+#include "ngtcp2_ppe.h"
+#include "ngtcp2_qlog.h"
+#include "ngtcp2_rst.h"
+#include "ngtcp2_conn_stat.h"
+#include "ngtcp2_dcidtr.h"
+
+typedef enum {
+  /* Client specific handshake states */
+  NGTCP2_CS_CLIENT_INITIAL,
+  NGTCP2_CS_CLIENT_WAIT_HANDSHAKE,
+  /* Server specific handshake states */
+  NGTCP2_CS_SERVER_INITIAL,
+  NGTCP2_CS_SERVER_WAIT_HANDSHAKE,
+  /* Shared by both client and server */
+  NGTCP2_CS_POST_HANDSHAKE,
+  NGTCP2_CS_CLOSING,
+  NGTCP2_CS_DRAINING,
+} ngtcp2_conn_state;
+
+/* NGTCP2_MAX_NUM_BUFFED_RX_PKTS is the maximum number of buffered
+   reordered packets. */
+#define NGTCP2_MAX_NUM_BUFFED_RX_PKTS 4
+
+/* NGTCP2_MAX_REORDERED_CRYPTO_DATA is the maximum offset of crypto
+   data which is not continuous.  In other words, there is a gap of
+   unreceived data. */
+#define NGTCP2_MAX_REORDERED_CRYPTO_DATA 65536
+
+/* NGTCP2_MAX_RETRIES is the number of Retry packet which client can
+   accept. */
+#define NGTCP2_MAX_RETRIES 3
+
+/* NGTCP2_MAX_SCID_POOL_SIZE is the maximum number of source
+   connection ID the local endpoint provides to the remote endpoint.
+   The chosen value was described in old draft.  Now a remote endpoint
+   tells the maximum value.  The value can be quite large, and we have
+   to put the sane limit.*/
+#define NGTCP2_MAX_SCID_POOL_SIZE 8
+
+/* NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS is the maximum number of ECN marked
+   packets sent in NGTCP2_ECN_STATE_TESTING period. */
+#define NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS 10
+
+/* NGTCP2_CCERR_MAX_REASONLEN is the maximum length of reason phrase
+   to remember.  If the received reason phrase is longer than this
+   value, it is truncated. */
+#define NGTCP2_CCERR_MAX_REASONLEN 1024
+
+/* 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
+   than Initial packet should be padded so that UDP datagram payload
+   is at least NGTCP2_MAX_UDP_PAYLOAD_SIZE bytes.  Initial packet
+   might be padded based on QUIC requirement regardless of this
+   flag. */
+#define NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING 0x01u
+/* NGTCP2_WRITE_PKT_FLAG_MORE indicates that more frames might come
+   and it should be encoded into the current packet. */
+#define NGTCP2_WRITE_PKT_FLAG_MORE 0x02u
+/* NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL is just like
+   NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING, but it requests to add
+   padding to the full UDP datagram payload size. */
+#define NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING_FULL 0x04u
+/* NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY adds padding to the QUIC
+   packet as much as possible if the packet is not empty. */
+#define NGTCP2_WRITE_PKT_FLAG_PADDING_IF_NOT_EMPTY 0x08u
+
+/*
+ * ngtcp2_max_frame is defined so that it covers the largest ACK
+ * frame.
+ */
+typedef union ngtcp2_max_frame {
+  ngtcp2_frame fr;
+  struct {
+    ngtcp2_ack ack;
+    /* ack includes 1 ngtcp2_ack_range. */
+    ngtcp2_ack_range ranges[NGTCP2_MAX_ACK_RANGES - 1];
+  } ackfr;
+} ngtcp2_max_frame;
+
+typedef struct ngtcp2_path_challenge_entry {
+  ngtcp2_path_storage ps;
+  uint8_t data[8];
+} ngtcp2_path_challenge_entry;
+
+void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent,
+                                      const ngtcp2_path *path,
+                                      const uint8_t *data);
+
+/* NGTCP2_CONN_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_CONN_FLAG_NONE 0x00u
+/* NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED is set when TLS stack
+   declares that TLS handshake has completed.  The condition of this
+   declaration varies between TLS implementations and this flag does
+   not indicate the completion of QUIC handshake.  Some
+   implementations declare TLS handshake completion as server when
+   they write off Server Finished and before deriving application rx
+   secret. */
+#define NGTCP2_CONN_FLAG_TLS_HANDSHAKE_COMPLETED 0x01u
+/* NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED is set when the first
+   Initial packet has successfully been processed. */
+#define NGTCP2_CONN_FLAG_INITIAL_PKT_PROCESSED 0x02u
+/* NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED is set if transport
+   parameters are received. */
+#define NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED 0x04u
+/* NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED is set when a
+   local transport parameters are applied. */
+#define NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED 0x08u
+/* NGTCP2_CONN_FLAG_RECV_RETRY is set when a client receives Retry
+   packet. */
+#define NGTCP2_CONN_FLAG_RECV_RETRY 0x10u
+/* NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED is set when 0-RTT packet is
+   rejected by a peer. */
+#define NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED 0x20u
+/* NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED is set when the expired
+   keep-alive timer has been cancelled. */
+#define NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED 0x40u
+/* NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED is set when an endpoint
+   confirmed completion of handshake. */
+#define NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED 0x80u
+/* NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED is set when the library
+   transitions its state to "post handshake". */
+#define NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED 0x0100u
+/* NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT is set when the early
+   handshake retransmission has done when server receives overlapping
+   Initial crypto data. */
+#define NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT 0x0200u
+/* NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT indicates that the local endpoint
+   sends a QUIC packet without Fixed Bit set if a remote endpoint
+   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. */
+#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
+   ngtcp2_ppe is stored in pkt struct of ngtcp2_conn. */
+#define NGTCP2_CONN_FLAG_PPE_PENDING 0x1000u
+/* NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE is set when idle timer
+   should be restarted on next write. */
+#define NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE 0x2000u
+/* NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED indicates that server as peer
+   verified client address.  This flag is only used by client. */
+#define NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED 0x4000u
+/* NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED indicates that an early key is
+   installed.  conn->early.ckm cannot be used for this purpose because
+   it might be discarded when a certain condition is met. */
+#define NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED 0x8000u
+/* NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR is set when the local
+   endpoint has initiated key update. */
+#define NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR 0x10000u
+
+typedef struct ngtcp2_pktns {
+  struct {
+    /* last_pkt_num is the packet number which the local endpoint sent
+       last time.*/
+    int64_t last_pkt_num;
+    struct {
+      /* next_pkt_num is the next packet number to skip. */
+      int64_t next_pkt_num;
+      /* exponent makes gap of skipping packets spread
+         exponentially. */
+      int64_t exponent;
+    } skip_pkt;
+    ngtcp2_frame_chain *frq;
+    /* non_ack_pkt_start_ts is the timestamp since the local endpoint
+       starts sending continuous non ACK-eliciting packets. */
+    ngtcp2_tstamp non_ack_pkt_start_ts;
+
+    struct {
+      /* ect0 is the number of QUIC packets, not UDP datagram, which
+         are sent in UDP datagram with ECT0 marking. */
+      size_t ect0;
+      /* start_pkt_num is the lowest packet number that are sent
+         during ECN validation period. */
+      int64_t start_pkt_num;
+      /* validation_pkt_sent is the number of QUIC packets sent during
+         validation period. */
+      size_t validation_pkt_sent;
+      /* validation_pkt_lost is the number of QUIC packets lost during
+         validation period. */
+      size_t validation_pkt_lost;
+    } ecn;
+  } tx;
+
+  struct {
+    /* pngap tracks received packet number in order to suppress
+       duplicated packet number. */
+    ngtcp2_gaptr pngap;
+    /* max_ack_eliciting_pkt_num is the largest ack-eliciting packet
+       number received so far. */
+    int64_t max_ack_eliciting_pkt_num;
+    /*
+     * buffed_pkts is buffered packets which cannot be decrypted with
+     * the current encryption level.
+     *
+     * In server Initial encryption level, 0-RTT packet may be buffered.
+     * In server Handshake encryption level, Short packet may be buffered.
+     *
+     * In client Initial encryption level, Handshake or Short packet may
+     * be buffered.  In client Handshake encryption level, Short packet
+     * may be buffered.
+     *
+     * - 0-RTT packet is only buffered in server Initial encryption
+     *   level ngtcp2_pktns.
+     *
+     * - Handshake packet is only buffered in client Handshake
+     *   encryption level ngtcp2_pktns.
+     *
+     * - Short packet is only buffered in Short encryption level
+     *   ngtcp2_pktns.
+     */
+    ngtcp2_pkt_chain *buffed_pkts;
+  } rx;
+
+  struct {
+    struct {
+      /* offset is the offset of crypto stream in this packet number
+         space. */
+      uint64_t offset;
+      /* ckm is a cryptographic key, and iv to encrypt outgoing
+         packets. */
+      ngtcp2_crypto_km *ckm;
+      /* hp_ctx is cipher context for packet header protection. */
+      ngtcp2_crypto_cipher_ctx hp_ctx;
+      /* data is the submitted crypto data. */
+      ngtcp2_buf_chain *data;
+    } tx;
+
+    struct {
+      /* ckm is a cryptographic key, and iv to decrypt incoming
+         packets. */
+      ngtcp2_crypto_km *ckm;
+      /* hp_ctx is cipher context for packet header protection. */
+      ngtcp2_crypto_cipher_ctx hp_ctx;
+    } rx;
+
+    ngtcp2_strm strm;
+    ngtcp2_crypto_ctx ctx;
+  } crypto;
+
+  ngtcp2_acktr acktr;
+  ngtcp2_rtb rtb;
+  ngtcp2_pktns_id id;
+} ngtcp2_pktns;
+
+typedef enum ngtcp2_ecn_state {
+  NGTCP2_ECN_STATE_TESTING,
+  NGTCP2_ECN_STATE_UNKNOWN,
+  NGTCP2_ECN_STATE_FAILED,
+  NGTCP2_ECN_STATE_CAPABLE,
+} ngtcp2_ecn_state;
+
+/* ngtcp2_early_transport_params is the values remembered by client
+   from the previous session. */
+typedef struct ngtcp2_early_transport_params {
+  uint64_t initial_max_streams_bidi;
+  uint64_t initial_max_streams_uni;
+  uint64_t initial_max_stream_data_bidi_local;
+  uint64_t initial_max_stream_data_bidi_remote;
+  uint64_t initial_max_stream_data_uni;
+  uint64_t initial_max_data;
+  uint64_t active_connection_id_limit;
+  uint64_t max_datagram_frame_size;
+} ngtcp2_early_transport_params;
+
+ngtcp2_static_ringbuf_def(path_challenge, 4,
+                          sizeof(ngtcp2_path_challenge_entry))
+
+ngtcp2_static_ringbuf_def(path_history, 4, sizeof(ngtcp2_path_history_entry))
+
+ngtcp2_objalloc_decl(strm, ngtcp2_strm, oplent)
+
+struct ngtcp2_conn {
+  ngtcp2_objalloc frc_objalloc;
+  ngtcp2_objalloc rtb_entry_objalloc;
+  ngtcp2_objalloc strm_objalloc;
+  ngtcp2_conn_state state;
+  ngtcp2_callbacks callbacks;
+  /* rcid is a connection ID present in Initial or 0-RTT packet from
+     client as destination connection ID.  Server uses this field to
+     check that duplicated Initial or 0-RTT packet are indeed sent to
+     this connection.  Client uses this field to validate
+     original_destination_connection_id transport parameter. */
+  ngtcp2_cid rcid;
+  /* oscid is the source connection ID initially used by the local
+     endpoint. */
+  ngtcp2_cid oscid;
+  /* retry_scid is the source connection ID from Retry packet.  Client
+     records it in order to verify retry_source_connection_id
+     transport parameter.  Server does not use this field. */
+  ngtcp2_cid retry_scid;
+  /* hs_local_addr is a local address used during handshake. */
+  ngtcp2_sockaddr_union hs_local_addr;
+  ngtcp2_pktns *in_pktns;
+  ngtcp2_pktns *hs_pktns;
+  ngtcp2_pktns pktns;
+
+  struct {
+    /* current is the current destination connection ID. */
+    ngtcp2_dcid current;
+    ngtcp2_dcidtr dtr;
+    /* seqgap tracks received sequence numbers in order to ignore
+       retransmitted duplicated NEW_CONNECTION_ID frame. */
+    ngtcp2_gaptr seqgap;
+    /* retire_prior_to is the largest retire_prior_to received so
+       far. */
+    uint64_t retire_prior_to;
+  } dcid;
+
+  struct {
+    /* set is a set of CID sent to peer.  The peer can use any CIDs in
+       this set.  This includes used CID as well as unused ones. */
+    ngtcp2_ksl set;
+    /* used is a set of CID used by peer.  The sort function of this
+       priority queue takes timestamp when CID is retired and sorts
+       them in ascending order. */
+    ngtcp2_pq used;
+    /* last_seq is the last sequence number of connection ID. */
+    uint64_t last_seq;
+    /* num_retired is the number of retired Connection ID still
+       included in set. */
+    size_t num_retired;
+    /* num_in_flight is the number of NEW_CONNECTION_ID frames that
+       are in-flight and not acknowledged yet. */
+    size_t num_in_flight;
+  } scid;
+
+  struct {
+    /* strmq contains ngtcp2_strm which has frames to send. */
+    ngtcp2_pq strmq;
+    /* offset is the offset the local endpoint has sent to the remote
+       endpoint. */
+    uint64_t offset;
+    /* max_offset is the maximum offset that local endpoint can
+       send. */
+    uint64_t max_offset;
+    /* last_blocked_offset is the largest offset where the
+       transmission of stream data is blocked. */
+    uint64_t last_blocked_offset;
+    /* last_max_data_ts is the timestamp when last MAX_DATA frame is
+       sent. */
+    ngtcp2_tstamp last_max_data_ts;
+
+    struct {
+      /* state is the state of ECN validation */
+      ngtcp2_ecn_state state;
+      /* validation_start_ts is the timestamp when ECN validation is
+         started.  It is UINT64_MAX if it has not started yet. */
+      ngtcp2_tstamp validation_start_ts;
+      /* dgram_sent is the number of UDP datagram sent during ECN
+         validation period. */
+      size_t dgram_sent;
+    } ecn;
+
+    struct {
+      /* pktlen is the number of bytes written before calling
+         ngtcp2_conn_update_pkt_tx_time which resets this field to
+         0. */
+      size_t pktlen;
+      /* next_ts is the time to send next packet.  It is UINT64_MAX if
+         packet pacing is disabled or expired.*/
+      ngtcp2_tstamp next_ts;
+      /* compensation is the amount of time that a local endpoint
+         waits too long for pacing.  This happens because there is an
+         overhead before start writing packets after pacing timer
+         expires.  If multiple QUIC connections are handled by a
+         single thread, which is typical use case for event loop based
+         servers, each processing of QUIC connection adds overhead,
+         for example, TLS handshake, and packet encryption/decryption,
+         etc. */
+      ngtcp2_duration compensation;
+    } pacing;
+  } tx;
+
+  struct {
+    /* unsent_max_offset is the maximum offset that remote endpoint
+       can send without extending MAX_DATA.  This limit is not yet
+       notified to the remote endpoint. */
+    uint64_t unsent_max_offset;
+    /* offset is the cumulative sum of stream data received for this
+       connection. */
+    uint64_t offset;
+    /* max_offset is the maximum offset that remote endpoint can
+       send. */
+    uint64_t max_offset;
+    /* window is the connection-level flow control window size. */
+    uint64_t window;
+    /* path_challenge stores received PATH_CHALLENGE data. */
+    ngtcp2_static_ringbuf_path_challenge path_challenge;
+    /* ccerr is the received connection close error. */
+    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;
+  } rx;
+
+  struct {
+    ngtcp2_crypto_km *ckm;
+    ngtcp2_crypto_cipher_ctx hp_ctx;
+    ngtcp2_crypto_ctx ctx;
+    /* discard_started_ts is the timestamp when the timer to discard
+       early key has started.  Used by server only. */
+    ngtcp2_tstamp discard_started_ts;
+    /* transport_params is the values remembered by client from the
+       previous session.  These are set by
+       ngtcp2_conn_set_early_remote_transport_params().  Server does
+       not use this field.  Server must not set values for these
+       parameters that are smaller than the remembered values. */
+    ngtcp2_early_transport_params transport_params;
+  } early;
+
+  struct {
+    ngtcp2_settings settings;
+    /* 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 {
+    /* transport_params is the received transport parameters during
+       handshake.  It is used for Short packet only. */
+    ngtcp2_transport_params *transport_params;
+    /* pending_transport_params is received transport parameters
+       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 {
+    struct {
+      /* new_tx_ckm is a new sender 1RTT key which has not been
+         used. */
+      ngtcp2_crypto_km *new_tx_ckm;
+      /* new_rx_ckm is a new receiver 1RTT key which has not
+         successfully decrypted incoming packet yet. */
+      ngtcp2_crypto_km *new_rx_ckm;
+      /* old_rx_ckm is an old receiver 1RTT key. */
+      ngtcp2_crypto_km *old_rx_ckm;
+      /* confirmed_ts is the time instant when the key update is
+         confirmed by the local endpoint last time.  UINT64_MAX means
+         undefined value. */
+      ngtcp2_tstamp confirmed_ts;
+    } key_update;
+
+    /* tls_native_handle is a native handle to TLS session object. */
+    void *tls_native_handle;
+    /* decrypt_hp_buf is a buffer which is used to write unprotected
+       packet header. */
+    ngtcp2_vec decrypt_hp_buf;
+    /* decrypt_buf is a buffer which is used to write decrypted data. */
+    ngtcp2_vec decrypt_buf;
+    /* retry_aead is AEAD to verify Retry packet integrity.  It is
+       used by client only. */
+    ngtcp2_crypto_aead retry_aead;
+    /* retry_aead_ctx is AEAD cipher context to verify Retry packet
+       integrity.  It is used by client only. */
+    ngtcp2_crypto_aead_ctx retry_aead_ctx;
+    /* tls_error is TLS related error. */
+    int tls_error;
+    /* tls_alert is TLS alert generated by the local endpoint. */
+    uint8_t tls_alert;
+    /* decryption_failure_count is the number of received packets that
+       fail authentication. */
+    uint64_t decryption_failure_count;
+  } crypto;
+
+  /* pkt contains the packet intermediate construction data to support
+     NGTCP2_WRITE_STREAM_FLAG_MORE */
+  struct {
+    ngtcp2_crypto_cc cc;
+    ngtcp2_pkt_hd hd;
+    ngtcp2_ppe ppe;
+    ngtcp2_frame_chain **pfrc;
+    int pkt_empty;
+    int hd_logged;
+    /* flags is bitwise OR of zero or more of
+       NGTCP2_RTB_ENTRY_FLAG_*. */
+    uint16_t rtb_entry_flags;
+    ngtcp2_ssize hs_spktlen;
+    int require_padding;
+  } pkt;
+
+  struct {
+    /* last_ts is a timestamp when a last packet is sent or received
+       on a current path. */
+    ngtcp2_tstamp last_ts;
+    /* timeout is keep-alive timeout.  When it expires, a packet
+       should be sent to a current path to keep connection alive.  It
+       might be used to keep NAT binding intact.  If 0 is set,
+       keep-alive timer is disabled. */
+    ngtcp2_duration timeout;
+  } keep_alive;
+
+  struct {
+    /* Initial keys for negotiated version.  If original version ==
+       negotiated version, these fields are not used. */
+    struct {
+      ngtcp2_crypto_km *ckm;
+      ngtcp2_crypto_cipher_ctx hp_ctx;
+    } rx;
+    struct {
+      ngtcp2_crypto_km *ckm;
+      ngtcp2_crypto_cipher_ctx hp_ctx;
+    } tx;
+    /* version is QUIC version that the above Initial keys are created
+       for. */
+    uint32_t version;
+    /* preferred_versions is the array of versions that are preferred
+       by the local endpoint.  Server negotiates one of those versions
+       in this array if a client initially selects a less preferred
+       version.  Client uses this field and original_version field to
+       prevent version downgrade attack if it reacted upon Version
+       Negotiation packet. */
+    uint32_t *preferred_versions;
+    /* preferred_versionslen is the number of versions stored in the
+       array pointed by preferred_versions.  This field is only used
+       by server. */
+    size_t preferred_versionslen;
+    /* available_versions is the versions that the local endpoint
+       sends in version_information transport parameter.  This is the
+       wire image of available_versions field of version_information
+       transport parameter. */
+    uint8_t *available_versions;
+    /* available_versionslen is the length of data pointed by
+       available_versions field. */
+    size_t available_versionslen;
+  } vneg;
+
+  ngtcp2_map strms;
+  ngtcp2_conn_stat cstat;
+  ngtcp2_pv *pv;
+  ngtcp2_pmtud *pmtud;
+  ngtcp2_log log;
+  ngtcp2_qlog qlog;
+  ngtcp2_rst rst;
+  ngtcp2_cc_algo cc_algo;
+  union {
+    ngtcp2_cc cc;
+    ngtcp2_cc_reno reno;
+    ngtcp2_cc_cubic cubic;
+    ngtcp2_cc_bbr bbr;
+  };
+  /* path_history remembers the paths that have been validated
+     successfully.  The path is added to this history when a local
+     endpoint migrates to the another path. */
+  ngtcp2_static_ringbuf_path_history path_history;
+  const ngtcp2_mem *mem;
+  /* idle_ts is the time instant when idle timer started. */
+  ngtcp2_tstamp idle_ts;
+  /* handshake_confirmed_ts is the time instant when handshake is
+     confirmed.  For server, it is confirmed when completed. */
+  ngtcp2_tstamp handshake_confirmed_ts;
+  void *user_data;
+  uint32_t client_chosen_version;
+  uint32_t negotiated_version;
+  /* flags is bitwise OR of zero or more of NGTCP2_CONN_FLAG_*. */
+  uint32_t flags;
+  int server;
+};
+
+typedef enum ngtcp2_vmsg_type {
+  NGTCP2_VMSG_TYPE_STREAM,
+  NGTCP2_VMSG_TYPE_DATAGRAM,
+} ngtcp2_vmsg_type;
+
+typedef struct ngtcp2_vmsg_stream {
+  /* strm is a stream that data is sent to. */
+  ngtcp2_strm *strm;
+  /* flags is bitwise OR of zero or more of
+     NGTCP2_WRITE_STREAM_FLAG_*. */
+  uint32_t flags;
+  /* data is the pointer to ngtcp2_vec array which contains the stream
+     data to send. */
+  const ngtcp2_vec *data;
+  /* datacnt is the number of ngtcp2_vec pointed by data. */
+  size_t datacnt;
+  /* pdatalen is the pointer to the variable which the number of bytes
+     written is assigned to if pdatalen is not NULL. */
+  ngtcp2_ssize *pdatalen;
+} ngtcp2_vmsg_stream;
+
+typedef struct ngtcp2_vmsg_datagram {
+  /* data is the pointer to ngtcp2_vec array which contains the data
+     to send. */
+  const ngtcp2_vec *data;
+  /* datacnt is the number of ngtcp2_vec pointed by data. */
+  size_t datacnt;
+  /* dgram_id is an opaque identifier chosen by an application. */
+  uint64_t dgram_id;
+  /* flags is bitwise OR of zero or more of
+     NGTCP2_WRITE_DATAGRAM_FLAG_*. */
+  uint32_t flags;
+  /* paccepted is the pointer to the variable which, if it is not
+     NULL, is assigned nonzero if data is written to a packet. */
+  int *paccepted;
+} ngtcp2_vmsg_datagram;
+
+typedef struct ngtcp2_vmsg {
+  ngtcp2_vmsg_type type;
+  union {
+    ngtcp2_vmsg_stream stream;
+    ngtcp2_vmsg_datagram datagram;
+  };
+} ngtcp2_vmsg;
+
+/*
+ * ngtcp2_conn_find_stream returns a stream whose stream ID is
+ * |stream_id|.  If no such stream is found, it returns NULL.
+ */
+ngtcp2_strm *ngtcp2_conn_find_stream(ngtcp2_conn *conn, int64_t stream_id);
+
+/*
+ * conn_init_stream initializes |strm|.  Its stream ID is |stream_id|.
+ * This function adds |strm| to conn->strms.  |strm| must be allocated
+ * by the caller.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-callback function failed.
+ */
+int ngtcp2_conn_init_stream(ngtcp2_conn *conn, ngtcp2_strm *strm,
+                            int64_t stream_id, void *stream_user_data);
+
+/*
+ * ngtcp2_conn_close_stream closes stream |strm|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Stream is not found.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_conn_close_stream closes stream |strm| if no further
+ * transmission and reception are allowed, and all reordered incoming
+ * data are emitted to the application, and the transmitted data are
+ * acked.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Stream is not found.
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+int ngtcp2_conn_close_stream_if_shut_rdwr(ngtcp2_conn *conn, ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_conn_update_rtt updates RTT measurements.  |rtt| is a latest
+ * RTT which is not adjusted by ack delay.  |ack_delay| is unscaled
+ * ack_delay included in ACK frame.  |ack_delay| is actually tainted
+ * (sent by peer), so don't assume that |ack_delay| is always smaller
+ * than, or equals to |rtt|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     RTT sample is ignored.
+ */
+int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt,
+                           ngtcp2_duration ack_delay, ngtcp2_tstamp ts);
+
+void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts);
+
+void ngtcp2_conn_cancel_loss_detection_timer(ngtcp2_conn *conn);
+
+int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_conn_detect_lost_pkt detects lost packets.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                                ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_conn_tx_strmq_top returns the ngtcp2_strm which sits on the
+ * top of queue.  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.
+ */
+void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn);
+
+/*
+ * ngtcp2_conn_tx_strmq_push pushes |strm| into tx_strmq.
+ *
+ *  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(ngtcp2_conn *conn, ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_conn_internal_expiry returns the minimum expiry time among
+ * all timers in |conn|.
+ */
+ngtcp2_tstamp ngtcp2_conn_internal_expiry(ngtcp2_conn *conn);
+
+ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path,
+                                    int pkt_info_version, ngtcp2_pkt_info *pi,
+                                    uint8_t *dest, size_t destlen,
+                                    uint8_t wflags, ngtcp2_vmsg *vmsg,
+                                    ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_conn_write_single_frame_pkt writes a packet which contains
+ * |fr| frame only in the buffer pointed by |dest| whose length if
+ * |destlen|.  |type| is a long packet type to send.  If |type| is 0,
+ * Short packet is used.  |dcid| is used as a destination connection
+ * ID.  |flags| is zero or more of NGTCP2_WRITE_PKT_FLAG_*.  Only
+ * NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING is recognized.
+ *
+ * The packet written by this function will not be retransmitted.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt(
+  ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen,
+  uint8_t type, uint8_t flags, const ngtcp2_cid *dcid, ngtcp2_frame *fr,
+  uint16_t rtb_entry_flags, const ngtcp2_path *path, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_conn_commit_local_transport_params commits the local
+ * transport parameters, which is currently set to
+ * conn->local.settings.transport_params.  This function will do some
+ * amends on transport parameters for adjusting default values.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     CID in preferred address equals to the original SCID.
+ */
+int ngtcp2_conn_commit_local_transport_params(ngtcp2_conn *conn);
+
+/*
+ * ngtcp2_conn_lost_pkt_expiry returns the earliest expiry time of
+ * lost packet.
+ */
+ngtcp2_tstamp ngtcp2_conn_lost_pkt_expiry(ngtcp2_conn *conn);
+
+/*
+ * ngtcp2_conn_remove_lost_pkt removes the expired lost packet.
+ */
+void ngtcp2_conn_remove_lost_pkt(ngtcp2_conn *conn, ngtcp2_tstamp ts);
+
+uint64_t ngtcp2_conn_tx_strmq_first_cycle(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_ack_delay_expiry` returns the expiry time point of
+ * delayed protected ACK.  One should call
+ * `ngtcp2_conn_cancel_expired_ack_delay_timer` and
+ * `ngtcp2_conn_write_pkt` (or `ngtcp2_conn_writev_stream`) when it
+ * expires.  It returns UINT64_MAX if there is no expiry.
+ */
+ngtcp2_tstamp ngtcp2_conn_ack_delay_expiry(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_cancel_expired_ack_delay_timer` stops expired ACK
+ * delay timer.  |ts| is the current time.  This function must be
+ * called when `ngtcp2_conn_ack_delay_expiry` <= ts.
+ */
+void ngtcp2_conn_cancel_expired_ack_delay_timer(ngtcp2_conn *conn,
+                                                ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_loss_detection_expiry` returns the expiry time point
+ * of loss detection timer.  One should call
+ * `ngtcp2_conn_on_loss_detection_timer` and `ngtcp2_conn_write_pkt`
+ * (or `ngtcp2_conn_writev_stream`) when it expires.  It returns
+ * UINT64_MAX if loss detection timer is not armed.
+ */
+ngtcp2_tstamp ngtcp2_conn_loss_detection_expiry(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_get_idle_expiry` returns the time when a connection
+ * should be closed if it continues to be idle.  If idle timeout is
+ * disabled, this function returns ``UINT64_MAX``.
+ */
+ngtcp2_tstamp ngtcp2_conn_get_idle_expiry(ngtcp2_conn *conn);
+
+ngtcp2_duration ngtcp2_conn_compute_pto(ngtcp2_conn *conn, ngtcp2_pktns *pktns);
+
+/*
+ * ngtcp2_conn_track_retired_dcid_seq tracks the sequence number |seq|
+ * of unacknowledged retiring Destination Connection ID.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
+ */
+int ngtcp2_conn_track_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq);
+
+/*
+ * ngtcp2_conn_untrack_retired_dcid_seq deletes the sequence number
+ * |seq| of unacknowledged retiring Destination Connection ID.  It is
+ * fine if such sequence number is not found.
+ */
+void ngtcp2_conn_untrack_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq);
+
+/*
+ * ngtcp2_conn_check_retired_dcid_tracked returns nonzero if |seq| has
+ * already been tracked.
+ */
+int ngtcp2_conn_check_retired_dcid_tracked(ngtcp2_conn *conn, uint64_t seq);
+
+/*
+ * ngtcp2_conn_server_negotiate_version negotiates QUIC version.  It
+ * is compatible version negotiation.  It returns the negotiated QUIC
+ * version.  This function must not be called by client.
+ */
+uint32_t
+ngtcp2_conn_server_negotiate_version(ngtcp2_conn *conn,
+                                     const ngtcp2_version_info *version_info);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_write_connection_close_pkt` writes a packet which
+ * contains a CONNECTION_CLOSE frame (type 0x1c) in the buffer pointed
+ * by |dest| whose capacity is |datalen|.
+ *
+ * If |path| is not ``NULL``, this function stores the network path
+ * with which the packet should be sent.  Each addr field must point
+ * to the buffer which should be at least ``sizeof(struct
+ * sockaddr_storage)`` bytes long.  The assignment might not be done
+ * if nothing is written to |dest|.
+ *
+ * If |pi| is not ``NULL``, this function stores packet metadata in it
+ * if it succeeds.  The metadata includes ECN markings.
+ *
+ * This function must not be called from inside the callback
+ * functions.
+ *
+ * At the moment, successful call to this function makes connection
+ * close.  We may change this behaviour in the future to allow
+ * graceful shutdown.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     The current state does not allow sending CONNECTION_CLOSE.
+ * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED`
+ *     Packet number is exhausted, and cannot send any more packet.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     User callback failed
+ */
+ngtcp2_ssize ngtcp2_conn_write_connection_close_pkt(
+  ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest,
+  size_t destlen, uint64_t error_code, const uint8_t *reason, size_t reasonlen,
+  ngtcp2_tstamp ts);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_write_application_close_pkt` writes a packet which
+ * contains a CONNECTION_CLOSE frame (type 0x1d) in the buffer pointed
+ * by |dest| whose capacity is |datalen|.
+ *
+ * If |path| is not ``NULL``, this function stores the network path
+ * with which the packet should be sent.  Each addr field must point
+ * to the buffer which should be at least ``sizeof(struct
+ * sockaddr_storage)`` bytes long.  The assignment might not be done
+ * if nothing is written to |dest|.
+ *
+ * If |pi| is not ``NULL``, this function stores packet metadata in it
+ * if it succeeds.  The metadata includes ECN markings.
+ *
+ * If handshake has not been confirmed yet, CONNECTION_CLOSE (type
+ * 0x1c) with error code :macro:`NGTCP2_APPLICATION_ERROR` is written
+ * instead.
+ *
+ * This function must not be called from inside the callback
+ * functions.
+ *
+ * At the moment, successful call to this function makes connection
+ * close.  We may change this behaviour in the future to allow
+ * graceful shutdown.
+ *
+ * This function returns the number of bytes written in |dest| if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory
+ * :macro:`NGTCP2_ERR_NOBUF`
+ *     Buffer is too small
+ * :macro:`NGTCP2_ERR_INVALID_STATE`
+ *     The current state does not allow sending CONNECTION_CLOSE.
+ * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED`
+ *     Packet number is exhausted, and cannot send any more packet.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     User callback failed
+ */
+ngtcp2_ssize ngtcp2_conn_write_application_close_pkt(
+  ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest,
+  size_t destlen, uint64_t app_error_code, const uint8_t *reason,
+  size_t reasonlen, ngtcp2_tstamp ts);
+
+int ngtcp2_conn_start_pmtud(ngtcp2_conn *conn);
+
+void ngtcp2_conn_stop_pmtud(ngtcp2_conn *conn);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_remote_transport_params` sets transport parameter
+ * |params| from a remote endpoint to |conn|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_TRANSPORT_PARAM`
+ *     Failed to validate a remote transport parameters.
+ * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE`
+ *     Version negotiation failure.
+ * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`
+ *     User callback failed
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int ngtcp2_conn_set_remote_transport_params(
+  ngtcp2_conn *conn, const ngtcp2_transport_params *params);
+
+/**
+ * @function
+ *
+ * `ngtcp2_conn_set_0rtt_remote_transport_params` sets |params| as
+ * transport parameters previously received from a server.  The
+ * parameters are used to send early data.  QUIC requires that client
+ * application should remember transport parameters along with a
+ * session ticket.
+ *
+ * At least following fields should be set:
+ *
+ * - initial_max_stream_id_bidi
+ * - initial_max_stream_id_uni
+ * - initial_max_stream_data_bidi_local
+ * - initial_max_stream_data_bidi_remote
+ * - initial_max_stream_data_uni
+ * - initial_max_data
+ * - active_connection_id_limit
+ * - max_datagram_frame_size (if DATAGRAM extension was negotiated)
+ *
+ * The following fields are ignored:
+ *
+ * - ack_delay_exponent
+ * - max_ack_delay
+ * - initial_scid
+ * - original_dcid
+ * - preferred_address and preferred_address_present
+ * - retry_scid and retry_scid_present
+ * - stateless_reset_token and stateless_reset_token_present
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :macro:`NGTCP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int ngtcp2_conn_set_0rtt_remote_transport_params(
+  ngtcp2_conn *conn, const ngtcp2_transport_params *params);
+
+/*
+ * ngtcp2_conn_discard_initial_state discards state for Initial packet
+ * number space.
+ */
+void ngtcp2_conn_discard_initial_state(ngtcp2_conn *conn, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_conn_discard_handshake_state discards state for Handshake
+ * packet number space.
+ */
+void ngtcp2_conn_discard_handshake_state(ngtcp2_conn *conn, ngtcp2_tstamp ts);
+
+void ngtcp2_conn_add_path_history(ngtcp2_conn *conn, const ngtcp2_dcid *dcid,
+                                  ngtcp2_tstamp ts);
+
+const ngtcp2_path_history_entry *
+ngtcp2_conn_find_path_history(ngtcp2_conn *conn, const ngtcp2_path *path,
+                              ngtcp2_tstamp ts);
+
+#endif /* !defined(NGTCP2_CONN_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_conn_stat.h b/third_party/ngtcp2/lib/ngtcp2_conn_stat.h
new file mode 100644 (file)
index 0000000..be041b9
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2023 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CONN_STAT_H
+#define NGTCP2_CONN_STAT_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/**
+ * @struct
+ *
+ * :type:`ngtcp2_conn_stat` holds various connection statistics, and
+ * computed data for recovery and congestion controller.
+ */
+typedef struct ngtcp2_conn_stat {
+  /**
+   * :member:`latest_rtt` is the latest RTT sample which is not
+   * adjusted by acknowledgement delay.
+   */
+  ngtcp2_duration latest_rtt;
+  /**
+   * :member:`min_rtt` is the minimum RTT seen so far.  It is not
+   * adjusted by acknowledgement delay.
+   */
+  ngtcp2_duration min_rtt;
+  /**
+   * :member:`smoothed_rtt` is the smoothed RTT.
+   */
+  ngtcp2_duration smoothed_rtt;
+  /**
+   * :member:`rttvar` is a mean deviation of observed RTT.
+   */
+  ngtcp2_duration rttvar;
+  /**
+   * :member:`initial_rtt` is the initial RTT which is used when no
+   * RTT sample is available.
+   */
+  ngtcp2_duration initial_rtt;
+  /**
+   * :member:`first_rtt_sample_ts` is the timestamp when the first RTT
+   * sample is obtained.
+   */
+  ngtcp2_tstamp first_rtt_sample_ts;
+  /**
+   * :member:`pto_count` is the count of successive PTO timer
+   * expiration.
+   */
+  size_t pto_count;
+  /**
+   * :member:`loss_detection_timer` is the deadline of the current
+   * loss detection timer.
+   */
+  ngtcp2_tstamp loss_detection_timer;
+  /**
+   * :member:`last_tx_pkt_ts` corresponds to
+   * time_of_last_ack_eliciting_packet in :rfc:`9002`.
+   */
+  ngtcp2_tstamp last_tx_pkt_ts[NGTCP2_PKTNS_ID_MAX];
+  /**
+   * :member:`loss_time` corresponds to loss_time in :rfc:`9002`.
+   */
+  ngtcp2_tstamp loss_time[NGTCP2_PKTNS_ID_MAX];
+  /**
+   * :member:`cwnd` is the size of congestion window.
+   */
+  uint64_t cwnd;
+  /**
+   * :member:`ssthresh` is slow start threshold.
+   */
+  uint64_t ssthresh;
+  /**
+   * :member:`congestion_recovery_start_ts` is the timestamp when
+   * congestion recovery started.
+   */
+  ngtcp2_tstamp congestion_recovery_start_ts;
+  /**
+   * :member:`bytes_in_flight` is the number in bytes of all sent
+   * packets which have not been acknowledged.
+   */
+  uint64_t bytes_in_flight;
+  /**
+   * :member:`max_tx_udp_payload_size` is the maximum size of UDP
+   * datagram payload that this endpoint transmits to the current
+   * path.  It is used by congestion controller to compute congestion
+   * window.
+   */
+  size_t max_tx_udp_payload_size;
+  /**
+   * :member:`delivery_rate_sec` is the current sending rate measured
+   * in byte per second.
+   */
+  uint64_t delivery_rate_sec;
+  /**
+   * :member:`pacing_interval_m` is the inverse of pacing rate, which
+   * is the current packet sending rate computed by a congestion
+   * controller.  0 if a congestion controller does not set pacing
+   * interval.  Even if this value is set to 0, the library paces
+   * packets.  The unit of this value is 1/1024 of nanoseconds.
+   */
+  uint64_t pacing_interval_m;
+  /**
+   * :member:`send_quantum` is the maximum size of a data aggregate
+   * scheduled and transmitted together.
+   */
+  size_t send_quantum;
+} ngtcp2_conn_stat;
+
+#endif /* !defined(NGTCP2_CONN_STAT_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_conv.c b/third_party/ngtcp2/lib/ngtcp2_conv.c
new file mode 100644 (file)
index 0000000..6528011
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_conv.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_str.h"
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_net.h"
+#include "ngtcp2_unreachable.h"
+
+const uint8_t *ngtcp2_get_uint64be(uint64_t *dest, const uint8_t *p) {
+  memcpy(dest, p, sizeof(*dest));
+  *dest = ngtcp2_ntohl64(*dest);
+  return p + sizeof(*dest);
+}
+
+const uint8_t *ngtcp2_get_uint32be(uint32_t *dest, const uint8_t *p) {
+  memcpy(dest, p, sizeof(*dest));
+  *dest = ngtcp2_ntohl(*dest);
+  return p + sizeof(*dest);
+}
+
+const uint8_t *ngtcp2_get_uint24be(uint32_t *dest, const uint8_t *p) {
+  *dest = 0;
+  memcpy(((uint8_t *)dest) + 1, p, 3);
+  *dest = ngtcp2_ntohl(*dest);
+  return p + 3;
+}
+
+const uint8_t *ngtcp2_get_uint16be(uint16_t *dest, const uint8_t *p) {
+  memcpy(dest, p, sizeof(*dest));
+  *dest = ngtcp2_ntohs(*dest);
+  return p + sizeof(*dest);
+}
+
+const uint8_t *ngtcp2_get_uint16(uint16_t *dest, const uint8_t *p) {
+  memcpy(dest, p, sizeof(*dest));
+  return p + sizeof(*dest);
+}
+
+static const uint8_t *get_uvarint(uint64_t *dest, const uint8_t *p) {
+  union {
+    uint8_t n8;
+    uint16_t n16;
+    uint32_t n32;
+    uint64_t n64;
+  } n;
+
+  switch (*p >> 6) {
+  case 0:
+    *dest = *p++;
+    return p;
+  case 1:
+    memcpy(&n, p, 2);
+    n.n8 &= 0x3f;
+    *dest = ngtcp2_ntohs(n.n16);
+
+    return p + 2;
+  case 2:
+    memcpy(&n, p, 4);
+    n.n8 &= 0x3f;
+    *dest = ngtcp2_ntohl(n.n32);
+
+    return p + 4;
+  case 3:
+    memcpy(&n, p, 8);
+    n.n8 &= 0x3f;
+    *dest = ngtcp2_ntohl64(n.n64);
+
+    return p + 8;
+  default:
+    ngtcp2_unreachable();
+  }
+}
+
+const uint8_t *ngtcp2_get_uvarint(uint64_t *dest, const uint8_t *p) {
+  return get_uvarint(dest, p);
+}
+
+const uint8_t *ngtcp2_get_varint(int64_t *dest, const uint8_t *p) {
+  return get_uvarint((uint64_t *)dest, p);
+}
+
+int64_t ngtcp2_get_pkt_num(const uint8_t *p, size_t pkt_numlen) {
+  uint32_t l;
+  uint16_t s;
+
+  switch (pkt_numlen) {
+  case 1:
+    return *p;
+  case 2:
+    ngtcp2_get_uint16be(&s, p);
+    return (int64_t)s;
+  case 3:
+    ngtcp2_get_uint24be(&l, p);
+    return (int64_t)l;
+  case 4:
+    ngtcp2_get_uint32be(&l, p);
+    return (int64_t)l;
+  default:
+    ngtcp2_unreachable();
+  }
+}
+
+uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n) {
+  n = ngtcp2_htonl64(n);
+  return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n));
+}
+
+uint8_t *ngtcp2_put_uint32be(uint8_t *p, uint32_t n) {
+  n = ngtcp2_htonl(n);
+  return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n));
+}
+
+uint8_t *ngtcp2_put_uint24be(uint8_t *p, uint32_t n) {
+  n = ngtcp2_htonl(n);
+  return ngtcp2_cpymem(p, ((const uint8_t *)&n) + 1, 3);
+}
+
+uint8_t *ngtcp2_put_uint16be(uint8_t *p, uint16_t n) {
+  n = ngtcp2_htons(n);
+  return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n));
+}
+
+uint8_t *ngtcp2_put_uint16(uint8_t *p, uint16_t n) {
+  return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n));
+}
+
+uint8_t *ngtcp2_put_uvarint(uint8_t *p, uint64_t n) {
+  uint8_t *rv;
+  if (n < 64) {
+    *p++ = (uint8_t)n;
+    return p;
+  }
+  if (n < 16384) {
+    rv = ngtcp2_put_uint16be(p, (uint16_t)n);
+    *p |= 0x40;
+    return rv;
+  }
+  if (n < 1073741824) {
+    rv = ngtcp2_put_uint32be(p, (uint32_t)n);
+    *p |= 0x80;
+    return rv;
+  }
+  assert(n < 4611686018427387904ULL);
+  rv = ngtcp2_put_uint64be(p, n);
+  *p |= 0xc0;
+  return rv;
+}
+
+uint8_t *ngtcp2_put_uvarint30(uint8_t *p, uint32_t n) {
+  uint8_t *rv;
+
+  assert(n < 1073741824);
+
+  rv = ngtcp2_put_uint32be(p, n);
+  *p |= 0x80;
+
+  return rv;
+}
+
+uint8_t *ngtcp2_put_pkt_num(uint8_t *p, int64_t pkt_num, size_t len) {
+  switch (len) {
+  case 1:
+    *p++ = (uint8_t)pkt_num;
+    return p;
+  case 2:
+    return ngtcp2_put_uint16be(p, (uint16_t)pkt_num);
+  case 3:
+    return ngtcp2_put_uint24be(p, (uint32_t)pkt_num);
+  case 4:
+    return ngtcp2_put_uint32be(p, (uint32_t)pkt_num);
+  default:
+    ngtcp2_unreachable();
+  }
+}
+
+size_t ngtcp2_get_uvarintlen(const uint8_t *p) {
+  return (size_t)(1u << (*p >> 6));
+}
+
+size_t ngtcp2_put_uvarintlen(uint64_t n) {
+  if (n < 64) {
+    return 1;
+  }
+  if (n < 16384) {
+    return 2;
+  }
+  if (n < 1073741824) {
+    return 4;
+  }
+  assert(n < 4611686018427387904ULL);
+  return 8;
+}
+
+uint64_t ngtcp2_ord_stream_id(int64_t stream_id) {
+  return (uint64_t)(stream_id >> 2) + 1;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_conv.h b/third_party/ngtcp2/lib/ngtcp2_conv.h
new file mode 100644 (file)
index 0000000..ad92468
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CONV_H
+#define NGTCP2_CONV_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/*
+ * ngtcp2_get_uint64be reads 8 bytes from |p| as 64 bits unsigned
+ * integer encoded as network byte order, and stores it in the buffer
+ * pointed by |dest| in host byte order.  It returns |p| + 8.
+ */
+const uint8_t *ngtcp2_get_uint64be(uint64_t *dest, const uint8_t *p);
+
+/*
+ * ngtcp2_get_uint32be reads 4 bytes from |p| as 32 bits unsigned
+ * integer encoded as network byte order, and stores it in the buffer
+ * pointed by |dest| in host byte order.  It returns |p| + 4.
+ */
+const uint8_t *ngtcp2_get_uint32be(uint32_t *dest, const uint8_t *p);
+
+/*
+ * ngtcp2_get_uint24be reads 3 bytes from |p| as 24 bits unsigned
+ * integer encoded as network byte order, and stores it in the buffer
+ * pointed by |dest| in host byte order.  It returns |p| + 3.
+ */
+const uint8_t *ngtcp2_get_uint24be(uint32_t *dest, const uint8_t *p);
+
+/*
+ * ngtcp2_get_uint16be reads 2 bytes from |p| as 16 bits unsigned
+ * integer encoded as network byte order, and stores it in the buffer
+ * pointed by |dest| in host byte order.  It returns |p| + 2.
+ */
+const uint8_t *ngtcp2_get_uint16be(uint16_t *dest, const uint8_t *p);
+
+/*
+ * ngtcp2_get_uint16 reads 2 bytes from |p| as 16 bits unsigned
+ * integer encoded as network byte order, and stores it in the buffer
+ * pointed by |dest| as is.  It returns |p| + 2.
+ */
+const uint8_t *ngtcp2_get_uint16(uint16_t *dest, const uint8_t *p);
+
+/*
+ * ngtcp2_get_uvarint reads variable-length unsigned integer from |p|,
+ * and stores it in the buffer pointed by |dest| in host byte order.
+ * It returns |p| plus the number of bytes read from |p|.
+ */
+const uint8_t *ngtcp2_get_uvarint(uint64_t *dest, const uint8_t *p);
+
+/*
+ * ngtcp2_get_varint reads variable-length unsigned integer from |p|,
+ * and casts it to the signed integer, and stores it in the buffer
+ * pointed by |dest| in host byte order.  No information should be
+ * lost in this cast, because the variable-length integer is 62
+ * bits. It returns |p| plus the number of bytes read from |p|.
+ */
+const uint8_t *ngtcp2_get_varint(int64_t *dest, const uint8_t *p);
+
+/*
+ * ngtcp2_get_pkt_num reads encoded packet number from |p|.  The
+ * packet number is encoed in |pkt_numlen| bytes.
+ */
+int64_t ngtcp2_get_pkt_num(const uint8_t *p, size_t pkt_numlen);
+
+/*
+ * ngtcp2_put_uint64be writes |n| in host byte order in |p| in network
+ * byte order.  It returns the one beyond of the last written
+ * position.
+ */
+uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n);
+
+/*
+ * ngtcp2_put_uint32be writes |n| in host byte order in |p| in network
+ * byte order.  It returns the one beyond of the last written
+ * position.
+ */
+uint8_t *ngtcp2_put_uint32be(uint8_t *p, uint32_t n);
+
+/*
+ * ngtcp2_put_uint24be writes |n| in host byte order in |p| in network
+ * byte order.  It writes only least significant 24 bits.  It returns
+ * the one beyond of the last written position.
+ */
+uint8_t *ngtcp2_put_uint24be(uint8_t *p, uint32_t n);
+
+/*
+ * ngtcp2_put_uint16be writes |n| in host byte order in |p| in network
+ * byte order.  It returns the one beyond of the last written
+ * position.
+ */
+uint8_t *ngtcp2_put_uint16be(uint8_t *p, uint16_t n);
+
+/*
+ * ngtcp2_put_uint16 writes |n| as is in |p|.  It returns the one
+ * beyond of the last written position.
+ */
+uint8_t *ngtcp2_put_uint16(uint8_t *p, uint16_t n);
+
+/*
+ * ngtcp2_put_uvarint writes |n| in |p| using variable-length integer
+ * encoding.  It returns the one beyond of the last written position.
+ */
+uint8_t *ngtcp2_put_uvarint(uint8_t *p, uint64_t n);
+
+/*
+ * ngtcp2_put_uvarint30 writes |n| in |p| using variable-length
+ * integer encoding.  |n| must be strictly less than 1073741824.  The
+ * function always encodes |n| in 4 bytes.  It returns the one beyond
+ * of the last written position.
+ */
+uint8_t *ngtcp2_put_uvarint30(uint8_t *p, uint32_t n);
+
+/*
+ * ngtcp2_put_pkt_num encodes |pkt_num| using |len| bytes.  It
+ * returns the one beyond of the last written position.
+ */
+uint8_t *ngtcp2_put_pkt_num(uint8_t *p, int64_t pkt_num, size_t len);
+
+/*
+ * ngtcp2_get_uvarintlen returns the required number of bytes to read
+ * variable-length integer starting at |p|.
+ */
+size_t ngtcp2_get_uvarintlen(const uint8_t *p);
+
+/*
+ * ngtcp2_put_uvarintlen returns the required number of bytes to
+ * encode |n|.
+ */
+size_t ngtcp2_put_uvarintlen(uint64_t n);
+
+/*
+ * ngtcp2_ord_stream_id returns the ordinal number of |stream_id|.
+ */
+uint64_t ngtcp2_ord_stream_id(int64_t stream_id);
+
+#endif /* !defined(NGTCP2_CONV_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_crypto.c b/third_party/ngtcp2/lib/ngtcp2_crypto.c
new file mode 100644 (file)
index 0000000..1f74e8c
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_crypto.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_net.h"
+
+int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret,
+                         size_t secretlen,
+                         const ngtcp2_crypto_aead_ctx *aead_ctx,
+                         const uint8_t *iv, size_t ivlen,
+                         const ngtcp2_mem *mem) {
+  int rv = ngtcp2_crypto_km_nocopy_new(pckm, secretlen, ivlen, mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (secretlen) {
+    memcpy((*pckm)->secret.base, secret, secretlen);
+  }
+
+  if (aead_ctx) {
+    (*pckm)->aead_ctx = *aead_ctx;
+  }
+
+  memcpy((*pckm)->iv.base, iv, ivlen);
+
+  return 0;
+}
+
+int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen,
+                                size_t ivlen, const ngtcp2_mem *mem) {
+  size_t len;
+  uint8_t *p;
+
+  len = sizeof(ngtcp2_crypto_km) + secretlen + ivlen;
+
+  *pckm = ngtcp2_mem_malloc(mem, len);
+  if (*pckm == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  p = (uint8_t *)(*pckm) + sizeof(ngtcp2_crypto_km);
+  (*pckm)->secret.base = p;
+  (*pckm)->secret.len = secretlen;
+  p += secretlen;
+  (*pckm)->iv.base = p;
+  (*pckm)->iv.len = ivlen;
+  (*pckm)->aead_ctx.native_handle = NULL;
+  (*pckm)->pkt_num = -1;
+  (*pckm)->use_count = 0;
+  (*pckm)->flags = NGTCP2_CRYPTO_KM_FLAG_NONE;
+
+  return 0;
+}
+
+void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, const ngtcp2_mem *mem) {
+  if (ckm == NULL) {
+    return;
+  }
+
+  if (ckm->secret.len) {
+#ifdef WIN32
+    SecureZeroMemory(ckm->secret.base, ckm->secret.len);
+#elif defined(HAVE_EXPLICIT_BZERO)
+    explicit_bzero(ckm->secret.base, ckm->secret.len);
+#elif defined(HAVE_MEMSET_S)
+    memset_s(ckm->secret.base, ckm->secret.len, 0, ckm->secret.len);
+#endif /* defined(HAVE_MEMSET_S) */
+  }
+
+  ngtcp2_mem_free(mem, ckm);
+}
+
+void ngtcp2_crypto_create_nonce(uint8_t *dest, const uint8_t *iv, size_t ivlen,
+                                int64_t pkt_num) {
+  uint64_t n;
+
+  assert(ivlen >= sizeof(n));
+
+  memcpy(dest, iv, ivlen);
+
+  dest += ivlen - sizeof(n);
+
+  memcpy(&n, dest, sizeof(n));
+  n ^= ngtcp2_htonl64((uint64_t)pkt_num);
+  memcpy(dest, &n, sizeof(n));
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_crypto.h b/third_party/ngtcp2/lib/ngtcp2_crypto.h
new file mode 100644 (file)
index 0000000..ca6d494
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_CRYPTO_H
+#define NGTCP2_CRYPTO_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+
+/* NGTCP2_INITIAL_AEAD_OVERHEAD is an overhead of AEAD used by Initial
+   packets.  Because QUIC uses AEAD_AES_128_GCM, the overhead is 16
+   bytes. */
+#define NGTCP2_INITIAL_AEAD_OVERHEAD 16
+
+/* NGTCP2_MAX_AEAD_OVERHEAD is the maximum AEAD overhead. */
+#define NGTCP2_MAX_AEAD_OVERHEAD 16
+
+/* NGTCP2_CRYPTO_KM_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_CRYPTO_KM_FLAG_NONE 0x00u
+/* NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE is set if key phase bit is
+   set. */
+#define NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE 0x01u
+
+typedef struct ngtcp2_crypto_km {
+  ngtcp2_vec secret;
+  ngtcp2_crypto_aead_ctx aead_ctx;
+  ngtcp2_vec iv;
+  /* 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. */
+  int64_t pkt_num;
+  /* use_count is the number of encryption applied with this key. */
+  uint64_t use_count;
+  /* flags is the bitwise OR of zero or more of
+     NGTCP2_CRYPTO_KM_FLAG_*. */
+  uint8_t flags;
+} ngtcp2_crypto_km;
+
+/*
+ * ngtcp2_crypto_km_new creates new ngtcp2_crypto_km object, and
+ * assigns its pointer to |*pckm|.  The |secret| of length
+ * |secretlen|, |aead_ctx|, and the |iv| of length |ivlen| are copied
+ * to |*pckm|.  If |secretlen| == 0, the function assumes no secret is
+ * given which is acceptable.  The sole reason to store secret is
+ * update keys.  Only 1RTT key can be updated.
+ */
+int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret,
+                         size_t secretlen,
+                         const ngtcp2_crypto_aead_ctx *aead_ctx,
+                         const uint8_t *iv, size_t ivlen,
+                         const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_crypto_km_nocopy_new is similar to ngtcp2_crypto_km_new, but
+ * it does not copy secret, aead context, and IV.
+ */
+int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen,
+                                size_t ivlen, const ngtcp2_mem *mem);
+
+void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, const ngtcp2_mem *mem);
+
+typedef struct ngtcp2_crypto_cc {
+  ngtcp2_crypto_aead aead;
+  ngtcp2_crypto_cipher hp;
+  ngtcp2_crypto_km *ckm;
+  ngtcp2_crypto_cipher_ctx hp_ctx;
+  ngtcp2_encrypt encrypt;
+  ngtcp2_decrypt decrypt;
+  ngtcp2_hp_mask hp_mask;
+} ngtcp2_crypto_cc;
+
+void ngtcp2_crypto_create_nonce(uint8_t *dest, const uint8_t *iv, size_t ivlen,
+                                int64_t pkt_num);
+
+#endif /* !defined(NGTCP2_CRYPTO_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_dcidtr.c b/third_party/ngtcp2/lib/ngtcp2_dcidtr.c
new file mode 100644 (file)
index 0000000..170b9f8
--- /dev/null
@@ -0,0 +1,485 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2025 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_dcidtr.h"
+
+#include <assert.h>
+
+#include "ngtcp2_tstamp.h"
+#include "ngtcp2_macro.h"
+
+void ngtcp2_dcidtr_init(ngtcp2_dcidtr *dtr) {
+  ngtcp2_static_ringbuf_dcid_unused_init(&dtr->unused);
+  ngtcp2_static_ringbuf_dcid_bound_init(&dtr->bound);
+  ngtcp2_static_ringbuf_dcid_retired_init(&dtr->retired);
+
+  dtr->retire_unacked.len = 0;
+}
+
+int ngtcp2_dcidtr_track_retired_seq(ngtcp2_dcidtr *dtr, uint64_t seq) {
+  if (dtr->retire_unacked.len >= ngtcp2_arraylen(dtr->retire_unacked.seqs)) {
+    return NGTCP2_ERR_CONNECTION_ID_LIMIT;
+  }
+
+  dtr->retire_unacked.seqs[dtr->retire_unacked.len++] = seq;
+
+  return 0;
+}
+
+void ngtcp2_dcidtr_untrack_retired_seq(ngtcp2_dcidtr *dtr, uint64_t seq) {
+  size_t i;
+
+  for (i = 0; i < dtr->retire_unacked.len; ++i) {
+    if (dtr->retire_unacked.seqs[i] != seq) {
+      continue;
+    }
+
+    if (i != dtr->retire_unacked.len - 1) {
+      dtr->retire_unacked.seqs[i] =
+        dtr->retire_unacked.seqs[dtr->retire_unacked.len - 1];
+    }
+
+    --dtr->retire_unacked.len;
+
+    return;
+  }
+}
+
+int ngtcp2_dcidtr_check_retired_seq_tracked(const ngtcp2_dcidtr *dtr,
+                                            uint64_t seq) {
+  size_t i;
+
+  for (i = 0; i < dtr->retire_unacked.len; ++i) {
+    if (dtr->retire_unacked.seqs[i] == seq) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static int dcidtr_on_retire(ngtcp2_dcidtr *dtr, const ngtcp2_dcid *dcid,
+                            ngtcp2_dcidtr_cb on_retire, void *user_data) {
+  int rv;
+
+  if (ngtcp2_dcidtr_check_retired_seq_tracked(dtr, dcid->seq)) {
+    return 0;
+  }
+
+  rv = ngtcp2_dcidtr_track_retired_seq(dtr, dcid->seq);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (!on_retire) {
+    return 0;
+  }
+
+  return on_retire(dcid, user_data);
+}
+
+ngtcp2_dcid *ngtcp2_dcidtr_find_bound_dcid(const ngtcp2_dcidtr *dtr,
+                                           const ngtcp2_path *path) {
+  ngtcp2_dcid *dcid;
+  const ngtcp2_ringbuf *rb = &dtr->bound.rb;
+  size_t i, len = ngtcp2_ringbuf_len(rb);
+
+  for (i = 0; i < len; ++i) {
+    dcid = ngtcp2_ringbuf_get(rb, i);
+
+    if (ngtcp2_path_eq(&dcid->ps.path, path)) {
+      return dcid;
+    }
+  }
+
+  return NULL;
+}
+
+ngtcp2_dcid *ngtcp2_dcidtr_bind_zerolen_dcid(ngtcp2_dcidtr *dtr,
+                                             const ngtcp2_path *path) {
+  ngtcp2_dcid *dcid = ngtcp2_ringbuf_push_back(&dtr->bound.rb);
+  ngtcp2_cid cid;
+
+  ngtcp2_cid_zero(&cid);
+  ngtcp2_dcid_init(dcid, 0, &cid, NULL);
+  ngtcp2_dcid_set_path(dcid, path);
+
+  return dcid;
+}
+
+int ngtcp2_dcidtr_bind_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid **pdest,
+                            const ngtcp2_path *path, ngtcp2_tstamp ts,
+                            ngtcp2_dcidtr_cb on_retire, void *user_data) {
+  const ngtcp2_dcid *src;
+  ngtcp2_dcid *dest;
+  int rv;
+
+  if (ngtcp2_ringbuf_full(&dtr->bound.rb)) {
+    rv = dcidtr_on_retire(dtr, ngtcp2_ringbuf_get(&dtr->bound.rb, 0), on_retire,
+                          user_data);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  src = ngtcp2_ringbuf_get(&dtr->unused.rb, 0);
+  dest = ngtcp2_ringbuf_push_back(&dtr->bound.rb);
+
+  ngtcp2_dcid_copy(dest, src);
+  dest->bound_ts = ts;
+  ngtcp2_dcid_set_path(dest, path);
+
+  ngtcp2_ringbuf_pop_front(&dtr->unused.rb);
+
+  *pdest = dest;
+
+  return 0;
+}
+
+int ngtcp2_dcidtr_verify_stateless_reset(const ngtcp2_dcidtr *dtr,
+                                         const ngtcp2_path *path,
+                                         const uint8_t *token) {
+  const ngtcp2_dcid *dcid;
+  const ngtcp2_ringbuf *rb = &dtr->bound.rb;
+  size_t i, len = ngtcp2_ringbuf_len(rb);
+
+  for (i = 0; i < len; ++i) {
+    dcid = ngtcp2_ringbuf_get(rb, i);
+    if (ngtcp2_dcid_verify_stateless_reset_token(dcid, path, token) == 0) {
+      return 0;
+    }
+  }
+
+  return NGTCP2_ERR_INVALID_ARGUMENT;
+}
+
+static int verify_token_uniqueness(const ngtcp2_ringbuf *rb, int *pfound,
+                                   uint64_t seq, const ngtcp2_cid *cid,
+                                   const uint8_t *token) {
+  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 0;
+}
+
+int ngtcp2_dcidtr_verify_token_uniqueness(const ngtcp2_dcidtr *dtr, int *pfound,
+                                          uint64_t seq, const ngtcp2_cid *cid,
+                                          const uint8_t *token) {
+  int rv;
+
+  rv = verify_token_uniqueness(&dtr->bound.rb, pfound, seq, cid, token);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return verify_token_uniqueness(&dtr->unused.rb, pfound, seq, cid, token);
+}
+
+static void remove_dcid_at(ngtcp2_ringbuf *rb, size_t at) {
+  const ngtcp2_dcid *src;
+  ngtcp2_dcid *dest;
+
+  if (at == 0) {
+    ngtcp2_ringbuf_pop_front(rb);
+    return;
+  }
+
+  if (at == ngtcp2_ringbuf_len(rb) - 1) {
+    ngtcp2_ringbuf_pop_back(rb);
+    return;
+  }
+
+  src = ngtcp2_ringbuf_get(rb, ngtcp2_ringbuf_len(rb) - 1);
+  dest = ngtcp2_ringbuf_get(rb, at);
+
+  ngtcp2_dcid_copy(dest, src);
+  ngtcp2_ringbuf_pop_back(rb);
+}
+
+static int dcidtr_retire_dcid_prior_to(ngtcp2_dcidtr *dtr, ngtcp2_ringbuf *rb,
+                                       uint64_t seq, ngtcp2_dcidtr_cb on_retire,
+                                       void *user_data) {
+  size_t i;
+  const ngtcp2_dcid *dcid;
+  int rv;
+
+  for (i = 0; i < ngtcp2_ringbuf_len(rb);) {
+    dcid = ngtcp2_ringbuf_get(rb, i);
+    if (dcid->seq >= seq) {
+      ++i;
+      continue;
+    }
+
+    rv = dcidtr_on_retire(dtr, dcid, on_retire, user_data);
+    if (rv != 0) {
+      return rv;
+    }
+
+    remove_dcid_at(rb, i);
+  }
+
+  return 0;
+}
+
+int ngtcp2_dcidtr_retire_inactive_dcid_prior_to(ngtcp2_dcidtr *dtr,
+                                                uint64_t seq,
+                                                ngtcp2_dcidtr_cb on_retire,
+                                                void *user_data) {
+  int rv;
+
+  rv =
+    dcidtr_retire_dcid_prior_to(dtr, &dtr->bound.rb, seq, on_retire, user_data);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return dcidtr_retire_dcid_prior_to(dtr, &dtr->unused.rb, seq, on_retire,
+                                     user_data);
+}
+
+int ngtcp2_dcidtr_retire_active_dcid(ngtcp2_dcidtr *dtr,
+                                     const ngtcp2_dcid *dcid, ngtcp2_tstamp ts,
+                                     ngtcp2_dcidtr_cb on_deactivate,
+                                     void *user_data) {
+  ngtcp2_ringbuf *rb = &dtr->retired.rb;
+  const ngtcp2_dcid *stale_dcid;
+  ngtcp2_dcid *dest;
+  int rv;
+
+  assert(dcid->cid.datalen);
+
+  if (ngtcp2_ringbuf_full(rb)) {
+    stale_dcid = ngtcp2_ringbuf_get(rb, 0);
+    rv = on_deactivate(stale_dcid, user_data);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  dest = ngtcp2_ringbuf_push_back(rb);
+  ngtcp2_dcid_copy(dest, dcid);
+  dest->retired_ts = ts;
+
+  return dcidtr_on_retire(dtr, dest, NULL, NULL);
+}
+
+int ngtcp2_dcidtr_remove_stale_retired_dcid(ngtcp2_dcidtr *dtr,
+                                            ngtcp2_duration timeout,
+                                            ngtcp2_tstamp ts,
+                                            ngtcp2_dcidtr_cb on_deactivate,
+                                            void *user_data) {
+  ngtcp2_ringbuf *rb = &dtr->retired.rb;
+  const ngtcp2_dcid *dcid;
+  int rv;
+
+  for (; ngtcp2_ringbuf_len(rb);) {
+    dcid = ngtcp2_ringbuf_get(rb, 0);
+    if (ngtcp2_tstamp_not_elapsed(dcid->retired_ts, timeout, ts)) {
+      break;
+    }
+
+    rv = on_deactivate(dcid, user_data);
+    if (rv != 0) {
+      return rv;
+    }
+
+    ngtcp2_ringbuf_pop_front(rb);
+  }
+
+  return 0;
+}
+
+int ngtcp2_dcidtr_pop_bound_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dest,
+                                 const ngtcp2_path *path) {
+  const ngtcp2_dcid *src;
+  ngtcp2_ringbuf *rb = &dtr->bound.rb;
+  size_t len = ngtcp2_ringbuf_len(rb);
+  size_t i;
+
+  for (i = 0; i < len; ++i) {
+    src = ngtcp2_ringbuf_get(rb, i);
+    if (ngtcp2_path_eq(&src->ps.path, path)) {
+      ngtcp2_dcid_copy(dest, src);
+      remove_dcid_at(rb, i);
+
+      return 0;
+    }
+  }
+
+  return NGTCP2_ERR_INVALID_ARGUMENT;
+}
+
+int ngtcp2_dcidtr_retire_stale_bound_dcid(ngtcp2_dcidtr *dtr,
+                                          ngtcp2_duration timeout,
+                                          ngtcp2_tstamp ts,
+                                          ngtcp2_dcidtr_cb on_retire,
+                                          void *user_data) {
+  ngtcp2_ringbuf *rb = &dtr->bound.rb;
+  size_t i;
+  const ngtcp2_dcid *dcid;
+  int rv;
+
+  for (i = 0; i < ngtcp2_ringbuf_len(rb);) {
+    dcid = ngtcp2_ringbuf_get(rb, i);
+
+    assert(dcid->cid.datalen);
+
+    if (ngtcp2_tstamp_not_elapsed(dcid->bound_ts, timeout, ts)) {
+      ++i;
+      continue;
+    }
+
+    rv = dcidtr_on_retire(dtr, dcid, on_retire, user_data);
+    if (rv != 0) {
+      return rv;
+    }
+
+    remove_dcid_at(rb, i);
+  }
+
+  return 0;
+}
+
+ngtcp2_tstamp ngtcp2_dcidtr_earliest_bound_ts(const ngtcp2_dcidtr *dtr) {
+  const ngtcp2_ringbuf *rb = &dtr->bound.rb;
+  size_t i, len = ngtcp2_ringbuf_len(rb);
+  ngtcp2_tstamp res = UINT64_MAX;
+  const ngtcp2_dcid *dcid;
+
+  for (i = 0; i < len; ++i) {
+    dcid = ngtcp2_ringbuf_get(rb, i);
+
+    assert(dcid->cid.datalen);
+    assert(dcid->bound_ts != UINT64_MAX);
+
+    res = ngtcp2_min_uint64(res, dcid->bound_ts);
+  }
+
+  return res;
+}
+
+ngtcp2_tstamp ngtcp2_dcidtr_earliest_retired_ts(const ngtcp2_dcidtr *dtr) {
+  const ngtcp2_ringbuf *rb = &dtr->retired.rb;
+  const ngtcp2_dcid *dcid;
+
+  if (ngtcp2_ringbuf_len(rb) == 0) {
+    return UINT64_MAX;
+  }
+
+  dcid = ngtcp2_ringbuf_get(rb, 0);
+
+  return dcid->retired_ts;
+}
+
+void ngtcp2_dcidtr_push_unused(ngtcp2_dcidtr *dtr, uint64_t seq,
+                               const ngtcp2_cid *cid, const uint8_t *token) {
+  ngtcp2_dcid *dcid = ngtcp2_ringbuf_push_back(&dtr->unused.rb);
+
+  ngtcp2_dcid_init(dcid, seq, cid, token);
+}
+
+void ngtcp2_dcidtr_pop_unused_cid_token(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dest) {
+  ngtcp2_ringbuf *rb = &dtr->unused.rb;
+  const ngtcp2_dcid *src;
+
+  assert(ngtcp2_ringbuf_len(rb));
+
+  src = ngtcp2_ringbuf_get(rb, 0);
+
+  dest->flags = NGTCP2_DCID_FLAG_NONE;
+  ngtcp2_dcid_copy_cid_token(dest, src);
+
+  ngtcp2_ringbuf_pop_front(rb);
+}
+
+void ngtcp2_dcidtr_pop_unused(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dest) {
+  ngtcp2_ringbuf *rb = &dtr->unused.rb;
+  const ngtcp2_dcid *src;
+
+  assert(ngtcp2_ringbuf_len(rb));
+
+  src = ngtcp2_ringbuf_get(rb, 0);
+
+  ngtcp2_dcid_copy(dest, src);
+
+  ngtcp2_ringbuf_pop_front(rb);
+}
+
+int ngtcp2_dcidtr_check_path_retired(const ngtcp2_dcidtr *dtr,
+                                     const ngtcp2_path *path) {
+  const ngtcp2_ringbuf *rb = &dtr->retired.rb;
+  size_t i, len = ngtcp2_ringbuf_len(rb);
+  const ngtcp2_dcid *dcid;
+
+  for (i = 0; i < len; ++i) {
+    dcid = ngtcp2_ringbuf_get(rb, i);
+    if (ngtcp2_path_eq(&dcid->ps.path, path)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+size_t ngtcp2_dcidtr_unused_len(const ngtcp2_dcidtr *dtr) {
+  return ngtcp2_ringbuf_len(&dtr->unused.rb);
+}
+
+size_t ngtcp2_dcidtr_bound_len(const ngtcp2_dcidtr *dtr) {
+  return ngtcp2_ringbuf_len(&dtr->bound.rb);
+}
+
+size_t ngtcp2_dcidtr_retired_len(const ngtcp2_dcidtr *dtr) {
+  return ngtcp2_ringbuf_len(&dtr->retired.rb);
+}
+
+size_t ngtcp2_dcidtr_inactive_len(const ngtcp2_dcidtr *dtr) {
+  return ngtcp2_ringbuf_len(&dtr->unused.rb) +
+         ngtcp2_ringbuf_len(&dtr->bound.rb);
+}
+
+int ngtcp2_dcidtr_unused_full(const ngtcp2_dcidtr *dtr) {
+  return ngtcp2_ringbuf_full(&dtr->unused.rb);
+}
+
+int ngtcp2_dcidtr_unused_empty(const ngtcp2_dcidtr *dtr) {
+  return ngtcp2_ringbuf_len(&dtr->unused.rb) == 0;
+}
+
+int ngtcp2_dcidtr_bound_full(const ngtcp2_dcidtr *dtr) {
+  return ngtcp2_ringbuf_full(&dtr->bound.rb);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_dcidtr.h b/third_party/ngtcp2/lib/ngtcp2_dcidtr.h
new file mode 100644 (file)
index 0000000..6304342
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2025 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_DCIDTR_H
+#define NGTCP2_DCIDTR_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_cid.h"
+#include "ngtcp2_ringbuf.h"
+
+/* NGTCP2_DCIDTR_MAX_BOUND_DCID_SIZE is the maximum number of
+   Destination Connection ID which has been bound to a particular
+   path, but not yet used as primary path, and path validation is not
+   performed from the local endpoint.  It must be the power of 2. */
+#define NGTCP2_DCIDTR_MAX_BOUND_DCID_SIZE 4
+/* NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE is the maximum number of
+   Destination Connection ID the remote endpoint provides to store.
+   It must be the power of 2. */
+#define NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE 8
+/* NGTCP2_DCIDTR_MAX_RETIRED_DCID_SIZE is the maximum number of
+   retired Destination Connection ID kept to catch in-flight packet on
+   a retired path.  It must be the power of 2. */
+#define NGTCP2_DCIDTR_MAX_RETIRED_DCID_SIZE 2
+
+ngtcp2_static_ringbuf_def(dcid_bound, NGTCP2_DCIDTR_MAX_BOUND_DCID_SIZE,
+                          sizeof(ngtcp2_dcid))
+ngtcp2_static_ringbuf_def(dcid_unused, NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE,
+                          sizeof(ngtcp2_dcid))
+ngtcp2_static_ringbuf_def(dcid_retired, NGTCP2_DCIDTR_MAX_RETIRED_DCID_SIZE,
+                          sizeof(ngtcp2_dcid))
+
+/*
+ * ngtcp2_dcidtr stores unused, bound, and retired Destination
+ * Connection IDs.
+ */
+typedef struct ngtcp2_dcidtr {
+  /* unused is a set of unused Destination Connection ID received from
+     a remote endpoint.  They are considered inactive. */
+  ngtcp2_static_ringbuf_dcid_unused unused;
+  /* bound is a set of Destination Connection IDs which are bound to
+     particular paths.  These paths are not validated yet.  They are
+     considered inactive. */
+  ngtcp2_static_ringbuf_dcid_bound bound;
+  /* retired is a set of Destination Connection ID retired by local
+     endpoint.  Keep them in 3*PTO to catch packets in flight along
+     the old path.  They are considered active. */
+  ngtcp2_static_ringbuf_dcid_retired retired;
+  struct {
+    /* seqs contains sequence number of Destination Connection ID
+       whose retirement is not acknowledged by the remote endpoint
+       yet. */
+    uint64_t seqs[NGTCP2_DCIDTR_MAX_UNUSED_DCID_SIZE * 2];
+    /* len is the number of sequence numbers that seq contains. */
+    size_t len;
+  } retire_unacked;
+} ngtcp2_dcidtr;
+
+typedef int (*ngtcp2_dcidtr_cb)(const ngtcp2_dcid *dcid, void *user_data);
+
+/*
+ * ngtcp2_dcidtr_init initializes |dtr|.
+ */
+void ngtcp2_dcidtr_init(ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_track_retired_seq tracks the sequence number |seq| of
+ * unacknowledged retiring Destination Connection ID.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_CONNECTION_ID_LIMIT
+ *     The number of unacknowledged retirement exceeds the limit.
+ */
+int ngtcp2_dcidtr_track_retired_seq(ngtcp2_dcidtr *dtr, uint64_t seq);
+
+/*
+ * ngtcp2_dcidtr_untrack_retired_seq deletes the sequence number |seq|
+ * of unacknowledged retiring Destination Connection ID.  It is fine
+ * if such sequence number is not found.
+ */
+void ngtcp2_dcidtr_untrack_retired_seq(ngtcp2_dcidtr *dtr, uint64_t seq);
+
+/*
+ * ngtcp2_dcidtr_check_retired_seq_tracked returns nonzero if |seq|
+ * has already been tracked.
+ */
+int ngtcp2_dcidtr_check_retired_seq_tracked(const ngtcp2_dcidtr *dtr,
+                                            uint64_t seq);
+
+/*
+ * ngtcp2_dcidtr_find_bound_dcid returns the pointer to ngtcp2_dcid
+ * that bound to |path|.  It returns NULL if there is no such
+ * ngtcp2_dcid.
+ */
+ngtcp2_dcid *ngtcp2_dcidtr_find_bound_dcid(const ngtcp2_dcidtr *dtr,
+                                           const ngtcp2_path *path);
+
+/*
+ * ngtcp2_dcidtr_bind_zerolen_dcid binds zero-length Destination
+ * Connection ID to |path|, and returns the pointer to the bound
+ * ngtcp2_dcid.
+ */
+ngtcp2_dcid *ngtcp2_dcidtr_bind_zerolen_dcid(ngtcp2_dcidtr *dtr,
+                                             const ngtcp2_path *path);
+
+/*
+ * ngtcp2_dcidtr_bind_dcid binds non-zero Destination Connection ID to
+ * |path|.  |ts| is the current timestamp.  The buffer space of bound
+ * Destination Connection ID is limited.  If it is full, the earliest
+ * one is removed.  |on_retire|, if specified, is called for the
+ * removed ngtcp2_dcid with |user_data|.  This function assigns the
+ * pointer to bound ngtcp2_dcid to |*pdest|.
+ *
+ * This function returns 0 if it succeeds, or negative error code that
+ * |on_retire| returns.
+ */
+int ngtcp2_dcidtr_bind_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid **pdest,
+                            const ngtcp2_path *path, ngtcp2_tstamp ts,
+                            ngtcp2_dcidtr_cb on_retire, void *user_data);
+
+/*
+ * ngtcp2_dcidtr_verify_stateless_reset verifies the stateless reset
+ * token |token| received from |path|.  It returns 0 if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     There is no Destination Connection ID that matches the given
+ *     |path| and |token|.
+ */
+int ngtcp2_dcidtr_verify_stateless_reset(const ngtcp2_dcidtr *dtr,
+                                         const ngtcp2_path *path,
+                                         const uint8_t *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.
+ *
+ * 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.
+ */
+int ngtcp2_dcidtr_verify_token_uniqueness(const ngtcp2_dcidtr *dtr, int *pfound,
+                                          uint64_t seq, const ngtcp2_cid *cid,
+                                          const uint8_t *token);
+
+/*
+ * ngtcp2_dcidtr_retire_inactive_dcid_prior_to retires inactive
+ * Destination Connection IDs (unused or bound) whose seq is less than
+ * |seq|.  For each retired ngtcp2_dcid, |on_retire|, if specified, is
+ * called with |user_data|.
+ *
+ * This function returns 0 if it succeeds, or negative error code that
+ * |on_retire| returns.
+ */
+int ngtcp2_dcidtr_retire_inactive_dcid_prior_to(ngtcp2_dcidtr *dtr,
+                                                uint64_t seq,
+                                                ngtcp2_dcidtr_cb on_retire,
+                                                void *user_data);
+
+/*
+ * ngtcp2_dcidtr_retire_active_dcid adds an active |dcid| to the
+ * retired Destination Connection ID buffer.  The buffer space of
+ * retired Destination Connection ID is limited.  If it is full, the
+ * earliest one is removed.  |on_deactivate| is called for the removed
+ * ngtcp2_dcid with |user_data|.
+ *
+ * This function returns 0 if it succeeds, or negative error code that
+ * |on_deactivate| returns.
+ */
+int ngtcp2_dcidtr_retire_active_dcid(ngtcp2_dcidtr *dtr,
+                                     const ngtcp2_dcid *dcid, ngtcp2_tstamp ts,
+                                     ngtcp2_dcidtr_cb on_deactivate,
+                                     void *user_data);
+
+/*
+ * ngtcp2_dcidtr_retire_stale_bound_dcid retires stale bound
+ * Destination Connection ID.  For each retired ngtcp2_dcid,
+ * |on_retire|, if specified, is called with |user_data|.
+ */
+int ngtcp2_dcidtr_retire_stale_bound_dcid(ngtcp2_dcidtr *dtr,
+                                          ngtcp2_duration timeout,
+                                          ngtcp2_tstamp ts,
+                                          ngtcp2_dcidtr_cb on_retire,
+                                          void *user_data);
+
+/*
+ * ngtcp2_dcidtr_remove_stale_retired_dcid removes stale retired
+ * Destination Connection ID.  For each removed ngtcp2_dcid,
+ * |on_deactivate| is called with |user_data|.
+ *
+ * This function returns 0 if it succeeds, or negative error code that
+ * |on_deactivate| returns.
+ */
+int ngtcp2_dcidtr_remove_stale_retired_dcid(ngtcp2_dcidtr *dtr,
+                                            ngtcp2_duration timeout,
+                                            ngtcp2_tstamp ts,
+                                            ngtcp2_dcidtr_cb on_deactivate,
+                                            void *user_data);
+
+/*
+ * ngtcp2_dcidtr_pop_bound_dcid removes Destination Connection ID that
+ * is bound to |path|, and copies it into the object pointed by
+ * |dest|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     No ngtcp2_dcid bound to |path| found.
+ */
+int ngtcp2_dcidtr_pop_bound_dcid(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dest,
+                                 const ngtcp2_path *path);
+
+/*
+ * ngtcp2_dcidtr_earliest_bound_ts returns earliest timestamp when a
+ * Destination Connection ID is bound.  If there is no bound
+ * Destination Connection ID, this function returns UINT64_MAX.
+ */
+ngtcp2_tstamp ngtcp2_dcidtr_earliest_bound_ts(const ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_earliest_retired_ts returns earliest timestamp when a
+ * Destination Connection ID is retired.  If there is no retired
+ * Destination Connection ID, this function returns UINT64_MAX.
+ */
+ngtcp2_tstamp ngtcp2_dcidtr_earliest_retired_ts(const ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_push_unused adds new Destination Connection ID to the
+ * unused buffer.  |seq| is its sequence number, |cid| is its
+ * Connection ID, and |token| is its stateless reset token.  If the
+ * buffer space is full, the earliest ngtcp2_dcid is removed.
+ */
+void ngtcp2_dcidtr_push_unused(ngtcp2_dcidtr *dtr, uint64_t seq,
+                               const ngtcp2_cid *cid, const uint8_t *token);
+
+/*
+ * ngtcp2_dcidtr_pop_unused_cid_token removes an unused Destination
+ * Connection ID, and copies it into the object pointed by |dcid| with
+ * ngtcp2_dcid_copy_cid_token.  This function assumes that there is at
+ * least one unused Destination Connection ID.
+ */
+void ngtcp2_dcidtr_pop_unused_cid_token(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dcid);
+
+/*
+ * ngtcp2_dcidtr_pop_unused removes an unused Destination Connection
+ * ID, and copies it into the object pointed by |dcid| with
+ * ngtcp2_dcid_copy.  This function assumes that there is at least one
+ * unused Destination Connection ID.
+ */
+void ngtcp2_dcidtr_pop_unused(ngtcp2_dcidtr *dtr, ngtcp2_dcid *dcid);
+
+/*
+ * ngtcp2_dcidtr_check_path_retired returns nonzero if |path| is
+ * included in retired Destination Connection IDs.
+ */
+int ngtcp2_dcidtr_check_path_retired(const ngtcp2_dcidtr *dtr,
+                                     const ngtcp2_path *path);
+
+/*
+ * ngtcp2_dcidtr_unused_len returns the number of unused Destination
+ * Connection ID.
+ */
+size_t ngtcp2_dcidtr_unused_len(const ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_bound_len returns the number of bound Destination
+ * Connection ID.
+ */
+size_t ngtcp2_dcidtr_bound_len(const ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_retired_len returns the number of retired Destination
+ * Connection ID.
+ */
+size_t ngtcp2_dcidtr_retired_len(const ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_inactive_len returns the number of unused and bound
+ * Destination Connection ID.
+ */
+size_t ngtcp2_dcidtr_inactive_len(const ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_unused_full returns nonzero if the buffer of unused
+ * Destination Connection ID is full.
+ */
+int ngtcp2_dcidtr_unused_full(const ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_unused_empty returns nonzero if the buffer of unused
+ * Destination Connection ID is empty.
+ */
+int ngtcp2_dcidtr_unused_empty(const ngtcp2_dcidtr *dtr);
+
+/*
+ * ngtcp2_dcidtr_bound_full returns nonzero if the buffer of bound
+ * Destination Connection ID is full.
+ */
+int ngtcp2_dcidtr_bound_full(const ngtcp2_dcidtr *dtr);
+
+#endif /* !defined(NGTCP2_DCIDTR_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_err.c b/third_party/ngtcp2/lib/ngtcp2_err.c
new file mode 100644 (file)
index 0000000..5e4794c
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_err.h"
+
+const char *ngtcp2_strerror(int liberr) {
+  switch (liberr) {
+  case 0:
+    return "NO_ERROR";
+  case NGTCP2_ERR_INVALID_ARGUMENT:
+    return "ERR_INVALID_ARGUMENT";
+  case NGTCP2_ERR_NOBUF:
+    return "ERR_NOBUF";
+  case NGTCP2_ERR_PROTO:
+    return "ERR_PROTO";
+  case NGTCP2_ERR_INVALID_STATE:
+    return "ERR_INVALID_STATE";
+  case NGTCP2_ERR_ACK_FRAME:
+    return "ERR_ACK_FRAME";
+  case NGTCP2_ERR_STREAM_ID_BLOCKED:
+    return "ERR_STREAM_ID_BLOCKED";
+  case NGTCP2_ERR_STREAM_IN_USE:
+    return "ERR_STREAM_IN_USE";
+  case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+    return "ERR_STREAM_DATA_BLOCKED";
+  case NGTCP2_ERR_FLOW_CONTROL:
+    return "ERR_FLOW_CONTROL";
+  case NGTCP2_ERR_CONNECTION_ID_LIMIT:
+    return "ERR_CONNECTION_ID_LIMIT";
+  case NGTCP2_ERR_STREAM_LIMIT:
+    return "ERR_STREAM_LIMIT";
+  case NGTCP2_ERR_FINAL_SIZE:
+    return "ERR_FINAL_SIZE";
+  case NGTCP2_ERR_CRYPTO:
+    return "ERR_CRYPTO";
+  case NGTCP2_ERR_PKT_NUM_EXHAUSTED:
+    return "ERR_PKT_NUM_EXHAUSTED";
+  case NGTCP2_ERR_NOMEM:
+    return "ERR_NOMEM";
+  case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM:
+    return "ERR_REQUIRED_TRANSPORT_PARAM";
+  case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM:
+    return "ERR_MALFORMED_TRANSPORT_PARAM";
+  case NGTCP2_ERR_FRAME_ENCODING:
+    return "ERR_FRAME_ENCODING";
+  case NGTCP2_ERR_DECRYPT:
+    return "ERR_DECRYPT";
+  case NGTCP2_ERR_STREAM_SHUT_WR:
+    return "ERR_STREAM_SHUT_WR";
+  case NGTCP2_ERR_STREAM_NOT_FOUND:
+    return "ERR_STREAM_NOT_FOUND";
+  case NGTCP2_ERR_STREAM_STATE:
+    return "ERR_STREAM_STATE";
+  case NGTCP2_ERR_RECV_VERSION_NEGOTIATION:
+    return "ERR_RECV_VERSION_NEGOTIATION";
+  case NGTCP2_ERR_CLOSING:
+    return "ERR_CLOSING";
+  case NGTCP2_ERR_DRAINING:
+    return "ERR_DRAINING";
+  case NGTCP2_ERR_TRANSPORT_PARAM:
+    return "ERR_TRANSPORT_PARAM";
+  case NGTCP2_ERR_DISCARD_PKT:
+    return "ERR_DISCARD_PKT";
+  case NGTCP2_ERR_CONN_ID_BLOCKED:
+    return "ERR_CONN_ID_BLOCKED";
+  case NGTCP2_ERR_CALLBACK_FAILURE:
+    return "ERR_CALLBACK_FAILURE";
+  case NGTCP2_ERR_INTERNAL:
+    return "ERR_INTERNAL";
+  case NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED:
+    return "ERR_CRYPTO_BUFFER_EXCEEDED";
+  case NGTCP2_ERR_WRITE_MORE:
+    return "ERR_WRITE_MORE";
+  case NGTCP2_ERR_RETRY:
+    return "ERR_RETRY";
+  case NGTCP2_ERR_DROP_CONN:
+    return "ERR_DROP_CONN";
+  case NGTCP2_ERR_AEAD_LIMIT_REACHED:
+    return "ERR_AEAD_LIMIT_REACHED";
+  case NGTCP2_ERR_NO_VIABLE_PATH:
+    return "ERR_NO_VIABLE_PATH";
+  case NGTCP2_ERR_VERSION_NEGOTIATION:
+    return "ERR_VERSION_NEGOTIATION";
+  case NGTCP2_ERR_HANDSHAKE_TIMEOUT:
+    return "ERR_HANDSHAKE_TIMEOUT";
+  case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE:
+    return "ERR_VERSION_NEGOTIATION_FAILURE";
+  case NGTCP2_ERR_IDLE_CLOSE:
+    return "ERR_IDLE_CLOSE";
+  default:
+    return "(unknown)";
+  }
+}
+
+int ngtcp2_err_is_fatal(int liberr) { return liberr < NGTCP2_ERR_FATAL; }
+
+uint64_t ngtcp2_err_infer_quic_transport_error_code(int liberr) {
+  switch (liberr) {
+  case 0:
+    return NGTCP2_NO_ERROR;
+  case NGTCP2_ERR_ACK_FRAME:
+  case NGTCP2_ERR_FRAME_ENCODING:
+    return NGTCP2_FRAME_ENCODING_ERROR;
+  case NGTCP2_ERR_FLOW_CONTROL:
+    return NGTCP2_FLOW_CONTROL_ERROR;
+  case NGTCP2_ERR_CONNECTION_ID_LIMIT:
+    return NGTCP2_CONNECTION_ID_LIMIT_ERROR;
+  case NGTCP2_ERR_STREAM_LIMIT:
+    return NGTCP2_STREAM_LIMIT_ERROR;
+  case NGTCP2_ERR_FINAL_SIZE:
+    return NGTCP2_FINAL_SIZE_ERROR;
+  case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM:
+  case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM:
+  case NGTCP2_ERR_TRANSPORT_PARAM:
+    return NGTCP2_TRANSPORT_PARAMETER_ERROR;
+  case NGTCP2_ERR_INVALID_ARGUMENT:
+  case NGTCP2_ERR_NOMEM:
+  case NGTCP2_ERR_CALLBACK_FAILURE:
+  case NGTCP2_ERR_HANDSHAKE_TIMEOUT:
+  case NGTCP2_ERR_PKT_NUM_EXHAUSTED:
+  case NGTCP2_ERR_INTERNAL:
+    return NGTCP2_INTERNAL_ERROR;
+  case NGTCP2_ERR_STREAM_STATE:
+    return NGTCP2_STREAM_STATE_ERROR;
+  case NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED:
+    return NGTCP2_CRYPTO_BUFFER_EXCEEDED;
+  case NGTCP2_ERR_AEAD_LIMIT_REACHED:
+    return NGTCP2_AEAD_LIMIT_REACHED;
+  case NGTCP2_ERR_NO_VIABLE_PATH:
+    return NGTCP2_NO_VIABLE_PATH;
+  case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE:
+    return NGTCP2_VERSION_NEGOTIATION_ERROR;
+  default:
+    return NGTCP2_PROTOCOL_VIOLATION;
+  }
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_err.h b/third_party/ngtcp2/lib/ngtcp2_err.h
new file mode 100644 (file)
index 0000000..44527b1
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_ERR_H
+#define NGTCP2_ERR_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#endif /* !defined(NGTCP2_ERR_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_frame_chain.c b/third_party/ngtcp2/lib/ngtcp2_frame_chain.c
new file mode 100644 (file)
index 0000000..0f6b06a
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2023 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_frame_chain.h"
+
+#include <string.h>
+#include <assert.h>
+
+ngtcp2_objalloc_def(frame_chain, ngtcp2_frame_chain, oplent)
+
+int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc,
+                                    ngtcp2_objalloc *objalloc) {
+  *pfrc = ngtcp2_objalloc_frame_chain_get(objalloc);
+  if (*pfrc == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_frame_chain_init(*pfrc);
+
+  return 0;
+}
+
+int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen,
+                                    const ngtcp2_mem *mem) {
+  *pfrc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_frame_chain) + extralen);
+  if (*pfrc == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_frame_chain_init(*pfrc);
+
+  return 0;
+}
+
+int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc,
+                                                   size_t datacnt,
+                                                   ngtcp2_objalloc *objalloc,
+                                                   const ngtcp2_mem *mem) {
+  if (datacnt > NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES) {
+    return ngtcp2_frame_chain_extralen_new(pfrc,
+                                           sizeof(ngtcp2_vec) * (datacnt - 1) -
+                                             NGTCP2_FRAME_CHAIN_STREAM_AVAIL,
+                                           mem);
+  }
+
+  return ngtcp2_frame_chain_objalloc_new(pfrc, objalloc);
+}
+
+int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc,
+                                              const uint8_t *token,
+                                              size_t tokenlen,
+                                              ngtcp2_objalloc *objalloc,
+                                              const ngtcp2_mem *mem) {
+  int rv;
+  uint8_t *p;
+  ngtcp2_frame *fr;
+
+  if (tokenlen > NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES) {
+    rv = ngtcp2_frame_chain_extralen_new(
+      pfrc, tokenlen - NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES, mem);
+  } else {
+    rv = ngtcp2_frame_chain_objalloc_new(pfrc, objalloc);
+  }
+  if (rv != 0) {
+    return rv;
+  }
+
+  fr = &(*pfrc)->fr;
+  fr->type = NGTCP2_FRAME_NEW_TOKEN;
+
+  p = (uint8_t *)fr + sizeof(ngtcp2_new_token);
+  memcpy(p, token, tokenlen);
+
+  fr->new_token.token = p;
+  fr->new_token.tokenlen = tokenlen;
+
+  return 0;
+}
+
+void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, const ngtcp2_mem *mem) {
+  ngtcp2_frame_chain_binder *binder;
+
+  if (frc == NULL) {
+    return;
+  }
+
+  binder = frc->binder;
+  if (binder && --binder->refcount == 0) {
+    ngtcp2_mem_free(mem, binder);
+  }
+
+  ngtcp2_mem_free(mem, frc);
+}
+
+void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc,
+                                     ngtcp2_objalloc *objalloc,
+                                     const ngtcp2_mem *mem) {
+  ngtcp2_frame_chain_binder *binder;
+
+  if (frc == NULL) {
+    return;
+  }
+
+  switch (frc->fr.type) {
+  case NGTCP2_FRAME_CRYPTO:
+  case NGTCP2_FRAME_STREAM:
+    if (frc->fr.stream.datacnt > NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES) {
+      ngtcp2_frame_chain_del(frc, mem);
+
+      return;
+    }
+
+    break;
+  case NGTCP2_FRAME_NEW_TOKEN:
+    if (frc->fr.new_token.tokenlen > NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES) {
+      ngtcp2_frame_chain_del(frc, mem);
+
+      return;
+    }
+
+    break;
+  }
+
+  binder = frc->binder;
+  if (binder && --binder->refcount == 0) {
+    ngtcp2_mem_free(mem, binder);
+  }
+
+  frc->binder = NULL;
+
+  ngtcp2_objalloc_frame_chain_release(objalloc, frc);
+}
+
+void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc) {
+  frc->next = NULL;
+  frc->binder = NULL;
+}
+
+void ngtcp2_frame_chain_list_objalloc_del(ngtcp2_frame_chain *frc,
+                                          ngtcp2_objalloc *objalloc,
+                                          const ngtcp2_mem *mem) {
+  ngtcp2_frame_chain *next;
+
+  for (; frc; frc = next) {
+    next = frc->next;
+
+    ngtcp2_frame_chain_objalloc_del(frc, objalloc, mem);
+  }
+}
+
+int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder,
+                                  const ngtcp2_mem *mem) {
+  *pbinder = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_frame_chain_binder));
+  if (*pbinder == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  return 0;
+}
+
+int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b,
+                             const ngtcp2_mem *mem) {
+  ngtcp2_frame_chain_binder *binder;
+  int rv;
+
+  assert(b->binder == NULL);
+
+  if (a->binder == NULL) {
+    rv = ngtcp2_frame_chain_binder_new(&binder, mem);
+    if (rv != 0) {
+      return rv;
+    }
+
+    a->binder = binder;
+    ++a->binder->refcount;
+  }
+
+  b->binder = a->binder;
+  ++b->binder->refcount;
+
+  return 0;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_frame_chain.h b/third_party/ngtcp2/lib/ngtcp2_frame_chain.h
new file mode 100644 (file)
index 0000000..e7b3363
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2023 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_FRAME_CHAIN_H
+#define NGTCP2_FRAME_CHAIN_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_objalloc.h"
+
+/* NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE indicates that no flag is
+   set. */
+#define NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE 0x00u
+/* NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK indicates that an information
+   which a frame carries has been acknowledged. */
+#define NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK 0x01u
+
+/*
+ * ngtcp2_frame_chain_binder binds 2 or more of ngtcp2_frame_chain to
+ * share the acknowledgement state.  In general, all
+ * ngtcp2_frame_chains bound to the same binder must have the same
+ * information.
+ */
+typedef struct ngtcp2_frame_chain_binder {
+  size_t refcount;
+  /* flags is bitwise OR of zero or more of
+     NGTCP2_FRAME_CHAIN_BINDER_FLAG_*. */
+  uint32_t flags;
+} ngtcp2_frame_chain_binder;
+
+int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder,
+                                  const ngtcp2_mem *mem);
+
+typedef struct ngtcp2_frame_chain ngtcp2_frame_chain;
+
+/*
+ * ngtcp2_frame_chain chains frames in a single packet.
+ */
+struct ngtcp2_frame_chain {
+  union {
+    struct {
+      ngtcp2_frame_chain *next;
+      ngtcp2_frame_chain_binder *binder;
+      ngtcp2_frame fr;
+    };
+
+    ngtcp2_opl_entry oplent;
+  };
+};
+
+ngtcp2_objalloc_decl(frame_chain, ngtcp2_frame_chain, oplent)
+
+/*
+ * ngtcp2_bind_frame_chains binds two frame chains |a| and |b| using
+ * new or existing ngtcp2_frame_chain_binder.  |a| might have non-NULL
+ * a->binder.  |b| must not have non-NULL b->binder.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b,
+                             const ngtcp2_mem *mem);
+
+/* NGTCP2_MAX_STREAM_DATACNT is the maximum number of ngtcp2_vec that
+   a ngtcp2_stream can include. */
+#define NGTCP2_MAX_STREAM_DATACNT 256
+
+/*
+ * ngtcp2_frame_chain_objalloc_new allocates ngtcp2_frame_chain using
+ * |objalloc|.
+ */
+int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc,
+                                    ngtcp2_objalloc *objalloc);
+
+/*
+ * ngtcp2_frame_chain_extralen_new allocates ngtcp2_frame_chain
+ * followed by |extralen| bytes.
+ */
+int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen,
+                                    const ngtcp2_mem *mem);
+
+/* NGTCP2_FRAME_CHAIN_STREAM_AVAIL is the number of additional bytes
+   available after ngtcp2_stream when it is embedded in
+   ngtcp2_frame. */
+#define NGTCP2_FRAME_CHAIN_STREAM_AVAIL                                        \
+  (sizeof(ngtcp2_frame) - sizeof(ngtcp2_stream))
+
+/* NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES is the number of datacnt
+   that changes allocation method.  If datacnt is more than this
+   value, ngtcp2_frame_chain is allocated without ngtcp2_objalloc.
+   Otherwise, it is allocated using ngtcp2_objalloc.  */
+#define NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES                                \
+  (NGTCP2_FRAME_CHAIN_STREAM_AVAIL / sizeof(ngtcp2_vec) + 1)
+
+/* NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES is the length of a token that
+   changes allocation method.  If the length is more than this value,
+   ngtcp2_frame_chain is allocated without ngtcp2_objalloc.
+   Otherwise, it is allocated using ngtcp2_objalloc. */
+#define NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES                                     \
+  (sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token))
+
+/*
+ * ngtcp2_frame_chain_stream_datacnt_objalloc_new allocates enough
+ * data to store additional |datacnt| - 1 ngtcp2_vec object after
+ * ngtcp2_stream object.  If no additional space is required, in other
+ * words, |datacnt| <= NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES,
+ * ngtcp2_frame_chain_objalloc_new is called internally.  Otherwise,
+ * ngtcp2_frame_chain_extralen_new is used and objalloc is not used.
+ * Therefore, it is important to call ngtcp2_frame_chain_objalloc_del
+ * without changing datacnt field.
+ */
+int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc,
+                                                   size_t datacnt,
+                                                   ngtcp2_objalloc *objalloc,
+                                                   const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_frame_chain_new_token_objalloc_new allocates enough space to
+ * store the given token.  If |tokenlen| <=
+ * NGTCP2_FRAME_CHAIN_NEW_TOKEN_THRES, ngtcp2_frame_chain_objalloc_new
+ * is called internally.  Otherwise, ngtcp2_frame_chain_extralen_new
+ * is used, and objalloc is not used.
+ */
+int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc,
+                                              const uint8_t *token,
+                                              size_t tokenlen,
+                                              ngtcp2_objalloc *objalloc,
+                                              const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_frame_chain_del deallocates |frc|.  It also deallocates the
+ * memory pointed by |frc|.
+ */
+void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_frame_chain_objalloc_del adds |frc| to |objalloc| for reuse.
+ * It might just delete |frc| depending on the frame type and the size
+ * of |frc|.
+ */
+void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc,
+                                     ngtcp2_objalloc *objalloc,
+                                     const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_frame_chain_init initializes |frc|.
+ */
+void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc);
+
+/*
+ * ngtcp2_frame_chain_list_objalloc_del adds all ngtcp2_frame_chain
+ * linked from |frc| to |objalloc| for reuse.  Depending on the frame type
+ * and its size, ngtcp2_frame_chain might be deleted instead.
+ */
+void ngtcp2_frame_chain_list_objalloc_del(ngtcp2_frame_chain *frc,
+                                          ngtcp2_objalloc *objalloc,
+                                          const ngtcp2_mem *mem);
+
+#endif /* !defined(NGTCP2_FRAME_CHAIN_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_gaptr.c b/third_party/ngtcp2/lib/ngtcp2_gaptr.c
new file mode 100644 (file)
index 0000000..d04b963
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_gaptr.h"
+
+#include <string.h>
+#include <assert.h>
+
+void ngtcp2_gaptr_init(ngtcp2_gaptr *gaptr, const ngtcp2_mem *mem) {
+  ngtcp2_ksl_init(&gaptr->gap, ngtcp2_ksl_range_compar, ngtcp2_ksl_range_search,
+                  sizeof(ngtcp2_range), mem);
+
+  gaptr->mem = mem;
+}
+
+static int gaptr_gap_init(ngtcp2_gaptr *gaptr) {
+  ngtcp2_range range = {
+    .end = UINT64_MAX,
+  };
+
+  return ngtcp2_ksl_insert(&gaptr->gap, NULL, &range, NULL);
+}
+
+void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr) {
+  if (gaptr == NULL) {
+    return;
+  }
+
+  ngtcp2_ksl_free(&gaptr->gap);
+}
+
+int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, uint64_t datalen) {
+  int rv;
+  ngtcp2_range k, m, l, r;
+  ngtcp2_range q = {
+    .begin = offset,
+    .end = offset + datalen,
+  };
+  ngtcp2_ksl_it it;
+
+  if (ngtcp2_ksl_len(&gaptr->gap) == 0) {
+    rv = gaptr_gap_init(gaptr);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  it = ngtcp2_ksl_lower_bound_search(&gaptr->gap, &q,
+                                     ngtcp2_ksl_range_exclusive_search);
+
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    k = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it);
+    m = ngtcp2_range_intersect(&q, &k);
+    if (!ngtcp2_range_len(&m)) {
+      break;
+    }
+
+    if (ngtcp2_range_eq(&k, &m)) {
+      ngtcp2_ksl_remove_hint(&gaptr->gap, &it, &it, &k);
+      continue;
+    }
+
+    ngtcp2_range_cut(&l, &r, &k, &m);
+
+    if (ngtcp2_range_len(&l)) {
+      ngtcp2_ksl_update_key(&gaptr->gap, &k, &l);
+
+      if (ngtcp2_range_len(&r)) {
+        rv = ngtcp2_ksl_insert(&gaptr->gap, &it, &r, NULL);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+    } else if (ngtcp2_range_len(&r)) {
+      ngtcp2_ksl_update_key(&gaptr->gap, &k, &r);
+    }
+
+    ngtcp2_ksl_it_next(&it);
+  }
+
+  return 0;
+}
+
+uint64_t ngtcp2_gaptr_first_gap_offset(const ngtcp2_gaptr *gaptr) {
+  ngtcp2_ksl_it it;
+
+  if (ngtcp2_ksl_len(&gaptr->gap) == 0) {
+    return 0;
+  }
+
+  it = ngtcp2_ksl_begin(&gaptr->gap);
+
+  return ((ngtcp2_range *)ngtcp2_ksl_it_key(&it))->begin;
+}
+
+ngtcp2_range ngtcp2_gaptr_get_first_gap_after(const ngtcp2_gaptr *gaptr,
+                                              uint64_t offset) {
+  ngtcp2_range q = {
+    .begin = offset,
+    .end = offset + 1,
+  };
+  ngtcp2_ksl_it it;
+
+  if (ngtcp2_ksl_len(&gaptr->gap) == 0) {
+    ngtcp2_range r = {
+      .end = UINT64_MAX,
+    };
+    return r;
+  }
+
+  it = ngtcp2_ksl_lower_bound_search(&gaptr->gap, &q,
+                                     ngtcp2_ksl_range_exclusive_search);
+
+  assert(!ngtcp2_ksl_it_end(&it));
+
+  return *(ngtcp2_range *)ngtcp2_ksl_it_key(&it);
+}
+
+int ngtcp2_gaptr_is_pushed(const ngtcp2_gaptr *gaptr, uint64_t offset,
+                           uint64_t datalen) {
+  ngtcp2_range q = {
+    .begin = offset,
+    .end = offset + datalen,
+  };
+  ngtcp2_ksl_it it;
+  ngtcp2_range m;
+
+  if (ngtcp2_ksl_len(&gaptr->gap) == 0) {
+    return 0;
+  }
+
+  it = ngtcp2_ksl_lower_bound_search(&gaptr->gap, &q,
+                                     ngtcp2_ksl_range_exclusive_search);
+
+  assert(!ngtcp2_ksl_it_end(&it));
+
+  m = ngtcp2_range_intersect(&q, (ngtcp2_range *)ngtcp2_ksl_it_key(&it));
+
+  return ngtcp2_range_len(&m) == 0;
+}
+
+void ngtcp2_gaptr_drop_first_gap(ngtcp2_gaptr *gaptr) {
+  ngtcp2_ksl_it it;
+  ngtcp2_range r;
+
+  if (ngtcp2_ksl_len(&gaptr->gap) == 0) {
+    return;
+  }
+
+  it = ngtcp2_ksl_begin(&gaptr->gap);
+
+  assert(!ngtcp2_ksl_it_end(&it));
+
+  r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it);
+
+  ngtcp2_ksl_remove_hint(&gaptr->gap, NULL, &it, &r);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_gaptr.h b/third_party/ngtcp2/lib/ngtcp2_gaptr.h
new file mode 100644 (file)
index 0000000..3120676
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_GAPTR_H
+#define NGTCP2_GAPTR_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+#include "ngtcp2_ksl.h"
+#include "ngtcp2_range.h"
+
+/*
+ * ngtcp2_gaptr maintains the gap in the range [0, UINT64_MAX).
+ */
+typedef struct ngtcp2_gaptr {
+  /* gap maintains the range of offset which is not pushed
+     yet. Initially, its range is [0, UINT64_MAX).  "gap" is the range
+     that is not pushed yet. */
+  ngtcp2_ksl gap;
+  /* mem is custom memory allocator */
+  const ngtcp2_mem *mem;
+} ngtcp2_gaptr;
+
+/*
+ * ngtcp2_gaptr_init initializes |gaptr|.
+ */
+void ngtcp2_gaptr_init(ngtcp2_gaptr *gaptr, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_gaptr_free frees resources allocated for |gaptr|.
+ */
+void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr);
+
+/*
+ * ngtcp2_gaptr_push pushes the range [offset, offset + datalen).
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, uint64_t datalen);
+
+/*
+ * ngtcp2_gaptr_first_gap_offset returns the offset to the first gap.
+ * If there is no gap, it returns UINT64_MAX.
+ */
+uint64_t ngtcp2_gaptr_first_gap_offset(const ngtcp2_gaptr *gaptr);
+
+/*
+ * ngtcp2_gaptr_get_first_gap_after returns the first gap which
+ * includes or comes after |offset|.
+ */
+ngtcp2_range ngtcp2_gaptr_get_first_gap_after(const ngtcp2_gaptr *gaptr,
+                                              uint64_t offset);
+
+/*
+ * ngtcp2_gaptr_is_pushed returns nonzero if range [offset, offset +
+ * datalen) is completely pushed into this object.
+ */
+int ngtcp2_gaptr_is_pushed(const ngtcp2_gaptr *gaptr, uint64_t offset,
+                           uint64_t datalen);
+
+/*
+ * ngtcp2_gaptr_drop_first_gap deletes the first gap entirely as if
+ * the range is pushed.  This function assumes that at least one gap
+ * exists.
+ */
+void ngtcp2_gaptr_drop_first_gap(ngtcp2_gaptr *gaptr);
+
+#endif /* !defined(NGTCP2_GAPTR_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_idtr.c b/third_party/ngtcp2/lib/ngtcp2_idtr.c
new file mode 100644 (file)
index 0000000..2cf9d3c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_idtr.h"
+
+#include <assert.h>
+
+void ngtcp2_idtr_init(ngtcp2_idtr *idtr, const ngtcp2_mem *mem) {
+  ngtcp2_gaptr_init(&idtr->gap, mem);
+}
+
+void ngtcp2_idtr_free(ngtcp2_idtr *idtr) {
+  if (idtr == NULL) {
+    return;
+  }
+
+  ngtcp2_gaptr_free(&idtr->gap);
+}
+
+/*
+ * id_from_stream_id translates |stream_id| to an internal ID.
+ */
+static uint64_t id_from_stream_id(int64_t stream_id) {
+  return (uint64_t)(stream_id >> 2);
+}
+
+int ngtcp2_idtr_open(ngtcp2_idtr *idtr, int64_t stream_id) {
+  uint64_t q;
+
+  q = id_from_stream_id(stream_id);
+
+  if (ngtcp2_gaptr_is_pushed(&idtr->gap, q, 1)) {
+    return NGTCP2_ERR_STREAM_IN_USE;
+  }
+
+  return ngtcp2_gaptr_push(&idtr->gap, q, 1);
+}
+
+int ngtcp2_idtr_is_open(const ngtcp2_idtr *idtr, int64_t stream_id) {
+  uint64_t q;
+
+  q = id_from_stream_id(stream_id);
+
+  return ngtcp2_gaptr_is_pushed(&idtr->gap, q, 1);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_idtr.h b/third_party/ngtcp2/lib/ngtcp2_idtr.h
new file mode 100644 (file)
index 0000000..0671f5e
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_IDTR_H
+#define NGTCP2_IDTR_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+#include "ngtcp2_gaptr.h"
+
+/*
+ * ngtcp2_idtr tracks the usage of stream ID.
+ */
+typedef struct ngtcp2_idtr {
+  /* gap maintains the range of an internal ID which is not used yet.
+     Initially, its range is [0, UINT64_MAX).  The internal ID and
+     stream ID are in the different number spaces.  See
+     id_from_stream_id to convert a stream ID to an internal ID. */
+  ngtcp2_gaptr gap;
+} ngtcp2_idtr;
+
+/*
+ * ngtcp2_idtr_init initializes |idtr|.
+ */
+void ngtcp2_idtr_init(ngtcp2_idtr *idtr, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_idtr_free frees resources allocated for |idtr|.
+ */
+void ngtcp2_idtr_free(ngtcp2_idtr *idtr);
+
+/*
+ * ngtcp2_idtr_open claims that |stream_id| is in use.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_STREAM_IN_USE
+ *     |stream_id| has already been used.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_idtr_open(ngtcp2_idtr *idtr, int64_t stream_id);
+
+/*
+ * ngtcp2_idtr_open returns nonzero if |stream_id| is in use.
+ */
+int ngtcp2_idtr_is_open(const ngtcp2_idtr *idtr, int64_t stream_id);
+
+#endif /* !defined(NGTCP2_IDTR_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_ksl.c b/third_party/ngtcp2/lib/ngtcp2_ksl.c
new file mode 100644 (file)
index 0000000..22c131a
--- /dev/null
@@ -0,0 +1,856 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_ksl.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "ngtcp2_macro.h"
+#include "ngtcp2_mem.h"
+#include "ngtcp2_range.h"
+
+static ngtcp2_ksl_blk null_blk;
+
+ngtcp2_objalloc_def(ksl_blk, ngtcp2_ksl_blk, oplent)
+
+static size_t ksl_nodelen(size_t keylen) {
+  assert(keylen >= sizeof(uint64_t));
+
+  return (sizeof(ngtcp2_ksl_node) + keylen - sizeof(uint64_t) + 0x7u) &
+         ~(uintptr_t)0x7u;
+}
+
+static size_t ksl_blklen(size_t nodelen) {
+  return sizeof(ngtcp2_ksl_blk) + nodelen * NGTCP2_KSL_MAX_NBLK -
+         sizeof(uint64_t);
+}
+
+/*
+ * ksl_node_set_key sets |key| to |node|.
+ */
+static void ksl_node_set_key(ngtcp2_ksl *ksl, ngtcp2_ksl_node *node,
+                             const void *key) {
+  memcpy(node->key, key, ksl->keylen);
+}
+
+void ngtcp2_ksl_init(ngtcp2_ksl *ksl, ngtcp2_ksl_compar compar,
+                     ngtcp2_ksl_search search, size_t keylen,
+                     const ngtcp2_mem *mem) {
+  size_t nodelen = ksl_nodelen(keylen);
+
+  ngtcp2_objalloc_init(&ksl->blkalloc,
+                       (ksl_blklen(nodelen) + 0xfu) & ~(uintptr_t)0xfu, mem);
+
+  ksl->head = NULL;
+  ksl->front = ksl->back = NULL;
+  ksl->compar = compar;
+  ksl->search = search;
+  ksl->n = 0;
+  ksl->keylen = keylen;
+  ksl->nodelen = nodelen;
+}
+
+static ngtcp2_ksl_blk *ksl_blk_objalloc_new(ngtcp2_ksl *ksl) {
+  return ngtcp2_objalloc_ksl_blk_len_get(&ksl->blkalloc,
+                                         ksl_blklen(ksl->nodelen));
+}
+
+static void ksl_blk_objalloc_del(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) {
+  ngtcp2_objalloc_ksl_blk_release(&ksl->blkalloc, blk);
+}
+
+static int ksl_head_init(ngtcp2_ksl *ksl) {
+  ngtcp2_ksl_blk *head = ksl_blk_objalloc_new(ksl);
+
+  if (!head) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  head->next = head->prev = NULL;
+  head->n = 0;
+  head->leaf = 1;
+
+  ksl->head = head;
+  ksl->front = ksl->back = head;
+
+  return 0;
+}
+
+#ifdef NOMEMPOOL
+/*
+ * ksl_free_blk frees |blk| recursively.
+ */
+static void ksl_free_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) {
+  size_t i;
+
+  if (!blk->leaf) {
+    for (i = 0; i < blk->n; ++i) {
+      ksl_free_blk(ksl, ngtcp2_ksl_nth_node(ksl, blk, i)->blk);
+    }
+  }
+
+  ksl_blk_objalloc_del(ksl, blk);
+}
+#endif /* defined(NOMEMPOOL) */
+
+void ngtcp2_ksl_free(ngtcp2_ksl *ksl) {
+  if (!ksl || !ksl->head) {
+    return;
+  }
+
+#ifdef NOMEMPOOL
+  ksl_free_blk(ksl, ksl->head);
+#endif /* defined(NOMEMPOOL) */
+
+  ngtcp2_objalloc_free(&ksl->blkalloc);
+}
+
+/*
+ * ksl_split_blk splits |blk| into 2 ngtcp2_ksl_blk objects.  The new
+ * ngtcp2_ksl_blk is always the "right" block.
+ *
+ * It returns the pointer to the ngtcp2_ksl_blk created which is the
+ * located at the right of |blk|, or NULL which indicates out of
+ * memory error.
+ */
+static ngtcp2_ksl_blk *ksl_split_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) {
+  ngtcp2_ksl_blk *rblk;
+
+  rblk = ksl_blk_objalloc_new(ksl);
+  if (rblk == NULL) {
+    return NULL;
+  }
+
+  rblk->next = blk->next;
+  blk->next = rblk;
+
+  if (rblk->next) {
+    rblk->next->prev = rblk;
+  } else if (ksl->back == blk) {
+    ksl->back = rblk;
+  }
+
+  rblk->prev = blk;
+  rblk->leaf = blk->leaf;
+
+  rblk->n = blk->n / 2;
+  blk->n -= rblk->n;
+
+  memcpy(rblk->nodes, blk->nodes + ksl->nodelen * blk->n,
+         ksl->nodelen * rblk->n);
+
+  assert(blk->n >= NGTCP2_KSL_MIN_NBLK);
+  assert(rblk->n >= NGTCP2_KSL_MIN_NBLK);
+
+  return rblk;
+}
+
+/*
+ * ksl_split_node splits a node included in |blk| at the position |i|
+ * into 2 adjacent nodes.  The new node is always inserted at the
+ * position |i+1|.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int ksl_split_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) {
+  ngtcp2_ksl_node *node;
+  ngtcp2_ksl_blk *lblk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk, *rblk;
+
+  rblk = ksl_split_blk(ksl, lblk);
+  if (rblk == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  memmove(blk->nodes + (i + 2) * ksl->nodelen,
+          blk->nodes + (i + 1) * ksl->nodelen,
+          ksl->nodelen * (blk->n - (i + 1)));
+
+  node = ngtcp2_ksl_nth_node(ksl, blk, i + 1);
+  node->blk = rblk;
+  ++blk->n;
+  ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, rblk, rblk->n - 1)->key);
+
+  node = ngtcp2_ksl_nth_node(ksl, blk, i);
+  ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key);
+
+  return 0;
+}
+
+/*
+ * ksl_split_head splits a head (root) block.  It increases the height
+ * of skip list by 1.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int ksl_split_head(ngtcp2_ksl *ksl) {
+  ngtcp2_ksl_blk *rblk = NULL, *lblk, *nhead = NULL;
+  ngtcp2_ksl_node *node;
+
+  rblk = ksl_split_blk(ksl, ksl->head);
+  if (rblk == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  lblk = ksl->head;
+
+  nhead = ksl_blk_objalloc_new(ksl);
+
+  if (nhead == NULL) {
+    ksl_blk_objalloc_del(ksl, rblk);
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  nhead->next = nhead->prev = NULL;
+  nhead->n = 2;
+  nhead->leaf = 0;
+
+  node = ngtcp2_ksl_nth_node(ksl, nhead, 0);
+  ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key);
+  node->blk = lblk;
+
+  node = ngtcp2_ksl_nth_node(ksl, nhead, 1);
+  ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, rblk, rblk->n - 1)->key);
+  node->blk = rblk;
+
+  ksl->head = nhead;
+
+  return 0;
+}
+
+/*
+ * ksl_insert_node inserts a node whose key is |key| with the
+ * associated |data| at the index of |i|.  This function assumes that
+ * the number of nodes contained by |blk| is strictly less than
+ * NGTCP2_KSL_MAX_NBLK.
+ */
+static void ksl_insert_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i,
+                            const ngtcp2_ksl_key *key, void *data) {
+  ngtcp2_ksl_node *node;
+
+  assert(blk->n < NGTCP2_KSL_MAX_NBLK);
+
+  memmove(blk->nodes + (i + 1) * ksl->nodelen, blk->nodes + i * ksl->nodelen,
+          ksl->nodelen * (blk->n - i));
+
+  node = ngtcp2_ksl_nth_node(ksl, blk, i);
+  ksl_node_set_key(ksl, node, key);
+  node->data = data;
+
+  ++blk->n;
+}
+
+int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it,
+                      const ngtcp2_ksl_key *key, void *data) {
+  ngtcp2_ksl_blk *blk;
+  ngtcp2_ksl_node *node;
+  size_t i;
+  int rv;
+
+  if (!ksl->head) {
+    rv = ksl_head_init(ksl);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (ksl->head->n == NGTCP2_KSL_MAX_NBLK) {
+    rv = ksl_split_head(ksl);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  blk = ksl->head;
+
+  for (;;) {
+    i = ksl->search(ksl, blk, key);
+
+    if (blk->leaf) {
+      if (i < blk->n &&
+          !ksl->compar(key, ngtcp2_ksl_nth_node(ksl, blk, i)->key)) {
+        if (it) {
+          *it = ngtcp2_ksl_end(ksl);
+        }
+
+        return NGTCP2_ERR_INVALID_ARGUMENT;
+      }
+
+      ksl_insert_node(ksl, blk, i, key, data);
+      ++ksl->n;
+
+      if (it) {
+        ngtcp2_ksl_it_init(it, ksl, blk, i);
+      }
+
+      return 0;
+    }
+
+    if (i == blk->n) {
+      /* This insertion extends the largest key in this subtree. */
+      for (; !blk->leaf;) {
+        node = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1);
+        if (node->blk->n == NGTCP2_KSL_MAX_NBLK) {
+          rv = ksl_split_node(ksl, blk, blk->n - 1);
+          if (rv != 0) {
+            return rv;
+          }
+
+          node = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1);
+        }
+
+        ksl_node_set_key(ksl, node, key);
+        blk = node->blk;
+      }
+
+      ksl_insert_node(ksl, blk, blk->n, key, data);
+      ++ksl->n;
+
+      if (it) {
+        ngtcp2_ksl_it_init(it, ksl, blk, blk->n - 1);
+      }
+
+      return 0;
+    }
+
+    node = ngtcp2_ksl_nth_node(ksl, blk, i);
+
+    if (node->blk->n == NGTCP2_KSL_MAX_NBLK) {
+      rv = ksl_split_node(ksl, blk, i);
+      if (rv != 0) {
+        return rv;
+      }
+
+      if (ksl->compar((ngtcp2_ksl_key *)node->key, key)) {
+        node = ngtcp2_ksl_nth_node(ksl, blk, i + 1);
+
+        if (ksl->compar((ngtcp2_ksl_key *)node->key, key)) {
+          ksl_node_set_key(ksl, node, key);
+        }
+      }
+    }
+
+    blk = node->blk;
+  }
+}
+
+/*
+ * ksl_remove_node removes the node included in |blk| at the index of
+ * |i|.
+ */
+static void ksl_remove_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) {
+  memmove(blk->nodes + i * ksl->nodelen, blk->nodes + (i + 1) * ksl->nodelen,
+          ksl->nodelen * (blk->n - (i + 1)));
+
+  --blk->n;
+}
+
+/*
+ * ksl_merge_node merges 2 nodes which are the nodes at the index of
+ * |i| and |i + 1|.
+ *
+ * If |blk| is the head (root) block and it contains just 2 nodes
+ * before merging nodes, the merged block becomes head block, which
+ * decreases the height of |ksl| by 1.
+ *
+ * This function returns the pointer to the merged block.
+ */
+static ngtcp2_ksl_blk *ksl_merge_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk,
+                                      size_t i) {
+  ngtcp2_ksl_node *lnode;
+  ngtcp2_ksl_blk *lblk, *rblk;
+
+  assert(i + 1 < blk->n);
+
+  lnode = ngtcp2_ksl_nth_node(ksl, blk, i);
+
+  lblk = lnode->blk;
+  rblk = ngtcp2_ksl_nth_node(ksl, blk, i + 1)->blk;
+
+  assert(lblk->n + rblk->n < NGTCP2_KSL_MAX_NBLK);
+
+  memcpy(lblk->nodes + ksl->nodelen * lblk->n, rblk->nodes,
+         ksl->nodelen * rblk->n);
+
+  lblk->n += rblk->n;
+  lblk->next = rblk->next;
+
+  if (lblk->next) {
+    lblk->next->prev = lblk;
+  } else if (ksl->back == rblk) {
+    ksl->back = lblk;
+  }
+
+  ksl_blk_objalloc_del(ksl, rblk);
+
+  if (ksl->head == blk && blk->n == 2) {
+    ksl_blk_objalloc_del(ksl, ksl->head);
+    ksl->head = lblk;
+  } else {
+    ksl_remove_node(ksl, blk, i + 1);
+    ksl_node_set_key(ksl, lnode,
+                     ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key);
+  }
+
+  return lblk;
+}
+
+/*
+ * ksl_shift_left moves the first nodes in blk->nodes[i]->blk->nodes
+ * to blk->nodes[i - 1]->blk->nodes in a manner that they have the
+ * same amount of nodes as much as possible.
+ */
+static void ksl_shift_left(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) {
+  ngtcp2_ksl_node *lnode, *rnode;
+  ngtcp2_ksl_blk *lblk, *rblk;
+  size_t n;
+
+  assert(i > 0);
+
+  lnode = ngtcp2_ksl_nth_node(ksl, blk, i - 1);
+  rnode = ngtcp2_ksl_nth_node(ksl, blk, i);
+
+  lblk = lnode->blk;
+  rblk = rnode->blk;
+
+  assert(lblk->n < NGTCP2_KSL_MAX_NBLK);
+  assert(rblk->n > NGTCP2_KSL_MIN_NBLK);
+
+  n = (lblk->n + rblk->n + 1) / 2 - lblk->n;
+
+  assert(n > 0);
+  assert(lblk->n <= NGTCP2_KSL_MAX_NBLK - n);
+  assert(rblk->n >= NGTCP2_KSL_MIN_NBLK + n);
+
+  memcpy(lblk->nodes + ksl->nodelen * lblk->n, rblk->nodes, ksl->nodelen * n);
+
+  lblk->n += (uint32_t)n;
+  rblk->n -= (uint32_t)n;
+
+  ksl_node_set_key(ksl, lnode,
+                   ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key);
+
+  memmove(rblk->nodes, rblk->nodes + ksl->nodelen * n, ksl->nodelen * rblk->n);
+}
+
+/*
+ * ksl_shift_right moves the last nodes in blk->nodes[i]->blk->nodes
+ * to blk->nodes[i + 1]->blk->nodes in a manner that they have the
+ * same amount of nodes as much as possible.
+ */
+static void ksl_shift_right(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) {
+  ngtcp2_ksl_node *lnode, *rnode;
+  ngtcp2_ksl_blk *lblk, *rblk;
+  size_t n;
+
+  assert(i < blk->n - 1);
+
+  lnode = ngtcp2_ksl_nth_node(ksl, blk, i);
+  rnode = ngtcp2_ksl_nth_node(ksl, blk, i + 1);
+
+  lblk = lnode->blk;
+  rblk = rnode->blk;
+
+  assert(lblk->n > NGTCP2_KSL_MIN_NBLK);
+  assert(rblk->n < NGTCP2_KSL_MAX_NBLK);
+
+  n = (lblk->n + rblk->n + 1) / 2 - rblk->n;
+
+  assert(n > 0);
+  assert(lblk->n >= NGTCP2_KSL_MIN_NBLK + n);
+  assert(rblk->n <= NGTCP2_KSL_MAX_NBLK - n);
+
+  memmove(rblk->nodes + ksl->nodelen * n, rblk->nodes, ksl->nodelen * rblk->n);
+
+  rblk->n += (uint32_t)n;
+  lblk->n -= (uint32_t)n;
+
+  memcpy(rblk->nodes, lblk->nodes + ksl->nodelen * lblk->n, ksl->nodelen * n);
+
+  ksl_node_set_key(ksl, lnode,
+                   ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key);
+}
+
+/*
+ * key_equal returns nonzero if |lhs| and |rhs| are equal using the
+ * function |compar|.
+ */
+static int key_equal(ngtcp2_ksl_compar compar, const ngtcp2_ksl_key *lhs,
+                     const ngtcp2_ksl_key *rhs) {
+  return !compar(lhs, rhs) && !compar(rhs, lhs);
+}
+
+int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it,
+                           const ngtcp2_ksl_it *hint,
+                           const ngtcp2_ksl_key *key) {
+  ngtcp2_ksl_blk *blk = hint->blk;
+
+  assert(ksl->head);
+
+  if (blk->n <= NGTCP2_KSL_MIN_NBLK) {
+    return ngtcp2_ksl_remove(ksl, it, key);
+  }
+
+  ksl_remove_node(ksl, blk, hint->i);
+
+  --ksl->n;
+
+  if (it) {
+    if (hint->i == blk->n && blk->next) {
+      ngtcp2_ksl_it_init(it, ksl, blk->next, 0);
+    } else {
+      ngtcp2_ksl_it_init(it, ksl, blk, hint->i);
+    }
+  }
+
+  return 0;
+}
+
+int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it,
+                      const ngtcp2_ksl_key *key) {
+  ngtcp2_ksl_blk *blk = ksl->head;
+  ngtcp2_ksl_node *node;
+  size_t i;
+
+  if (!blk) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (!blk->leaf && blk->n == 2 &&
+      ngtcp2_ksl_nth_node(ksl, blk, 0)->blk->n == NGTCP2_KSL_MIN_NBLK &&
+      ngtcp2_ksl_nth_node(ksl, blk, 1)->blk->n == NGTCP2_KSL_MIN_NBLK) {
+    blk = ksl_merge_node(ksl, blk, 0);
+  }
+
+  for (;;) {
+    i = ksl->search(ksl, blk, key);
+
+    if (i == blk->n) {
+      if (it) {
+        *it = ngtcp2_ksl_end(ksl);
+      }
+
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    if (blk->leaf) {
+      if (ksl->compar(key, ngtcp2_ksl_nth_node(ksl, blk, i)->key)) {
+        if (it) {
+          *it = ngtcp2_ksl_end(ksl);
+        }
+
+        return NGTCP2_ERR_INVALID_ARGUMENT;
+      }
+
+      ksl_remove_node(ksl, blk, i);
+      --ksl->n;
+
+      if (it) {
+        if (blk->n == i && blk->next) {
+          ngtcp2_ksl_it_init(it, ksl, blk->next, 0);
+        } else {
+          ngtcp2_ksl_it_init(it, ksl, blk, i);
+        }
+      }
+
+      return 0;
+    }
+
+    node = ngtcp2_ksl_nth_node(ksl, blk, i);
+
+    if (node->blk->n > NGTCP2_KSL_MIN_NBLK) {
+      blk = node->blk;
+      continue;
+    }
+
+    assert(node->blk->n == NGTCP2_KSL_MIN_NBLK);
+
+    if (i + 1 < blk->n &&
+        ngtcp2_ksl_nth_node(ksl, blk, i + 1)->blk->n > NGTCP2_KSL_MIN_NBLK) {
+      ksl_shift_left(ksl, blk, i + 1);
+      blk = node->blk;
+
+      continue;
+    }
+
+    if (i > 0 &&
+        ngtcp2_ksl_nth_node(ksl, blk, i - 1)->blk->n > NGTCP2_KSL_MIN_NBLK) {
+      ksl_shift_right(ksl, blk, i - 1);
+      blk = node->blk;
+
+      continue;
+    }
+
+    if (i + 1 < blk->n) {
+      blk = ksl_merge_node(ksl, blk, i);
+      continue;
+    }
+
+    assert(i > 0);
+
+    blk = ksl_merge_node(ksl, blk, i - 1);
+  }
+}
+
+ngtcp2_ksl_it ngtcp2_ksl_lower_bound(const ngtcp2_ksl *ksl,
+                                     const ngtcp2_ksl_key *key) {
+  return ngtcp2_ksl_lower_bound_search(ksl, key, ksl->search);
+}
+
+ngtcp2_ksl_it ngtcp2_ksl_lower_bound_search(const ngtcp2_ksl *ksl,
+                                            const ngtcp2_ksl_key *key,
+                                            ngtcp2_ksl_search search) {
+  ngtcp2_ksl_blk *blk = ksl->head;
+  ngtcp2_ksl_it it;
+  size_t i;
+
+  if (!blk) {
+    ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0);
+    return it;
+  }
+
+  for (;;) {
+    i = search(ksl, blk, key);
+
+    if (blk->leaf) {
+      if (i == blk->n && blk->next) {
+        blk = blk->next;
+        i = 0;
+      }
+
+      ngtcp2_ksl_it_init(&it, ksl, blk, i);
+
+      return it;
+    }
+
+    if (i == blk->n) {
+      /* This happens if descendant has smaller key.  Fast forward to
+         find last node in this subtree. */
+      for (; !blk->leaf; blk = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1)->blk)
+        ;
+
+      if (blk->next) {
+        blk = blk->next;
+        i = 0;
+      } else {
+        i = blk->n;
+      }
+
+      ngtcp2_ksl_it_init(&it, ksl, blk, i);
+
+      return it;
+    }
+
+    blk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk;
+  }
+}
+
+void ngtcp2_ksl_update_key(ngtcp2_ksl *ksl, const ngtcp2_ksl_key *old_key,
+                           const ngtcp2_ksl_key *new_key) {
+  ngtcp2_ksl_blk *blk = ksl->head;
+  ngtcp2_ksl_node *node;
+  size_t i;
+
+  assert(ksl->head);
+
+  for (;;) {
+    i = ksl->search(ksl, blk, old_key);
+
+    assert(i < blk->n);
+    node = ngtcp2_ksl_nth_node(ksl, blk, i);
+
+    if (blk->leaf) {
+      assert(key_equal(ksl->compar, (ngtcp2_ksl_key *)node->key, old_key));
+      ksl_node_set_key(ksl, node, new_key);
+
+      return;
+    }
+
+    if (key_equal(ksl->compar, (ngtcp2_ksl_key *)node->key, old_key) ||
+        ksl->compar((ngtcp2_ksl_key *)node->key, new_key)) {
+      ksl_node_set_key(ksl, node, new_key);
+    }
+
+    blk = node->blk;
+  }
+}
+
+size_t ngtcp2_ksl_len(const ngtcp2_ksl *ksl) { return ksl->n; }
+
+void ngtcp2_ksl_clear(ngtcp2_ksl *ksl) {
+  if (!ksl->head) {
+    return;
+  }
+
+#ifdef NOMEMPOOL
+  ksl_free_blk(ksl, ksl->head);
+#endif /* defined(NOMEMPOOL) */
+
+  ksl->front = ksl->back = ksl->head = NULL;
+  ksl->n = 0;
+
+  ngtcp2_objalloc_clear(&ksl->blkalloc);
+}
+
+#ifndef WIN32
+static void ksl_print(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk,
+                      size_t level) {
+  size_t i;
+  ngtcp2_ksl_node *node;
+
+  fprintf(stderr, "LV=%zu n=%u\n", level, blk->n);
+
+  if (blk->leaf) {
+    for (i = 0; i < blk->n; ++i) {
+      node = ngtcp2_ksl_nth_node(ksl, blk, i);
+      fprintf(stderr, " %" PRId64, *(int64_t *)(void *)node->key);
+    }
+
+    fprintf(stderr, "\n");
+
+    return;
+  }
+
+  for (i = 0; i < blk->n; ++i) {
+    ksl_print(ksl, ngtcp2_ksl_nth_node(ksl, blk, i)->blk, level + 1);
+  }
+}
+
+void ngtcp2_ksl_print(const ngtcp2_ksl *ksl) {
+  if (!ksl->head) {
+    return;
+  }
+
+  ksl_print(ksl, ksl->head, 0);
+}
+#endif /* !defined(WIN32) */
+
+ngtcp2_ksl_it ngtcp2_ksl_begin(const ngtcp2_ksl *ksl) {
+  ngtcp2_ksl_it it;
+
+  if (ksl->head) {
+    ngtcp2_ksl_it_init(&it, ksl, ksl->front, 0);
+  } else {
+    ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0);
+  }
+
+  return it;
+}
+
+ngtcp2_ksl_it ngtcp2_ksl_end(const ngtcp2_ksl *ksl) {
+  ngtcp2_ksl_it it;
+
+  if (ksl->head) {
+    ngtcp2_ksl_it_init(&it, ksl, ksl->back, ksl->back->n);
+  } else {
+    ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0);
+  }
+
+  return it;
+}
+
+void ngtcp2_ksl_it_init(ngtcp2_ksl_it *it, const ngtcp2_ksl *ksl,
+                        ngtcp2_ksl_blk *blk, size_t i) {
+  it->ksl = ksl;
+  it->blk = blk;
+  it->i = i;
+}
+
+void ngtcp2_ksl_it_prev(ngtcp2_ksl_it *it) {
+  assert(!ngtcp2_ksl_it_begin(it));
+
+  if (it->i == 0) {
+    it->blk = it->blk->prev;
+    it->i = it->blk->n - 1;
+  } else {
+    --it->i;
+  }
+}
+
+int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it) {
+  return it->i == 0 && it->blk->prev == NULL;
+}
+
+int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs,
+                            const ngtcp2_ksl_key *rhs) {
+  const ngtcp2_range *a = lhs, *b = rhs;
+  return a->begin < b->begin;
+}
+
+ngtcp2_ksl_search_def(range, ngtcp2_ksl_range_compar)
+
+size_t ngtcp2_ksl_range_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk,
+                               const ngtcp2_ksl_key *key) {
+  return ksl_range_search(ksl, blk, key);
+}
+
+int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs,
+                                      const ngtcp2_ksl_key *rhs) {
+  const ngtcp2_range *a = lhs, *b = rhs;
+  return a->begin < b->begin && !(ngtcp2_max_uint64(a->begin, b->begin) <
+                                  ngtcp2_min_uint64(a->end, b->end));
+}
+
+ngtcp2_ksl_search_def(range_exclusive, ngtcp2_ksl_range_exclusive_compar)
+
+size_t ngtcp2_ksl_range_exclusive_search(const ngtcp2_ksl *ksl,
+                                         ngtcp2_ksl_blk *blk,
+                                         const ngtcp2_ksl_key *key) {
+  return ksl_range_exclusive_search(ksl, blk, key);
+}
+
+int ngtcp2_ksl_uint64_less(const ngtcp2_ksl_key *lhs,
+                           const ngtcp2_ksl_key *rhs) {
+  return *(uint64_t *)lhs < *(uint64_t *)rhs;
+}
+
+ngtcp2_ksl_search_def(uint64_less, ngtcp2_ksl_uint64_less)
+
+size_t ngtcp2_ksl_uint64_less_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk,
+                                     const ngtcp2_ksl_key *key) {
+  return ksl_uint64_less_search(ksl, blk, key);
+}
+
+int ngtcp2_ksl_int64_greater(const ngtcp2_ksl_key *lhs,
+                             const ngtcp2_ksl_key *rhs) {
+  return *(int64_t *)lhs > *(int64_t *)rhs;
+}
+
+ngtcp2_ksl_search_def(int64_greater, ngtcp2_ksl_int64_greater)
+
+size_t ngtcp2_ksl_int64_greater_search(const ngtcp2_ksl *ksl,
+                                       ngtcp2_ksl_blk *blk,
+                                       const ngtcp2_ksl_key *key) {
+  return ksl_int64_greater_search(ksl, blk, key);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_ksl.h b/third_party/ngtcp2/lib/ngtcp2_ksl.h
new file mode 100644 (file)
index 0000000..de78bcb
--- /dev/null
@@ -0,0 +1,425 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_KSL_H
+#define NGTCP2_KSL_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <stdlib.h>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_objalloc.h"
+
+#define NGTCP2_KSL_DEGR 16
+/* NGTCP2_KSL_MAX_NBLK is the maximum number of nodes which a single
+   block can contain. */
+#define NGTCP2_KSL_MAX_NBLK (2 * NGTCP2_KSL_DEGR - 1)
+/* NGTCP2_KSL_MIN_NBLK is the minimum number of nodes which a single
+   block other than root must contain. */
+#define NGTCP2_KSL_MIN_NBLK (NGTCP2_KSL_DEGR - 1)
+
+/*
+ * ngtcp2_ksl_key represents key in ngtcp2_ksl.
+ */
+typedef void ngtcp2_ksl_key;
+
+typedef struct ngtcp2_ksl_node ngtcp2_ksl_node;
+
+typedef struct ngtcp2_ksl_blk ngtcp2_ksl_blk;
+
+/*
+ * ngtcp2_ksl_node is a node which contains either ngtcp2_ksl_blk or
+ * opaque data.  If a node is an internal node, it contains
+ * ngtcp2_ksl_blk.  Otherwise, it has data.  The key is stored at the
+ * location starting at key.
+ */
+struct ngtcp2_ksl_node {
+  union {
+    ngtcp2_ksl_blk *blk;
+    void *data;
+  };
+  union {
+    uint64_t align;
+    /* key is a buffer to include key associated to this node.
+       Because the length of key is unknown until ngtcp2_ksl_init is
+       called, the actual buffer will be allocated after this
+       field. */
+    uint8_t key[1];
+  };
+};
+
+/*
+ * ngtcp2_ksl_blk contains ngtcp2_ksl_node objects.
+ */
+struct ngtcp2_ksl_blk {
+  union {
+    struct {
+      /* next points to the next block if leaf field is nonzero. */
+      ngtcp2_ksl_blk *next;
+      /* prev points to the previous block if leaf field is
+         nonzero. */
+      ngtcp2_ksl_blk *prev;
+      /* n is the number of nodes this object contains in nodes. */
+      uint32_t n;
+      /* leaf is nonzero if this block contains leaf nodes. */
+      uint32_t leaf;
+      union {
+        uint64_t align;
+        /* nodes is a buffer to contain NGTCP2_KSL_MAX_NBLK
+           ngtcp2_ksl_node objects.  Because ngtcp2_ksl_node object is
+           allocated along with the additional variable length key
+           storage, the size of buffer is unknown until ngtcp2_ksl_init is
+           called. */
+        uint8_t nodes[1];
+      };
+    };
+
+    ngtcp2_opl_entry oplent;
+  };
+};
+
+ngtcp2_objalloc_decl(ksl_blk, ngtcp2_ksl_blk, oplent)
+
+/*
+ * ngtcp2_ksl_compar is a function type which returns nonzero if key
+ * |lhs| should be placed before |rhs|.  It returns 0 otherwise.
+ */
+typedef int (*ngtcp2_ksl_compar)(const ngtcp2_ksl_key *lhs,
+                                 const ngtcp2_ksl_key *rhs);
+
+typedef struct ngtcp2_ksl ngtcp2_ksl;
+
+/*
+ * ngtcp2_ksl_search is a function to search for the first element in
+ * |blk|->nodes which is not ordered before |key|.  It returns the
+ * index of such element.  It returns |blk|->n if there is no such
+ * element.
+ */
+typedef size_t (*ngtcp2_ksl_search)(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk,
+                                    const ngtcp2_ksl_key *key);
+
+/*
+ * ngtcp2_ksl_search_def is a macro to implement ngtcp2_ksl_search
+ * with COMPAR which is supposed to be ngtcp2_ksl_compar.
+ */
+#define ngtcp2_ksl_search_def(NAME, COMPAR)                                    \
+  static size_t ksl_##NAME##_search(                                           \
+    const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, const ngtcp2_ksl_key *key) {   \
+    size_t i;                                                                  \
+    ngtcp2_ksl_node *node;                                                     \
+                                                                               \
+    for (i = 0, node = (ngtcp2_ksl_node *)(void *)blk->nodes;                  \
+         i < blk->n && COMPAR((ngtcp2_ksl_key *)node->key, key); ++i,          \
+        node = (ngtcp2_ksl_node *)(void *)((uint8_t *)node + ksl->nodelen))    \
+      ;                                                                        \
+                                                                               \
+    return i;                                                                  \
+  }
+
+typedef struct ngtcp2_ksl_it ngtcp2_ksl_it;
+
+/*
+ * ngtcp2_ksl_it is a bidirectional iterator to iterate nodes.
+ */
+struct ngtcp2_ksl_it {
+  const ngtcp2_ksl *ksl;
+  ngtcp2_ksl_blk *blk;
+  size_t i;
+};
+
+/*
+ * ngtcp2_ksl is a deterministic paged skip list.
+ */
+struct ngtcp2_ksl {
+  ngtcp2_objalloc blkalloc;
+  /* head points to the root block. */
+  ngtcp2_ksl_blk *head;
+  /* front points to the first leaf block. */
+  ngtcp2_ksl_blk *front;
+  /* back points to the last leaf block. */
+  ngtcp2_ksl_blk *back;
+  ngtcp2_ksl_compar compar;
+  ngtcp2_ksl_search search;
+  /* n is the number of elements stored. */
+  size_t n;
+  /* keylen is the size of key */
+  size_t keylen;
+  /* nodelen is the actual size of ngtcp2_ksl_node including key
+     storage. */
+  size_t nodelen;
+};
+
+/*
+ * ngtcp2_ksl_init initializes |ksl|.  |compar| specifies compare
+ * function.  |search| is a search function which must use |compar|.
+ * |keylen| is the length of key and must be at least
+ * sizeof(uint64_t).
+ */
+void ngtcp2_ksl_init(ngtcp2_ksl *ksl, ngtcp2_ksl_compar compar,
+                     ngtcp2_ksl_search search, size_t keylen,
+                     const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_ksl_free frees resources allocated for |ksl|.  If |ksl| is
+ * NULL, this function does nothing.  It does not free the memory
+ * region pointed by |ksl| itself.
+ */
+void ngtcp2_ksl_free(ngtcp2_ksl *ksl);
+
+/*
+ * ngtcp2_ksl_insert inserts |key| with its associated |data|.  On
+ * successful insertion, the iterator points to the inserted node is
+ * stored in |*it| if |it| is not NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     |key| already exists.
+ */
+int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it,
+                      const ngtcp2_ksl_key *key, void *data);
+
+/*
+ * ngtcp2_ksl_remove removes the |key| from |ksl|.
+ *
+ * This function assigns the iterator to |*it|, which points to the
+ * node which is located at the right next of the removed node if |it|
+ * is not NULL.  If |key| is not found, no deletion takes place and
+ * the return value of ngtcp2_ksl_end(ksl) is assigned to |*it| if
+ * |it| is not NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     |key| does not exist.
+ */
+int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it,
+                      const ngtcp2_ksl_key *key);
+
+/*
+ * ngtcp2_ksl_remove_hint removes the |key| from |ksl|.  |hint| must
+ * point to the same node denoted by |key|.  |hint| is used to remove
+ * a node efficiently in some cases.  Other than that, it behaves
+ * exactly like ngtcp2_ksl_remove.  |it| and |hint| can point to the
+ * same object.
+ */
+int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it,
+                           const ngtcp2_ksl_it *hint,
+                           const ngtcp2_ksl_key *key);
+
+/*
+ * ngtcp2_ksl_lower_bound returns the iterator which points to the
+ * first node which has the key which is equal to |key| or the last
+ * node which satisfies !compar(&node->key, key).  If there is no such
+ * node, it returns the iterator which satisfies ngtcp2_ksl_it_end(it)
+ * != 0.
+ */
+ngtcp2_ksl_it ngtcp2_ksl_lower_bound(const ngtcp2_ksl *ksl,
+                                     const ngtcp2_ksl_key *key);
+
+/*
+ * ngtcp2_ksl_lower_bound_search works like ngtcp2_ksl_lower_bound,
+ * but it takes custom function |search| to do lower bound search.
+ */
+ngtcp2_ksl_it ngtcp2_ksl_lower_bound_search(const ngtcp2_ksl *ksl,
+                                            const ngtcp2_ksl_key *key,
+                                            ngtcp2_ksl_search search);
+
+/*
+ * ngtcp2_ksl_update_key replaces the key of nodes which has |old_key|
+ * with |new_key|.  |new_key| must be strictly greater than the
+ * previous node and strictly smaller than the next node.
+ */
+void ngtcp2_ksl_update_key(ngtcp2_ksl *ksl, const ngtcp2_ksl_key *old_key,
+                           const ngtcp2_ksl_key *new_key);
+
+/*
+ * ngtcp2_ksl_begin returns the iterator which points to the first
+ * node.  If there is no node in |ksl|, it returns the iterator which
+ * satisfies both ngtcp2_ksl_it_begin(it) != 0 and
+ * ngtcp2_ksl_it_end(it) != 0.
+ */
+ngtcp2_ksl_it ngtcp2_ksl_begin(const ngtcp2_ksl *ksl);
+
+/*
+ * ngtcp2_ksl_end returns the iterator which points to the node
+ * following the last node.  The returned object satisfies
+ * ngtcp2_ksl_it_end().  If there is no node in |ksl|, it returns the
+ * iterator which satisfies ngtcp2_ksl_it_begin(it) != 0 and
+ * ngtcp2_ksl_it_end(it) != 0.
+ */
+ngtcp2_ksl_it ngtcp2_ksl_end(const ngtcp2_ksl *ksl);
+
+/*
+ * ngtcp2_ksl_len returns the number of elements stored in |ksl|.
+ */
+size_t ngtcp2_ksl_len(const ngtcp2_ksl *ksl);
+
+/*
+ * ngtcp2_ksl_clear removes all elements stored in |ksl|.
+ */
+void ngtcp2_ksl_clear(ngtcp2_ksl *ksl);
+
+/*
+ * ngtcp2_ksl_nth_node returns the |n|th node under |blk|.
+ */
+#define ngtcp2_ksl_nth_node(KSL, BLK, N)                                       \
+  ((ngtcp2_ksl_node *)(void *)((BLK)->nodes + (KSL)->nodelen * (N)))
+
+#ifndef WIN32
+/*
+ * ngtcp2_ksl_print prints its internal state in stderr.  It assumes
+ * that the key is of type int64_t.  This function should be used for
+ * the debugging purpose only.
+ */
+void ngtcp2_ksl_print(const ngtcp2_ksl *ksl);
+#endif /* !defined(WIN32) */
+
+/*
+ * ngtcp2_ksl_it_init initializes |it|.
+ */
+void ngtcp2_ksl_it_init(ngtcp2_ksl_it *it, const ngtcp2_ksl *ksl,
+                        ngtcp2_ksl_blk *blk, size_t i);
+
+/*
+ * ngtcp2_ksl_it_get returns the data associated to the node which
+ * |it| points to.  It is undefined to call this function when
+ * ngtcp2_ksl_it_end(it) returns nonzero.
+ */
+#define ngtcp2_ksl_it_get(IT)                                                  \
+  ngtcp2_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->data
+
+/*
+ * ngtcp2_ksl_it_next advances the iterator by one.  It is undefined
+ * if this function is called when ngtcp2_ksl_it_end(it) returns
+ * nonzero.
+ */
+#define ngtcp2_ksl_it_next(IT)                                                 \
+  (++(IT)->i == (IT)->blk->n && (IT)->blk->next                                \
+     ? ((IT)->blk = (IT)->blk->next, (IT)->i = 0)                              \
+     : 0)
+
+/*
+ * ngtcp2_ksl_it_prev moves backward the iterator by one.  It is
+ * undefined if this function is called when ngtcp2_ksl_it_begin(it)
+ * returns nonzero.
+ */
+void ngtcp2_ksl_it_prev(ngtcp2_ksl_it *it);
+
+/*
+ * ngtcp2_ksl_it_end returns nonzero if |it| points to the one beyond
+ * the last node.
+ */
+#define ngtcp2_ksl_it_end(IT)                                                  \
+  ((IT)->blk->n == (IT)->i && (IT)->blk->next == NULL)
+
+/*
+ * ngtcp2_ksl_it_begin returns nonzero if |it| points to the first
+ * node.  |it| might satisfy both ngtcp2_ksl_it_begin(it) != 0 and
+ * ngtcp2_ksl_it_end(it) != 0 if the skip list has no node.
+ */
+int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it);
+
+/*
+ * ngtcp2_ksl_key returns the key of the node which |it| points to.
+ * It is undefined to call this function when ngtcp2_ksl_it_end(it)
+ * returns nonzero.
+ */
+#define ngtcp2_ksl_it_key(IT)                                                  \
+  ((ngtcp2_ksl_key *)ngtcp2_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->key)
+
+/*
+ * ngtcp2_ksl_range_compar is an implementation of ngtcp2_ksl_compar.
+ * |lhs| and |rhs| must point to ngtcp2_range object, and the function
+ * returns nonzero if ((const ngtcp2_range *)lhs)->begin < ((const
+ * ngtcp2_range *)rhs)->begin.
+ */
+int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs,
+                            const ngtcp2_ksl_key *rhs);
+
+/*
+ * ngtcp2_ksl_range_search is an implementation of ngtcp2_ksl_search
+ * that uses ngtcp2_ksl_range_compar.
+ */
+size_t ngtcp2_ksl_range_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk,
+                               const ngtcp2_ksl_key *key);
+
+/*
+ * ngtcp2_ksl_range_exclusive_compar is an implementation of
+ * ngtcp2_ksl_compar.  |lhs| and |rhs| must point to ngtcp2_range
+ * object, and the function returns nonzero if ((const ngtcp2_range
+ * *)lhs)->begin < ((const ngtcp2_range *)rhs)->begin, and the 2
+ * ranges do not intersect.
+ */
+int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs,
+                                      const ngtcp2_ksl_key *rhs);
+
+/*
+ * ngtcp2_ksl_range_exclusive_search is an implementation of
+ * ngtcp2_ksl_search that uses ngtcp2_ksl_range_exclusive_compar.
+ */
+size_t ngtcp2_ksl_range_exclusive_search(const ngtcp2_ksl *ksl,
+                                         ngtcp2_ksl_blk *blk,
+                                         const ngtcp2_ksl_key *key);
+
+/*
+ * ngtcp2_ksl_uint64_less is an implementation of ngtcp2_ksl_compar.
+ * |lhs| and |rhs| must point to uint64_t objects, and the function
+ * returns nonzero if *(uint64_t *)|lhs| < *(uint64_t *)|rhs|.
+ */
+int ngtcp2_ksl_uint64_less(const ngtcp2_ksl_key *lhs,
+                           const ngtcp2_ksl_key *rhs);
+
+/*
+ * ngtcp2_ksl_uint64_less_search is an implementation of
+ * ngtcp2_ksl_search that uses ngtcp2_ksl_uint64_less.
+ */
+size_t ngtcp2_ksl_uint64_less_search(const ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk,
+                                     const ngtcp2_ksl_key *key);
+
+/*
+ * ngtcp2_ksl_int64_greater is an implementation of ngtcp2_ksl_compar.
+ * |lhs| and |rhs| must point to int64_t objects, and the function
+ * returns nonzero if *(int64_t *)|lhs| > *(int64_t *)|rhs|.
+ */
+int ngtcp2_ksl_int64_greater(const ngtcp2_ksl_key *lhs,
+                             const ngtcp2_ksl_key *rhs);
+
+/*
+ * ngtcp2_ksl_int64_greater_search is an implementation of
+ * ngtcp2_ksl_search that uses ngtcp2_ksl_int64_greater.
+ */
+size_t ngtcp2_ksl_int64_greater_search(const ngtcp2_ksl *ksl,
+                                       ngtcp2_ksl_blk *blk,
+                                       const ngtcp2_ksl_key *key);
+
+#endif /* !defined(NGTCP2_KSL_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_log.c b/third_party/ngtcp2/lib/ngtcp2_log.c
new file mode 100644 (file)
index 0000000..fc4eb44
--- /dev/null
@@ -0,0 +1,834 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_log.h"
+
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#  include <unistd.h>
+#endif /* defined(HAVE_UNISTD_H) */
+#include <assert.h>
+#include <string.h>
+
+#include "ngtcp2_str.h"
+#include "ngtcp2_vec.h"
+#include "ngtcp2_macro.h"
+#include "ngtcp2_conv.h"
+#include "ngtcp2_unreachable.h"
+#include "ngtcp2_net.h"
+
+void ngtcp2_log_init(ngtcp2_log *log, const ngtcp2_cid *scid,
+                     ngtcp2_printf log_printf, ngtcp2_tstamp ts,
+                     void *user_data) {
+  if (scid) {
+    ngtcp2_encode_hex(log->scid, scid->data, scid->datalen);
+  } else {
+    log->scid[0] = '\0';
+  }
+  log->log_printf = log_printf;
+  log->events = 0xff;
+  log->ts = log->last_ts = ts;
+  log->user_data = user_data;
+}
+
+/*
+ * # Log header
+ *
+ * <LEVEL><TIMESTAMP> <SCID> <EVENT>
+ *
+ * <LEVEL>:
+ *   Log level.  I=Info, W=Warning, E=Error
+ *
+ * <TIMESTAMP>:
+ *   Timestamp relative to ngtcp2_log.ts field in milliseconds
+ *   resolution.
+ *
+ * <SCID>:
+ *   Source Connection ID in hex string.
+ *
+ * <EVENT>:
+ *   Event.  See ngtcp2_log_event.
+ *
+ * # Frame event
+ *
+ * <DIR> <PKN> <PKTNAME> <FRAMENAME>(<FRAMETYPE>)
+ *
+ * <DIR>:
+ *   Flow direction.  tx=transmission, rx=reception
+ *
+ * <PKN>:
+ *   Packet number.
+ *
+ * <PKTNAME>:
+ *   Packet name.  (e.g., Initial, Handshake, 1RTT)
+ *
+ * <FRAMENAME>:
+ *   Frame name.  (e.g., STREAM, ACK, PING)
+ *
+ * <FRAMETYPE>:
+ *   Frame type in hex string.
+ */
+
+#define NGTCP2_LOG_BUFLEN 4096
+
+/* TODO Split second and remaining fraction with comma */
+#define NGTCP2_LOG_HD "I%08" PRIu64 " 0x%s %s"
+#define NGTCP2_LOG_PKT NGTCP2_LOG_HD " %s %" PRId64 " %s"
+#define NGTCP2_LOG_TP NGTCP2_LOG_HD " remote transport_parameters"
+
+#define NGTCP2_LOG_FRM_HD_FIELDS(DIR)                                          \
+  timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "frm",      \
+    (DIR), hd->pkt_num, strpkttype(hd)
+
+#define NGTCP2_LOG_PKT_HD_FIELDS(DIR)                                          \
+  timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "pkt",      \
+    (DIR), hd->pkt_num, strpkttype(hd)
+
+#define NGTCP2_LOG_TP_HD_FIELDS                                                \
+  timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "cry"
+
+static const char *strerrorcode(uint64_t error_code) {
+  switch (error_code) {
+  case NGTCP2_NO_ERROR:
+    return "NO_ERROR";
+  case NGTCP2_INTERNAL_ERROR:
+    return "INTERNAL_ERROR";
+  case NGTCP2_CONNECTION_REFUSED:
+    return "CONNECTION_REFUSED";
+  case NGTCP2_FLOW_CONTROL_ERROR:
+    return "FLOW_CONTROL_ERROR";
+  case NGTCP2_STREAM_LIMIT_ERROR:
+    return "STREAM_LIMIT_ERROR";
+  case NGTCP2_STREAM_STATE_ERROR:
+    return "STREAM_STATE_ERROR";
+  case NGTCP2_FINAL_SIZE_ERROR:
+    return "FINAL_SIZE_ERROR";
+  case NGTCP2_FRAME_ENCODING_ERROR:
+    return "FRAME_ENCODING_ERROR";
+  case NGTCP2_TRANSPORT_PARAMETER_ERROR:
+    return "TRANSPORT_PARAMETER_ERROR";
+  case NGTCP2_CONNECTION_ID_LIMIT_ERROR:
+    return "CONNECTION_ID_LIMIT_ERROR";
+  case NGTCP2_PROTOCOL_VIOLATION:
+    return "PROTOCOL_VIOLATION";
+  case NGTCP2_INVALID_TOKEN:
+    return "INVALID_TOKEN";
+  case NGTCP2_APPLICATION_ERROR:
+    return "APPLICATION_ERROR";
+  case NGTCP2_CRYPTO_BUFFER_EXCEEDED:
+    return "CRYPTO_BUFFER_EXCEEDED";
+  case NGTCP2_KEY_UPDATE_ERROR:
+    return "KEY_UPDATE_ERROR";
+  case NGTCP2_VERSION_NEGOTIATION_ERROR:
+    return "VERSION_NEGOTIATION_ERROR";
+  default:
+    if (0x100u <= error_code && error_code <= 0x1ffu) {
+      return "CRYPTO_ERROR";
+    }
+    return "(unknown)";
+  }
+}
+
+static const char *strapperrorcode(uint64_t app_error_code) {
+  (void)app_error_code;
+  return "(unknown)";
+}
+
+static const char *strpkttype_long(uint8_t type) {
+  switch (type) {
+  case NGTCP2_PKT_INITIAL:
+    return "Initial";
+  case NGTCP2_PKT_RETRY:
+    return "Retry";
+  case NGTCP2_PKT_HANDSHAKE:
+    return "Handshake";
+  case NGTCP2_PKT_0RTT:
+    return "0RTT";
+  default:
+    return "(unknown)";
+  }
+}
+
+static const char *strpkttype(const ngtcp2_pkt_hd *hd) {
+  if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) {
+    return strpkttype_long(hd->type);
+  }
+
+  switch (hd->type) {
+  case NGTCP2_PKT_VERSION_NEGOTIATION:
+    return "VN";
+  case NGTCP2_PKT_STATELESS_RESET:
+    return "SR";
+  case NGTCP2_PKT_1RTT:
+    return "1RTT";
+  default:
+    return "(unknown)";
+  }
+}
+
+static const char *strpkttype_type_flags(uint8_t type, uint8_t flags) {
+  ngtcp2_pkt_hd hd = {0};
+
+  hd.type = type;
+  hd.flags = flags;
+
+  return strpkttype(&hd);
+}
+
+static const char *strevent(ngtcp2_log_event ev) {
+  switch (ev) {
+  case NGTCP2_LOG_EVENT_CON:
+    return "con";
+  case NGTCP2_LOG_EVENT_PKT:
+    return "pkt";
+  case NGTCP2_LOG_EVENT_FRM:
+    return "frm";
+  case NGTCP2_LOG_EVENT_LDC:
+    return "ldc";
+  case NGTCP2_LOG_EVENT_CRY:
+    return "cry";
+  case NGTCP2_LOG_EVENT_PTV:
+    return "ptv";
+  case NGTCP2_LOG_EVENT_CCA:
+    return "cca";
+  case NGTCP2_LOG_EVENT_NONE:
+  default:
+    return "non";
+  }
+}
+
+static uint64_t timestamp_cast(uint64_t ns) { return ns / NGTCP2_MILLISECONDS; }
+
+static void log_fr_stream(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                          const ngtcp2_stream *fr, const char *dir) {
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " STREAM(0x%02" PRIx64 ") id=0x%" PRIx64
+                    " fin=%d offset=%" PRIu64 " len=%" PRIu64 " uni=%d"),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type | fr->flags, fr->stream_id, fr->fin,
+    fr->offset, ngtcp2_vec_len(fr->data, fr->datacnt),
+    (fr->stream_id & 0x2) != 0);
+}
+
+static void log_fr_ack(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                       const ngtcp2_ack *fr, const char *dir) {
+  int64_t largest_ack, min_ack;
+  size_t i;
+
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") largest_ack=%" PRId64
+                    " ack_delay=%" PRIu64 "(%" PRIu64 ") ack_range_count=%zu"),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->largest_ack,
+    fr->ack_delay_unscaled / NGTCP2_MILLISECONDS, fr->ack_delay, fr->rangecnt);
+
+  largest_ack = fr->largest_ack;
+  min_ack = fr->largest_ack - (int64_t)fr->first_ack_range;
+
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") range=[%" PRId64
+                                  "..%" PRId64 "] len=%" PRIu64),
+                  NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, largest_ack, min_ack,
+                  fr->first_ack_range);
+
+  for (i = 0; i < fr->rangecnt; ++i) {
+    const ngtcp2_ack_range *range = &fr->ranges[i];
+    largest_ack = min_ack - (int64_t)range->gap - 2;
+    min_ack = largest_ack - (int64_t)range->len;
+    log->log_printf(log->user_data,
+                    (NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") range=[%" PRId64
+                                    "..%" PRId64 "] gap=%" PRIu64
+                                    " len=%" PRIu64),
+                    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, largest_ack,
+                    min_ack, range->gap, range->len);
+  }
+
+  if (fr->type == NGTCP2_FRAME_ACK_ECN) {
+    log->log_printf(log->user_data,
+                    (NGTCP2_LOG_PKT " ACK(0x%02" PRIx64 ") ect0=%" PRIu64
+                                    " ect1=%" PRIu64 " ce=%" PRIu64),
+                    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->ecn.ect0,
+                    fr->ecn.ect1, fr->ecn.ce);
+  }
+}
+
+static void log_fr_padding(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                           const ngtcp2_padding *fr, const char *dir) {
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_PKT " PADDING(0x%02" PRIx64 ") len=%zu"),
+                  NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->len);
+}
+
+static void log_fr_reset_stream(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                const ngtcp2_reset_stream *fr,
+                                const char *dir) {
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " RESET_STREAM(0x%02" PRIx64 ") id=0x%" PRIx64
+                    " app_error_code=%s(0x%" PRIx64 ") final_size=%" PRIu64),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id,
+    strapperrorcode(fr->app_error_code), fr->app_error_code, fr->final_size);
+}
+
+static void log_fr_connection_close(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                    const ngtcp2_connection_close *fr,
+                                    const char *dir) {
+  char reason[256];
+  size_t reasonlen = ngtcp2_min_size(sizeof(reason) - 1, fr->reasonlen);
+
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " CONNECTION_CLOSE(0x%02" PRIx64
+                    ") error_code=%s(0x%" PRIx64 ") "
+                    "frame_type=%" PRIx64 " reason_len=%zu reason=[%s]"),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type,
+    fr->type == NGTCP2_FRAME_CONNECTION_CLOSE ? strerrorcode(fr->error_code)
+                                              : strapperrorcode(fr->error_code),
+    fr->error_code, fr->frame_type, fr->reasonlen,
+    ngtcp2_encode_printable_ascii(reason, fr->reason, reasonlen));
+}
+
+static void log_fr_max_data(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                            const ngtcp2_max_data *fr, const char *dir) {
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " MAX_DATA(0x%02" PRIx64 ") max_data=%" PRIu64),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_data);
+}
+
+static void log_fr_max_stream_data(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                   const ngtcp2_max_stream_data *fr,
+                                   const char *dir) {
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_PKT " MAX_STREAM_DATA(0x%02" PRIx64
+                                  ") id=0x%" PRIx64
+                                  " max_stream_data=%" PRIu64),
+                  NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id,
+                  fr->max_stream_data);
+}
+
+static void log_fr_max_streams(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                               const ngtcp2_max_streams *fr, const char *dir) {
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " MAX_STREAMS(0x%02" PRIx64 ") max_streams=%" PRIu64),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_streams);
+}
+
+static void log_fr_ping(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                        const ngtcp2_ping *fr, const char *dir) {
+  log->log_printf(log->user_data, (NGTCP2_LOG_PKT " PING(0x%02" PRIx64 ")"),
+                  NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type);
+}
+
+static void log_fr_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                const ngtcp2_data_blocked *fr,
+                                const char *dir) {
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " DATA_BLOCKED(0x%02" PRIx64 ") offset=%" PRIu64),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->offset);
+}
+
+static void log_fr_stream_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                       const ngtcp2_stream_data_blocked *fr,
+                                       const char *dir) {
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_PKT " STREAM_DATA_BLOCKED(0x%02" PRIx64
+                                  ") id=0x%" PRIx64 " offset=%" PRIu64),
+                  NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id,
+                  fr->offset);
+}
+
+static void log_fr_streams_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                   const ngtcp2_streams_blocked *fr,
+                                   const char *dir) {
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " STREAMS_BLOCKED(0x%02" PRIx64 ") max_streams=%" PRIu64),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_streams);
+}
+
+static void log_fr_new_connection_id(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                     const ngtcp2_new_connection_id *fr,
+                                     const char *dir) {
+  uint8_t buf[sizeof(fr->stateless_reset_token) * 2 + 1];
+  uint8_t cid[sizeof(fr->cid.data) * 2 + 1];
+
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " NEW_CONNECTION_ID(0x%02" PRIx64 ") seq=%" PRIu64
+                    " cid=0x%s retire_prior_to=%" PRIu64
+                    " stateless_reset_token=0x%s"),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->seq,
+    (const char *)ngtcp2_encode_hex(cid, fr->cid.data, fr->cid.datalen),
+    fr->retire_prior_to,
+    (const char *)ngtcp2_encode_hex(buf, fr->stateless_reset_token,
+                                    sizeof(fr->stateless_reset_token)));
+}
+
+static void log_fr_stop_sending(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                const ngtcp2_stop_sending *fr,
+                                const char *dir) {
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_PKT " STOP_SENDING(0x%02" PRIx64 ") id=0x%" PRIx64
+                                  " app_error_code=%s(0x%" PRIx64 ")"),
+                  NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id,
+                  strapperrorcode(fr->app_error_code), fr->app_error_code);
+}
+
+static void log_fr_path_challenge(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                  const ngtcp2_path_challenge *fr,
+                                  const char *dir) {
+  uint8_t buf[sizeof(fr->data) * 2 + 1];
+
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " PATH_CHALLENGE(0x%02" PRIx64 ") data=0x%s"),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type,
+    (const char *)ngtcp2_encode_hex(buf, fr->data, sizeof(fr->data)));
+}
+
+static void log_fr_path_response(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                 const ngtcp2_path_response *fr,
+                                 const char *dir) {
+  uint8_t buf[sizeof(fr->data) * 2 + 1];
+
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " PATH_RESPONSE(0x%02" PRIx64 ") data=0x%s"),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type,
+    (const char *)ngtcp2_encode_hex(buf, fr->data, sizeof(fr->data)));
+}
+
+static void log_fr_crypto(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                          const ngtcp2_stream *fr, const char *dir) {
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " CRYPTO(0x%02" PRIx64 ") offset=%" PRIu64 " len=%" PRIu64),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->offset,
+    ngtcp2_vec_len(fr->data, fr->datacnt));
+}
+
+static void log_fr_new_token(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                             const ngtcp2_new_token *fr, const char *dir) {
+  /* Show at most first 64 bytes of token.  If token is longer than 64
+     bytes, log first 64 bytes and then append "*" */
+  uint8_t buf[128 + 1 + 1];
+  uint8_t *p;
+
+  if (fr->tokenlen > 64) {
+    p = ngtcp2_encode_hex(buf, fr->token, 64);
+    p[128] = '*';
+    p[129] = '\0';
+  } else {
+    p = ngtcp2_encode_hex(buf, fr->token, fr->tokenlen);
+  }
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " NEW_TOKEN(0x%02" PRIx64 ") token=0x%s len=%zu"),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, (const char *)p, fr->tokenlen);
+}
+
+static void log_fr_retire_connection_id(ngtcp2_log *log,
+                                        const ngtcp2_pkt_hd *hd,
+                                        const ngtcp2_retire_connection_id *fr,
+                                        const char *dir) {
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_PKT " RETIRE_CONNECTION_ID(0x%02" PRIx64 ") seq=%" PRIu64),
+    NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->seq);
+}
+
+static void log_fr_handshake_done(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                                  const ngtcp2_handshake_done *fr,
+                                  const char *dir) {
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_PKT " HANDSHAKE_DONE(0x%02" PRIx64 ")"),
+                  NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type);
+}
+
+static void log_fr_datagram(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                            const ngtcp2_datagram *fr, const char *dir) {
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_PKT " DATAGRAM(0x%02" PRIx64 ") len=%" PRIu64),
+                  NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type,
+                  ngtcp2_vec_len(fr->data, fr->datacnt));
+}
+
+static void log_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                   const ngtcp2_frame *fr, const char *dir) {
+  switch (fr->type) {
+  case NGTCP2_FRAME_STREAM:
+    log_fr_stream(log, hd, &fr->stream, dir);
+    break;
+  case NGTCP2_FRAME_ACK:
+  case NGTCP2_FRAME_ACK_ECN:
+    log_fr_ack(log, hd, &fr->ack, dir);
+    break;
+  case NGTCP2_FRAME_PADDING:
+    log_fr_padding(log, hd, &fr->padding, dir);
+    break;
+  case NGTCP2_FRAME_RESET_STREAM:
+    log_fr_reset_stream(log, hd, &fr->reset_stream, dir);
+    break;
+  case NGTCP2_FRAME_CONNECTION_CLOSE:
+  case NGTCP2_FRAME_CONNECTION_CLOSE_APP:
+    log_fr_connection_close(log, hd, &fr->connection_close, dir);
+    break;
+  case NGTCP2_FRAME_MAX_DATA:
+    log_fr_max_data(log, hd, &fr->max_data, dir);
+    break;
+  case NGTCP2_FRAME_MAX_STREAM_DATA:
+    log_fr_max_stream_data(log, hd, &fr->max_stream_data, dir);
+    break;
+  case NGTCP2_FRAME_MAX_STREAMS_BIDI:
+  case NGTCP2_FRAME_MAX_STREAMS_UNI:
+    log_fr_max_streams(log, hd, &fr->max_streams, dir);
+    break;
+  case NGTCP2_FRAME_PING:
+    log_fr_ping(log, hd, &fr->ping, dir);
+    break;
+  case NGTCP2_FRAME_DATA_BLOCKED:
+    log_fr_data_blocked(log, hd, &fr->data_blocked, dir);
+    break;
+  case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
+    log_fr_stream_data_blocked(log, hd, &fr->stream_data_blocked, dir);
+    break;
+  case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI:
+  case NGTCP2_FRAME_STREAMS_BLOCKED_UNI:
+    log_fr_streams_blocked(log, hd, &fr->streams_blocked, dir);
+    break;
+  case NGTCP2_FRAME_NEW_CONNECTION_ID:
+    log_fr_new_connection_id(log, hd, &fr->new_connection_id, dir);
+    break;
+  case NGTCP2_FRAME_STOP_SENDING:
+    log_fr_stop_sending(log, hd, &fr->stop_sending, dir);
+    break;
+  case NGTCP2_FRAME_PATH_CHALLENGE:
+    log_fr_path_challenge(log, hd, &fr->path_challenge, dir);
+    break;
+  case NGTCP2_FRAME_PATH_RESPONSE:
+    log_fr_path_response(log, hd, &fr->path_response, dir);
+    break;
+  case NGTCP2_FRAME_CRYPTO:
+    log_fr_crypto(log, hd, &fr->stream, dir);
+    break;
+  case NGTCP2_FRAME_NEW_TOKEN:
+    log_fr_new_token(log, hd, &fr->new_token, dir);
+    break;
+  case NGTCP2_FRAME_RETIRE_CONNECTION_ID:
+    log_fr_retire_connection_id(log, hd, &fr->retire_connection_id, dir);
+    break;
+  case NGTCP2_FRAME_HANDSHAKE_DONE:
+    log_fr_handshake_done(log, hd, &fr->handshake_done, dir);
+    break;
+  case NGTCP2_FRAME_DATAGRAM:
+  case NGTCP2_FRAME_DATAGRAM_LEN:
+    log_fr_datagram(log, hd, &fr->datagram, dir);
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+}
+
+void ngtcp2_log_rx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                      const ngtcp2_frame *fr) {
+  if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_FRM)) {
+    return;
+  }
+
+  log_fr(log, hd, fr, "rx");
+}
+
+void ngtcp2_log_tx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                      const ngtcp2_frame *fr) {
+  if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_FRM)) {
+    return;
+  }
+
+  log_fr(log, hd, fr, "tx");
+}
+
+void ngtcp2_log_rx_vn(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                      const uint32_t *sv, size_t nsv) {
+  size_t i;
+
+  if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_PKT)) {
+    return;
+  }
+
+  for (i = 0; i < nsv; ++i) {
+    log->log_printf(log->user_data, (NGTCP2_LOG_PKT " v=0x%08x"),
+                    NGTCP2_LOG_PKT_HD_FIELDS("rx"), sv[i]);
+  }
+}
+
+void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset *sr) {
+  uint8_t buf[sizeof(sr->stateless_reset_token) * 2 + 1];
+  ngtcp2_pkt_hd shd;
+  ngtcp2_pkt_hd *hd = &shd;
+
+  if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_PKT)) {
+    return;
+  }
+
+  memset(&shd, 0, sizeof(shd));
+
+  shd.type = NGTCP2_PKT_STATELESS_RESET;
+
+  log->log_printf(
+    log->user_data, (NGTCP2_LOG_PKT " token=0x%s randlen=%zu"),
+    NGTCP2_LOG_PKT_HD_FIELDS("rx"),
+    (const char *)ngtcp2_encode_hex(buf, sr->stateless_reset_token,
+                                    sizeof(sr->stateless_reset_token)),
+    sr->randlen);
+}
+
+void ngtcp2_log_remote_tp(ngtcp2_log *log,
+                          const ngtcp2_transport_params *params) {
+  uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN * 2 + 1];
+  uint8_t addr[16 * 2 + 7 + 1];
+  uint8_t cid[NGTCP2_MAX_CIDLEN * 2 + 1];
+  size_t i;
+  const ngtcp2_sockaddr_in *sa_in;
+  const ngtcp2_sockaddr_in6 *sa_in6;
+  const uint8_t *p;
+  uint32_t version;
+
+  if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_CRY)) {
+    return;
+  }
+
+  if (params->stateless_reset_token_present) {
+    log->log_printf(
+      log->user_data, (NGTCP2_LOG_TP " stateless_reset_token=0x%s"),
+      NGTCP2_LOG_TP_HD_FIELDS,
+      (const char *)ngtcp2_encode_hex(token, params->stateless_reset_token,
+                                      sizeof(params->stateless_reset_token)));
+  }
+
+  if (params->preferred_addr_present) {
+    if (params->preferred_addr.ipv4_present) {
+      sa_in = &params->preferred_addr.ipv4;
+
+      log->log_printf(log->user_data,
+                      (NGTCP2_LOG_TP " preferred_address.ipv4_addr=%s"),
+                      NGTCP2_LOG_TP_HD_FIELDS,
+                      (const char *)ngtcp2_encode_ipv4(
+                        addr, (const uint8_t *)&sa_in->sin_addr));
+      log->log_printf(log->user_data,
+                      (NGTCP2_LOG_TP " preferred_address.ipv4_port=%u"),
+                      NGTCP2_LOG_TP_HD_FIELDS, ngtcp2_ntohs(sa_in->sin_port));
+    }
+
+    if (params->preferred_addr.ipv6_present) {
+      sa_in6 = &params->preferred_addr.ipv6;
+
+      log->log_printf(log->user_data,
+                      (NGTCP2_LOG_TP " preferred_address.ipv6_addr=%s"),
+                      NGTCP2_LOG_TP_HD_FIELDS,
+                      (const char *)ngtcp2_encode_ipv6(
+                        addr, (const uint8_t *)&sa_in6->sin6_addr));
+      log->log_printf(log->user_data,
+                      (NGTCP2_LOG_TP " preferred_address.ipv6_port=%u"),
+                      NGTCP2_LOG_TP_HD_FIELDS, ngtcp2_ntohs(sa_in6->sin6_port));
+    }
+
+    log->log_printf(
+      log->user_data, (NGTCP2_LOG_TP " preferred_address.cid=0x%s"),
+      NGTCP2_LOG_TP_HD_FIELDS,
+      (const char *)ngtcp2_encode_hex(cid, params->preferred_addr.cid.data,
+                                      params->preferred_addr.cid.datalen));
+    log->log_printf(
+      log->user_data,
+      (NGTCP2_LOG_TP " preferred_address.stateless_reset_token=0x%s"),
+      NGTCP2_LOG_TP_HD_FIELDS,
+      (const char *)ngtcp2_encode_hex(
+        token, params->preferred_addr.stateless_reset_token,
+        sizeof(params->preferred_addr.stateless_reset_token)));
+  }
+
+  if (params->original_dcid_present) {
+    log->log_printf(
+      log->user_data,
+      (NGTCP2_LOG_TP " original_destination_connection_id=0x%s"),
+      NGTCP2_LOG_TP_HD_FIELDS,
+      (const char *)ngtcp2_encode_hex(cid, params->original_dcid.data,
+                                      params->original_dcid.datalen));
+  }
+
+  if (params->retry_scid_present) {
+    log->log_printf(
+      log->user_data, (NGTCP2_LOG_TP " retry_source_connection_id=0x%s"),
+      NGTCP2_LOG_TP_HD_FIELDS,
+      (const char *)ngtcp2_encode_hex(cid, params->retry_scid.data,
+                                      params->retry_scid.datalen));
+  }
+
+  if (params->initial_scid_present) {
+    log->log_printf(
+      log->user_data, (NGTCP2_LOG_TP " initial_source_connection_id=0x%s"),
+      NGTCP2_LOG_TP_HD_FIELDS,
+      (const char *)ngtcp2_encode_hex(cid, params->initial_scid.data,
+                                      params->initial_scid.datalen));
+  }
+
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_TP " initial_max_stream_data_bidi_local=%" PRIu64),
+    NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_bidi_local);
+  log->log_printf(
+    log->user_data,
+    (NGTCP2_LOG_TP " initial_max_stream_data_bidi_remote=%" PRIu64),
+    NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_bidi_remote);
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_TP " initial_max_stream_data_uni=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_uni);
+  log->log_printf(log->user_data, (NGTCP2_LOG_TP " initial_max_data=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_data);
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_TP " initial_max_streams_bidi=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_streams_bidi);
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_TP " initial_max_streams_uni=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_streams_uni);
+  log->log_printf(log->user_data, (NGTCP2_LOG_TP " max_idle_timeout=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS,
+                  params->max_idle_timeout / NGTCP2_MILLISECONDS);
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_TP " max_udp_payload_size=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->max_udp_payload_size);
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_TP " ack_delay_exponent=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->ack_delay_exponent);
+  log->log_printf(log->user_data, (NGTCP2_LOG_TP " max_ack_delay=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS,
+                  params->max_ack_delay / NGTCP2_MILLISECONDS);
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_TP " active_connection_id_limit=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->active_connection_id_limit);
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_TP " disable_active_migration=%d"),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->disable_active_migration);
+  log->log_printf(log->user_data,
+                  (NGTCP2_LOG_TP " max_datagram_frame_size=%" PRIu64),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->max_datagram_frame_size);
+  log->log_printf(log->user_data, (NGTCP2_LOG_TP " grease_quic_bit=%d"),
+                  NGTCP2_LOG_TP_HD_FIELDS, params->grease_quic_bit);
+
+  if (params->version_info_present) {
+    log->log_printf(
+      log->user_data,
+      (NGTCP2_LOG_TP " version_information.chosen_version=0x%08x"),
+      NGTCP2_LOG_TP_HD_FIELDS, params->version_info.chosen_version);
+
+    assert(!(params->version_info.available_versionslen & 0x3));
+
+    for (i = 0, p = params->version_info.available_versions;
+         i < params->version_info.available_versionslen;
+         i += sizeof(uint32_t)) {
+      p = ngtcp2_get_uint32be(&version, p);
+
+      log->log_printf(
+        log->user_data,
+        (NGTCP2_LOG_TP " version_information.available_versions[%zu]=0x%08x"),
+        NGTCP2_LOG_TP_HD_FIELDS, i >> 2, version);
+    }
+  }
+}
+
+void ngtcp2_log_pkt_lost(ngtcp2_log *log, int64_t pkt_num, uint8_t type,
+                         uint8_t flags, ngtcp2_tstamp sent_ts) {
+  if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_LDC)) {
+    return;
+  }
+
+  ngtcp2_log_info(log, NGTCP2_LOG_EVENT_LDC,
+                  "pkn=%" PRId64 " lost type=%s sent_ts=%" PRIu64, pkt_num,
+                  strpkttype_type_flags(type, flags), sent_ts);
+}
+
+static void log_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                       const char *dir) {
+  uint8_t dcid[sizeof(hd->dcid.data) * 2 + 1];
+  uint8_t scid[sizeof(hd->scid.data) * 2 + 1];
+
+  if (!log->log_printf || !(log->events & NGTCP2_LOG_EVENT_PKT)) {
+    return;
+  }
+
+  if (hd->type == NGTCP2_PKT_1RTT) {
+    ngtcp2_log_info(
+      log, NGTCP2_LOG_EVENT_PKT, "%s pkn=%" PRId64 " dcid=0x%s type=%s k=%d",
+      dir, hd->pkt_num,
+      (const char *)ngtcp2_encode_hex(dcid, hd->dcid.data, hd->dcid.datalen),
+      strpkttype(hd), (hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE) != 0);
+  } else {
+    ngtcp2_log_info(
+      log, NGTCP2_LOG_EVENT_PKT,
+      "%s pkn=%" PRId64 " dcid=0x%s scid=0x%s version=0x%08x type=%s len=%zu",
+      dir, hd->pkt_num,
+      (const char *)ngtcp2_encode_hex(dcid, hd->dcid.data, hd->dcid.datalen),
+      (const char *)ngtcp2_encode_hex(scid, hd->scid.data, hd->scid.datalen),
+      hd->version, strpkttype(hd), hd->len);
+  }
+}
+
+void ngtcp2_log_rx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) {
+  log_pkt_hd(log, hd, "rx");
+}
+
+void ngtcp2_log_tx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) {
+  log_pkt_hd(log, hd, "tx");
+}
+
+void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt,
+                     ...) {
+  va_list ap;
+  int n;
+  char buf[NGTCP2_LOG_BUFLEN];
+
+  if (!log->log_printf || !(log->events & ev)) {
+    return;
+  }
+
+  va_start(ap, fmt);
+  n = vsnprintf(buf, sizeof(buf), fmt, ap);
+  va_end(ap);
+
+  if (n < 0 || (size_t)n >= sizeof(buf)) {
+    return;
+  }
+
+  log->log_printf(log->user_data, (NGTCP2_LOG_HD " %s"),
+                  timestamp_cast(log->last_ts - log->ts), log->scid,
+                  strevent(ev), buf);
+}
+
+void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) {
+  ngtcp2_log_info(log, NGTCP2_LOG_EVENT_PKT,
+                  "cancel tx pkn=%" PRId64 " type=%s", hd->pkt_num,
+                  strpkttype(hd));
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_log.h b/third_party/ngtcp2/lib/ngtcp2_log.h
new file mode 100644 (file)
index 0000000..13fb81a
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_LOG_H
+#define NGTCP2_LOG_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_pkt.h"
+
+typedef struct ngtcp2_log {
+  /* log_printf is a sink to write log.  NULL means no logging
+     output. */
+  ngtcp2_printf log_printf;
+  /* events is an event filter.  Only events set in this field are
+     emitted. */
+  uint8_t events;
+  /* ts is the time point used to write time delta in the log. */
+  ngtcp2_tstamp ts;
+  /* last_ts is the most recent time point that this object is
+     told. */
+  ngtcp2_tstamp last_ts;
+  /* user_data is user-defined opaque data which is passed to
+     log_pritnf. */
+  void *user_data;
+  /* scid is SCID encoded as NULL-terminated hex string. */
+  uint8_t scid[NGTCP2_MAX_CIDLEN * 2 + 1];
+} ngtcp2_log;
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_log_event` defines an event of ngtcp2 library
+ * internal logger.
+ */
+typedef enum ngtcp2_log_event {
+  /**
+   * :enum:`NGTCP2_LOG_EVENT_NONE` represents no event.
+   */
+  NGTCP2_LOG_EVENT_NONE,
+  /**
+   * :enum:`NGTCP2_LOG_EVENT_CON` is a connection (catch-all) event
+   */
+  NGTCP2_LOG_EVENT_CON = 0x1,
+  /**
+   * :enum:`NGTCP2_LOG_EVENT_PKT` is a packet event.
+   */
+  NGTCP2_LOG_EVENT_PKT = 0x2,
+  /**
+   * :enum:`NGTCP2_LOG_EVENT_FRM` is a QUIC frame event.
+   */
+  NGTCP2_LOG_EVENT_FRM = 0x4,
+  /**
+   * :enum:`NGTCP2_LOG_EVENT_LDC` is a loss detection and congestion
+   * control event.
+   */
+  NGTCP2_LOG_EVENT_LDC = 0x8,
+  /**
+   * :enum:`NGTCP2_LOG_EVENT_CRY` is a crypto event.
+   */
+  NGTCP2_LOG_EVENT_CRY = 0x10,
+  /**
+   * :enum:`NGTCP2_LOG_EVENT_PTV` is a path validation event.
+   */
+  NGTCP2_LOG_EVENT_PTV = 0x20,
+  /**
+   * :enum:`NGTCP2_LOG_EVENT_CCA` is a congestion controller algorithm
+   * event.
+   */
+  NGTCP2_LOG_EVENT_CCA = 0x40,
+} ngtcp2_log_event;
+
+void ngtcp2_log_init(ngtcp2_log *log, const ngtcp2_cid *scid,
+                     ngtcp2_printf log_printf, ngtcp2_tstamp ts,
+                     void *user_data);
+
+void ngtcp2_log_rx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                      const ngtcp2_frame *fr);
+void ngtcp2_log_tx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                      const ngtcp2_frame *fr);
+
+void ngtcp2_log_rx_vn(ngtcp2_log *log, const ngtcp2_pkt_hd *hd,
+                      const uint32_t *sv, size_t nsv);
+
+void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset *sr);
+
+void ngtcp2_log_remote_tp(ngtcp2_log *log,
+                          const ngtcp2_transport_params *params);
+
+void ngtcp2_log_pkt_lost(ngtcp2_log *log, int64_t pkt_num, uint8_t type,
+                         uint8_t flags, ngtcp2_tstamp sent_ts);
+
+void ngtcp2_log_rx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd);
+
+void ngtcp2_log_tx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd);
+
+void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd);
+
+/**
+ * @function
+ *
+ * `ngtcp2_log_info` writes info level log.
+ */
+void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt,
+                     ...);
+
+#endif /* !defined(NGTCP2_LOG_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_macro.h b/third_party/ngtcp2/lib/ngtcp2_macro.h
new file mode 100644 (file)
index 0000000..dfe5e0a
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_MACRO_H
+#define NGTCP2_MACRO_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <stddef.h>
+
+#include <ngtcp2/ngtcp2.h>
+
+#define ngtcp2_struct_of(ptr, type, member)                                    \
+  ((type *)(void *)((char *)(ptr) - offsetof(type, member)))
+
+/* ngtcp2_list_insert inserts |T| before |*PD|.  The contract is that
+   this is singly linked list, and the next element is pointed by next
+   field of the previous element.  |PD| must be a pointer to the
+   pointer to the next field of the previous element of |*PD|: if C is
+   the previous element of |PD|, PD = &C->next. */
+#define ngtcp2_list_insert(T, PD)                                              \
+  do {                                                                         \
+    (T)->next = *(PD);                                                         \
+    *(PD) = (T);                                                               \
+  } while (0)
+
+/*
+ * ngtcp2_arraylen returns the number of elements in array |A|.
+ */
+#define ngtcp2_arraylen(A) (sizeof(A) / sizeof(A[0]))
+
+#define ngtcp2_max_def(SUFFIX, T)                                              \
+  static inline T ngtcp2_max_##SUFFIX(T a, T b) { return a < b ? b : a; }
+
+ngtcp2_max_def(int8, int8_t)
+ngtcp2_max_def(int16, int16_t)
+ngtcp2_max_def(int32, int32_t)
+ngtcp2_max_def(int64, int64_t)
+ngtcp2_max_def(uint8, uint8_t)
+ngtcp2_max_def(uint16, uint16_t)
+ngtcp2_max_def(uint32, uint32_t)
+ngtcp2_max_def(uint64, uint64_t)
+ngtcp2_max_def(size, size_t)
+
+#define ngtcp2_min_def(SUFFIX, T)                                              \
+  static inline T ngtcp2_min_##SUFFIX(T a, T b) { return a < b ? a : b; }
+
+ngtcp2_min_def(int8, int8_t)
+ngtcp2_min_def(int16, int16_t)
+ngtcp2_min_def(int32, int32_t)
+ngtcp2_min_def(int64, int64_t)
+ngtcp2_min_def(uint8, uint8_t)
+ngtcp2_min_def(uint16, uint16_t)
+ngtcp2_min_def(uint32, uint32_t)
+ngtcp2_min_def(uint64, uint64_t)
+ngtcp2_min_def(size, size_t)
+
+#endif /* !defined(NGTCP2_MACRO_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_map.c b/third_party/ngtcp2/lib/ngtcp2_map.c
new file mode 100644 (file)
index 0000000..5e4726e
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_map.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "ngtcp2_conv.h"
+
+#define NGTCP2_INITIAL_HASHBITS 4
+
+void ngtcp2_map_init(ngtcp2_map *map, const ngtcp2_mem *mem) {
+  map->mem = mem;
+  map->hashbits = 0;
+  map->table = NULL;
+  map->size = 0;
+}
+
+void ngtcp2_map_free(ngtcp2_map *map) {
+  if (!map) {
+    return;
+  }
+
+  ngtcp2_mem_free(map->mem, map->table);
+}
+
+int ngtcp2_map_each(const ngtcp2_map *map, int (*func)(void *data, void *ptr),
+                    void *ptr) {
+  int rv;
+  size_t i;
+  ngtcp2_map_bucket *bkt;
+  size_t tablelen;
+
+  if (map->size == 0) {
+    return 0;
+  }
+
+  tablelen = 1u << map->hashbits;
+
+  for (i = 0; i < tablelen; ++i) {
+    bkt = &map->table[i];
+
+    if (bkt->data == NULL) {
+      continue;
+    }
+
+    rv = func(bkt->data, ptr);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+static size_t hash(ngtcp2_map_key_type key, size_t bits) {
+  return (size_t)((key * 11400714819323198485llu) >> (64 - bits));
+}
+
+static void map_bucket_swap(ngtcp2_map_bucket *a, ngtcp2_map_bucket *b) {
+  ngtcp2_map_bucket c = *a;
+
+  *a = *b;
+  *b = c;
+}
+
+#ifndef WIN32
+void ngtcp2_map_print_distance(const ngtcp2_map *map) {
+  size_t i;
+  size_t idx;
+  ngtcp2_map_bucket *bkt;
+  size_t tablelen;
+
+  if (map->size == 0) {
+    return;
+  }
+
+  tablelen = 1u << map->hashbits;
+
+  for (i = 0; i < tablelen; ++i) {
+    bkt = &map->table[i];
+
+    if (bkt->data == NULL) {
+      fprintf(stderr, "@%zu <EMPTY>\n", i);
+      continue;
+    }
+
+    idx = hash(bkt->key, map->hashbits);
+    fprintf(stderr, "@%zu hash=%zu key=%" PRIu64 " base=%zu distance=%u\n", i,
+            hash(bkt->key, map->hashbits), bkt->key, idx, bkt->psl);
+  }
+}
+#endif /* !defined(WIN32) */
+
+static int insert(ngtcp2_map_bucket *table, size_t hashbits,
+                  ngtcp2_map_key_type key, void *data) {
+  size_t idx = hash(key, hashbits);
+  ngtcp2_map_bucket b = {
+    .key = key,
+    .data = data,
+  };
+  ngtcp2_map_bucket *bkt;
+  size_t mask = (1u << hashbits) - 1;
+
+  for (;;) {
+    bkt = &table[idx];
+
+    if (bkt->data == NULL) {
+      *bkt = b;
+      return 0;
+    }
+
+    if (b.psl > bkt->psl) {
+      map_bucket_swap(bkt, &b);
+    } else if (bkt->key == key) {
+      /* TODO This check is just a waste after first swap or if this
+         function is called from map_resize.  That said, there is no
+         difference with or without this conditional in performance
+         wise. */
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    ++b.psl;
+    idx = (idx + 1) & mask;
+  }
+}
+
+static int map_resize(ngtcp2_map *map, size_t new_hashbits) {
+  size_t i;
+  ngtcp2_map_bucket *new_table;
+  ngtcp2_map_bucket *bkt;
+  size_t tablelen;
+  int rv;
+  (void)rv;
+
+  new_table =
+    ngtcp2_mem_calloc(map->mem, 1u << new_hashbits, sizeof(ngtcp2_map_bucket));
+  if (new_table == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  if (map->size) {
+    tablelen = 1u << map->hashbits;
+
+    for (i = 0; i < tablelen; ++i) {
+      bkt = &map->table[i];
+      if (bkt->data == NULL) {
+        continue;
+      }
+
+      rv = insert(new_table, new_hashbits, bkt->key, bkt->data);
+
+      assert(0 == rv);
+    }
+  }
+
+  ngtcp2_mem_free(map->mem, map->table);
+  map->hashbits = new_hashbits;
+  map->table = new_table;
+
+  return 0;
+}
+
+int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data) {
+  int rv;
+
+  assert(data);
+
+  /* Load factor is 0.75 */
+  /* Under the very initial condition, that is map->size == 0 and
+     map->hashbits == 0, 4 > 3 still holds nicely. */
+  if ((map->size + 1) * 4 > (1u << map->hashbits) * 3) {
+    if (map->hashbits) {
+      rv = map_resize(map, map->hashbits + 1);
+      if (rv != 0) {
+        return rv;
+      }
+    } else {
+      rv = map_resize(map, NGTCP2_INITIAL_HASHBITS);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+  }
+
+  rv = insert(map->table, map->hashbits, key, data);
+  if (rv != 0) {
+    return rv;
+  }
+
+  ++map->size;
+
+  return 0;
+}
+
+void *ngtcp2_map_find(const ngtcp2_map *map, ngtcp2_map_key_type key) {
+  size_t idx;
+  ngtcp2_map_bucket *bkt;
+  size_t psl = 0;
+  size_t mask;
+
+  if (map->size == 0) {
+    return NULL;
+  }
+
+  idx = hash(key, map->hashbits);
+  mask = (1u << map->hashbits) - 1;
+
+  for (;;) {
+    bkt = &map->table[idx];
+
+    if (bkt->data == NULL || psl > bkt->psl) {
+      return NULL;
+    }
+
+    if (bkt->key == key) {
+      return bkt->data;
+    }
+
+    ++psl;
+    idx = (idx + 1) & mask;
+  }
+}
+
+int ngtcp2_map_remove(ngtcp2_map *map, ngtcp2_map_key_type key) {
+  size_t idx;
+  ngtcp2_map_bucket *b, *bkt;
+  size_t psl = 0;
+  size_t mask;
+
+  if (map->size == 0) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  idx = hash(key, map->hashbits);
+  mask = (1u << map->hashbits) - 1;
+
+  for (;;) {
+    bkt = &map->table[idx];
+
+    if (bkt->data == NULL || psl > bkt->psl) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    if (bkt->key == key) {
+      b = bkt;
+      idx = (idx + 1) & mask;
+
+      for (;;) {
+        bkt = &map->table[idx];
+        if (bkt->data == NULL || bkt->psl == 0) {
+          b->data = NULL;
+          break;
+        }
+
+        --bkt->psl;
+        *b = *bkt;
+        b = bkt;
+
+        idx = (idx + 1) & mask;
+      }
+
+      --map->size;
+
+      return 0;
+    }
+
+    ++psl;
+    idx = (idx + 1) & mask;
+  }
+}
+
+void ngtcp2_map_clear(ngtcp2_map *map) {
+  if (map->size == 0) {
+    return;
+  }
+
+  memset(map->table, 0, sizeof(*map->table) * (1u << map->hashbits));
+  map->size = 0;
+}
+
+size_t ngtcp2_map_size(const ngtcp2_map *map) { return map->size; }
diff --git a/third_party/ngtcp2/lib/ngtcp2_map.h b/third_party/ngtcp2/lib/ngtcp2_map.h
new file mode 100644 (file)
index 0000000..9d882fb
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_MAP_H
+#define NGTCP2_MAP_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+
+/* Implementation of unordered map */
+
+typedef uint64_t ngtcp2_map_key_type;
+
+typedef struct ngtcp2_map_bucket {
+  uint32_t psl;
+  ngtcp2_map_key_type key;
+  void *data;
+} ngtcp2_map_bucket;
+
+typedef struct ngtcp2_map {
+  ngtcp2_map_bucket *table;
+  const ngtcp2_mem *mem;
+  size_t size;
+  size_t hashbits;
+} ngtcp2_map;
+
+/*
+ * ngtcp2_map_init initializes the map |map|.
+ */
+void ngtcp2_map_init(ngtcp2_map *map, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_map_free deallocates any resources allocated for |map|.  The
+ * stored entries are not freed by this function.  Use
+ * ngtcp2_map_each() to free each entry.
+ */
+void ngtcp2_map_free(ngtcp2_map *map);
+
+/*
+ * ngtcp2_map_insert inserts the new |data| with the |key| to the map
+ * |map|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     The item associated by |key| already exists.
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data);
+
+/*
+ * ngtcp2_map_find returns the entry associated by the key |key|.  If
+ * there is no such entry, this function returns NULL.
+ */
+void *ngtcp2_map_find(const ngtcp2_map *map, ngtcp2_map_key_type key);
+
+/*
+ * ngtcp2_map_remove removes the entry associated by the key |key|
+ * from the |map|.  The removed entry is not freed by this function.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     The entry associated by |key| does not exist.
+ */
+int ngtcp2_map_remove(ngtcp2_map *map, ngtcp2_map_key_type key);
+
+/*
+ * ngtcp2_map_clear removes all entries from |map|.  The removed entry
+ * is not freed by this function.
+ */
+void ngtcp2_map_clear(ngtcp2_map *map);
+
+/*
+ * ngtcp2_map_size returns the number of items stored in the map
+ * |map|.
+ */
+size_t ngtcp2_map_size(const ngtcp2_map *map);
+
+/*
+ * ngtcp2_map_each applies the function |func| to each entry in the
+ * |map| with the optional user supplied pointer |ptr|.
+ *
+ * If the |func| returns 0, this function calls the |func| with the
+ * next entry.  If the |func| returns nonzero, it will not call the
+ * |func| for further entries and return the return value of the
+ * |func| immediately.  Thus, this function returns 0 if all the
+ * invocations of the |func| return 0, or nonzero value which the last
+ * invocation of |func| returns.
+ */
+int ngtcp2_map_each(const ngtcp2_map *map, int (*func)(void *data, void *ptr),
+                    void *ptr);
+
+#ifndef WIN32
+void ngtcp2_map_print_distance(const ngtcp2_map *map);
+#endif /* !defined(WIN32) */
+
+#endif /* !defined(NGTCP2_MAP_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_mem.c b/third_party/ngtcp2/lib/ngtcp2_mem.c
new file mode 100644 (file)
index 0000000..48f430f
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2014 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_mem.h"
+
+#include <stdio.h>
+
+static void *default_malloc(size_t size, void *user_data) {
+  (void)user_data;
+
+  return malloc(size);
+}
+
+static void default_free(void *ptr, void *user_data) {
+  (void)user_data;
+
+  free(ptr);
+}
+
+static void *default_calloc(size_t nmemb, size_t size, void *user_data) {
+  (void)user_data;
+
+  return calloc(nmemb, size);
+}
+
+static void *default_realloc(void *ptr, size_t size, void *user_data) {
+  (void)user_data;
+
+  return realloc(ptr, size);
+}
+
+static const ngtcp2_mem mem_default = {
+  .malloc = default_malloc,
+  .free = default_free,
+  .calloc = default_calloc,
+  .realloc = default_realloc,
+};
+
+const ngtcp2_mem *ngtcp2_mem_default(void) { return &mem_default; }
+
+#ifndef MEMDEBUG
+void *ngtcp2_mem_malloc(const ngtcp2_mem *mem, size_t size) {
+  return mem->malloc(size, mem->user_data);
+}
+
+void ngtcp2_mem_free(const ngtcp2_mem *mem, void *ptr) {
+  mem->free(ptr, mem->user_data);
+}
+
+void *ngtcp2_mem_calloc(const ngtcp2_mem *mem, size_t nmemb, size_t size) {
+  return mem->calloc(nmemb, size, mem->user_data);
+}
+
+void *ngtcp2_mem_realloc(const ngtcp2_mem *mem, void *ptr, size_t size) {
+  return mem->realloc(ptr, size, mem->user_data);
+}
+#else  /* defined(MEMDEBUG) */
+void *ngtcp2_mem_malloc_debug(const ngtcp2_mem *mem, size_t size,
+                              const char *func, const char *file, size_t line) {
+  void *nptr = mem->malloc(size, mem->user_data);
+
+  fprintf(stderr, "malloc %p size=%zu in %s at %s:%zu\n", nptr, size, func,
+          file, line);
+
+  return nptr;
+}
+
+void ngtcp2_mem_free_debug(const ngtcp2_mem *mem, void *ptr, const char *func,
+                           const char *file, size_t line) {
+  fprintf(stderr, "free ptr=%p in %s at %s:%zu\n", ptr, func, file, line);
+
+  mem->free(ptr, mem->user_data);
+}
+
+void *ngtcp2_mem_calloc_debug(const ngtcp2_mem *mem, size_t nmemb, size_t size,
+                              const char *func, const char *file, size_t line) {
+  void *nptr = mem->calloc(nmemb, size, mem->user_data);
+
+  fprintf(stderr, "calloc %p nmemb=%zu size=%zu in %s at %s:%zu\n", nptr, nmemb,
+          size, func, file, line);
+
+  return nptr;
+}
+
+void *ngtcp2_mem_realloc_debug(const ngtcp2_mem *mem, void *ptr, size_t size,
+                               const char *func, const char *file,
+                               size_t line) {
+  void *nptr = mem->realloc(ptr, size, mem->user_data);
+
+  fprintf(stderr, "realloc %p ptr=%p size=%zu in %s at %s:%zu\n", nptr, ptr,
+          size, func, file, line);
+
+  return nptr;
+}
+#endif /* defined(MEMDEBUG) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_mem.h b/third_party/ngtcp2/lib/ngtcp2_mem.h
new file mode 100644 (file)
index 0000000..9f81875
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2014 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_MEM_H
+#define NGTCP2_MEM_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/* Convenient wrapper functions to call allocator function in
+   |mem|. */
+#ifndef MEMDEBUG
+void *ngtcp2_mem_malloc(const ngtcp2_mem *mem, size_t size);
+
+void ngtcp2_mem_free(const ngtcp2_mem *mem, void *ptr);
+
+void *ngtcp2_mem_calloc(const ngtcp2_mem *mem, size_t nmemb, size_t size);
+
+void *ngtcp2_mem_realloc(const ngtcp2_mem *mem, void *ptr, size_t size);
+#else /* defined(MEMDEBUG) */
+void *ngtcp2_mem_malloc_debug(const ngtcp2_mem *mem, size_t size,
+                              const char *func, const char *file, size_t line);
+
+#  define ngtcp2_mem_malloc(MEM, SIZE)                                         \
+    ngtcp2_mem_malloc_debug((MEM), (SIZE), __func__, __FILE__, __LINE__)
+
+void ngtcp2_mem_free_debug(const ngtcp2_mem *mem, void *ptr, const char *func,
+                           const char *file, size_t line);
+
+#  define ngtcp2_mem_free(MEM, PTR)                                            \
+    ngtcp2_mem_free_debug((MEM), (PTR), __func__, __FILE__, __LINE__)
+
+void *ngtcp2_mem_calloc_debug(const ngtcp2_mem *mem, size_t nmemb, size_t size,
+                              const char *func, const char *file, size_t line);
+
+#  define ngtcp2_mem_calloc(MEM, NMEMB, SIZE)                                  \
+    ngtcp2_mem_calloc_debug((MEM), (NMEMB), (SIZE), __func__, __FILE__,        \
+                            __LINE__)
+
+void *ngtcp2_mem_realloc_debug(const ngtcp2_mem *mem, void *ptr, size_t size,
+                               const char *func, const char *file, size_t line);
+
+#  define ngtcp2_mem_realloc(MEM, PTR, SIZE)                                   \
+    ngtcp2_mem_realloc_debug((MEM), (PTR), (SIZE), __func__, __FILE__, __LINE__)
+#endif /* defined(MEMDEBUG) */
+
+#endif /* !defined(NGTCP2_MEM_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_net.h b/third_party/ngtcp2/lib/ngtcp2_net.h
new file mode 100644 (file)
index 0000000..103a2fb
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_NET_H
+#define NGTCP2_NET_H
+
+/* This header file is explicitly allowed to be shared with
+   ngtcp2_crypto library. */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#ifdef HAVE_ARPA_INET_H
+#  include <arpa/inet.h>
+#endif /* defined(HAVE_ARPA_INET_H) */
+
+#ifdef HAVE_NETINET_IN_H
+#  include <netinet/in.h>
+#endif /* defined(HAVE_NETINET_IN_H) */
+
+#ifdef HAVE_BYTESWAP_H
+#  include <byteswap.h>
+#endif /* defined(HAVE_BYTESWAP_H) */
+
+#ifdef HAVE_ENDIAN_H
+#  include <endian.h>
+#endif /* defined(HAVE_ENDIAN_H) */
+
+#ifdef HAVE_SYS_ENDIAN_H
+#  include <sys/endian.h>
+#endif /* defined(HAVE_SYS_ENDIAN_H) */
+
+#ifdef __APPLE__
+#  include <libkern/OSByteOrder.h>
+#endif /* defined(__APPLE__) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#if HAVE_DECL_BE64TOH
+#  define ngtcp2_ntohl64(N) be64toh(N)
+#  define ngtcp2_htonl64(N) htobe64(N)
+#else /* !HAVE_DECL_BE64TOH */
+#  ifdef WORDS_BIGENDIAN
+#    define ngtcp2_ntohl64(N) (N)
+#    define ngtcp2_htonl64(N) (N)
+#  else /* !defined(WORDS_BIGENDIAN) */
+#    if HAVE_DECL_BSWAP_64
+#      define ngtcp2_bswap64 bswap_64
+#    elif defined(WIN32)
+#      define ngtcp2_bswap64 _byteswap_uint64
+#    elif defined(__APPLE__)
+#      define ngtcp2_bswap64 OSSwapInt64
+#    else /* !(HAVE_DECL_BSWAP_64 || defined(WIN32) || defined(__APPLE__)) */
+#      define ngtcp2_bswap64(N)                                                \
+        ((uint64_t)(ngtcp2_ntohl((uint32_t)(N))) << 32 |                       \
+         ngtcp2_ntohl((uint32_t)((N) >> 32)))
+#    endif /* !(HAVE_DECL_BSWAP_64 || defined(WIN32) || defined(__APPLE__)) */
+#    define ngtcp2_ntohl64(N) ngtcp2_bswap64(N)
+#    define ngtcp2_htonl64(N) ngtcp2_bswap64(N)
+#  endif /* !defined(WORDS_BIGENDIAN) */
+#endif   /* !HAVE_DECL_BE64TOH */
+
+#ifdef WIN32
+/* Windows requires ws2_32 library for ntonl family functions.  We
+   define inline functions for those function so that we don't have
+   dependency on that lib. */
+
+#  ifdef _MSC_VER
+#    define STIN static __inline
+#  else /* !defined(_MSC_VER) */
+#    define STIN static inline
+#  endif /* !defined(_MSC_VER) */
+
+STIN uint32_t ngtcp2_htonl(uint32_t hostlong) {
+  uint32_t res;
+  unsigned char *p = (unsigned char *)&res;
+  *p++ = (unsigned char)(hostlong >> 24);
+  *p++ = (hostlong >> 16) & 0xffu;
+  *p++ = (hostlong >> 8) & 0xffu;
+  *p = hostlong & 0xffu;
+  return res;
+}
+
+STIN uint16_t ngtcp2_htons(uint16_t hostshort) {
+  uint16_t res;
+  unsigned char *p = (unsigned char *)&res;
+  *p++ = (unsigned char)(hostshort >> 8);
+  *p = hostshort & 0xffu;
+  return res;
+}
+
+STIN uint32_t ngtcp2_ntohl(uint32_t netlong) {
+  uint32_t res;
+  unsigned char *p = (unsigned char *)&netlong;
+  res = (uint32_t)(*p++ << 24);
+  res += (uint32_t)(*p++ << 16);
+  res += (uint32_t)(*p++ << 8);
+  res += *p;
+  return res;
+}
+
+STIN uint16_t ngtcp2_ntohs(uint16_t netshort) {
+  uint16_t res;
+  unsigned char *p = (unsigned char *)&netshort;
+  res = (uint16_t)(*p++ << 8);
+  res += *p;
+  return res;
+}
+
+#else /* !defined(WIN32) */
+
+#  define ngtcp2_htonl htonl
+#  define ngtcp2_htons htons
+#  define ngtcp2_ntohl ntohl
+#  define ngtcp2_ntohs ntohs
+
+#endif /* !defined(WIN32) */
+
+#endif /* !defined(NGTCP2_NET_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_objalloc.c b/third_party/ngtcp2/lib/ngtcp2_objalloc.c
new file mode 100644 (file)
index 0000000..8b06cdd
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_objalloc.h"
+
+void ngtcp2_objalloc_init(ngtcp2_objalloc *objalloc, size_t blklen,
+                          const ngtcp2_mem *mem) {
+  ngtcp2_balloc_init(&objalloc->balloc, blklen, mem);
+  ngtcp2_opl_init(&objalloc->opl);
+}
+
+void ngtcp2_objalloc_free(ngtcp2_objalloc *objalloc) {
+  ngtcp2_balloc_free(&objalloc->balloc);
+}
+
+void ngtcp2_objalloc_clear(ngtcp2_objalloc *objalloc) {
+  ngtcp2_opl_clear(&objalloc->opl);
+  ngtcp2_balloc_clear(&objalloc->balloc);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_objalloc.h b/third_party/ngtcp2/lib/ngtcp2_objalloc.h
new file mode 100644 (file)
index 0000000..cf23de7
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_OBJALLOC_H
+#define NGTCP2_OBJALLOC_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_balloc.h"
+#include "ngtcp2_opl.h"
+#include "ngtcp2_macro.h"
+#include "ngtcp2_mem.h"
+
+/*
+ * ngtcp2_objalloc combines ngtcp2_balloc and ngtcp2_opl, and provides
+ * an object pool with the custom allocator to reduce the allocation
+ * and deallocation overheads for small objects.
+ */
+typedef struct ngtcp2_objalloc {
+  ngtcp2_balloc balloc;
+  ngtcp2_opl opl;
+} ngtcp2_objalloc;
+
+/*
+ * ngtcp2_objalloc_init initializes |objalloc|.  |blklen| is directly
+ * passed to ngtcp2_balloc_init.
+ */
+void ngtcp2_objalloc_init(ngtcp2_objalloc *objalloc, size_t blklen,
+                          const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_objalloc_free releases all allocated resources.
+ */
+void ngtcp2_objalloc_free(ngtcp2_objalloc *objalloc);
+
+/*
+ * ngtcp2_objalloc_clear releases all allocated resources and
+ * initializes its state.
+ */
+void ngtcp2_objalloc_clear(ngtcp2_objalloc *objalloc);
+
+#ifndef NOMEMPOOL
+#  define ngtcp2_objalloc_decl(NAME, TYPE, OPLENTFIELD)                        \
+    inline static void ngtcp2_objalloc_##NAME##_init(                          \
+      ngtcp2_objalloc *objalloc, size_t nmemb, const ngtcp2_mem *mem) {        \
+      ngtcp2_objalloc_init(                                                    \
+        objalloc, ((sizeof(TYPE) + 0xfu) & ~(uintptr_t)0xfu) * nmemb, mem);    \
+    }                                                                          \
+                                                                               \
+    TYPE *ngtcp2_objalloc_##NAME##_get(ngtcp2_objalloc *objalloc);             \
+                                                                               \
+    TYPE *ngtcp2_objalloc_##NAME##_len_get(ngtcp2_objalloc *objalloc,          \
+                                           size_t len);                        \
+                                                                               \
+    inline static void ngtcp2_objalloc_##NAME##_release(                       \
+      ngtcp2_objalloc *objalloc, TYPE *obj) {                                  \
+      ngtcp2_opl_push(&objalloc->opl, &obj->OPLENTFIELD);                      \
+    }
+
+#  define ngtcp2_objalloc_def(NAME, TYPE, OPLENTFIELD)                         \
+    TYPE *ngtcp2_objalloc_##NAME##_get(ngtcp2_objalloc *objalloc) {            \
+      ngtcp2_opl_entry *oplent = ngtcp2_opl_pop(&objalloc->opl);               \
+      TYPE *obj;                                                               \
+      int rv;                                                                  \
+                                                                               \
+      if (!oplent) {                                                           \
+        rv =                                                                   \
+          ngtcp2_balloc_get(&objalloc->balloc, (void **)&obj, sizeof(TYPE));   \
+        if (rv != 0) {                                                         \
+          return NULL;                                                         \
+        }                                                                      \
+                                                                               \
+        return obj;                                                            \
+      }                                                                        \
+                                                                               \
+      return ngtcp2_struct_of(oplent, TYPE, OPLENTFIELD);                      \
+    }                                                                          \
+                                                                               \
+    TYPE *ngtcp2_objalloc_##NAME##_len_get(ngtcp2_objalloc *objalloc,          \
+                                           size_t len) {                       \
+      ngtcp2_opl_entry *oplent = ngtcp2_opl_pop(&objalloc->opl);               \
+      TYPE *obj;                                                               \
+      int rv;                                                                  \
+                                                                               \
+      if (!oplent) {                                                           \
+        rv = ngtcp2_balloc_get(&objalloc->balloc, (void **)&obj, len);         \
+        if (rv != 0) {                                                         \
+          return NULL;                                                         \
+        }                                                                      \
+                                                                               \
+        return obj;                                                            \
+      }                                                                        \
+                                                                               \
+      return ngtcp2_struct_of(oplent, TYPE, OPLENTFIELD);                      \
+    }
+#else /* defined(NOMEMPOOL) */
+#  define ngtcp2_objalloc_decl(NAME, TYPE, OPLENTFIELD)                        \
+    inline static void ngtcp2_objalloc_##NAME##_init(                          \
+      ngtcp2_objalloc *objalloc, size_t nmemb, const ngtcp2_mem *mem) {        \
+      ngtcp2_objalloc_init(                                                    \
+        objalloc, ((sizeof(TYPE) + 0xfu) & ~(uintptr_t)0xfu) * nmemb, mem);    \
+    }                                                                          \
+                                                                               \
+    inline static TYPE *ngtcp2_objalloc_##NAME##_get(                          \
+      ngtcp2_objalloc *objalloc) {                                             \
+      return ngtcp2_mem_malloc(objalloc->balloc.mem, sizeof(TYPE));            \
+    }                                                                          \
+                                                                               \
+    inline static TYPE *ngtcp2_objalloc_##NAME##_len_get(                      \
+      ngtcp2_objalloc *objalloc, size_t len) {                                 \
+      return ngtcp2_mem_malloc(objalloc->balloc.mem, len);                     \
+    }                                                                          \
+                                                                               \
+    inline static void ngtcp2_objalloc_##NAME##_release(                       \
+      ngtcp2_objalloc *objalloc, TYPE *obj) {                                  \
+      ngtcp2_mem_free(objalloc->balloc.mem, obj);                              \
+    }
+
+#  define ngtcp2_objalloc_def(NAME, TYPE, OPLENTFIELD)
+#endif /* defined(NOMEMPOOL) */
+
+#endif /* !defined(NGTCP2_OBJALLOC_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_opl.c b/third_party/ngtcp2/lib/ngtcp2_opl.c
new file mode 100644 (file)
index 0000000..a29361c
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_opl.h"
+
+void ngtcp2_opl_init(ngtcp2_opl *opl) { opl->head = NULL; }
+
+void ngtcp2_opl_push(ngtcp2_opl *opl, ngtcp2_opl_entry *ent) {
+  ent->next = opl->head;
+  opl->head = ent;
+}
+
+ngtcp2_opl_entry *ngtcp2_opl_pop(ngtcp2_opl *opl) {
+  ngtcp2_opl_entry *ent = opl->head;
+
+  if (!ent) {
+    return NULL;
+  }
+
+  opl->head = ent->next;
+
+  return ent;
+}
+
+void ngtcp2_opl_clear(ngtcp2_opl *opl) { opl->head = NULL; }
diff --git a/third_party/ngtcp2/lib/ngtcp2_opl.h b/third_party/ngtcp2/lib/ngtcp2_opl.h
new file mode 100644 (file)
index 0000000..f2df3f6
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_OPL_H
+#define NGTCP2_OPL_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+typedef struct ngtcp2_opl_entry ngtcp2_opl_entry;
+
+struct ngtcp2_opl_entry {
+  ngtcp2_opl_entry *next;
+};
+
+/*
+ * ngtcp2_opl is an object memory pool.
+ */
+typedef struct ngtcp2_opl {
+  ngtcp2_opl_entry *head;
+} ngtcp2_opl;
+
+/*
+ * ngtcp2_opl_init initializes |opl|.
+ */
+void ngtcp2_opl_init(ngtcp2_opl *opl);
+
+/*
+ * ngtcp2_opl_push inserts |ent| to |opl| head.
+ */
+void ngtcp2_opl_push(ngtcp2_opl *opl, ngtcp2_opl_entry *ent);
+
+/*
+ * ngtcp2_opl_pop removes the first ngtcp2_opl_entry from |opl| and
+ * returns it.  If |opl| does not have any entry, it returns NULL.
+ */
+ngtcp2_opl_entry *ngtcp2_opl_pop(ngtcp2_opl *opl);
+
+void ngtcp2_opl_clear(ngtcp2_opl *opl);
+
+#endif /* !defined(NGTCP2_OPL_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_path.c b/third_party/ngtcp2/lib/ngtcp2_path.c
new file mode 100644 (file)
index 0000000..8323873
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_path.h"
+
+#include <string.h>
+
+#include "ngtcp2_addr.h"
+
+void ngtcp2_path_init(ngtcp2_path *path, const ngtcp2_addr *local,
+                      const ngtcp2_addr *remote) {
+  path->local = *local;
+  path->remote = *remote;
+}
+
+void ngtcp2_path_copy(ngtcp2_path *dest, const ngtcp2_path *src) {
+  ngtcp2_addr_copy(&dest->local, &src->local);
+  ngtcp2_addr_copy(&dest->remote, &src->remote);
+  dest->user_data = src->user_data;
+}
+
+int ngtcp2_path_eq(const ngtcp2_path *a, const ngtcp2_path *b) {
+  return ngtcp2_addr_eq(&a->local, &b->local) &&
+         ngtcp2_addr_eq(&a->remote, &b->remote);
+}
+
+void ngtcp2_path_storage_init(ngtcp2_path_storage *ps,
+                              const ngtcp2_sockaddr *local_addr,
+                              ngtcp2_socklen local_addrlen,
+                              const ngtcp2_sockaddr *remote_addr,
+                              ngtcp2_socklen remote_addrlen, void *user_data) {
+  ngtcp2_addr_init(&ps->path.local, (const ngtcp2_sockaddr *)&ps->local_addrbuf,
+                   0);
+  ngtcp2_addr_init(&ps->path.remote,
+                   (const ngtcp2_sockaddr *)&ps->remote_addrbuf, 0);
+
+  ngtcp2_addr_copy_byte(&ps->path.local, local_addr, local_addrlen);
+  ngtcp2_addr_copy_byte(&ps->path.remote, remote_addr, remote_addrlen);
+
+  ps->path.user_data = user_data;
+}
+
+void ngtcp2_path_storage_init2(ngtcp2_path_storage *ps,
+                               const ngtcp2_path *path) {
+  ngtcp2_path_storage_init(ps, path->local.addr, path->local.addrlen,
+                           path->remote.addr, path->remote.addrlen,
+                           path->user_data);
+}
+
+void ngtcp2_path_storage_zero(ngtcp2_path_storage *ps) {
+  ngtcp2_addr_init(&ps->path.local, (const ngtcp2_sockaddr *)&ps->local_addrbuf,
+                   0);
+  ngtcp2_addr_init(&ps->path.remote,
+                   (const ngtcp2_sockaddr *)&ps->remote_addrbuf, 0);
+  ps->path.user_data = NULL;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_path.h b/third_party/ngtcp2/lib/ngtcp2_path.h
new file mode 100644 (file)
index 0000000..a708378
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_PATH_H
+#define NGTCP2_PATH_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/*
+ * ngtcp2_path_init initializes |path| with the given addresses.  Note
+ * that the buffer pointed by local->addr and remote->addr are not
+ * copied.  Their pointer values are assigned instead.
+ */
+void ngtcp2_path_init(ngtcp2_path *path, const ngtcp2_addr *local,
+                      const ngtcp2_addr *remote);
+
+/*
+ * ngtcp2_path_storage_init2 initializes |ps| using |path| as initial
+ * data.
+ */
+void ngtcp2_path_storage_init2(ngtcp2_path_storage *ps,
+                               const ngtcp2_path *path);
+
+#endif /* !defined(NGTCP2_PATH_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_pkt.c b/third_party/ngtcp2/lib/ngtcp2_pkt.c
new file mode 100644 (file)
index 0000000..af8a059
--- /dev/null
@@ -0,0 +1,2573 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_pkt.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "ngtcp2_conv.h"
+#include "ngtcp2_str.h"
+#include "ngtcp2_macro.h"
+#include "ngtcp2_cid.h"
+#include "ngtcp2_mem.h"
+#include "ngtcp2_vec.h"
+#include "ngtcp2_unreachable.h"
+
+int ngtcp2_pkt_chain_new(ngtcp2_pkt_chain **ppc, const ngtcp2_path *path,
+                         const ngtcp2_pkt_info *pi, const uint8_t *pkt,
+                         size_t pktlen, size_t dgramlen, ngtcp2_tstamp ts,
+                         const ngtcp2_mem *mem) {
+  *ppc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pkt_chain) + pktlen);
+  if (*ppc == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_path_storage_init2(&(*ppc)->path, path);
+  (*ppc)->pi = *pi;
+  (*ppc)->next = NULL;
+  (*ppc)->pkt = (uint8_t *)(*ppc) + sizeof(ngtcp2_pkt_chain);
+  (*ppc)->pktlen = pktlen;
+  (*ppc)->dgramlen = dgramlen;
+  (*ppc)->ts = ts;
+
+  memcpy((*ppc)->pkt, pkt, pktlen);
+
+  return 0;
+}
+
+void ngtcp2_pkt_chain_del(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem) {
+  ngtcp2_mem_free(mem, pc);
+}
+
+int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest, const uint8_t *data,
+                                  size_t datalen, size_t short_dcidlen) {
+  size_t len;
+  uint32_t version;
+  size_t dcidlen, scidlen;
+  int supported_version;
+
+  assert(datalen);
+
+  if (data[0] & NGTCP2_HEADER_FORM_BIT) {
+    /* 1 byte (Header Form, Fixed Bit, Long Packet Type, Type-Specific bits)
+     * 4 bytes Version
+     * 1 byte DCID Length
+     * 1 byte SCID Length
+     */
+    len = 1 + 4 + 1 + 1;
+    if (datalen < len) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    dcidlen = data[5];
+    len += dcidlen;
+
+    if (datalen < len) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    scidlen = data[5 + 1 + dcidlen];
+    len += scidlen;
+
+    if (datalen < len) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    ngtcp2_get_uint32be(&version, &data[1]);
+
+    supported_version = ngtcp2_is_supported_version(version);
+
+    if (supported_version &&
+        (dcidlen > NGTCP2_MAX_CIDLEN || scidlen > NGTCP2_MAX_CIDLEN)) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    if (version && !supported_version &&
+        datalen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    dest->version = version;
+    dest->dcid = &data[6];
+    dest->dcidlen = dcidlen;
+    dest->scid = &data[6 + dcidlen + 1];
+    dest->scidlen = scidlen;
+
+    if (!version) {
+      /* VN */
+      return 0;
+    }
+
+    if (!supported_version) {
+      return NGTCP2_ERR_VERSION_NEGOTIATION;
+    }
+
+    return 0;
+  }
+
+  assert(short_dcidlen <= NGTCP2_MAX_CIDLEN);
+
+  len = 1 + short_dcidlen;
+  if (datalen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  dest->version = 0;
+  dest->dcid = &data[1];
+  dest->dcidlen = short_dcidlen;
+  dest->scid = NULL;
+  dest->scidlen = 0;
+
+  return 0;
+}
+
+void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type,
+                        const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
+                        int64_t pkt_num, size_t pkt_numlen, uint32_t version) {
+  hd->flags = flags;
+  hd->type = type;
+
+  if (dcid) {
+    hd->dcid = *dcid;
+  } else {
+    ngtcp2_cid_zero(&hd->dcid);
+  }
+
+  if (scid) {
+    hd->scid = *scid;
+  } else {
+    ngtcp2_cid_zero(&hd->scid);
+  }
+
+  hd->pkt_num = pkt_num;
+  hd->token = NULL;
+  hd->tokenlen = 0;
+  hd->pkt_numlen = pkt_numlen;
+  hd->version = version;
+  hd->len = 0;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
+                                       size_t pktlen) {
+  uint8_t type;
+  uint32_t version;
+  size_t dcil, scil;
+  const uint8_t *p;
+  size_t len = 0;
+  size_t ntokenlen = 0;
+  const uint8_t *token = NULL;
+  size_t tokenlen = 0;
+  size_t nlonglen = 0;
+  size_t longlen = 0;
+  uint64_t vi;
+  uint8_t flags = NGTCP2_PKT_FLAG_LONG_FORM;
+
+  if (pktlen < 5) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (!(pkt[0] & NGTCP2_HEADER_FORM_BIT)) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  ngtcp2_get_uint32be(&version, &pkt[1]);
+
+  if (version == 0) {
+    type = NGTCP2_PKT_VERSION_NEGOTIATION;
+    /* Version Negotiation is not a long header packet. */
+    flags = NGTCP2_PKT_FLAG_NONE;
+    /* This must be Version Negotiation packet which lacks packet
+       number and payload length fields. */
+    len = 5 + 2;
+  } else {
+    if (!(pkt[0] & NGTCP2_FIXED_BIT_MASK)) {
+      flags |= NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR;
+    }
+
+    type = ngtcp2_pkt_get_type_long(version, pkt[0]);
+    switch (type) {
+    case 0:
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    case NGTCP2_PKT_INITIAL:
+      len = 1 /* Token Length */ + NGTCP2_MIN_LONG_HEADERLEN -
+            1; /* Cut packet number field */
+      break;
+    case NGTCP2_PKT_RETRY:
+      /* Retry packet does not have packet number and length fields */
+      len = 5 + 2;
+      break;
+    case NGTCP2_PKT_HANDSHAKE:
+    case NGTCP2_PKT_0RTT:
+      len = NGTCP2_MIN_LONG_HEADERLEN - 1; /* Cut packet number field */
+      break;
+    default:
+      /* Unreachable */
+      ngtcp2_unreachable();
+    }
+  }
+
+  if (pktlen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  p = &pkt[5];
+  dcil = *p;
+
+  if (dcil > NGTCP2_MAX_CIDLEN) {
+    /* QUIC v1 implementation never expect to receive CID length more
+       than NGTCP2_MAX_CIDLEN. */
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+  len += dcil;
+
+  if (pktlen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  p += 1 + dcil;
+  scil = *p;
+
+  if (scil > NGTCP2_MAX_CIDLEN) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  len += scil;
+
+  if (pktlen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  p += 1 + scil;
+
+  if (type == NGTCP2_PKT_INITIAL) {
+    /* Token Length */
+    ntokenlen = ngtcp2_get_uvarintlen(p);
+    len += ntokenlen - 1;
+
+    if (pktlen < len) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    p = ngtcp2_get_uvarint(&vi, p);
+    if (pktlen - len < vi) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    tokenlen = (size_t)vi;
+    len += tokenlen;
+
+    if (tokenlen) {
+      token = p;
+    }
+
+    p += tokenlen;
+  }
+
+  switch (type) {
+  case NGTCP2_PKT_RETRY:
+    break;
+  default:
+    if (!(flags & NGTCP2_PKT_FLAG_LONG_FORM)) {
+      assert(type == NGTCP2_PKT_VERSION_NEGOTIATION);
+      /* Version Negotiation is not a long header packet. */
+      break;
+    }
+
+    /* Length */
+    nlonglen = ngtcp2_get_uvarintlen(p);
+    len += nlonglen - 1;
+
+    if (pktlen < len) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+
+    ngtcp2_get_uvarint(&vi, p);
+#if SIZE_MAX < UINT64_MAX
+    if (vi > SIZE_MAX) {
+      return NGTCP2_ERR_INVALID_ARGUMENT;
+    }
+#endif /* SIZE_MAX < UINT64_MAX */
+
+    longlen = (size_t)vi;
+  }
+
+  dest->flags = flags;
+  dest->type = type;
+  dest->version = version;
+  dest->pkt_num = 0;
+  dest->pkt_numlen = 0;
+
+  p = &pkt[6];
+  ngtcp2_cid_init(&dest->dcid, p, dcil);
+  p += dcil + 1;
+  ngtcp2_cid_init(&dest->scid, p, scil);
+  p += scil;
+
+  dest->token = token;
+  dest->tokenlen = tokenlen;
+  p += ntokenlen + tokenlen;
+
+  dest->len = longlen;
+
+#ifndef NDEBUG
+  p += nlonglen;
+#endif /* !defined(NDEBUG) */
+
+  assert((size_t)(p - pkt) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt,
+                                        size_t pktlen, size_t dcidlen) {
+  size_t len = 1 + dcidlen;
+  const uint8_t *p = pkt;
+  uint8_t flags = NGTCP2_PKT_FLAG_NONE;
+
+  assert(dcidlen <= NGTCP2_MAX_CIDLEN);
+
+  if (pktlen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (pkt[0] & NGTCP2_HEADER_FORM_BIT) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (!(pkt[0] & NGTCP2_FIXED_BIT_MASK)) {
+    flags |= NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR;
+  }
+
+  p = &pkt[1];
+
+  dest->type = NGTCP2_PKT_1RTT;
+
+  ngtcp2_cid_init(&dest->dcid, p, dcidlen);
+  p += dcidlen;
+
+  /* Set 0 to SCID so that we don't accidentally reference it and gets
+     garbage. */
+  ngtcp2_cid_zero(&dest->scid);
+
+  dest->flags = flags;
+  dest->version = 0;
+  dest->len = 0;
+  dest->pkt_num = 0;
+  dest->pkt_numlen = 0;
+  dest->token = NULL;
+  dest->tokenlen = 0;
+
+  assert((size_t)(p - pkt) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen,
+                                       const ngtcp2_pkt_hd *hd) {
+  uint8_t *p;
+  size_t len = NGTCP2_MIN_LONG_HEADERLEN + hd->dcid.datalen + hd->scid.datalen -
+               2; /* NGTCP2_MIN_LONG_HEADERLEN includes 1 byte for
+                     len and 1 byte for packet number. */
+
+  if (hd->type != NGTCP2_PKT_RETRY) {
+    len += NGTCP2_PKT_LENGTHLEN /* Length */ + hd->pkt_numlen;
+  }
+
+  if (hd->type == NGTCP2_PKT_INITIAL) {
+    len += ngtcp2_put_uvarintlen(hd->tokenlen) + hd->tokenlen;
+  }
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p = (uint8_t)(NGTCP2_HEADER_FORM_BIT |
+                 (ngtcp2_pkt_versioned_type(hd->version, hd->type) << 4) |
+                 (uint8_t)(hd->pkt_numlen - 1));
+
+  if (!(hd->flags & NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR)) {
+    *p |= NGTCP2_FIXED_BIT_MASK;
+  }
+
+  ++p;
+
+  p = ngtcp2_put_uint32be(p, hd->version);
+  *p++ = (uint8_t)hd->dcid.datalen;
+
+  if (hd->dcid.datalen) {
+    p = ngtcp2_cpymem(p, hd->dcid.data, hd->dcid.datalen);
+  }
+
+  *p++ = (uint8_t)hd->scid.datalen;
+
+  if (hd->scid.datalen) {
+    p = ngtcp2_cpymem(p, hd->scid.data, hd->scid.datalen);
+  }
+
+  if (hd->type == NGTCP2_PKT_INITIAL) {
+    p = ngtcp2_put_uvarint(p, hd->tokenlen);
+
+    if (hd->tokenlen) {
+      p = ngtcp2_cpymem(p, hd->token, hd->tokenlen);
+    }
+  }
+
+  if (hd->type != NGTCP2_PKT_RETRY) {
+    p = ngtcp2_put_uvarint30(p, (uint32_t)hd->len);
+    p = ngtcp2_put_pkt_num(p, hd->pkt_num, hd->pkt_numlen);
+  }
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen,
+                                        const ngtcp2_pkt_hd *hd) {
+  uint8_t *p;
+  size_t len = 1 + hd->dcid.datalen + hd->pkt_numlen;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p = (uint8_t)(hd->pkt_numlen - 1);
+
+  if (!(hd->flags & NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR)) {
+    *p |= NGTCP2_FIXED_BIT_MASK;
+  }
+
+  if (hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE) {
+    *p |= NGTCP2_SHORT_KEY_PHASE_BIT;
+  }
+
+  ++p;
+
+  if (hd->dcid.datalen) {
+    p = ngtcp2_cpymem(p, hd->dcid.data, hd->dcid.datalen);
+  }
+
+  p = ngtcp2_put_pkt_num(p, hd->pkt_num, hd->pkt_numlen);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload,
+                                     size_t payloadlen) {
+  uint8_t type;
+
+  if (payloadlen == 0) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  type = payload[0];
+
+  switch (type) {
+  case NGTCP2_FRAME_PADDING:
+    return ngtcp2_pkt_decode_padding_frame(&dest->padding, payload, payloadlen);
+  case NGTCP2_FRAME_RESET_STREAM:
+    return ngtcp2_pkt_decode_reset_stream_frame(&dest->reset_stream, payload,
+                                                payloadlen);
+  case NGTCP2_FRAME_CONNECTION_CLOSE:
+  case NGTCP2_FRAME_CONNECTION_CLOSE_APP:
+    return ngtcp2_pkt_decode_connection_close_frame(&dest->connection_close,
+                                                    payload, payloadlen);
+  case NGTCP2_FRAME_MAX_DATA:
+    return ngtcp2_pkt_decode_max_data_frame(&dest->max_data, payload,
+                                            payloadlen);
+  case NGTCP2_FRAME_MAX_STREAM_DATA:
+    return ngtcp2_pkt_decode_max_stream_data_frame(&dest->max_stream_data,
+                                                   payload, payloadlen);
+  case NGTCP2_FRAME_MAX_STREAMS_BIDI:
+  case NGTCP2_FRAME_MAX_STREAMS_UNI:
+    return ngtcp2_pkt_decode_max_streams_frame(&dest->max_streams, payload,
+                                               payloadlen);
+  case NGTCP2_FRAME_PING:
+    return ngtcp2_pkt_decode_ping_frame(&dest->ping, payload, payloadlen);
+  case NGTCP2_FRAME_DATA_BLOCKED:
+    return ngtcp2_pkt_decode_data_blocked_frame(&dest->data_blocked, payload,
+                                                payloadlen);
+  case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
+    return ngtcp2_pkt_decode_stream_data_blocked_frame(
+      &dest->stream_data_blocked, payload, payloadlen);
+  case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI:
+  case NGTCP2_FRAME_STREAMS_BLOCKED_UNI:
+    return ngtcp2_pkt_decode_streams_blocked_frame(&dest->streams_blocked,
+                                                   payload, payloadlen);
+  case NGTCP2_FRAME_NEW_CONNECTION_ID:
+    return ngtcp2_pkt_decode_new_connection_id_frame(&dest->new_connection_id,
+                                                     payload, payloadlen);
+  case NGTCP2_FRAME_STOP_SENDING:
+    return ngtcp2_pkt_decode_stop_sending_frame(&dest->stop_sending, payload,
+                                                payloadlen);
+  case NGTCP2_FRAME_ACK:
+  case NGTCP2_FRAME_ACK_ECN:
+    return ngtcp2_pkt_decode_ack_frame(&dest->ack, payload, payloadlen);
+  case NGTCP2_FRAME_PATH_CHALLENGE:
+    return ngtcp2_pkt_decode_path_challenge_frame(&dest->path_challenge,
+                                                  payload, payloadlen);
+  case NGTCP2_FRAME_PATH_RESPONSE:
+    return ngtcp2_pkt_decode_path_response_frame(&dest->path_response, payload,
+                                                 payloadlen);
+  case NGTCP2_FRAME_CRYPTO:
+    return ngtcp2_pkt_decode_crypto_frame(&dest->stream, payload, payloadlen);
+  case NGTCP2_FRAME_NEW_TOKEN:
+    return ngtcp2_pkt_decode_new_token_frame(&dest->new_token, payload,
+                                             payloadlen);
+  case NGTCP2_FRAME_RETIRE_CONNECTION_ID:
+    return ngtcp2_pkt_decode_retire_connection_id_frame(
+      &dest->retire_connection_id, payload, payloadlen);
+  case NGTCP2_FRAME_HANDSHAKE_DONE:
+    return ngtcp2_pkt_decode_handshake_done_frame(&dest->handshake_done,
+                                                  payload, payloadlen);
+  case NGTCP2_FRAME_DATAGRAM:
+  case NGTCP2_FRAME_DATAGRAM_LEN:
+    return ngtcp2_pkt_decode_datagram_frame(&dest->datagram, payload,
+                                            payloadlen);
+  default:
+    if ((type & ~(NGTCP2_FRAME_STREAM - 1)) == NGTCP2_FRAME_STREAM) {
+      return ngtcp2_pkt_decode_stream_frame(&dest->stream, payload, payloadlen);
+    }
+
+    /* For frame types > 0xff, use ngtcp2_get_uvarintlen and
+       ngtcp2_get_uvarint to get a frame type, and then switch over
+       it.  Verify that payloadlen >= ngtcp2_get_uvarintlen(payload)
+       before calling ngtcp2_get_uvarint(payload). */
+
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_stream_frame(ngtcp2_stream *dest,
+                                            const uint8_t *payload,
+                                            size_t payloadlen) {
+  uint8_t type;
+  size_t len = 1 + 1;
+  const uint8_t *p;
+  size_t datalen = 0;
+  size_t ndatalen = 0;
+  size_t n;
+  uint64_t vi;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  type = payload[0];
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  if (type & NGTCP2_STREAM_OFF_BIT) {
+    ++len;
+    if (payloadlen < len) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    n = ngtcp2_get_uvarintlen(p);
+    len += n - 1;
+
+    if (payloadlen < len) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    p += n;
+  }
+
+  if (type & NGTCP2_STREAM_LEN_BIT) {
+    ++len;
+    if (payloadlen < len) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    ndatalen = ngtcp2_get_uvarintlen(p);
+    len += ndatalen - 1;
+
+    if (payloadlen < len) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    /* p = */ ngtcp2_get_uvarint(&vi, p);
+    if (payloadlen - len < vi) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    datalen = (size_t)vi;
+    len += datalen;
+  } else {
+    len = payloadlen;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_STREAM;
+  dest->flags = (uint8_t)(type & ~NGTCP2_FRAME_STREAM);
+  dest->fin = (type & NGTCP2_STREAM_FIN_BIT) != 0;
+  p = ngtcp2_get_varint(&dest->stream_id, p);
+
+  if (type & NGTCP2_STREAM_OFF_BIT) {
+    p = ngtcp2_get_uvarint(&dest->offset, p);
+  } else {
+    dest->offset = 0;
+  }
+
+  if (type & NGTCP2_STREAM_LEN_BIT) {
+    p += ndatalen;
+  } else {
+    datalen = payloadlen - (size_t)(p - payload);
+  }
+
+  if (datalen) {
+    dest->data[0].len = datalen;
+    dest->data[0].base = (uint8_t *)p;
+    dest->datacnt = 1;
+    p += datalen;
+  } else {
+    dest->datacnt = 0;
+  }
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_ack_frame(ngtcp2_ack *dest,
+                                         const uint8_t *payload,
+                                         size_t payloadlen) {
+  size_t rangecnt, max_rangecnt;
+  size_t nrangecnt;
+  size_t len = 1 + 1 + 1 + 1 + 1;
+  const uint8_t *p;
+  size_t i, j;
+  ngtcp2_ack_range *range;
+  size_t n;
+  uint8_t type;
+  uint64_t vi;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  type = payload[0];
+
+  p = payload + 1;
+
+  /* Largest Acknowledged */
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  /* ACK Delay */
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  /* ACK Range Count */
+  nrangecnt = ngtcp2_get_uvarintlen(p);
+  len += nrangecnt - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = ngtcp2_get_uvarint(&vi, p);
+  if (vi > SIZE_MAX / (1 + 1) || payloadlen - len < vi * (1 + 1)) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  rangecnt = (size_t)vi;
+  len += rangecnt * (1 + 1);
+
+  /* First ACK Range */
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  for (i = 0; i < rangecnt; ++i) {
+    /* Gap, and Additional ACK Range */
+    for (j = 0; j < 2; ++j) {
+      n = ngtcp2_get_uvarintlen(p);
+      len += n - 1;
+
+      if (payloadlen < len) {
+        return NGTCP2_ERR_FRAME_ENCODING;
+      }
+
+      p += n;
+    }
+  }
+
+  if (type == NGTCP2_FRAME_ACK_ECN) {
+    len += 3;
+    if (payloadlen < len) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    for (i = 0; i < 3; ++i) {
+      n = ngtcp2_get_uvarintlen(p);
+      len += n - 1;
+
+      if (payloadlen < len) {
+        return NGTCP2_ERR_FRAME_ENCODING;
+      }
+
+      p += n;
+    }
+  }
+
+  /* TODO We might not decode all ranges.  It could be very large. */
+  max_rangecnt = ngtcp2_min_size(NGTCP2_MAX_ACK_RANGES, rangecnt);
+
+  p = payload + 1;
+
+  dest->type = type;
+  p = ngtcp2_get_varint(&dest->largest_ack, p);
+  p = ngtcp2_get_uvarint(&dest->ack_delay, p);
+  /* This value will be assigned in the upper layer. */
+  dest->ack_delay_unscaled = 0;
+  dest->rangecnt = max_rangecnt;
+  p += nrangecnt;
+  p = ngtcp2_get_uvarint(&dest->first_ack_range, p);
+
+  for (i = 0; i < max_rangecnt; ++i) {
+    range = &dest->ranges[i];
+    p = ngtcp2_get_uvarint(&range->gap, p);
+    p = ngtcp2_get_uvarint(&range->len, p);
+  }
+
+  for (; i < rangecnt; ++i) {
+    p += ngtcp2_get_uvarintlen(p);
+    p += ngtcp2_get_uvarintlen(p);
+  }
+
+  if (type == NGTCP2_FRAME_ACK_ECN) {
+    p = ngtcp2_get_uvarint(&dest->ecn.ect0, p);
+    p = ngtcp2_get_uvarint(&dest->ecn.ect1, p);
+    p = ngtcp2_get_uvarint(&dest->ecn.ce, p);
+  }
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_padding_frame(ngtcp2_padding *dest,
+                                             const uint8_t *payload,
+                                             size_t payloadlen) {
+  const uint8_t *p, *ep;
+
+  assert(payloadlen > 0);
+
+  p = payload + 1;
+  ep = payload + payloadlen;
+
+  for (; p != ep && *p == NGTCP2_FRAME_PADDING; ++p)
+    ;
+
+  dest->type = NGTCP2_FRAME_PADDING;
+  dest->len = (size_t)(p - payload);
+
+  return (ngtcp2_ssize)dest->len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_reset_stream_frame(ngtcp2_reset_stream *dest,
+                                                  const uint8_t *payload,
+                                                  size_t payloadlen) {
+  size_t len = 1 + 1 + 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_RESET_STREAM;
+  p = ngtcp2_get_varint(&dest->stream_id, p);
+  p = ngtcp2_get_uvarint(&dest->app_error_code, p);
+  p = ngtcp2_get_uvarint(&dest->final_size, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_connection_close_frame(
+  ngtcp2_connection_close *dest, const uint8_t *payload, size_t payloadlen) {
+  size_t len = 1 + 1 + 1;
+  const uint8_t *p;
+  size_t reasonlen;
+  size_t nreasonlen;
+  size_t n;
+  uint8_t type;
+  uint64_t vi;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  type = payload[0];
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  if (type == NGTCP2_FRAME_CONNECTION_CLOSE) {
+    ++len;
+
+    n = ngtcp2_get_uvarintlen(p);
+    len += n - 1;
+
+    if (payloadlen < len) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    p += n;
+  }
+
+  nreasonlen = ngtcp2_get_uvarintlen(p);
+  len += nreasonlen - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = ngtcp2_get_uvarint(&vi, p);
+  if (payloadlen - len < vi) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  reasonlen = (size_t)vi;
+  len += reasonlen;
+
+  p = payload + 1;
+
+  dest->type = type;
+  p = ngtcp2_get_uvarint(&dest->error_code, p);
+
+  if (type == NGTCP2_FRAME_CONNECTION_CLOSE) {
+    p = ngtcp2_get_uvarint(&dest->frame_type, p);
+  } else {
+    dest->frame_type = 0;
+  }
+
+  dest->reasonlen = reasonlen;
+  p += nreasonlen;
+
+  if (reasonlen == 0) {
+    dest->reason = NULL;
+  } else {
+    dest->reason = (uint8_t *)p;
+    p += reasonlen;
+  }
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_max_data_frame(ngtcp2_max_data *dest,
+                                              const uint8_t *payload,
+                                              size_t payloadlen) {
+  size_t len = 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  dest->type = NGTCP2_FRAME_MAX_DATA;
+  p = ngtcp2_get_uvarint(&dest->max_data, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_max_stream_data_frame(
+  ngtcp2_max_stream_data *dest, const uint8_t *payload, size_t payloadlen) {
+  size_t len = 1 + 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_MAX_STREAM_DATA;
+  p = ngtcp2_get_varint(&dest->stream_id, p);
+  p = ngtcp2_get_uvarint(&dest->max_stream_data, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_max_streams_frame(ngtcp2_max_streams *dest,
+                                                 const uint8_t *payload,
+                                                 size_t payloadlen) {
+  size_t len = 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  dest->type = payload[0];
+  p = ngtcp2_get_uvarint(&dest->max_streams, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_ping_frame(ngtcp2_ping *dest,
+                                          const uint8_t *payload,
+                                          size_t payloadlen) {
+  (void)payload;
+  (void)payloadlen;
+
+  dest->type = NGTCP2_FRAME_PING;
+  return 1;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_data_blocked_frame(ngtcp2_data_blocked *dest,
+                                                  const uint8_t *payload,
+                                                  size_t payloadlen) {
+  size_t len = 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  dest->type = NGTCP2_FRAME_DATA_BLOCKED;
+  p = ngtcp2_get_uvarint(&dest->offset, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_stream_data_blocked_frame(
+  ngtcp2_stream_data_blocked *dest, const uint8_t *payload, size_t payloadlen) {
+  size_t len = 1 + 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_STREAM_DATA_BLOCKED;
+  p = ngtcp2_get_varint(&dest->stream_id, p);
+  p = ngtcp2_get_uvarint(&dest->offset, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_streams_blocked_frame(
+  ngtcp2_streams_blocked *dest, const uint8_t *payload, size_t payloadlen) {
+  size_t len = 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  dest->type = payload[0];
+  p = ngtcp2_get_uvarint(&dest->max_streams, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_new_connection_id_frame(
+  ngtcp2_new_connection_id *dest, const uint8_t *payload, size_t payloadlen) {
+  size_t len = 1 + 1 + 1 + 1 + 16;
+  const uint8_t *p;
+  size_t n;
+  size_t cil;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  cil = *p;
+  if (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  len += cil;
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_NEW_CONNECTION_ID;
+  p = ngtcp2_get_uvarint(&dest->seq, p);
+  p = ngtcp2_get_uvarint(&dest->retire_prior_to, p);
+  ++p;
+  ngtcp2_cid_init(&dest->cid, p, cil);
+  p += cil;
+  p = ngtcp2_get_bytes(dest->stateless_reset_token, p,
+                       NGTCP2_STATELESS_RESET_TOKENLEN);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_stop_sending_frame(ngtcp2_stop_sending *dest,
+                                                  const uint8_t *payload,
+                                                  size_t payloadlen) {
+  size_t len = 1 + 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_STOP_SENDING;
+  p = ngtcp2_get_varint(&dest->stream_id, p);
+  p = ngtcp2_get_uvarint(&dest->app_error_code, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_path_challenge_frame(ngtcp2_path_challenge *dest,
+                                                    const uint8_t *payload,
+                                                    size_t payloadlen) {
+  size_t len = 1 + 8;
+  const uint8_t *p;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_PATH_CHALLENGE;
+  ngtcp2_cpymem(dest->data, p, sizeof(dest->data));
+  p += sizeof(dest->data);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_path_response_frame(ngtcp2_path_response *dest,
+                                                   const uint8_t *payload,
+                                                   size_t payloadlen) {
+  size_t len = 1 + 8;
+  const uint8_t *p;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_PATH_RESPONSE;
+  ngtcp2_cpymem(dest->data, p, sizeof(dest->data));
+  p += sizeof(dest->data);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_crypto_frame(ngtcp2_stream *dest,
+                                            const uint8_t *payload,
+                                            size_t payloadlen) {
+  size_t len = 1 + 1 + 1;
+  const uint8_t *p;
+  size_t datalen;
+  size_t ndatalen;
+  size_t n;
+  uint64_t vi;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p += n;
+
+  ndatalen = ngtcp2_get_uvarintlen(p);
+  len += ndatalen - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = ngtcp2_get_uvarint(&vi, p);
+  if (payloadlen - len < vi) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  datalen = (size_t)vi;
+  len += datalen;
+
+  p = payload + 1;
+
+  dest->type = NGTCP2_FRAME_CRYPTO;
+  dest->flags = 0;
+  dest->fin = 0;
+  dest->stream_id = 0;
+  p = ngtcp2_get_uvarint(&dest->offset, p);
+  dest->data[0].len = datalen;
+  p += ndatalen;
+
+  if (dest->data[0].len) {
+    dest->data[0].base = (uint8_t *)p;
+    p += dest->data[0].len;
+    dest->datacnt = 1;
+  } else {
+    dest->data[0].base = NULL;
+    dest->datacnt = 0;
+  }
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_new_token_frame(ngtcp2_new_token *dest,
+                                               const uint8_t *payload,
+                                               size_t payloadlen) {
+  size_t len = 1 + 1;
+  const uint8_t *p;
+  size_t n;
+  size_t datalen;
+  uint64_t vi;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = ngtcp2_get_uvarint(&vi, p);
+  if (payloadlen - len < vi) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  datalen = (size_t)vi;
+  len += datalen;
+
+  dest->type = NGTCP2_FRAME_NEW_TOKEN;
+  dest->tokenlen = datalen;
+  dest->token = (uint8_t *)p;
+  p += dest->tokenlen;
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_decode_retire_connection_id_frame(ngtcp2_retire_connection_id *dest,
+                                             const uint8_t *payload,
+                                             size_t payloadlen) {
+  size_t len = 1 + 1;
+  const uint8_t *p;
+  size_t n;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  p = payload + 1;
+
+  n = ngtcp2_get_uvarintlen(p);
+  len += n - 1;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  dest->type = NGTCP2_FRAME_RETIRE_CONNECTION_ID;
+  p = ngtcp2_get_uvarint(&dest->seq, p);
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_handshake_done_frame(ngtcp2_handshake_done *dest,
+                                                    const uint8_t *payload,
+                                                    size_t payloadlen) {
+  (void)payload;
+  (void)payloadlen;
+
+  dest->type = NGTCP2_FRAME_HANDSHAKE_DONE;
+  return 1;
+}
+
+ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest,
+                                              const uint8_t *payload,
+                                              size_t payloadlen) {
+  size_t len = 1;
+  const uint8_t *p;
+  uint8_t type;
+  size_t datalen;
+  size_t n;
+  uint64_t vi;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_FRAME_ENCODING;
+  }
+
+  type = payload[0];
+
+  p = payload + 1;
+
+  switch (type) {
+  case NGTCP2_FRAME_DATAGRAM:
+    datalen = payloadlen - 1;
+    len = payloadlen;
+    break;
+  case NGTCP2_FRAME_DATAGRAM_LEN:
+    ++len;
+    if (payloadlen < len) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    n = ngtcp2_get_uvarintlen(p);
+    len += n - 1;
+
+    if (payloadlen < len) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    p = ngtcp2_get_uvarint(&vi, p);
+    if (payloadlen - len < vi) {
+      return NGTCP2_ERR_FRAME_ENCODING;
+    }
+
+    datalen = (size_t)vi;
+    len += datalen;
+
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  dest->type = type;
+
+  if (datalen == 0) {
+    dest->datacnt = 0;
+    dest->data = NULL;
+  } else {
+    dest->datacnt = 1;
+    dest->data = dest->rdata;
+    dest->rdata[0].len = datalen;
+
+    dest->rdata[0].base = (uint8_t *)p;
+    p += datalen;
+  }
+
+  assert((size_t)(p - payload) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen,
+                                     ngtcp2_frame *fr) {
+  switch (fr->type) {
+  case NGTCP2_FRAME_STREAM:
+    return ngtcp2_pkt_encode_stream_frame(out, outlen, &fr->stream);
+  case NGTCP2_FRAME_ACK:
+  case NGTCP2_FRAME_ACK_ECN:
+    return ngtcp2_pkt_encode_ack_frame(out, outlen, &fr->ack);
+  case NGTCP2_FRAME_PADDING:
+    return ngtcp2_pkt_encode_padding_frame(out, outlen, &fr->padding);
+  case NGTCP2_FRAME_RESET_STREAM:
+    return ngtcp2_pkt_encode_reset_stream_frame(out, outlen, &fr->reset_stream);
+  case NGTCP2_FRAME_CONNECTION_CLOSE:
+  case NGTCP2_FRAME_CONNECTION_CLOSE_APP:
+    return ngtcp2_pkt_encode_connection_close_frame(out, outlen,
+                                                    &fr->connection_close);
+  case NGTCP2_FRAME_MAX_DATA:
+    return ngtcp2_pkt_encode_max_data_frame(out, outlen, &fr->max_data);
+  case NGTCP2_FRAME_MAX_STREAM_DATA:
+    return ngtcp2_pkt_encode_max_stream_data_frame(out, outlen,
+                                                   &fr->max_stream_data);
+  case NGTCP2_FRAME_MAX_STREAMS_BIDI:
+  case NGTCP2_FRAME_MAX_STREAMS_UNI:
+    return ngtcp2_pkt_encode_max_streams_frame(out, outlen, &fr->max_streams);
+  case NGTCP2_FRAME_PING:
+    return ngtcp2_pkt_encode_ping_frame(out, outlen, &fr->ping);
+  case NGTCP2_FRAME_DATA_BLOCKED:
+    return ngtcp2_pkt_encode_data_blocked_frame(out, outlen, &fr->data_blocked);
+  case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
+    return ngtcp2_pkt_encode_stream_data_blocked_frame(
+      out, outlen, &fr->stream_data_blocked);
+  case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI:
+  case NGTCP2_FRAME_STREAMS_BLOCKED_UNI:
+    return ngtcp2_pkt_encode_streams_blocked_frame(out, outlen,
+                                                   &fr->streams_blocked);
+  case NGTCP2_FRAME_NEW_CONNECTION_ID:
+    return ngtcp2_pkt_encode_new_connection_id_frame(out, outlen,
+                                                     &fr->new_connection_id);
+  case NGTCP2_FRAME_STOP_SENDING:
+    return ngtcp2_pkt_encode_stop_sending_frame(out, outlen, &fr->stop_sending);
+  case NGTCP2_FRAME_PATH_CHALLENGE:
+    return ngtcp2_pkt_encode_path_challenge_frame(out, outlen,
+                                                  &fr->path_challenge);
+  case NGTCP2_FRAME_PATH_RESPONSE:
+    return ngtcp2_pkt_encode_path_response_frame(out, outlen,
+                                                 &fr->path_response);
+  case NGTCP2_FRAME_CRYPTO:
+    return ngtcp2_pkt_encode_crypto_frame(out, outlen, &fr->stream);
+  case NGTCP2_FRAME_NEW_TOKEN:
+    return ngtcp2_pkt_encode_new_token_frame(out, outlen, &fr->new_token);
+  case NGTCP2_FRAME_RETIRE_CONNECTION_ID:
+    return ngtcp2_pkt_encode_retire_connection_id_frame(
+      out, outlen, &fr->retire_connection_id);
+  case NGTCP2_FRAME_HANDSHAKE_DONE:
+    return ngtcp2_pkt_encode_handshake_done_frame(out, outlen,
+                                                  &fr->handshake_done);
+  case NGTCP2_FRAME_DATAGRAM:
+  case NGTCP2_FRAME_DATAGRAM_LEN:
+    return ngtcp2_pkt_encode_datagram_frame(out, outlen, &fr->datagram);
+  default:
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen,
+                                            ngtcp2_stream *fr) {
+  size_t len = 1;
+  uint8_t flags = NGTCP2_STREAM_LEN_BIT;
+  uint8_t *p;
+  size_t i;
+  size_t datalen = 0;
+
+  if (fr->fin) {
+    flags |= NGTCP2_STREAM_FIN_BIT;
+  }
+
+  if (fr->offset) {
+    flags |= NGTCP2_STREAM_OFF_BIT;
+    len += ngtcp2_put_uvarintlen(fr->offset);
+  }
+
+  len += ngtcp2_put_uvarintlen((uint64_t)fr->stream_id);
+
+  for (i = 0; i < fr->datacnt; ++i) {
+    datalen += fr->data[i].len;
+  }
+
+  len += ngtcp2_put_uvarintlen(datalen);
+  len += datalen;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = flags | NGTCP2_FRAME_STREAM;
+
+  fr->flags = flags;
+
+  p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id);
+
+  if (fr->offset) {
+    p = ngtcp2_put_uvarint(p, fr->offset);
+  }
+
+  p = ngtcp2_put_uvarint(p, datalen);
+
+  for (i = 0; i < fr->datacnt; ++i) {
+    assert(fr->data[i].len);
+    assert(fr->data[i].base);
+    p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len);
+  }
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_ack_frame(uint8_t *out, size_t outlen,
+                                         const ngtcp2_ack *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->largest_ack) +
+               ngtcp2_put_uvarintlen(fr->ack_delay) +
+               ngtcp2_put_uvarintlen(fr->rangecnt) +
+               ngtcp2_put_uvarintlen(fr->first_ack_range);
+  uint8_t *p;
+  size_t i;
+  const ngtcp2_ack_range *range;
+
+  for (i = 0; i < fr->rangecnt; ++i) {
+    range = &fr->ranges[i];
+    len += ngtcp2_put_uvarintlen(range->gap);
+    len += ngtcp2_put_uvarintlen(range->len);
+  }
+
+  if (fr->type == NGTCP2_FRAME_ACK_ECN) {
+    len += ngtcp2_put_uvarintlen(fr->ecn.ect0) +
+           ngtcp2_put_uvarintlen(fr->ecn.ect1) +
+           ngtcp2_put_uvarintlen(fr->ecn.ce);
+  }
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = (uint8_t)fr->type;
+  p = ngtcp2_put_uvarint(p, (uint64_t)fr->largest_ack);
+  p = ngtcp2_put_uvarint(p, fr->ack_delay);
+  p = ngtcp2_put_uvarint(p, fr->rangecnt);
+  p = ngtcp2_put_uvarint(p, fr->first_ack_range);
+
+  for (i = 0; i < fr->rangecnt; ++i) {
+    range = &fr->ranges[i];
+    p = ngtcp2_put_uvarint(p, range->gap);
+    p = ngtcp2_put_uvarint(p, range->len);
+  }
+
+  if (fr->type == NGTCP2_FRAME_ACK_ECN) {
+    p = ngtcp2_put_uvarint(p, fr->ecn.ect0);
+    p = ngtcp2_put_uvarint(p, fr->ecn.ect1);
+    p = ngtcp2_put_uvarint(p, fr->ecn.ce);
+  }
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_padding_frame(uint8_t *out, size_t outlen,
+                                             const ngtcp2_padding *fr) {
+  if (outlen < fr->len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  memset(out, 0, fr->len);
+
+  return (ngtcp2_ssize)fr->len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_reset_stream_frame(uint8_t *out, size_t outlen,
+                                     const ngtcp2_reset_stream *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) +
+               ngtcp2_put_uvarintlen(fr->app_error_code) +
+               ngtcp2_put_uvarintlen(fr->final_size);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_RESET_STREAM;
+  p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id);
+  p = ngtcp2_put_uvarint(p, fr->app_error_code);
+  p = ngtcp2_put_uvarint(p, fr->final_size);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_connection_close_frame(uint8_t *out, size_t outlen,
+                                         const ngtcp2_connection_close *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen(fr->error_code) +
+               (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE
+                  ? ngtcp2_put_uvarintlen(fr->frame_type)
+                  : 0) +
+               ngtcp2_put_uvarintlen(fr->reasonlen) + fr->reasonlen;
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = (uint8_t)fr->type;
+  p = ngtcp2_put_uvarint(p, fr->error_code);
+
+  if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) {
+    p = ngtcp2_put_uvarint(p, fr->frame_type);
+  }
+
+  p = ngtcp2_put_uvarint(p, fr->reasonlen);
+
+  if (fr->reasonlen) {
+    p = ngtcp2_cpymem(p, fr->reason, fr->reasonlen);
+  }
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_max_data_frame(uint8_t *out, size_t outlen,
+                                              const ngtcp2_max_data *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_data);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_MAX_DATA;
+  p = ngtcp2_put_uvarint(p, fr->max_data);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_max_stream_data_frame(uint8_t *out, size_t outlen,
+                                        const ngtcp2_max_stream_data *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) +
+               ngtcp2_put_uvarintlen(fr->max_stream_data);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_MAX_STREAM_DATA;
+  p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id);
+  p = ngtcp2_put_uvarint(p, fr->max_stream_data);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_max_streams_frame(uint8_t *out, size_t outlen,
+                                                 const ngtcp2_max_streams *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_streams);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = (uint8_t)fr->type;
+  p = ngtcp2_put_uvarint(p, fr->max_streams);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_ping_frame(uint8_t *out, size_t outlen,
+                                          const ngtcp2_ping *fr) {
+  (void)fr;
+
+  if (outlen < 1) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  *out++ = NGTCP2_FRAME_PING;
+
+  return 1;
+}
+
+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);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_DATA_BLOCKED;
+  p = ngtcp2_put_uvarint(p, fr->offset);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+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);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_STREAM_DATA_BLOCKED;
+  p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id);
+  p = ngtcp2_put_uvarint(p, fr->offset);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_streams_blocked_frame(uint8_t *out, size_t outlen,
+                                        const ngtcp2_streams_blocked *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_streams);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = (uint8_t)fr->type;
+  p = ngtcp2_put_uvarint(p, fr->max_streams);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_new_connection_id_frame(uint8_t *out, size_t outlen,
+                                          const ngtcp2_new_connection_id *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen(fr->seq) +
+               ngtcp2_put_uvarintlen(fr->retire_prior_to) + 1 +
+               fr->cid.datalen + NGTCP2_STATELESS_RESET_TOKENLEN;
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_NEW_CONNECTION_ID;
+  p = ngtcp2_put_uvarint(p, fr->seq);
+  p = ngtcp2_put_uvarint(p, fr->retire_prior_to);
+  *p++ = (uint8_t)fr->cid.datalen;
+  p = ngtcp2_cpymem(p, fr->cid.data, fr->cid.datalen);
+  p = ngtcp2_cpymem(p, fr->stateless_reset_token,
+                    NGTCP2_STATELESS_RESET_TOKENLEN);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_stop_sending_frame(uint8_t *out, size_t outlen,
+                                     const ngtcp2_stop_sending *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) +
+               ngtcp2_put_uvarintlen(fr->app_error_code);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_STOP_SENDING;
+  p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id);
+  p = ngtcp2_put_uvarint(p, fr->app_error_code);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_path_challenge_frame(uint8_t *out, size_t outlen,
+                                       const ngtcp2_path_challenge *fr) {
+  size_t len = 1 + 8;
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_PATH_CHALLENGE;
+  p = ngtcp2_cpymem(p, fr->data, sizeof(fr->data));
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_path_response_frame(uint8_t *out, size_t outlen,
+                                      const ngtcp2_path_response *fr) {
+  size_t len = 1 + 8;
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_PATH_RESPONSE;
+  p = ngtcp2_cpymem(p, fr->data, sizeof(fr->data));
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_crypto_frame(uint8_t *out, size_t outlen,
+                                            const ngtcp2_stream *fr) {
+  size_t len = 1;
+  uint8_t *p;
+  size_t i;
+  size_t datalen = 0;
+
+  len += ngtcp2_put_uvarintlen(fr->offset);
+
+  for (i = 0; i < fr->datacnt; ++i) {
+    datalen += fr->data[i].len;
+  }
+
+  len += ngtcp2_put_uvarintlen(datalen);
+  len += datalen;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_CRYPTO;
+
+  p = ngtcp2_put_uvarint(p, fr->offset);
+  p = ngtcp2_put_uvarint(p, datalen);
+
+  for (i = 0; i < fr->datacnt; ++i) {
+    assert(fr->data[i].base);
+    p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len);
+  }
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen,
+                                               const ngtcp2_new_token *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen(fr->tokenlen) + fr->tokenlen;
+  uint8_t *p;
+
+  assert(fr->tokenlen);
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_NEW_TOKEN;
+
+  p = ngtcp2_put_uvarint(p, fr->tokenlen);
+  p = ngtcp2_cpymem(p, fr->token, fr->tokenlen);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_retire_connection_id_frame(
+  uint8_t *out, size_t outlen, const ngtcp2_retire_connection_id *fr) {
+  size_t len = 1 + ngtcp2_put_uvarintlen(fr->seq);
+  uint8_t *p;
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = NGTCP2_FRAME_RETIRE_CONNECTION_ID;
+
+  p = ngtcp2_put_uvarint(p, fr->seq);
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_encode_handshake_done_frame(uint8_t *out, size_t outlen,
+                                       const ngtcp2_handshake_done *fr) {
+  (void)fr;
+
+  if (outlen < 1) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  *out++ = NGTCP2_FRAME_HANDSHAKE_DONE;
+
+  return 1;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_datagram_frame(uint8_t *out, size_t outlen,
+                                              const ngtcp2_datagram *fr) {
+  uint64_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt);
+  uint64_t len =
+    1 +
+    (fr->type == NGTCP2_FRAME_DATAGRAM ? 0 : ngtcp2_put_uvarintlen(datalen)) +
+    datalen;
+  uint8_t *p;
+  size_t i;
+
+  assert(fr->type == NGTCP2_FRAME_DATAGRAM ||
+         fr->type == NGTCP2_FRAME_DATAGRAM_LEN);
+
+  if (outlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = out;
+
+  *p++ = (uint8_t)fr->type;
+
+  if (fr->type == NGTCP2_FRAME_DATAGRAM_LEN) {
+    p = ngtcp2_put_uvarint(p, datalen);
+  }
+
+  for (i = 0; i < fr->datacnt; ++i) {
+    if (fr->data[i].len == 0) {
+      continue;
+    }
+
+    p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len);
+  }
+
+  assert((size_t)(p - out) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+ngtcp2_ssize ngtcp2_pkt_write_version_negotiation(
+  uint8_t *dest, size_t destlen, uint8_t unused_random, const uint8_t *dcid,
+  size_t dcidlen, const uint8_t *scid, size_t scidlen, const uint32_t *sv,
+  size_t nsv) {
+  size_t len = 1 + 4 + 1 + dcidlen + 1 + scidlen + nsv * 4;
+  uint8_t *p;
+  size_t i;
+
+  assert(dcidlen < 256);
+  assert(scidlen < 256);
+
+  if (destlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = dest;
+
+  *p++ = 0xc0 | unused_random;
+  p = ngtcp2_put_uint32be(p, 0);
+  *p++ = (uint8_t)dcidlen;
+
+  if (dcidlen) {
+    p = ngtcp2_cpymem(p, dcid, dcidlen);
+  }
+
+  *p++ = (uint8_t)scidlen;
+
+  if (scidlen) {
+    p = ngtcp2_cpymem(p, scid, scidlen);
+  }
+
+  for (i = 0; i < nsv; ++i) {
+    p = ngtcp2_put_uint32be(p, sv[i]);
+  }
+
+  assert((size_t)(p - dest) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+size_t ngtcp2_pkt_decode_version_negotiation(uint32_t *dest,
+                                             const uint8_t *payload,
+                                             size_t payloadlen) {
+  const uint8_t *end = payload + payloadlen;
+
+  assert((payloadlen % sizeof(uint32_t)) == 0);
+
+  for (; payload != end;) {
+    payload = ngtcp2_get_uint32be(dest++, payload);
+  }
+
+  return payloadlen / sizeof(uint32_t);
+}
+
+int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr,
+                                      const uint8_t *payload,
+                                      size_t payloadlen) {
+  const uint8_t *p = payload;
+
+  if (payloadlen <
+      NGTCP2_MIN_STATELESS_RESET_RANDLEN + NGTCP2_STATELESS_RESET_TOKENLEN) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  sr->rand = p;
+  sr->randlen = payloadlen - NGTCP2_STATELESS_RESET_TOKENLEN;
+  p += sr->randlen;
+  memcpy(sr->stateless_reset_token, p, NGTCP2_STATELESS_RESET_TOKENLEN);
+
+  return 0;
+}
+
+int ngtcp2_pkt_decode_retry(ngtcp2_pkt_retry *dest, const uint8_t *payload,
+                            size_t payloadlen) {
+  size_t len = /* token */ 1 + NGTCP2_RETRY_TAGLEN;
+
+  if (payloadlen < len) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  dest->token = (uint8_t *)payload;
+  dest->tokenlen = (size_t)(payloadlen - NGTCP2_RETRY_TAGLEN);
+  ngtcp2_cpymem(dest->tag, payload + dest->tokenlen, NGTCP2_RETRY_TAGLEN);
+
+  return 0;
+}
+
+int64_t ngtcp2_pkt_adjust_pkt_num(int64_t max_pkt_num, int64_t pkt_num,
+                                  size_t pkt_numlen) {
+  int64_t expected = max_pkt_num + 1;
+  int64_t win = (int64_t)1 << (pkt_numlen * 8);
+  int64_t hwin = win / 2;
+  int64_t mask = win - 1;
+  int64_t cand = (expected & ~mask) | pkt_num;
+
+  if (cand <= expected - hwin) {
+    assert(cand <= (int64_t)NGTCP2_MAX_VARINT - win);
+    return cand + win;
+  }
+
+  if (cand > expected + hwin && cand >= win) {
+    return cand - win;
+  }
+
+  return cand;
+}
+
+int ngtcp2_pkt_validate_ack(const ngtcp2_ack *fr, int64_t min_pkt_num) {
+  int64_t largest_ack = fr->largest_ack;
+  size_t i;
+
+  if (largest_ack < (int64_t)fr->first_ack_range) {
+    return NGTCP2_ERR_ACK_FRAME;
+  }
+
+  largest_ack -= (int64_t)fr->first_ack_range;
+
+  if (largest_ack < min_pkt_num) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  for (i = 0; i < fr->rangecnt; ++i) {
+    if (largest_ack < (int64_t)fr->ranges[i].gap + 2) {
+      return NGTCP2_ERR_ACK_FRAME;
+    }
+
+    largest_ack -= (int64_t)fr->ranges[i].gap + 2;
+
+    if (largest_ack < (int64_t)fr->ranges[i].len) {
+      return NGTCP2_ERR_ACK_FRAME;
+    }
+
+    largest_ack -= (int64_t)fr->ranges[i].len;
+
+    if (largest_ack < min_pkt_num) {
+      return NGTCP2_ERR_PROTO;
+    }
+  }
+
+  return 0;
+}
+
+ngtcp2_ssize
+ngtcp2_pkt_write_stateless_reset(uint8_t *dest, size_t destlen,
+                                 const uint8_t *stateless_reset_token,
+                                 const uint8_t *rand, size_t randlen) {
+  uint8_t *p;
+
+  if (destlen <
+      NGTCP2_MIN_STATELESS_RESET_RANDLEN + NGTCP2_STATELESS_RESET_TOKENLEN) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  if (randlen < NGTCP2_MIN_STATELESS_RESET_RANDLEN) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  p = dest;
+
+  randlen = ngtcp2_min_size(destlen - NGTCP2_STATELESS_RESET_TOKENLEN, randlen);
+
+  p = ngtcp2_cpymem(p, rand, randlen);
+  p = ngtcp2_cpymem(p, stateless_reset_token, NGTCP2_STATELESS_RESET_TOKENLEN);
+  *dest = (uint8_t)((*dest & 0x7fu) | 0x40u);
+
+  return p - dest;
+}
+
+ngtcp2_ssize ngtcp2_pkt_write_retry(
+  uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid,
+  const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token,
+  size_t tokenlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead,
+  const ngtcp2_crypto_aead_ctx *aead_ctx) {
+  ngtcp2_pkt_hd hd;
+  uint8_t pseudo_retry[1500];
+  ngtcp2_ssize pseudo_retrylen;
+  uint8_t tag[NGTCP2_RETRY_TAGLEN];
+  int rv;
+  uint8_t *p;
+  size_t offset;
+  const uint8_t *nonce;
+  size_t noncelen;
+
+  assert(tokenlen > 0);
+  assert(!ngtcp2_cid_eq(scid, odcid));
+
+  /* Retry packet is sent at most once per one connection attempt.  In
+     the first connection attempt, client has to send random DCID
+     which is at least NGTCP2_MIN_INITIAL_DCIDLEN bytes long. */
+  if (odcid->datalen < NGTCP2_MIN_INITIAL_DCIDLEN) {
+    return NGTCP2_ERR_INVALID_ARGUMENT;
+  }
+
+  ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_RETRY, dcid,
+                     scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version);
+
+  pseudo_retrylen =
+    ngtcp2_pkt_encode_pseudo_retry(pseudo_retry, sizeof(pseudo_retry), &hd,
+                                   /* unused = */ 0, odcid, token, tokenlen);
+  if (pseudo_retrylen < 0) {
+    return pseudo_retrylen;
+  }
+
+  switch (version) {
+  case NGTCP2_PROTO_VER_V1:
+  default:
+    nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V1;
+    noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1;
+    break;
+  case NGTCP2_PROTO_VER_V2:
+    nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V2;
+    noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1;
+    break;
+  }
+
+  /* OpenSSL does not like NULL plaintext. */
+  rv = encrypt(tag, aead, aead_ctx, (const uint8_t *)"", 0, nonce, noncelen,
+               pseudo_retry, (size_t)pseudo_retrylen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  offset = 1 + odcid->datalen;
+  if (destlen < (size_t)pseudo_retrylen + sizeof(tag) - offset) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = ngtcp2_cpymem(dest, pseudo_retry + offset,
+                    (size_t)pseudo_retrylen - offset);
+  p = ngtcp2_cpymem(p, tag, sizeof(tag));
+
+  return p - dest;
+}
+
+ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry(
+  uint8_t *dest, size_t destlen, const ngtcp2_pkt_hd *hd, uint8_t unused,
+  const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen) {
+  uint8_t *p = dest;
+  ngtcp2_ssize nwrite;
+
+  if (destlen < 1 + odcid->datalen) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  *p++ = (uint8_t)odcid->datalen;
+  p = ngtcp2_cpymem(p, odcid->data, odcid->datalen);
+  destlen -= (size_t)(p - dest);
+
+  nwrite = ngtcp2_pkt_encode_hd_long(p, destlen, hd);
+  if (nwrite < 0) {
+    return nwrite;
+  }
+
+  if (destlen < (size_t)nwrite + tokenlen) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  *p &= 0xf0;
+  *p |= unused;
+
+  p += nwrite;
+
+  p = ngtcp2_cpymem(p, token, tokenlen);
+
+  return p - dest;
+}
+
+int ngtcp2_pkt_verify_retry_tag(uint32_t version, const ngtcp2_pkt_retry *retry,
+                                const uint8_t *pkt, size_t pktlen,
+                                ngtcp2_encrypt encrypt,
+                                const ngtcp2_crypto_aead *aead,
+                                const ngtcp2_crypto_aead_ctx *aead_ctx) {
+  uint8_t pseudo_retry[1500];
+  size_t pseudo_retrylen;
+  uint8_t *p = pseudo_retry;
+  int rv;
+  uint8_t tag[NGTCP2_RETRY_TAGLEN];
+  const uint8_t *nonce;
+  size_t noncelen;
+
+  assert(pktlen >= sizeof(retry->tag));
+
+  if (sizeof(pseudo_retry) <
+      1 + retry->odcid.datalen + pktlen - sizeof(retry->tag)) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  *p++ = (uint8_t)retry->odcid.datalen;
+  p = ngtcp2_cpymem(p, retry->odcid.data, retry->odcid.datalen);
+  p = ngtcp2_cpymem(p, pkt, pktlen - sizeof(retry->tag));
+
+  pseudo_retrylen = (size_t)(p - pseudo_retry);
+
+  switch (version) {
+  case NGTCP2_PROTO_VER_V1:
+  default:
+    nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V1;
+    noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1;
+    break;
+  case NGTCP2_PROTO_VER_V2:
+    nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V2;
+    noncelen = sizeof(NGTCP2_RETRY_NONCE_V2) - 1;
+    break;
+  }
+
+  /* OpenSSL does not like NULL plaintext. */
+  rv = encrypt(tag, aead, aead_ctx, (const uint8_t *)"", 0, nonce, noncelen,
+               pseudo_retry, pseudo_retrylen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (0 != memcmp(retry->tag, tag, sizeof(retry->tag))) {
+    return NGTCP2_ERR_PROTO;
+  }
+
+  return 0;
+}
+
+size_t ngtcp2_pkt_stream_max_datalen(int64_t stream_id, uint64_t offset,
+                                     uint64_t len, size_t left) {
+  size_t n = 1 /* type */ + ngtcp2_put_uvarintlen((uint64_t)stream_id) +
+             (offset ? ngtcp2_put_uvarintlen(offset) : 0);
+
+  if (left <= n) {
+    return (size_t)-1;
+  }
+
+  left -= n;
+
+  if (left > 8 + 1073741823 && len > 1073741823) {
+    len = ngtcp2_min_uint64(len, 4611686018427387903lu);
+    return (size_t)ngtcp2_min_uint64(len, (uint64_t)(left - 8));
+  }
+
+  if (left > 4 + 16383 && len > 16383) {
+    len = ngtcp2_min_uint64(len, 1073741823);
+    return (size_t)ngtcp2_min_uint64(len, (uint64_t)(left - 4));
+  }
+
+  if (left > 2 + 63 && len > 63) {
+    len = ngtcp2_min_uint64(len, 16383);
+    return (size_t)ngtcp2_min_uint64(len, (uint64_t)(left - 2));
+  }
+
+  len = ngtcp2_min_uint64(len, 63);
+  return (size_t)ngtcp2_min_uint64(len, (uint64_t)(left - 1));
+}
+
+size_t ngtcp2_pkt_crypto_max_datalen(uint64_t offset, size_t len, size_t left) {
+  size_t n = 1 /* type */ + ngtcp2_put_uvarintlen(offset);
+
+  /* CRYPTO frame must contain nonzero length data.  Return -1 if
+     there is no space to write crypto data. */
+  if (left <= n + 1) {
+    return (size_t)-1;
+  }
+
+  left -= n;
+
+  if (left > 8 + 1073741823 && len > 1073741823) {
+#if SIZE_MAX == UINT64_MAX
+    len = ngtcp2_min_size(len, 4611686018427387903lu);
+#endif /* SIZE_MAX == UINT64_MAX */
+    return ngtcp2_min_size(len, left - 8);
+  }
+
+  if (left > 4 + 16383 && len > 16383) {
+    len = ngtcp2_min_size(len, 1073741823);
+    return ngtcp2_min_size(len, left - 4);
+  }
+
+  if (left > 2 + 63 && len > 63) {
+    len = ngtcp2_min_size(len, 16383);
+    return ngtcp2_min_size(len, left - 2);
+  }
+
+  len = ngtcp2_min_size(len, 63);
+  return ngtcp2_min_size(len, left - 1);
+}
+
+size_t ngtcp2_pkt_datagram_framelen(size_t len) {
+  return 1 /* type */ + ngtcp2_put_uvarintlen(len) + len;
+}
+
+int ngtcp2_is_supported_version(uint32_t version) {
+  switch (version) {
+  case NGTCP2_PROTO_VER_V1:
+  case NGTCP2_PROTO_VER_V2:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int ngtcp2_is_reserved_version(uint32_t version) {
+  return (version & NGTCP2_RESERVED_VERSION_MASK) ==
+         NGTCP2_RESERVED_VERSION_MASK;
+}
+
+uint8_t ngtcp2_pkt_get_type_long(uint32_t version, uint8_t c) {
+  uint8_t pkt_type = (uint8_t)((c & NGTCP2_LONG_TYPE_MASK) >> 4);
+
+  switch (version) {
+  case NGTCP2_PROTO_VER_V2:
+    switch (pkt_type) {
+    case NGTCP2_PKT_TYPE_INITIAL_V2:
+      return NGTCP2_PKT_INITIAL;
+    case NGTCP2_PKT_TYPE_0RTT_V2:
+      return NGTCP2_PKT_0RTT;
+    case NGTCP2_PKT_TYPE_HANDSHAKE_V2:
+      return NGTCP2_PKT_HANDSHAKE;
+    case NGTCP2_PKT_TYPE_RETRY_V2:
+      return NGTCP2_PKT_RETRY;
+    default:
+      return 0;
+    }
+  default:
+    if (!ngtcp2_is_supported_version(version)) {
+      return 0;
+    }
+
+    switch (pkt_type) {
+    case NGTCP2_PKT_TYPE_INITIAL_V1:
+      return NGTCP2_PKT_INITIAL;
+    case NGTCP2_PKT_TYPE_0RTT_V1:
+      return NGTCP2_PKT_0RTT;
+    case NGTCP2_PKT_TYPE_HANDSHAKE_V1:
+      return NGTCP2_PKT_HANDSHAKE;
+    case NGTCP2_PKT_TYPE_RETRY_V1:
+      return NGTCP2_PKT_RETRY;
+    default:
+      return 0;
+    }
+  }
+}
+
+uint8_t ngtcp2_pkt_versioned_type(uint32_t version, uint32_t pkt_type) {
+  switch (version) {
+  case NGTCP2_PROTO_VER_V2:
+    switch (pkt_type) {
+    case NGTCP2_PKT_INITIAL:
+      return NGTCP2_PKT_TYPE_INITIAL_V2;
+    case NGTCP2_PKT_0RTT:
+      return NGTCP2_PKT_TYPE_0RTT_V2;
+    case NGTCP2_PKT_HANDSHAKE:
+      return NGTCP2_PKT_TYPE_HANDSHAKE_V2;
+    case NGTCP2_PKT_RETRY:
+      return NGTCP2_PKT_TYPE_RETRY_V2;
+    default:
+      ngtcp2_unreachable();
+    }
+  default:
+    /* Assume that unsupported versions share the numeric long packet
+       types with QUIC v1 in order to send a packet to elicit Version
+       Negotiation packet. */
+
+    switch (pkt_type) {
+    case NGTCP2_PKT_INITIAL:
+      return NGTCP2_PKT_TYPE_INITIAL_V1;
+    case NGTCP2_PKT_0RTT:
+      return NGTCP2_PKT_TYPE_0RTT_V1;
+    case NGTCP2_PKT_HANDSHAKE:
+      return NGTCP2_PKT_TYPE_HANDSHAKE_V1;
+    case NGTCP2_PKT_RETRY:
+      return NGTCP2_PKT_TYPE_RETRY_V1;
+    default:
+      ngtcp2_unreachable();
+    }
+  }
+}
+
+int ngtcp2_pkt_verify_reserved_bits(uint8_t c) {
+  if (c & NGTCP2_HEADER_FORM_BIT) {
+    return (c & NGTCP2_LONG_RESERVED_BIT_MASK) == 0 ? 0 : NGTCP2_ERR_PROTO;
+  }
+
+  return (c & NGTCP2_SHORT_RESERVED_BIT_MASK) == 0 ? 0 : NGTCP2_ERR_PROTO;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_pkt.h b/third_party/ngtcp2/lib/ngtcp2_pkt.h
new file mode 100644 (file)
index 0000000..756076e
--- /dev/null
@@ -0,0 +1,1230 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_PKT_H
+#define NGTCP2_PKT_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/* QUIC header macros */
+#define NGTCP2_HEADER_FORM_BIT 0x80
+#define NGTCP2_FIXED_BIT_MASK 0x40
+#define NGTCP2_PKT_NUMLEN_MASK 0x03
+
+/* Long header specific macros */
+#define NGTCP2_LONG_TYPE_MASK 0x30
+#define NGTCP2_LONG_RESERVED_BIT_MASK 0x0c
+
+/* Short header specific macros */
+#define NGTCP2_SHORT_RESERVED_BIT_MASK 0x18
+#define NGTCP2_SHORT_KEY_PHASE_BIT 0x04
+
+/* NGTCP2_SR_TYPE is a Type field of Stateless Reset. */
+#define NGTCP2_SR_TYPE 0x1f
+
+/* NGTCP2_MIN_LONG_HEADERLEN is the minimum length of long header.
+   That is (1|1|TT|RR|PP)<1> + VERSION<4> + DCIL<1> + SCIL<1> +
+   LENGTH<1> + PKN<1> */
+#define NGTCP2_MIN_LONG_HEADERLEN (1 + 4 + 1 + 1 + 1 + 1)
+
+/* STREAM frame specific macros */
+#define NGTCP2_STREAM_FIN_BIT 0x01
+#define NGTCP2_STREAM_LEN_BIT 0x02
+#define NGTCP2_STREAM_OFF_BIT 0x04
+
+/* NGTCP2_MIN_QUIC_PKTLEN is the minimum length of a valid QUIC
+   packet. */
+#define NGTCP2_MIN_QUIC_PKTLEN 21
+
+/* NGTCP2_STREAM_OVERHEAD is the maximum number of bytes required
+   other than payload for STREAM frame.  That is from type field to
+   the beginning of the payload. */
+#define NGTCP2_STREAM_OVERHEAD (1 + 8 + 8 + 8)
+
+/* NGTCP2_CRYPTO_OVERHEAD is the maximum number of bytes required
+   other than payload for CRYPTO frame.  That is from type field to
+   the beginning of the payload. */
+#define NGTCP2_CRYPTO_OVERHEAD (1 + 8 + 8)
+
+/* NGTCP2_DATAGRAM_OVERHEAD is the maximum number of bytes required
+   other than payload for DATAGRAM frame.  That is from type field to
+   the beginning of the payload. */
+#define NGTCP2_DATAGRAM_OVERHEAD (1 + 8)
+
+/* NGTCP2_MAX_SERVER_STREAM_ID_BIDI is the maximum bidirectional
+   server stream ID. */
+#define NGTCP2_MAX_SERVER_STREAM_ID_BIDI ((int64_t)0x3ffffffffffffffdll)
+/* NGTCP2_MAX_CLIENT_STREAM_ID_BIDI is the maximum bidirectional
+   client stream ID. */
+#define NGTCP2_MAX_CLIENT_STREAM_ID_BIDI ((int64_t)0x3ffffffffffffffcll)
+/* NGTCP2_MAX_SERVER_STREAM_ID_UNI is the maximum unidirectional
+   server stream ID. */
+#define NGTCP2_MAX_SERVER_STREAM_ID_UNI ((int64_t)0x3fffffffffffffffll)
+/* NGTCP2_MAX_CLIENT_STREAM_ID_UNI is the maximum unidirectional
+   client stream ID. */
+#define NGTCP2_MAX_CLIENT_STREAM_ID_UNI ((int64_t)0x3ffffffffffffffell)
+
+/* NGTCP2_MAX_NUM_ACK_RANGES is the maximum number of Additional ACK
+   ranges which this library can create, or decode. */
+#define NGTCP2_MAX_ACK_RANGES 32
+
+/* NGTCP2_MAX_PKT_NUM is the maximum packet number. */
+#define NGTCP2_MAX_PKT_NUM ((int64_t)((1ll << 62) - 1))
+
+/* NGTCP2_MIN_PKT_EXPANDLEN is the minimum packet size expansion to
+   hide/trigger Stateless Reset. */
+#define NGTCP2_MIN_PKT_EXPANDLEN 22
+
+/* NGTCP2_RETRY_TAGLEN is the length of Retry packet integrity tag. */
+#define NGTCP2_RETRY_TAGLEN 16
+
+/* NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE is the maximum UDP datagram
+   payload size that this library can write. */
+#define NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE ((1 << 24) - 1)
+
+/* NGTCP2_PKT_LENGTHLEN is the number of bytes that is occupied by
+   Length field in Long packet header. */
+#define NGTCP2_PKT_LENGTHLEN 4
+
+/* NGTCP2_PKT_TYPE_INITIAL_V1 is Initial long header packet type for
+   QUIC v1. */
+#define NGTCP2_PKT_TYPE_INITIAL_V1 0x0
+/* NGTCP2_PKT_TYPE_0RTT_V1 is 0RTT long header packet type for QUIC
+   v1. */
+#define NGTCP2_PKT_TYPE_0RTT_V1 0x1
+/* NGTCP2_PKT_TYPE_HANDSHAKE_V1 is Handshake long header packet type
+   for QUIC v1. */
+#define NGTCP2_PKT_TYPE_HANDSHAKE_V1 0x2
+/* NGTCP2_PKT_TYPE_RETRY_V1 is Retry long header packet type for QUIC
+   v1. */
+#define NGTCP2_PKT_TYPE_RETRY_V1 0x3
+
+/* NGTCP2_PKT_TYPE_INITIAL_V2 is Initial long header packet type for
+   QUIC v2. */
+#define NGTCP2_PKT_TYPE_INITIAL_V2 0x1
+/* NGTCP2_PKT_TYPE_0RTT_V2 is 0RTT long header packet type for QUIC
+   v2. */
+#define NGTCP2_PKT_TYPE_0RTT_V2 0x2
+/* NGTCP2_PKT_TYPE_HANDSHAKE_V2 is Handshake long header packet type
+   for QUIC v2. */
+#define NGTCP2_PKT_TYPE_HANDSHAKE_V2 0x3
+/* NGTCP2_PKT_TYPE_RETRY_V2 is Retry long header packet type for QUIC
+   v2. */
+#define NGTCP2_PKT_TYPE_RETRY_V2 0x0
+
+typedef struct ngtcp2_pkt_retry {
+  ngtcp2_cid odcid;
+  uint8_t *token;
+  size_t tokenlen;
+  uint8_t tag[NGTCP2_RETRY_TAGLEN];
+} ngtcp2_pkt_retry;
+
+#define NGTCP2_FRAME_PADDING 0x00
+#define NGTCP2_FRAME_PING 0x01
+#define NGTCP2_FRAME_ACK 0x02
+#define NGTCP2_FRAME_ACK_ECN 0x03
+#define NGTCP2_FRAME_RESET_STREAM 0x04
+#define NGTCP2_FRAME_STOP_SENDING 0x05
+#define NGTCP2_FRAME_CRYPTO 0x06
+#define NGTCP2_FRAME_NEW_TOKEN 0x07
+#define NGTCP2_FRAME_STREAM 0x08
+#define NGTCP2_FRAME_MAX_DATA 0x10
+#define NGTCP2_FRAME_MAX_STREAM_DATA 0x11
+#define NGTCP2_FRAME_MAX_STREAMS_BIDI 0x12
+#define NGTCP2_FRAME_MAX_STREAMS_UNI 0x13
+#define NGTCP2_FRAME_DATA_BLOCKED 0x14
+#define NGTCP2_FRAME_STREAM_DATA_BLOCKED 0x15
+#define NGTCP2_FRAME_STREAMS_BLOCKED_BIDI 0x16
+#define NGTCP2_FRAME_STREAMS_BLOCKED_UNI 0x17
+#define NGTCP2_FRAME_NEW_CONNECTION_ID 0x18
+#define NGTCP2_FRAME_RETIRE_CONNECTION_ID 0x19
+#define NGTCP2_FRAME_PATH_CHALLENGE 0x1a
+#define NGTCP2_FRAME_PATH_RESPONSE 0x1b
+#define NGTCP2_FRAME_CONNECTION_CLOSE 0x1c
+#define NGTCP2_FRAME_CONNECTION_CLOSE_APP 0x1d
+#define NGTCP2_FRAME_HANDSHAKE_DONE 0x1e
+#define NGTCP2_FRAME_DATAGRAM 0x30
+#define NGTCP2_FRAME_DATAGRAM_LEN 0x31
+
+/* ngtcp2_stream represents STREAM and CRYPTO frames. */
+typedef struct ngtcp2_stream {
+  uint64_t type;
+  /**
+   * flags of decoded STREAM frame.  This gets ignored when encoding
+   * STREAM frame.  CRYPTO frame does not include this field, and must
+   * set it to 0.
+   */
+  uint8_t flags;
+  /* CRYPTO frame does not include this field, and must set it to
+     0. */
+  uint8_t fin;
+  /* CRYPTO frame does not include this field, and must set it to
+     0. */
+  int64_t stream_id;
+  uint64_t offset;
+  /* datacnt is the number of elements that data contains.  Although
+     the length of data is 1 in this definition, the library may
+     allocate extra bytes to hold more elements. */
+  size_t datacnt;
+  /* data is the array of ngtcp2_vec which references data. */
+  ngtcp2_vec data[1];
+} ngtcp2_stream;
+
+typedef struct ngtcp2_ack_range {
+  uint64_t gap;
+  uint64_t len;
+} ngtcp2_ack_range;
+
+typedef struct ngtcp2_ack {
+  uint64_t type;
+  int64_t largest_ack;
+  uint64_t ack_delay;
+  /**
+   * ack_delay_unscaled is an ack_delay multiplied by
+   * 2**ack_delay_component * NGTCP2_MICROSECONDS.
+   */
+  ngtcp2_duration ack_delay_unscaled;
+  struct {
+    uint64_t ect0;
+    uint64_t ect1;
+    uint64_t ce;
+  } ecn;
+  uint64_t first_ack_range;
+  size_t rangecnt;
+  ngtcp2_ack_range ranges[1];
+} ngtcp2_ack;
+
+typedef struct ngtcp2_padding {
+  uint64_t type;
+  /**
+   * The length of contiguous PADDING frames.
+   */
+  size_t len;
+} ngtcp2_padding;
+
+typedef struct ngtcp2_reset_stream {
+  uint64_t type;
+  int64_t stream_id;
+  uint64_t app_error_code;
+  uint64_t final_size;
+} ngtcp2_reset_stream;
+
+typedef struct ngtcp2_connection_close {
+  uint64_t type;
+  uint64_t error_code;
+  uint64_t frame_type;
+  size_t reasonlen;
+  uint8_t *reason;
+} ngtcp2_connection_close;
+
+typedef struct ngtcp2_max_data {
+  uint64_t type;
+  /**
+   * max_data is Maximum Data.
+   */
+  uint64_t max_data;
+} ngtcp2_max_data;
+
+typedef struct ngtcp2_max_stream_data {
+  uint64_t type;
+  int64_t stream_id;
+  uint64_t max_stream_data;
+} ngtcp2_max_stream_data;
+
+typedef struct ngtcp2_max_streams {
+  uint64_t type;
+  uint64_t max_streams;
+} ngtcp2_max_streams;
+
+typedef struct ngtcp2_ping {
+  uint64_t type;
+} ngtcp2_ping;
+
+typedef struct ngtcp2_data_blocked {
+  uint64_t type;
+  uint64_t offset;
+} ngtcp2_data_blocked;
+
+typedef struct ngtcp2_stream_data_blocked {
+  uint64_t type;
+  int64_t stream_id;
+  uint64_t offset;
+} ngtcp2_stream_data_blocked;
+
+typedef struct ngtcp2_streams_blocked {
+  uint64_t type;
+  uint64_t max_streams;
+} ngtcp2_streams_blocked;
+
+typedef struct ngtcp2_new_connection_id {
+  uint64_t type;
+  uint64_t seq;
+  uint64_t retire_prior_to;
+  ngtcp2_cid cid;
+  uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN];
+} ngtcp2_new_connection_id;
+
+typedef struct ngtcp2_stop_sending {
+  uint64_t type;
+  int64_t stream_id;
+  uint64_t app_error_code;
+} ngtcp2_stop_sending;
+
+typedef struct ngtcp2_path_challenge {
+  uint64_t type;
+  uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN];
+} ngtcp2_path_challenge;
+
+typedef struct ngtcp2_path_response {
+  uint64_t type;
+  uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN];
+} ngtcp2_path_response;
+
+typedef struct ngtcp2_new_token {
+  uint64_t type;
+  uint8_t *token;
+  size_t tokenlen;
+} ngtcp2_new_token;
+
+typedef struct ngtcp2_retire_connection_id {
+  uint64_t type;
+  uint64_t seq;
+} ngtcp2_retire_connection_id;
+
+typedef struct ngtcp2_handshake_done {
+  uint64_t type;
+} ngtcp2_handshake_done;
+
+typedef struct ngtcp2_datagram {
+  uint64_t type;
+  /* dgram_id is an opaque identifier chosen by an application. */
+  uint64_t dgram_id;
+  /* datacnt is the number of elements that data contains. */
+  size_t datacnt;
+  /* data is a pointer to ngtcp2_vec array that stores data. */
+  ngtcp2_vec *data;
+  /* rdata is conveniently embedded to ngtcp2_datagram, so that data
+     field can just point to the address of this field to store a
+     single vector which is the case when DATAGRAM is received from a
+     remote endpoint. */
+  ngtcp2_vec rdata[1];
+} ngtcp2_datagram;
+
+typedef union ngtcp2_frame {
+  uint64_t type;
+  ngtcp2_stream stream;
+  ngtcp2_ack ack;
+  ngtcp2_padding padding;
+  ngtcp2_reset_stream reset_stream;
+  ngtcp2_connection_close connection_close;
+  ngtcp2_max_data max_data;
+  ngtcp2_max_stream_data max_stream_data;
+  ngtcp2_max_streams max_streams;
+  ngtcp2_ping ping;
+  ngtcp2_data_blocked data_blocked;
+  ngtcp2_stream_data_blocked stream_data_blocked;
+  ngtcp2_streams_blocked streams_blocked;
+  ngtcp2_new_connection_id new_connection_id;
+  ngtcp2_stop_sending stop_sending;
+  ngtcp2_path_challenge path_challenge;
+  ngtcp2_path_response path_response;
+  ngtcp2_new_token new_token;
+  ngtcp2_retire_connection_id retire_connection_id;
+  ngtcp2_handshake_done handshake_done;
+  ngtcp2_datagram datagram;
+  /* Extend ngtcp2_frame so that ngtcp2_stream has at least additional
+     3 ngtcp2_vec, totaling 4 slots, which can store HEADERS header,
+     HEADERS payload, DATA header, and DATA payload in the standard
+     sized ngtcp2_frame_chain. */
+  uint8_t pad[sizeof(ngtcp2_stream) + sizeof(ngtcp2_vec) * 3];
+} ngtcp2_frame;
+
+typedef struct ngtcp2_pkt_chain ngtcp2_pkt_chain;
+
+/*
+ * ngtcp2_pkt_chain is the chain of incoming packets buffered.
+ */
+struct ngtcp2_pkt_chain {
+  ngtcp2_path_storage path;
+  ngtcp2_pkt_info pi;
+  ngtcp2_pkt_chain *next;
+  uint8_t *pkt;
+  /* pktlen is length of a QUIC packet. */
+  size_t pktlen;
+  /* dgramlen is length of UDP datagram payload that a QUIC packet is
+     included. */
+  size_t dgramlen;
+  ngtcp2_tstamp ts;
+};
+
+/*
+ * ngtcp2_pkt_chain_new allocates ngtcp2_pkt_chain objects, and
+ * assigns its pointer to |*ppc|.  The content of buffer pointed by
+ * |pkt| of length |pktlen| is copied into |*ppc|.  The packet is
+ * obtained via the network |path|.  The values of path->local and
+ * path->remote are copied into |*ppc|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_pkt_chain_new(ngtcp2_pkt_chain **ppc, const ngtcp2_path *path,
+                         const ngtcp2_pkt_info *pi, const uint8_t *pkt,
+                         size_t pktlen, size_t dgramlen, ngtcp2_tstamp ts,
+                         const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_pkt_chain_del deallocates |pc|.  It also frees the memory
+ * pointed by |pc|.
+ */
+void ngtcp2_pkt_chain_del(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_pkt_hd_init initializes |hd| with the given values.  If
+ * |dcid| and/or |scid| is NULL, Destination Connection ID and/or
+ * Source Connection ID of |hd| is empty respectively.  |pkt_numlen|
+ * is the number of bytes used to encode |pkt_num| and either 1, 2, or
+ * 4.  |version| is QUIC version for long header.
+ */
+void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type,
+                        const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
+                        int64_t pkt_num, size_t pkt_numlen, uint32_t version);
+
+/*
+ * ngtcp2_pkt_encode_hd_long encodes |hd| as QUIC long header into
+ * |out| which has length |outlen|.  It returns the number of bytes
+ * written into |out| if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer is too short
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen,
+                                       const ngtcp2_pkt_hd *hd);
+
+/*
+ * ngtcp2_pkt_encode_hd_short encodes |hd| as QUIC short header into
+ * |out| which has length |outlen|.  It returns the number of bytes
+ * written into |out| if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer is too short
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen,
+                                        const ngtcp2_pkt_hd *hd);
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_decode_frame` decodes a QUIC frame from the buffer
+ * pointed by |payload| whose length is |payloadlen|.
+ *
+ * This function returns the number of bytes read to decode a single
+ * frame if it succeeds, or one of the following negative error codes:
+ *
+ * :enum:`NGTCP2_ERR_FRAME_ENCODING`
+ *     Frame is badly formatted; or frame type is unknown; or
+ *     |payloadlen| is 0.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload,
+                                     size_t payloadlen);
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_encode_frame` encodes a frame |fr| into the buffer
+ * pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written to the buffer, or
+ * one of the following negative error codes:
+ *
+ * :enum:`NGTCP2_ERR_NOBUF`
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen,
+                                     ngtcp2_frame *fr);
+
+/*
+ * ngtcp2_pkt_decode_version_negotiation decodes Version Negotiation
+ * packet payload |payload| of length |payloadlen|, and stores the
+ * result in |dest|.  |dest| must have enough capacity to store the
+ * result.  |payloadlen| also must be a multiple of sizeof(uint32_t).
+ *
+ * This function returns the number of versions written in |dest|.
+ */
+size_t ngtcp2_pkt_decode_version_negotiation(uint32_t *dest,
+                                             const uint8_t *payload,
+                                             size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_stateless_reset decodes Stateless Reset payload
+ * |payload| of length |payloadlen|.  The |payload| must start with
+ * Stateless Reset Token.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Payloadlen is too short.
+ */
+int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr,
+                                      const uint8_t *payload,
+                                      size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_retry decodes Retry packet payload |payload| of
+ * length |payloadlen|.  The |payload| must start with Retry token
+ * field.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     Payloadlen is too short.
+ */
+int ngtcp2_pkt_decode_retry(ngtcp2_pkt_retry *dest, const uint8_t *payload,
+                            size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_stream_frame decodes STREAM frame from |payload|
+ * of length |payloadlen|.  The result is stored in the object pointed
+ * by |dest|.  STREAM frame must start at payload[0].  This function
+ * finishes when it decodes one STREAM frame, and returns the exact
+ * number of bytes read to decode a frame if it succeeds, or one of
+ * the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include STREAM frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_stream_frame(ngtcp2_stream *dest,
+                                            const uint8_t *payload,
+                                            size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_ack_frame decodes ACK frame from |payload| of
+ * length |payloadlen|.  The result is stored in the object pointed by
+ * |dest|.  ACK frame must start at payload[0].  This function
+ * finishes when it decodes one ACK frame, and returns the exact
+ * number of bytes read to decode a frame if it succeeds, or one of
+ * the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include ACK frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_ack_frame(ngtcp2_ack *dest,
+                                         const uint8_t *payload,
+                                         size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_padding_frame decodes contiguous PADDING frames
+ * from |payload| of length |payloadlen|.  It continues to parse
+ * frames as long as the frame type is PADDING.  It finishes when it
+ * encounters the frame type which is not PADDING, or all input data
+ * is read.  The first byte (payload[0]) must be NGTCP2_FRAME_PADDING.
+ * This function returns the exact number of bytes read to decode
+ * PADDING frames.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_padding_frame(ngtcp2_padding *dest,
+                                             const uint8_t *payload,
+                                             size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_reset_stream_frame decodes RESET_STREAM frame
+ * from |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  RESET_STREAM frame must start at
+ * payload[0].  This function finishes when it decodes one
+ * RESET_STREAM frame, and returns the exact number of bytes read to
+ * decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include RESET_STREAM frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_reset_stream_frame(ngtcp2_reset_stream *dest,
+                                                  const uint8_t *payload,
+                                                  size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_connection_close_frame decodes CONNECTION_CLOSE
+ * frame from |payload| of length |payloadlen|.  The result is stored
+ * in the object pointed by |dest|.  CONNECTION_CLOSE frame must start
+ * at payload[0].  This function finishes when it decodes one
+ * CONNECTION_CLOSE frame, and returns the exact number of bytes read
+ * to decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include CONNECTION_CLOSE frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_connection_close_frame(
+  ngtcp2_connection_close *dest, const uint8_t *payload, size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_max_data_frame decodes MAX_DATA frame from
+ * |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  MAX_DATA frame must start at payload[0].
+ * This function finishes when it decodes one MAX_DATA frame, and
+ * returns the exact number of bytes read to decode a frame if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include MAX_DATA frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_max_data_frame(ngtcp2_max_data *dest,
+                                              const uint8_t *payload,
+                                              size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_max_stream_data_frame decodes MAX_STREAM_DATA
+ * frame from |payload| of length |payloadlen|.  The result is stored
+ * in the object pointed by |dest|.  MAX_STREAM_DATA frame must start
+ * at payload[0].  This function finishes when it decodes one
+ * MAX_STREAM_DATA frame, and returns the exact number of bytes read
+ * to decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include MAX_STREAM_DATA frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_max_stream_data_frame(
+  ngtcp2_max_stream_data *dest, const uint8_t *payload, size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_max_streams_frame decodes MAX_STREAMS frame from
+ * |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  MAX_STREAMS frame must start at
+ * payload[0].  This function finishes when it decodes one MAX_STREAMS
+ * frame, and returns the exact number of bytes read to decode a frame
+ * if it succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include MAX_STREAMS frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_max_streams_frame(ngtcp2_max_streams *dest,
+                                                 const uint8_t *payload,
+                                                 size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_ping_frame decodes PING frame from |payload| of
+ * length |payloadlen|.  The result is stored in the object pointed by
+ * |dest|.  PING frame must start at payload[0].  This function
+ * finishes when it decodes one PING frame, and returns the exact
+ * number of bytes read to decode a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_ping_frame(ngtcp2_ping *dest,
+                                          const uint8_t *payload,
+                                          size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_data_blocked_frame decodes DATA_BLOCKED frame
+ * from |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  DATA_BLOCKED frame must start at
+ * payload[0].  This function finishes when it decodes one
+ * DATA_BLOCKED frame, and returns the exact number of bytes read to
+ * decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include DATA_BLOCKED frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_data_blocked_frame(ngtcp2_data_blocked *dest,
+                                                  const uint8_t *payload,
+                                                  size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_stream_data_blocked_frame decodes
+ * STREAM_DATA_BLOCKED frame from |payload| of length |payloadlen|.
+ * The result is stored in the object pointed by |dest|.
+ * STREAM_DATA_BLOCKED frame must start at payload[0].  This function
+ * finishes when it decodes one STREAM_DATA_BLOCKED frame, and returns
+ * the exact number of bytes read to decode a frame if it succeeds, or
+ * one of the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include STREAM_DATA_BLOCKED frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_stream_data_blocked_frame(
+  ngtcp2_stream_data_blocked *dest, const uint8_t *payload, size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_streams_blocked_frame decodes STREAMS_BLOCKED
+ * frame from |payload| of length |payloadlen|.  The result is stored
+ * in the object pointed by |dest|.  STREAMS_BLOCKED frame must start
+ * at payload[0].  This function finishes when it decodes one
+ * STREAMS_BLOCKED frame, and returns the exact number of bytes read
+ * to decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include STREAMS_BLOCKED frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_streams_blocked_frame(
+  ngtcp2_streams_blocked *dest, const uint8_t *payload, size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_new_connection_id_frame decodes NEW_CONNECTION_ID
+ * frame from |payload| of length |payloadlen|.  The result is stored
+ * in the object pointed by |dest|.  NEW_CONNECTION_ID frame must
+ * start at payload[0].  This function finishes when it decodes one
+ * NEW_CONNECTION_ID frame, and returns the exact number of bytes read
+ * to decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include NEW_CONNECTION_ID frame; or the
+ *     length of Connection ID is strictly less than NGTCP2_MIN_CIDLEN
+ *     or greater than NGTCP2_MAX_CIDLEN.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_new_connection_id_frame(
+  ngtcp2_new_connection_id *dest, const uint8_t *payload, size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_stop_sending_frame decodes STOP_SENDING frame
+ * from |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  STOP_SENDING frame must start at
+ * payload[0].  This function finishes when it decodes one
+ * STOP_SENDING frame, and returns the exact number of bytes read to
+ * decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include STOP_SENDING frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_stop_sending_frame(ngtcp2_stop_sending *dest,
+                                                  const uint8_t *payload,
+                                                  size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_path_challenge_frame decodes PATH_CHALLENGE frame
+ * from |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  PATH_CHALLENGE frame must start at
+ * payload[0].  This function finishes when it decodes one
+ * PATH_CHALLENGE frame, and returns the exact number of bytes read to
+ * decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include PATH_CHALLENGE frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_path_challenge_frame(ngtcp2_path_challenge *dest,
+                                                    const uint8_t *payload,
+                                                    size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_path_response_frame decodes PATH_RESPONSE frame
+ * from |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  PATH_RESPONSE frame must start at
+ * payload[0].  This function finishes when it decodes one
+ * PATH_RESPONSE frame, and returns the exact number of bytes read to
+ * decode a frame if it succeeds, or one of the following negative
+ * error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include PATH_RESPONSE frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_path_response_frame(ngtcp2_path_response *dest,
+                                                   const uint8_t *payload,
+                                                   size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_crypto_frame decodes CRYPTO frame from |payload|
+ * of length |payloadlen|.  The result is stored in the object pointed
+ * by |dest|.  CRYPTO frame must start at payload[0].  This function
+ * finishes when it decodes one CRYPTO frame, and returns the exact
+ * number of bytes read to decode a frame if it succeeds, or one of
+ * the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include CRYPTO frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_crypto_frame(ngtcp2_stream *dest,
+                                            const uint8_t *payload,
+                                            size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_new_token_frame decodes NEW_TOKEN frame from
+ * |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  NEW_TOKEN frame must start at
+ * payload[0].  This function finishes when it decodes one NEW_TOKEN
+ * frame, and returns the exact number of bytes read to decode a frame
+ * if it succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include NEW_TOKEN frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_new_token_frame(ngtcp2_new_token *dest,
+                                               const uint8_t *payload,
+                                               size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_retire_connection_id_frame decodes
+ * RETIRE_CONNECTION_ID frame from |payload| of length |payloadlen|.
+ * The result is stored in the object pointed by |dest|.
+ * RETIRE_CONNECTION_ID frame must start at payload[0].  This function
+ * finishes when it decodes one RETIRE_CONNECTION_ID frame, and
+ * returns the exact number of bytes read to decode a frame if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include RETIRE_CONNECTION_ID frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_retire_connection_id_frame(
+  ngtcp2_retire_connection_id *dest, const uint8_t *payload, size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_handshake_done_frame decodes HANDSHAKE_DONE frame
+ * from |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  HANDSHAKE_DONE frame must start at
+ * payload[0].  This function finishes when it decodes one
+ * HANDSHAKE_DONE frame, and returns the exact number of bytes read to
+ * decode a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_handshake_done_frame(ngtcp2_handshake_done *dest,
+                                                    const uint8_t *payload,
+                                                    size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_decode_datagram_frame decodes DATAGRAM frame from
+ * |payload| of length |payloadlen|.  The result is stored in the
+ * object pointed by |dest|.  DATAGRAM frame must start at payload[0].
+ * This function finishes when it decodes one DATAGRAM frame, and
+ * returns the exact number of bytes read to decode a frame if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_FRAME_ENCODING
+ *     Payload is too short to include DATAGRAM frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest,
+                                              const uint8_t *payload,
+                                              size_t payloadlen);
+
+/*
+ * ngtcp2_pkt_encode_stream_frame encodes STREAM frame |fr| into the
+ * buffer pointed by |out| of length |outlen|.
+ *
+ * This function assigns <the serialized frame type> &
+ * ~NGTCP2_FRAME_STREAM to fr->flags.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen,
+                                            ngtcp2_stream *fr);
+
+/*
+ * ngtcp2_pkt_encode_ack_frame encodes ACK frame |fr| into the buffer
+ * pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_ack_frame(uint8_t *out, size_t outlen,
+                                         const ngtcp2_ack *fr);
+
+/*
+ * ngtcp2_pkt_encode_padding_frame encodes PADDING frame |fr| into the
+ * buffer pointed by |out| of length |outlen|.
+ *
+ * This function encodes consecutive fr->len PADDING frames.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write frame(s).
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_padding_frame(uint8_t *out, size_t outlen,
+                                             const ngtcp2_padding *fr);
+
+/*
+ * ngtcp2_pkt_encode_reset_stream_frame encodes RESET_STREAM frame
+ * |fr| into the buffer pointed by |out| of length |buflen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_reset_stream_frame(uint8_t *out, size_t outlen,
+                                     const ngtcp2_reset_stream *fr);
+
+/*
+ * ngtcp2_pkt_encode_connection_close_frame encodes CONNECTION_CLOSE
+ * frame |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_connection_close_frame(uint8_t *out, size_t outlen,
+                                         const ngtcp2_connection_close *fr);
+
+/*
+ * ngtcp2_pkt_encode_max_data_frame encodes MAX_DATA frame |fr| into
+ * the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_max_data_frame(uint8_t *out, size_t outlen,
+                                              const ngtcp2_max_data *fr);
+
+/*
+ * ngtcp2_pkt_encode_max_stream_data_frame encodes MAX_STREAM_DATA
+ * frame |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_max_stream_data_frame(uint8_t *out, size_t outlen,
+                                        const ngtcp2_max_stream_data *fr);
+
+/*
+ * ngtcp2_pkt_encode_max_streams_frame encodes MAX_STREAMS
+ * frame |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_max_streams_frame(uint8_t *out, size_t outlen,
+                                                 const ngtcp2_max_streams *fr);
+
+/*
+ * ngtcp2_pkt_encode_ping_frame encodes PING frame |fr| into the
+ * buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_ping_frame(uint8_t *out, size_t outlen,
+                                          const ngtcp2_ping *fr);
+
+/*
+ * ngtcp2_pkt_encode_data_blocked_frame encodes DATA_BLOCKED frame
+ * |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_data_blocked_frame(uint8_t *out, size_t outlen,
+                                     const ngtcp2_data_blocked *fr);
+
+/*
+ * ngtcp2_pkt_encode_stream_data_blocked_frame encodes
+ * STREAM_DATA_BLOCKED frame |fr| into the buffer pointed by |out| of
+ * length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_stream_data_blocked_frame(
+  uint8_t *out, size_t outlen, const ngtcp2_stream_data_blocked *fr);
+
+/*
+ * ngtcp2_pkt_encode_streams_blocked_frame encodes STREAMS_BLOCKED
+ * frame |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_streams_blocked_frame(uint8_t *out, size_t outlen,
+                                        const ngtcp2_streams_blocked *fr);
+
+/*
+ * ngtcp2_pkt_encode_new_connection_id_frame encodes NEW_CONNECTION_ID
+ * frame |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_new_connection_id_frame(uint8_t *out, size_t outlen,
+                                          const ngtcp2_new_connection_id *fr);
+
+/*
+ * ngtcp2_pkt_encode_stop_sending_frame encodes STOP_SENDING frame
+ * |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_stop_sending_frame(uint8_t *out, size_t outlen,
+                                     const ngtcp2_stop_sending *fr);
+
+/*
+ * ngtcp2_pkt_encode_path_challenge_frame encodes PATH_CHALLENGE frame
+ * |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_path_challenge_frame(uint8_t *out, size_t outlen,
+                                       const ngtcp2_path_challenge *fr);
+
+/*
+ * ngtcp2_pkt_encode_path_response_frame encodes PATH_RESPONSE frame
+ * |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_path_response_frame(uint8_t *out, size_t outlen,
+                                      const ngtcp2_path_response *fr);
+
+/*
+ * ngtcp2_pkt_encode_crypto_frame encodes CRYPTO frame |fr| into the
+ * buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_crypto_frame(uint8_t *out, size_t outlen,
+                                            const ngtcp2_stream *fr);
+
+/*
+ * ngtcp2_pkt_encode_new_token_frame encodes NEW_TOKEN frame |fr| into
+ * the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen,
+                                               const ngtcp2_new_token *fr);
+
+/*
+ * ngtcp2_pkt_encode_retire_connection_id_frame encodes
+ * RETIRE_CONNECTION_ID frame |fr| into the buffer pointed by |out| of
+ * length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_retire_connection_id_frame(
+  uint8_t *out, size_t outlen, const ngtcp2_retire_connection_id *fr);
+
+/*
+ * ngtcp2_pkt_encode_handshake_done_frame encodes HANDSHAKE_DONE frame
+ * |fr| into the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize
+ngtcp2_pkt_encode_handshake_done_frame(uint8_t *out, size_t outlen,
+                                       const ngtcp2_handshake_done *fr);
+
+/*
+ * ngtcp2_pkt_encode_datagram_frame encodes DATAGRAM frame |fr| into
+ * the buffer pointed by |out| of length |outlen|.
+ *
+ * This function returns the number of bytes written if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     Buffer does not have enough capacity to write a frame.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_datagram_frame(uint8_t *out, size_t outlen,
+                                              const ngtcp2_datagram *fr);
+
+/*
+ * ngtcp2_pkt_adjust_pkt_num finds the full 62 bits packet number for
+ * |pkt_num|, which is encoded in |pkt_numlen| bytes.  The
+ * |max_pkt_num| is the highest successfully authenticated packet
+ * number.
+ */
+int64_t ngtcp2_pkt_adjust_pkt_num(int64_t max_pkt_num, int64_t pkt_num,
+                                  size_t pkt_numlen);
+
+/*
+ * ngtcp2_pkt_validate_ack verifies whether |fr| is malformed or not.
+ * |min_pkt_num| is the minimum packet number that a local endpoint
+ * sends.  It is an error to receive acknowledgements for a packet
+ * less than |min_pkt_num|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_ACK_FRAME
+ *     ACK frame is malformed
+ * NGTCP2_ERR_PROTO
+ *     |fr| contains a packet number less than |min_pkt_num|.
+ */
+int ngtcp2_pkt_validate_ack(const ngtcp2_ack *fr, int64_t min_pkt_num);
+
+/*
+ * ngtcp2_pkt_stream_max_datalen returns the maximum number of bytes
+ * which can be sent for stream denoted by |stream_id|.  |offset| is
+ * an offset within the stream.  |len| is the estimated number of
+ * bytes to send.  |left| is the size of buffer.  If |left| is too
+ * small to write STREAM frame, this function returns (size_t)-1.
+ */
+size_t ngtcp2_pkt_stream_max_datalen(int64_t stream_id, uint64_t offset,
+                                     uint64_t len, size_t left);
+
+/*
+ * ngtcp2_pkt_crypto_max_datalen returns the maximum number of bytes
+ * which can be sent for crypto stream.  |offset| is an offset within
+ * the crypto stream.  |len| is the estimated number of bytes to send.
+ * |left| is the size of buffer.  If |left| is too small to write
+ * CRYPTO frame, this function returns (size_t)-1.
+ */
+size_t ngtcp2_pkt_crypto_max_datalen(uint64_t offset, size_t len, size_t left);
+
+/*
+ * ngtcp2_pkt_datagram_framelen returns the length of DATAGRAM frame
+ * to encode |len| bytes of data.
+ */
+size_t ngtcp2_pkt_datagram_framelen(size_t len);
+
+/*
+ * ngtcp2_pkt_verify_reserved_bits verifies that the first byte |c| of
+ * the packet header has the correct reserved bits.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_PROTO
+ *     Reserved bits has wrong value.
+ */
+int ngtcp2_pkt_verify_reserved_bits(uint8_t c);
+
+/*
+ * ngtcp2_pkt_encode_pseudo_retry encodes Retry pseudo-packet in the
+ * buffer pointed by |dest| of length |destlen|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_BUF
+ *     Buffer is too short.
+ */
+ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry(
+  uint8_t *dest, size_t destlen, const ngtcp2_pkt_hd *hd, uint8_t unused,
+  const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen);
+
+/*
+ * ngtcp2_pkt_verify_retry_tag verifies Retry packet.  The buffer
+ * pointed by |pkt| of length |pktlen| must contain Retry packet
+ * including packet header.  The odcid and tag fields of |retry| must
+ * be specified.  |aead| must be AEAD_AES_128_GCM.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_PROTO
+ *     Verification failed.
+ */
+int ngtcp2_pkt_verify_retry_tag(uint32_t version, const ngtcp2_pkt_retry *retry,
+                                const uint8_t *pkt, size_t pktlen,
+                                ngtcp2_encrypt encrypt,
+                                const ngtcp2_crypto_aead *aead,
+                                const ngtcp2_crypto_aead_ctx *aead_ctx);
+
+/*
+ * ngtcp2_pkt_versioned_type returns versioned packet type for a
+ * version |version| that corresponds to the version-independent
+ * |pkt_type|.
+ */
+uint8_t ngtcp2_pkt_versioned_type(uint32_t version, uint32_t pkt_type);
+
+/**
+ * @function
+ *
+ * `ngtcp2_pkt_get_type_long` returns the version-independent long
+ * packet type.  |version| is the QUIC version.  |c| is the first byte
+ * of Long packet header.  If |version| is not supported by the
+ * library, it returns 0.
+ */
+uint8_t ngtcp2_pkt_get_type_long(uint32_t version, uint8_t c);
+
+#endif /* !defined(NGTCP2_PKT_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_pktns_id.h b/third_party/ngtcp2/lib/ngtcp2_pktns_id.h
new file mode 100644 (file)
index 0000000..9cf8444
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2023 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_PKTNS_ID_H
+#define NGTCP2_PKTNS_ID_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/**
+ * @enum
+ *
+ * :type:`ngtcp2_pktns_id` defines packet number space identifier.
+ */
+typedef enum ngtcp2_pktns_id {
+  /**
+   * :enum:`NGTCP2_PKTNS_ID_INITIAL` is the Initial packet number
+   * space.
+   */
+  NGTCP2_PKTNS_ID_INITIAL,
+  /**
+   * :enum:`NGTCP2_PKTNS_ID_HANDSHAKE` is the Handshake packet number
+   * space.
+   */
+  NGTCP2_PKTNS_ID_HANDSHAKE,
+  /**
+   * :enum:`NGTCP2_PKTNS_ID_APPLICATION` is the Application data
+   * packet number space.
+   */
+  NGTCP2_PKTNS_ID_APPLICATION,
+  /**
+   * :enum:`NGTCP2_PKTNS_ID_MAX` is defined to get the number of
+   * packet number spaces.
+   */
+  NGTCP2_PKTNS_ID_MAX
+} ngtcp2_pktns_id;
+
+#endif /* !defined(NGTCP2_PKTNS_ID_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_pmtud.c b/third_party/ngtcp2/lib/ngtcp2_pmtud.c
new file mode 100644 (file)
index 0000000..ebd113f
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_pmtud.h"
+
+#include <assert.h>
+
+#include "ngtcp2_mem.h"
+#include "ngtcp2_macro.h"
+
+/* NGTCP2_PMTUD_PROBE_NUM_MAX is the maximum number of packets sent
+   for each probe. */
+#define NGTCP2_PMTUD_PROBE_NUM_MAX 3
+
+static uint16_t pmtud_default_probes[] = {
+  1454 - 48, /* The well known MTU used by a domestic optic fiber
+                service in Japan. */
+  1390 - 48, /* Typical Tunneled MTU */
+  1280 - 48, /* IPv6 minimum MTU */
+  1492 - 48, /* PPPoE */
+};
+
+int ngtcp2_pmtud_new(ngtcp2_pmtud **ppmtud, size_t max_udp_payload_size,
+                     size_t hard_max_udp_payload_size, int64_t tx_pkt_num,
+                     const uint16_t *probes, size_t probeslen,
+                     const ngtcp2_mem *mem) {
+  ngtcp2_pmtud *pmtud = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pmtud));
+
+  if (pmtud == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  pmtud->mem = mem;
+  pmtud->mtu_idx = 0;
+  pmtud->num_pkts_sent = 0;
+  pmtud->expiry = UINT64_MAX;
+  pmtud->tx_pkt_num = tx_pkt_num;
+  pmtud->max_udp_payload_size = max_udp_payload_size;
+  pmtud->hard_max_udp_payload_size = hard_max_udp_payload_size;
+  pmtud->min_fail_udp_payload_size = SIZE_MAX;
+
+  if (probeslen) {
+    pmtud->probes = probes;
+    pmtud->probeslen = probeslen;
+  } else {
+    pmtud->probes = pmtud_default_probes;
+    pmtud->probeslen = ngtcp2_arraylen(pmtud_default_probes);
+  }
+
+  for (; pmtud->mtu_idx < pmtud->probeslen; ++pmtud->mtu_idx) {
+    if (pmtud->probes[pmtud->mtu_idx] > pmtud->hard_max_udp_payload_size) {
+      continue;
+    }
+    if (pmtud->probes[pmtud->mtu_idx] > pmtud->max_udp_payload_size) {
+      break;
+    }
+  }
+
+  *ppmtud = pmtud;
+
+  return 0;
+}
+
+void ngtcp2_pmtud_del(ngtcp2_pmtud *pmtud) {
+  if (!pmtud) {
+    return;
+  }
+
+  ngtcp2_mem_free(pmtud->mem, pmtud);
+}
+
+size_t ngtcp2_pmtud_probelen(ngtcp2_pmtud *pmtud) {
+  assert(pmtud->mtu_idx < pmtud->probeslen);
+
+  return pmtud->probes[pmtud->mtu_idx];
+}
+
+void ngtcp2_pmtud_probe_sent(ngtcp2_pmtud *pmtud, ngtcp2_duration pto,
+                             ngtcp2_tstamp ts) {
+  ngtcp2_tstamp timeout;
+
+  if (++pmtud->num_pkts_sent < NGTCP2_PMTUD_PROBE_NUM_MAX) {
+    timeout = pto;
+  } else {
+    timeout = 3 * pto;
+  }
+
+  pmtud->expiry = ts + timeout;
+}
+
+int ngtcp2_pmtud_require_probe(ngtcp2_pmtud *pmtud) {
+  return pmtud->expiry == UINT64_MAX;
+}
+
+static void pmtud_next_probe(ngtcp2_pmtud *pmtud) {
+  assert(pmtud->mtu_idx < pmtud->probeslen);
+
+  ++pmtud->mtu_idx;
+  pmtud->num_pkts_sent = 0;
+  pmtud->expiry = UINT64_MAX;
+
+  for (; pmtud->mtu_idx < pmtud->probeslen; ++pmtud->mtu_idx) {
+    if (pmtud->probes[pmtud->mtu_idx] <= pmtud->max_udp_payload_size ||
+        pmtud->probes[pmtud->mtu_idx] > pmtud->hard_max_udp_payload_size) {
+      continue;
+    }
+
+    if (pmtud->probes[pmtud->mtu_idx] < pmtud->min_fail_udp_payload_size) {
+      break;
+    }
+  }
+}
+
+void ngtcp2_pmtud_probe_success(ngtcp2_pmtud *pmtud, size_t payloadlen) {
+  pmtud->max_udp_payload_size =
+    ngtcp2_max_size(pmtud->max_udp_payload_size, payloadlen);
+
+  assert(pmtud->mtu_idx < pmtud->probeslen);
+
+  if (pmtud->probes[pmtud->mtu_idx] > pmtud->max_udp_payload_size) {
+    return;
+  }
+
+  pmtud_next_probe(pmtud);
+}
+
+void ngtcp2_pmtud_handle_expiry(ngtcp2_pmtud *pmtud, ngtcp2_tstamp ts) {
+  if (ts < pmtud->expiry) {
+    return;
+  }
+
+  pmtud->expiry = UINT64_MAX;
+
+  if (pmtud->num_pkts_sent < NGTCP2_PMTUD_PROBE_NUM_MAX) {
+    return;
+  }
+
+  pmtud->min_fail_udp_payload_size = ngtcp2_min_size(
+    pmtud->min_fail_udp_payload_size, pmtud->probes[pmtud->mtu_idx]);
+
+  pmtud_next_probe(pmtud);
+}
+
+int ngtcp2_pmtud_finished(ngtcp2_pmtud *pmtud) {
+  return pmtud->mtu_idx >= pmtud->probeslen;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_pmtud.h b/third_party/ngtcp2/lib/ngtcp2_pmtud.h
new file mode 100644 (file)
index 0000000..53fc6a5
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_PMTUD_H
+#define NGTCP2_PMTUD_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+typedef struct ngtcp2_pmtud {
+  const ngtcp2_mem *mem;
+  /* mtu_idx is the index of UDP payload size candidates to try
+     out. */
+  size_t mtu_idx;
+  /* num_pkts_sent is the number of mtu_idx sized UDP datagram payload
+     sent */
+  size_t num_pkts_sent;
+  /* expiry is the expired, if it is reached, send out the next UDP
+     datagram.  UINT64_MAX means no expiry, or expiration is canceled.
+     In either case, new probe packet should be sent unless we have
+     done all attempts. */
+  ngtcp2_tstamp expiry;
+  /* tx_pkt_num is the smallest outgoing packet number where the
+     current discovery is performed.  In other words, acknowledging
+     packet whose packet number lower than that does not indicate the
+     success of Path MTU Discovery. */
+  int64_t tx_pkt_num;
+  /* max_udp_payload_size is the maximum UDP payload size which is
+     known to work. */
+  size_t max_udp_payload_size;
+  /* hard_max_udp_payload_size is the maximum UDP payload size that is
+     going to be probed. */
+  size_t hard_max_udp_payload_size;
+  /* min_fail_udp_payload_size is the minimum UDP payload size that is
+     known to fail. */
+  size_t min_fail_udp_payload_size;
+  /* probes is the array of UDP datagram payload size to probe. */
+  const uint16_t *probes;
+  /* probeslen is the number of probes pointed by probes. */
+  size_t probeslen;
+} ngtcp2_pmtud;
+
+/*
+ * ngtcp2_pmtud_new creates new ngtcp2_pmtud object, and assigns its
+ * pointer to |*ppmtud|.  |max_udp_payload_size| is the maximum UDP
+ * payload size that is known to work for the current path.
+ * |tx_pkt_num| should be the next packet number to send, which is
+ * used to differentiate the PMTUD probe packet sent by the previous
+ * PMTUD.  PMTUD might finish immediately if |max_udp_payload_size| is
+ * larger than or equal to all UDP payload probe candidates.
+ * Therefore, call ngtcp2_pmtud_finished to check this situation.
+ *
+ * The array pointed by |pmtud_probes| of length |pmtud_probeslen|
+ * specifies UDP datagram payload size to probe.  If |pmtud_probeslen|
+ * is zero, the default probes are used.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_pmtud_new(ngtcp2_pmtud **ppmtud, size_t max_udp_payload_size,
+                     size_t hard_max_udp_payload_size, int64_t tx_pkt_num,
+                     const uint16_t *pmtud_probes, size_t pmtud_probeslen,
+                     const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_pmtud_del deletes |pmtud|.
+ */
+void ngtcp2_pmtud_del(ngtcp2_pmtud *pmtud);
+
+/*
+ * ngtcp2_pmtud_probelen returns the length of UDP payload size for a
+ * PMTUD probe packet.
+ */
+size_t ngtcp2_pmtud_probelen(ngtcp2_pmtud *pmtud);
+
+/*
+ * ngtcp2_pmtud_probe_sent should be invoked when a PMTUD probe packet is
+ * sent.
+ */
+void ngtcp2_pmtud_probe_sent(ngtcp2_pmtud *pmtud, ngtcp2_duration pto,
+                             ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_pmtud_require_probe returns nonzero if a PMTUD probe packet
+ * should be sent.
+ */
+int ngtcp2_pmtud_require_probe(ngtcp2_pmtud *pmtud);
+
+/*
+ * ngtcp2_pmtud_probe_success should be invoked when a PMTUD probe
+ * UDP datagram sized |payloadlen| is acknowledged.
+ */
+void ngtcp2_pmtud_probe_success(ngtcp2_pmtud *pmtud, size_t payloadlen);
+
+/*
+ * ngtcp2_pmtud_handle_expiry handles expiry.
+ */
+void ngtcp2_pmtud_handle_expiry(ngtcp2_pmtud *pmtud, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_pmtud_finished returns nonzero if PMTUD has finished.
+ */
+int ngtcp2_pmtud_finished(ngtcp2_pmtud *pmtud);
+
+#endif /* !defined(NGTCP2_PMTUD_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_ppe.c b/third_party/ngtcp2/lib/ngtcp2_ppe.c
new file mode 100644 (file)
index 0000000..4d19312
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_ppe.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_str.h"
+#include "ngtcp2_conv.h"
+#include "ngtcp2_macro.h"
+
+void ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen,
+                     size_t dgram_offset, ngtcp2_crypto_cc *cc) {
+  ngtcp2_buf_init(&ppe->buf, out, outlen);
+
+  ppe->dgram_offset = dgram_offset;
+  ppe->hdlen = 0;
+  ppe->len_offset = 0;
+  ppe->pkt_num_offset = 0;
+  ppe->pkt_numlen = 0;
+  ppe->pkt_num = 0;
+  ppe->cc = cc;
+}
+
+int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd) {
+  ngtcp2_ssize rv;
+  ngtcp2_buf *buf = &ppe->buf;
+  size_t buf_left = ngtcp2_buf_left(buf);
+  ngtcp2_crypto_cc *cc = ppe->cc;
+
+  if (buf_left <= cc->aead.max_overhead) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) {
+    ppe->len_offset = 1 + 4 + 1 + hd->dcid.datalen + 1 + hd->scid.datalen;
+
+    if (hd->type == NGTCP2_PKT_INITIAL) {
+      ppe->len_offset += ngtcp2_put_uvarintlen(hd->tokenlen) + hd->tokenlen;
+    }
+
+    ppe->pkt_num_offset = ppe->len_offset + NGTCP2_PKT_LENGTHLEN;
+
+    rv = ngtcp2_pkt_encode_hd_long(buf->last, buf_left - cc->aead.max_overhead,
+                                   hd);
+  } else {
+    ppe->pkt_num_offset = 1 + hd->dcid.datalen;
+
+    rv = ngtcp2_pkt_encode_hd_short(buf->last, buf_left - cc->aead.max_overhead,
+                                    hd);
+  }
+
+  if (rv < 0) {
+    return (int)rv;
+  }
+
+  buf->last += rv;
+
+  ppe->pkt_numlen = hd->pkt_numlen;
+  ppe->hdlen = (size_t)rv;
+  ppe->pkt_num = hd->pkt_num;
+
+  return 0;
+}
+
+int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, ngtcp2_frame *fr) {
+  ngtcp2_ssize rv;
+  ngtcp2_buf *buf = &ppe->buf;
+  size_t buf_left = ngtcp2_buf_left(buf);
+  ngtcp2_crypto_cc *cc = ppe->cc;
+
+  if (buf_left <= cc->aead.max_overhead) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  rv = ngtcp2_pkt_encode_frame(buf->last, buf_left - cc->aead.max_overhead, fr);
+  if (rv < 0) {
+    return (int)rv;
+  }
+
+  buf->last += rv;
+
+  return 0;
+}
+
+/*
+ * ppe_sample_offset returns the offset to sample for packet number
+ * encryption.
+ */
+static size_t ppe_sample_offset(ngtcp2_ppe *ppe) {
+  return ppe->pkt_num_offset + 4;
+}
+
+ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) {
+  ngtcp2_buf *buf = &ppe->buf;
+  ngtcp2_crypto_cc *cc = ppe->cc;
+  uint8_t *payload = buf->begin + ppe->hdlen;
+  size_t payloadlen = ngtcp2_buf_len(buf) - ppe->hdlen;
+  uint8_t mask[NGTCP2_HP_SAMPLELEN];
+  uint8_t *p;
+  size_t i;
+  int rv;
+
+  assert(cc->encrypt);
+  assert(cc->hp_mask);
+
+  if (ppe->len_offset) {
+    ngtcp2_put_uvarint30(
+      buf->begin + ppe->len_offset,
+      (uint16_t)(payloadlen + ppe->pkt_numlen + cc->aead.max_overhead));
+  }
+
+  ngtcp2_crypto_create_nonce(ppe->nonce, cc->ckm->iv.base, cc->ckm->iv.len,
+                             ppe->pkt_num);
+
+  rv = cc->encrypt(payload, &cc->aead, &cc->ckm->aead_ctx, payload, payloadlen,
+                   ppe->nonce, cc->ckm->iv.len, buf->begin, ppe->hdlen);
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  buf->last = payload + payloadlen + cc->aead.max_overhead;
+
+  /* Make sure that we have enough space to get sample */
+  assert(ppe_sample_offset(ppe) + NGTCP2_HP_SAMPLELEN <= ngtcp2_buf_len(buf));
+
+  rv = cc->hp_mask(mask, &cc->hp, &cc->hp_ctx,
+                   buf->begin + ppe_sample_offset(ppe));
+  if (rv != 0) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  p = buf->begin;
+  if (*p & NGTCP2_HEADER_FORM_BIT) {
+    *p = (uint8_t)(*p ^ (mask[0] & 0x0f));
+  } else {
+    *p = (uint8_t)(*p ^ (mask[0] & 0x1f));
+  }
+
+  p = buf->begin + ppe->pkt_num_offset;
+  for (i = 0; i < ppe->pkt_numlen; ++i) {
+    *(p + i) ^= mask[i + 1];
+  }
+
+  if (ppkt != NULL) {
+    *ppkt = buf->begin;
+  }
+
+  return (ngtcp2_ssize)ngtcp2_buf_len(buf);
+}
+
+size_t ngtcp2_ppe_left(const ngtcp2_ppe *ppe) {
+  ngtcp2_crypto_cc *cc = ppe->cc;
+  size_t buf_left = ngtcp2_buf_left(&ppe->buf);
+
+  if (buf_left <= cc->aead.max_overhead) {
+    return 0;
+  }
+
+  return buf_left - cc->aead.max_overhead;
+}
+
+size_t ngtcp2_ppe_padding_size(ngtcp2_ppe *ppe, size_t n) {
+  ngtcp2_crypto_cc *cc = ppe->cc;
+  ngtcp2_buf *buf = &ppe->buf;
+  size_t pktlen = ngtcp2_buf_len(buf) + cc->aead.max_overhead;
+  size_t len = 0;
+  size_t max_samplelen;
+
+  n = ngtcp2_min_size(n, ngtcp2_buf_cap(buf));
+  if (pktlen < n) {
+    len = n - pktlen;
+  }
+
+  /* Ensure header protection sample */
+  max_samplelen =
+    ngtcp2_buf_len(buf) + cc->aead.max_overhead - ppe_sample_offset(ppe);
+
+  if (max_samplelen < NGTCP2_HP_SAMPLELEN) {
+    len = ngtcp2_max_size(len, NGTCP2_HP_SAMPLELEN - max_samplelen);
+  }
+
+  assert(ngtcp2_buf_left(buf) >= len + cc->aead.max_overhead);
+
+  if (len == 0) {
+    return 0;
+  }
+
+  buf->last = ngtcp2_setmem(buf->last, 0, len);
+
+  return len;
+}
+
+size_t ngtcp2_ppe_dgram_padding(ngtcp2_ppe *ppe) {
+  return ngtcp2_ppe_dgram_padding_size(ppe, NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+}
+
+size_t ngtcp2_ppe_dgram_padding_size(ngtcp2_ppe *ppe, size_t n) {
+  ngtcp2_crypto_cc *cc = ppe->cc;
+  ngtcp2_buf *buf = &ppe->buf;
+  size_t dgramlen =
+    ppe->dgram_offset + ngtcp2_buf_len(buf) + cc->aead.max_overhead;
+  size_t len;
+
+  n = ngtcp2_min_size(n, ppe->dgram_offset + ngtcp2_buf_cap(buf));
+
+  if (dgramlen >= n) {
+    return 0;
+  }
+
+  len = n - dgramlen;
+  buf->last = ngtcp2_setmem(buf->last, 0, len);
+
+  return len;
+}
+
+int ngtcp2_ppe_ensure_hp_sample(ngtcp2_ppe *ppe) {
+  ngtcp2_buf *buf = &ppe->buf;
+
+  return ngtcp2_buf_left(buf) >= (4 - ppe->pkt_numlen) + NGTCP2_HP_SAMPLELEN;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_ppe.h b/third_party/ngtcp2/lib/ngtcp2_ppe.h
new file mode 100644 (file)
index 0000000..9874b36
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_PPE_H
+#define NGTCP2_PPE_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_buf.h"
+#include "ngtcp2_crypto.h"
+
+/*
+ * ngtcp2_ppe is the QUIC Packet Encoder.
+ */
+typedef struct ngtcp2_ppe {
+  /* buf is the buffer where a QUIC packet is written. */
+  ngtcp2_buf buf;
+  /* cc is the encryption context that includes callback functions to
+     encrypt a QUIC packet, and AEAD cipher, etc. */
+  ngtcp2_crypto_cc *cc;
+  /* dgram_offset is the offset in UDP datagram payload that this QUIC
+     packet is positioned at. */
+  size_t dgram_offset;
+  /* hdlen is the number of bytes for packet header written in buf. */
+  size_t hdlen;
+  /* len_offset is the offset to Length field. */
+  size_t len_offset;
+  /* pkt_num_offset is the offset to packet number field. */
+  size_t pkt_num_offset;
+  /* pkt_numlen is the number of bytes used to encode a packet
+     number */
+  size_t pkt_numlen;
+  /* pkt_num is the packet number written in buf. */
+  int64_t pkt_num;
+  /* nonce is the buffer to store nonce.  It should be equal or longer
+     than the length of IV. */
+  uint8_t nonce[32];
+} ngtcp2_ppe;
+
+/*
+ * ngtcp2_ppe_init initializes |ppe| with the given buffer.
+ */
+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|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     The buffer is too small.
+ */
+int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd);
+
+/*
+ * ngtcp2_ppe_encode_frame encodes |fr|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOBUF
+ *     The buffer is too small.
+ */
+int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, ngtcp2_frame *fr);
+
+/*
+ * ngtcp2_ppe_final encrypts QUIC packet payload.  If |ppkt| is not
+ * NULL, the pointer to the packet is assigned to it.
+ *
+ * This function returns the length of QUIC packet, including header,
+ * and payload if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User-defined callback function failed.
+ */
+ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt);
+
+/*
+ * ngtcp2_ppe_left returns the number of bytes left to write
+ * additional frames.  This does not count AEAD overhead.
+ */
+size_t ngtcp2_ppe_left(const ngtcp2_ppe *ppe);
+
+/*
+ * ngtcp2_ppe_dgram_padding is equivalent to call
+ * ngtcp2_ppe_dgram_padding_size(ppe, NGTCP2_MAX_UDP_PAYLOAD_SIZE).
+ * This function should be called just before calling
+ * ngtcp2_ppe_final().
+ *
+ * This function returns the number of bytes padded.
+ */
+size_t ngtcp2_ppe_dgram_padding(ngtcp2_ppe *ppe);
+
+/*
+ * ngtcp2_ppe_dgram_padding_size adds PADDING frame so that the size
+ * of a UDP datagram payload is at least |n| bytes long.  If it is
+ * unable to add PADDING in that way, this function still adds PADDING
+ * frame as much as possible.  This function should be called just
+ * before calling ngtcp2_ppe_final().
+ *
+ * This function returns the number of bytes added as padding.
+ */
+size_t ngtcp2_ppe_dgram_padding_size(ngtcp2_ppe *ppe, size_t n);
+
+/*
+ * ngtcp2_ppe_padding_size adds PADDING frame so that the size of QUIC
+ * packet is at least |n| bytes long and the current payload has
+ * enough space for header protection sample.  If it is unable to add
+ * PADDING at least |n| bytes, this function still adds PADDING frames
+ * as much as possible.  This function also adds PADDING frames so
+ * that the minimum padding requirement of header protection is met.
+ * Those padding may be larger than |n| bytes.  It is recommended to
+ * make sure that ngtcp2_ppe_ensure_hp_sample succeeds after writing
+ * QUIC packet header.  This function should be called just before
+ * calling ngtcp2_ppe_final().
+ *
+ * This function returns the number of bytes added as padding.
+ */
+size_t ngtcp2_ppe_padding_size(ngtcp2_ppe *ppe, size_t n);
+
+/*
+ * ngtcp2_ppe_ensure_hp_sample returns nonzero if the buffer has
+ * enough space for header protection sample.  This should be called
+ * right after packet header is written.
+ */
+int ngtcp2_ppe_ensure_hp_sample(ngtcp2_ppe *ppe);
+
+#endif /* !defined(NGTCP2_PPE_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_pq.c b/third_party/ngtcp2/lib/ngtcp2_pq.c
new file mode 100644 (file)
index 0000000..162bed0
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_pq.h"
+
+#include <assert.h>
+
+#include "ngtcp2_macro.h"
+
+void ngtcp2_pq_init(ngtcp2_pq *pq, ngtcp2_pq_less less, const ngtcp2_mem *mem) {
+  pq->q = NULL;
+  pq->mem = mem;
+  pq->length = 0;
+  pq->capacity = 0;
+  pq->less = less;
+}
+
+void ngtcp2_pq_free(ngtcp2_pq *pq) {
+  if (!pq) {
+    return;
+  }
+
+  ngtcp2_mem_free(pq->mem, pq->q);
+}
+
+static void swap(ngtcp2_pq *pq, size_t i, size_t j) {
+  ngtcp2_pq_entry *a = pq->q[i];
+  ngtcp2_pq_entry *b = pq->q[j];
+
+  pq->q[i] = b;
+  b->index = i;
+  pq->q[j] = a;
+  a->index = j;
+}
+
+static void bubble_up(ngtcp2_pq *pq, size_t index) {
+  size_t parent;
+
+  while (index) {
+    parent = (index - 1) / 2;
+    if (!pq->less(pq->q[index], pq->q[parent])) {
+      return;
+    }
+
+    swap(pq, parent, index);
+    index = parent;
+  }
+}
+
+int ngtcp2_pq_push(ngtcp2_pq *pq, ngtcp2_pq_entry *item) {
+  if (pq->capacity <= pq->length) {
+    void *nq;
+    size_t ncapacity;
+
+    ncapacity = ngtcp2_max_size(4, pq->capacity * 2);
+
+    nq =
+      ngtcp2_mem_realloc(pq->mem, pq->q, ncapacity * sizeof(ngtcp2_pq_entry *));
+    if (nq == NULL) {
+      return NGTCP2_ERR_NOMEM;
+    }
+
+    pq->capacity = ncapacity;
+    pq->q = nq;
+  }
+
+  pq->q[pq->length] = item;
+  item->index = pq->length;
+  ++pq->length;
+  bubble_up(pq, item->index);
+
+  return 0;
+}
+
+ngtcp2_pq_entry *ngtcp2_pq_top(const ngtcp2_pq *pq) {
+  assert(pq->length);
+  return pq->q[0];
+}
+
+static void bubble_down(ngtcp2_pq *pq, size_t index) {
+  size_t i, j, minindex;
+
+  for (;;) {
+    j = index * 2 + 1;
+    minindex = index;
+
+    for (i = 0; i < 2; ++i, ++j) {
+      if (j >= pq->length) {
+        break;
+      }
+
+      if (pq->less(pq->q[j], pq->q[minindex])) {
+        minindex = j;
+      }
+    }
+
+    if (minindex == index) {
+      return;
+    }
+
+    swap(pq, index, minindex);
+    index = minindex;
+  }
+}
+
+void ngtcp2_pq_pop(ngtcp2_pq *pq) {
+  assert(pq->length);
+
+  pq->q[0] = pq->q[pq->length - 1];
+  pq->q[0]->index = 0;
+  --pq->length;
+  bubble_down(pq, 0);
+}
+
+void ngtcp2_pq_remove(ngtcp2_pq *pq, ngtcp2_pq_entry *item) {
+  assert(pq->q[item->index] == item);
+
+  if (item->index == 0) {
+    ngtcp2_pq_pop(pq);
+    return;
+  }
+
+  if (item->index == pq->length - 1) {
+    --pq->length;
+    return;
+  }
+
+  pq->q[item->index] = pq->q[pq->length - 1];
+  pq->q[item->index]->index = item->index;
+  --pq->length;
+
+  if (pq->less(item, pq->q[item->index])) {
+    bubble_down(pq, item->index);
+  } else {
+    bubble_up(pq, item->index);
+  }
+}
+
+int ngtcp2_pq_empty(const ngtcp2_pq *pq) { return pq->length == 0; }
+
+size_t ngtcp2_pq_size(const ngtcp2_pq *pq) { return pq->length; }
diff --git a/third_party/ngtcp2/lib/ngtcp2_pq.h b/third_party/ngtcp2/lib/ngtcp2_pq.h
new file mode 100644 (file)
index 0000000..aa195a9
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2012 nghttp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_PQ_H
+#define NGTCP2_PQ_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+
+/* Implementation of priority queue */
+
+/* NGTCP2_PQ_BAD_INDEX is the priority queue index which indicates
+   that an entry is not queued.  Assigning this value to
+   ngtcp2_pq_entry.index can check that the entry is queued or not. */
+#define NGTCP2_PQ_BAD_INDEX SIZE_MAX
+
+typedef struct ngtcp2_pq_entry {
+  size_t index;
+} ngtcp2_pq_entry;
+
+/* ngtcp2_pq_less is a "less" function, that returns nonzero if |lhs|
+   is considered to be less than |rhs|. */
+typedef int (*ngtcp2_pq_less)(const ngtcp2_pq_entry *lhs,
+                              const ngtcp2_pq_entry *rhs);
+
+typedef struct ngtcp2_pq {
+  /* q is a pointer to an array that stores the items. */
+  ngtcp2_pq_entry **q;
+  /* mem is a memory allocator. */
+  const ngtcp2_mem *mem;
+  /* length is the number of items stored. */
+  size_t length;
+  /* capacity is the maximum number of items this queue can store.
+     This is automatically extended when length is reached to this
+     limit. */
+  size_t capacity;
+  /* less is the less function to compare items. */
+  ngtcp2_pq_less less;
+} ngtcp2_pq;
+
+/*
+ * ngtcp2_pq_init initializes |pq| with compare function |cmp|.
+ */
+void ngtcp2_pq_init(ngtcp2_pq *pq, ngtcp2_pq_less less, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_pq_free deallocates any resources allocated for |pq|.  The
+ * stored items are not freed by this function.
+ */
+void ngtcp2_pq_free(ngtcp2_pq *pq);
+
+/*
+ * ngtcp2_pq_push adds |item| to |pq|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_pq_push(ngtcp2_pq *pq, ngtcp2_pq_entry *item);
+
+/*
+ * ngtcp2_pq_top returns item at the top of |pq|.  It is undefined if
+ * |pq| is empty.
+ */
+ngtcp2_pq_entry *ngtcp2_pq_top(const ngtcp2_pq *pq);
+
+/*
+ * ngtcp2_pq_pop pops item at the top of |pq|.  The popped item is not
+ * freed by this function.  It is undefined if |pq| is empty.
+ */
+void ngtcp2_pq_pop(ngtcp2_pq *pq);
+
+/*
+ * ngtcp2_pq_empty returns nonzero if |pq| is empty.
+ */
+int ngtcp2_pq_empty(const ngtcp2_pq *pq);
+
+/*
+ * ngtcp2_pq_size returns the number of items |pq| contains.
+ */
+size_t ngtcp2_pq_size(const ngtcp2_pq *pq);
+
+/*
+ * ngtcp2_pq_remove removes |item| from |pq|.  |pq| must contain
+ * |item| otherwise the behavior is undefined.
+ */
+void ngtcp2_pq_remove(ngtcp2_pq *pq, ngtcp2_pq_entry *item);
+
+#endif /* !defined(NGTCP2_PQ_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_pv.c b/third_party/ngtcp2/lib/ngtcp2_pv.c
new file mode 100644 (file)
index 0000000..471f84c
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_pv.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_mem.h"
+#include "ngtcp2_log.h"
+#include "ngtcp2_macro.h"
+#include "ngtcp2_addr.h"
+
+void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data,
+                          ngtcp2_tstamp expiry, uint8_t flags) {
+  memcpy(pvent->data, data, sizeof(pvent->data));
+  pvent->expiry = expiry;
+  pvent->flags = flags;
+}
+
+int ngtcp2_pv_new(ngtcp2_pv **ppv, const ngtcp2_dcid *dcid,
+                  ngtcp2_duration timeout, uint8_t flags, ngtcp2_log *log,
+                  const ngtcp2_mem *mem) {
+  (*ppv) = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pv));
+  if (*ppv == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_static_ringbuf_pv_ents_init(&(*ppv)->ents);
+
+  ngtcp2_dcid_copy(&(*ppv)->dcid, dcid);
+
+  (*ppv)->mem = mem;
+  (*ppv)->log = log;
+  (*ppv)->timeout = timeout;
+  (*ppv)->fallback_pto = 0;
+  (*ppv)->started_ts = UINT64_MAX;
+  (*ppv)->probe_pkt_left = NGTCP2_PV_NUM_PROBE_PKT;
+  (*ppv)->round = 0;
+  (*ppv)->flags = flags;
+
+  return 0;
+}
+
+void ngtcp2_pv_del(ngtcp2_pv *pv) {
+  if (pv == NULL) {
+    return;
+  }
+  ngtcp2_mem_free(pv->mem, pv);
+}
+
+void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const uint8_t *data,
+                         ngtcp2_tstamp expiry, uint8_t flags,
+                         ngtcp2_tstamp ts) {
+  ngtcp2_pv_entry *ent;
+
+  assert(pv->probe_pkt_left);
+
+  if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0) {
+    pv->started_ts = ts;
+  }
+
+  ent = ngtcp2_ringbuf_push_back(&pv->ents.rb);
+  ngtcp2_pv_entry_init(ent, data, expiry, flags);
+
+  pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_CANCEL_TIMER;
+  --pv->probe_pkt_left;
+}
+
+int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, const uint8_t *data) {
+  size_t len = ngtcp2_ringbuf_len(&pv->ents.rb);
+  size_t i;
+  ngtcp2_pv_entry *ent;
+
+  if (len == 0) {
+    return NGTCP2_ERR_INVALID_STATE;
+  }
+
+  for (i = 0; i < len; ++i) {
+    ent = ngtcp2_ringbuf_get(&pv->ents.rb, i);
+    if (memcmp(ent->data, data, sizeof(ent->data)) == 0) {
+      *pflags = ent->flags;
+      ngtcp2_log_info(pv->log, NGTCP2_LOG_EVENT_PTV, "path has been validated");
+      return 0;
+    }
+  }
+
+  return NGTCP2_ERR_INVALID_ARGUMENT;
+}
+
+void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts) {
+  ngtcp2_pv_entry *ent;
+
+  if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0) {
+    return;
+  }
+
+  ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1);
+
+  if (ent->expiry > ts) {
+    return;
+  }
+
+  ++pv->round;
+  pv->probe_pkt_left = NGTCP2_PV_NUM_PROBE_PKT;
+}
+
+int ngtcp2_pv_should_send_probe(ngtcp2_pv *pv) {
+  return pv->probe_pkt_left > 0;
+}
+
+int ngtcp2_pv_validation_timed_out(ngtcp2_pv *pv, ngtcp2_tstamp ts) {
+  ngtcp2_tstamp t;
+  ngtcp2_pv_entry *ent;
+
+  if (pv->started_ts == UINT64_MAX) {
+    return 0;
+  }
+
+  assert(ngtcp2_ringbuf_len(&pv->ents.rb));
+
+  ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1);
+
+  t = pv->started_ts + pv->timeout;
+  t = ngtcp2_max_uint64(t, ent->expiry);
+
+  return t <= 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) {
+    return UINT64_MAX;
+  }
+
+  ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1);
+
+  return ent->expiry;
+}
+
+void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts) {
+  ngtcp2_tstamp expiry = ngtcp2_pv_next_expiry(pv);
+
+  if (expiry > ts) {
+    return;
+  }
+
+  pv->flags |= NGTCP2_PV_FLAG_CANCEL_TIMER;
+}
+
+void ngtcp2_pv_set_fallback(ngtcp2_pv *pv, const ngtcp2_dcid *dcid,
+                            ngtcp2_duration pto) {
+  pv->flags |= NGTCP2_PV_FLAG_FALLBACK_PRESENT;
+  ngtcp2_dcid_copy(&pv->fallback_dcid, dcid);
+  pv->fallback_pto = pto;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_pv.h b/third_party/ngtcp2/lib/ngtcp2_pv.h
new file mode 100644 (file)
index 0000000..2d07e41
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_PV_H
+#define NGTCP2_PV_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_cid.h"
+#include "ngtcp2_ringbuf.h"
+
+/* NGTCP2_PV_MAX_ENTRIES is the maximum number of entries that
+   ngtcp2_pv can contain.  It must be power of 2. */
+#define NGTCP2_PV_MAX_ENTRIES 8
+/* NGTCP2_PV_NUM_PROBE_PKT is the number of probe packets containing
+   PATH_CHALLENGE sent at a time. */
+#define NGTCP2_PV_NUM_PROBE_PKT 2
+
+typedef struct ngtcp2_log ngtcp2_log;
+
+typedef struct ngtcp2_frame_chain ngtcp2_frame_chain;
+
+/* NGTCP2_PV_ENTRY_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_PV_ENTRY_FLAG_NONE 0x00u
+/* NGTCP2_PV_ENTRY_FLAG_UNDERSIZED indicates that UDP datagram which
+   contains PATH_CHALLENGE is undersized (< 1200 bytes) */
+#define NGTCP2_PV_ENTRY_FLAG_UNDERSIZED 0x01u
+
+typedef struct ngtcp2_pv_entry {
+  /* expiry is the timestamp when this PATH_CHALLENGE expires. */
+  ngtcp2_tstamp expiry;
+  /* flags is zero or more of NGTCP2_PV_ENTRY_FLAG_*. */
+  uint8_t flags;
+  /* data is a byte string included in PATH_CHALLENGE. */
+  uint8_t data[8];
+} ngtcp2_pv_entry;
+
+void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data,
+                          ngtcp2_tstamp expiry, uint8_t flags);
+
+/* NGTCP2_PV_FLAG_NONE indicates no flag is set. */
+#define NGTCP2_PV_FLAG_NONE 0x00u
+/* 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. */
+#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. */
+#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
+
+typedef struct ngtcp2_pv ngtcp2_pv;
+
+ngtcp2_static_ringbuf_def(pv_ents, NGTCP2_PV_MAX_ENTRIES,
+                          sizeof(ngtcp2_pv_entry))
+/*
+ * ngtcp2_pv is the context of a single path validation.
+ */
+struct ngtcp2_pv {
+  const ngtcp2_mem *mem;
+  ngtcp2_log *log;
+  /* dcid is DCID and path this path validation uses. */
+  ngtcp2_dcid dcid;
+  /* fallback_dcid is the usually validated DCID and used as a
+     fallback if this path validation fails. */
+  ngtcp2_dcid fallback_dcid;
+  /* ents is the ring buffer of ngtcp2_pv_entry */
+  ngtcp2_static_ringbuf_pv_ents ents;
+  /* timeout is the duration within which this path validation should
+     succeed. */
+  ngtcp2_duration timeout;
+  /* fallback_pto is PTO of fallback connection. */
+  ngtcp2_duration fallback_pto;
+  /* started_ts is the timestamp this path validation starts. */
+  ngtcp2_tstamp started_ts;
+  /* round is the number of times that probe_pkt_left is reset. */
+  size_t round;
+  /* probe_pkt_left is the number of probe packets containing
+     PATH_CHALLENGE which can be send without waiting for an
+     expiration of a previous flight. */
+  size_t probe_pkt_left;
+  /* flags is bitwise-OR of zero or more of NGTCP2_PV_FLAG_*. */
+  uint8_t flags;
+};
+
+/*
+ * ngtcp2_pv_new creates new ngtcp2_pv object and assigns its pointer
+ * to |*ppv|.  This function makes a copy of |dcid|.  |timeout| is a
+ * duration within which this path validation must succeed.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_pv_new(ngtcp2_pv **ppv, const ngtcp2_dcid *dcid,
+                  ngtcp2_duration timeout, uint8_t flags, ngtcp2_log *log,
+                  const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_pv_del deallocates |pv|.  This function frees memory |pv|
+ * points too.
+ */
+void ngtcp2_pv_del(ngtcp2_pv *pv);
+
+/*
+ * ngtcp2_pv_add_entry adds new entry with |data|.  |expiry| is the
+ * expiry time of the entry.
+ */
+void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const uint8_t *data,
+                         ngtcp2_tstamp expiry, uint8_t flags, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_pv_full returns nonzero if |pv| is full of ngtcp2_pv_entry.
+ */
+int ngtcp2_pv_full(ngtcp2_pv *pv);
+
+/*
+ * ngtcp2_pv_validate validates that the received |data| matches the
+ * one of the existing entry.  The flag of ngtcp2_pv_entry that
+ * matches |data| is assigned to |*pflags| if this function succeeds.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_PATH_VALIDATION_FAILED
+ *     path validation has failed and must be abandoned
+ * NGTCP2_ERR_INVALID_STATE
+ *     |pv| includes no entry
+ * NGTCP2_ERR_INVALID_ARGUMENT
+ *     |pv| does not have an entry which has |data| and |path|
+ */
+int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, const uint8_t *data);
+
+/*
+ * ngtcp2_pv_handle_entry_expiry checks expiry of existing entries.
+ */
+void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_pv_should_send_probe returns nonzero if new entry can be
+ * added by ngtcp2_pv_add_entry.
+ */
+int ngtcp2_pv_should_send_probe(ngtcp2_pv *pv);
+
+/*
+ * ngtcp2_pv_validation_timed_out returns nonzero if the path
+ * validation fails because of timeout.
+ */
+int ngtcp2_pv_validation_timed_out(ngtcp2_pv *pv, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_pv_next_expiry returns the earliest expiry.
+ */
+ngtcp2_tstamp ngtcp2_pv_next_expiry(ngtcp2_pv *pv);
+
+/*
+ * ngtcp2_pv_cancel_expired_timer cancels the expired timer.
+ */
+void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_pv_set_fallback sets |dcid| and |pto| as fallback.
+ */
+void ngtcp2_pv_set_fallback(ngtcp2_pv *pv, const ngtcp2_dcid *dcid,
+                            ngtcp2_duration pto);
+
+#endif /* !defined(NGTCP2_PV_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_qlog.c b/third_party/ngtcp2/lib/ngtcp2_qlog.c
new file mode 100644 (file)
index 0000000..c0f9207
--- /dev/null
@@ -0,0 +1,1223 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_qlog.h"
+
+#include <assert.h>
+
+#include "ngtcp2_str.h"
+#include "ngtcp2_vec.h"
+#include "ngtcp2_conv.h"
+#include "ngtcp2_net.h"
+#include "ngtcp2_unreachable.h"
+#include "ngtcp2_conn_stat.h"
+
+void ngtcp2_qlog_init(ngtcp2_qlog *qlog, ngtcp2_qlog_write write,
+                      ngtcp2_tstamp ts, void *user_data) {
+  qlog->write = write;
+  qlog->ts = qlog->last_ts = ts;
+  qlog->user_data = user_data;
+}
+
+#define write_verbatim(DEST, S) ngtcp2_cpymem((DEST), (S), sizeof(S) - 1)
+
+static uint8_t *write_string_impl(uint8_t *p, const uint8_t *data,
+                                  size_t datalen) {
+  *p++ = '"';
+  if (datalen) {
+    p = ngtcp2_cpymem(p, data, datalen);
+  }
+  *p++ = '"';
+  return p;
+}
+
+#define write_string(DEST, S)                                                  \
+  write_string_impl((DEST), (const uint8_t *)(S), sizeof(S) - 1)
+
+#define NGTCP2_LOWER_XDIGITS "0123456789abcdef"
+
+static uint8_t *write_hex(uint8_t *p, const uint8_t *data, size_t datalen) {
+  const uint8_t *b = data, *end = data + datalen;
+  *p++ = '"';
+  for (; b != end; ++b) {
+    *p++ = (uint8_t)NGTCP2_LOWER_XDIGITS[*b >> 4];
+    *p++ = (uint8_t)NGTCP2_LOWER_XDIGITS[*b & 0xf];
+  }
+  *p++ = '"';
+  return p;
+}
+
+static uint8_t *write_cid(uint8_t *p, const ngtcp2_cid *cid) {
+  return write_hex(p, cid->data, cid->datalen);
+}
+
+static uint8_t *write_number(uint8_t *p, uint64_t n) {
+  size_t nlen = 0;
+  uint64_t t;
+  uint8_t *res;
+
+  if (n == 0) {
+    *p++ = '0';
+    return p;
+  }
+  for (t = n; t; t /= 10, ++nlen)
+    ;
+  p += nlen;
+  res = p;
+  for (; n; n /= 10) {
+    *--p = (uint8_t)((n % 10) + '0');
+  }
+  return res;
+}
+
+static uint8_t *write_tstamp(uint8_t *p, ngtcp2_tstamp ts) {
+  return write_number(p, ts / NGTCP2_MILLISECONDS);
+}
+
+static uint8_t *write_duration(uint8_t *p, ngtcp2_duration duration) {
+  return write_number(p, duration / NGTCP2_MILLISECONDS);
+}
+
+static uint8_t *write_bool(uint8_t *p, int b) {
+  if (b) {
+    return ngtcp2_cpymem(p, "true", sizeof("true") - 1);
+  }
+  return ngtcp2_cpymem(p, "false", sizeof("false") - 1);
+}
+
+static uint8_t *write_pair_impl(uint8_t *p, const uint8_t *name, size_t namelen,
+                                const ngtcp2_vec *value) {
+  p = write_string_impl(p, name, namelen);
+  *p++ = ':';
+  return write_string_impl(p, value->base, value->len);
+}
+
+#define write_pair(DEST, NAME, VALUE)                                          \
+  write_pair_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, (VALUE))
+
+static uint8_t *write_pair_hex_impl(uint8_t *p, const uint8_t *name,
+                                    size_t namelen, const uint8_t *value,
+                                    size_t valuelen) {
+  p = write_string_impl(p, name, namelen);
+  *p++ = ':';
+  return write_hex(p, value, valuelen);
+}
+
+#define write_pair_hex(DEST, NAME, VALUE, VALUELEN)                            \
+  write_pair_hex_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1,       \
+                      (VALUE), (VALUELEN))
+
+static uint8_t *write_pair_number_impl(uint8_t *p, const uint8_t *name,
+                                       size_t namelen, uint64_t value) {
+  p = write_string_impl(p, name, namelen);
+  *p++ = ':';
+  return write_number(p, value);
+}
+
+#define write_pair_number(DEST, NAME, VALUE)                                   \
+  write_pair_number_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1,    \
+                         (VALUE))
+
+static uint8_t *write_pair_duration_impl(uint8_t *p, const uint8_t *name,
+                                         size_t namelen,
+                                         ngtcp2_duration duration) {
+  p = write_string_impl(p, name, namelen);
+  *p++ = ':';
+  return write_duration(p, duration);
+}
+
+#define write_pair_duration(DEST, NAME, VALUE)                                 \
+  write_pair_duration_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1,  \
+                           (VALUE))
+
+static uint8_t *write_pair_tstamp_impl(uint8_t *p, const uint8_t *name,
+                                       size_t namelen, ngtcp2_tstamp ts) {
+  p = write_string_impl(p, name, namelen);
+  *p++ = ':';
+  return write_tstamp(p, ts);
+}
+
+#define write_pair_tstamp(DEST, NAME, VALUE)                                   \
+  write_pair_tstamp_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1,    \
+                         (VALUE))
+
+static uint8_t *write_pair_bool_impl(uint8_t *p, const uint8_t *name,
+                                     size_t namelen, int b) {
+  p = write_string_impl(p, name, namelen);
+  *p++ = ':';
+  return write_bool(p, b);
+}
+
+#define write_pair_bool(DEST, NAME, VALUE)                                     \
+  write_pair_bool_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1,      \
+                       (VALUE))
+
+static uint8_t *write_pair_cid_impl(uint8_t *p, const uint8_t *name,
+                                    size_t namelen, const ngtcp2_cid *cid) {
+  p = write_string_impl(p, name, namelen);
+  *p++ = ':';
+  return write_cid(p, cid);
+}
+
+#define write_pair_cid(DEST, NAME, VALUE)                                      \
+  write_pair_cid_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1,       \
+                      (VALUE))
+
+#define ngtcp2_make_vec_lit(S) {(uint8_t *)(S), sizeof((S)) - 1}
+
+static uint8_t *write_common_fields(uint8_t *p, const ngtcp2_cid *odcid) {
+  p = write_verbatim(
+    p, "\"common_fields\":{\"protocol_type\":[\"QUIC\"],\"time_format\":"
+       "\"relative\",\"reference_time\":0,\"group_id\":");
+  p = write_cid(p, odcid);
+  *p++ = '}';
+  return p;
+}
+
+static uint8_t *write_trace(uint8_t *p, int server, const ngtcp2_cid *odcid) {
+  p = write_verbatim(
+    p, "\"trace\":{\"vantage_point\":{\"name\":\"ngtcp2\",\"type\":");
+  if (server) {
+    p = write_string(p, "server");
+  } else {
+    p = write_string(p, "client");
+  }
+  p = write_verbatim(p, "},");
+  p = write_common_fields(p, odcid);
+  *p++ = '}';
+  return p;
+}
+
+void ngtcp2_qlog_start(ngtcp2_qlog *qlog, const ngtcp2_cid *odcid, int server) {
+  uint8_t buf[1024];
+  uint8_t *p = buf;
+
+  if (!qlog->write) {
+    return;
+  }
+
+  p = write_verbatim(
+    p, "\x1e{\"qlog_format\":\"JSON-SEQ\",\"qlog_version\":\"0.3\",");
+  p = write_trace(p, server, odcid);
+  p = write_verbatim(p, "}\n");
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf,
+              (size_t)(p - buf));
+}
+
+void ngtcp2_qlog_end(ngtcp2_qlog *qlog) {
+  uint8_t buf[1] = {0};
+
+  if (!qlog->write) {
+    return;
+  }
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_FIN, &buf, 0);
+}
+
+static ngtcp2_vec vec_pkt_type_initial = ngtcp2_make_vec_lit("initial");
+static ngtcp2_vec vec_pkt_type_handshake = ngtcp2_make_vec_lit("handshake");
+static ngtcp2_vec vec_pkt_type_0rtt = ngtcp2_make_vec_lit("0RTT");
+static ngtcp2_vec vec_pkt_type_1rtt = ngtcp2_make_vec_lit("1RTT");
+static ngtcp2_vec vec_pkt_type_retry = ngtcp2_make_vec_lit("retry");
+static ngtcp2_vec vec_pkt_type_version_negotiation =
+  ngtcp2_make_vec_lit("version_negotiation");
+static ngtcp2_vec vec_pkt_type_stateless_reset =
+  ngtcp2_make_vec_lit("stateless_reset");
+static ngtcp2_vec vec_pkt_type_unknown = ngtcp2_make_vec_lit("unknown");
+
+static const ngtcp2_vec *qlog_pkt_type(const ngtcp2_pkt_hd *hd) {
+  if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) {
+    switch (hd->type) {
+    case NGTCP2_PKT_INITIAL:
+      return &vec_pkt_type_initial;
+    case NGTCP2_PKT_HANDSHAKE:
+      return &vec_pkt_type_handshake;
+    case NGTCP2_PKT_0RTT:
+      return &vec_pkt_type_0rtt;
+    case NGTCP2_PKT_RETRY:
+      return &vec_pkt_type_retry;
+    default:
+      return &vec_pkt_type_unknown;
+    }
+  }
+
+  switch (hd->type) {
+  case NGTCP2_PKT_VERSION_NEGOTIATION:
+    return &vec_pkt_type_version_negotiation;
+  case NGTCP2_PKT_STATELESS_RESET:
+    return &vec_pkt_type_stateless_reset;
+  case NGTCP2_PKT_1RTT:
+    return &vec_pkt_type_1rtt;
+  default:
+    return &vec_pkt_type_unknown;
+  }
+}
+
+static uint8_t *write_pkt_hd(uint8_t *p, const ngtcp2_pkt_hd *hd) {
+  /*
+   * {"packet_type":"version_negotiation","packet_number":"0000000000000000000","token":{"data":""}}
+   */
+#define NGTCP2_QLOG_PKT_HD_OVERHEAD 95
+
+  *p++ = '{';
+  p = write_pair(p, "packet_type", qlog_pkt_type(hd));
+  *p++ = ',';
+  p = write_pair_number(p, "packet_number", (uint64_t)hd->pkt_num);
+  if (hd->type == NGTCP2_PKT_INITIAL && hd->tokenlen) {
+    p = write_verbatim(p, ",\"token\":{");
+    p = write_pair_hex(p, "data", hd->token, hd->tokenlen);
+    *p++ = '}';
+  }
+  /* TODO Write DCIL and DCID */
+  /* TODO Write SCIL and SCID */
+  *p++ = '}';
+  return p;
+}
+
+static uint8_t *write_padding_frame(uint8_t *p, const ngtcp2_padding *fr) {
+  (void)fr;
+
+  /* {"frame_type":"padding"} */
+#define NGTCP2_QLOG_PADDING_FRAME_OVERHEAD 24
+
+  return write_verbatim(p, "{\"frame_type\":\"padding\"}");
+}
+
+static uint8_t *write_ping_frame(uint8_t *p, const ngtcp2_ping *fr) {
+  (void)fr;
+
+  /* {"frame_type":"ping"} */
+#define NGTCP2_QLOG_PING_FRAME_OVERHEAD 21
+
+  return write_verbatim(p, "{\"frame_type\":\"ping\"}");
+}
+
+static uint8_t *write_ack_frame(uint8_t *p, const ngtcp2_ack *fr) {
+  int64_t largest_ack, min_ack;
+  size_t i;
+  const ngtcp2_ack_range *range;
+
+  /*
+   * {"frame_type":"ack","ack_delay":0000000000000000000,"acked_ranges":[]}
+   *
+   * each range:
+   * [0000000000000000000,0000000000000000000],
+   *
+   * ecn:
+   * ,"ect1":0000000000000000000,"ect0":0000000000000000000,"ce":0000000000000000000
+   */
+#define NGTCP2_QLOG_ACK_FRAME_BASE_OVERHEAD 70
+#define NGTCP2_QLOG_ACK_FRAME_RANGE_OVERHEAD 42
+#define NGTCP2_QLOG_ACK_FRAME_ECN_OVERHEAD 79
+
+  p = write_verbatim(p, "{\"frame_type\":\"ack\",");
+  p = write_pair_duration(p, "ack_delay", fr->ack_delay_unscaled);
+  p = write_verbatim(p, ",\"acked_ranges\":[");
+
+  largest_ack = fr->largest_ack;
+  min_ack = fr->largest_ack - (int64_t)fr->first_ack_range;
+
+  *p++ = '[';
+  p = write_number(p, (uint64_t)min_ack);
+  if (largest_ack != min_ack) {
+    *p++ = ',';
+    p = write_number(p, (uint64_t)largest_ack);
+  }
+  *p++ = ']';
+
+  for (i = 0; i < fr->rangecnt; ++i) {
+    range = &fr->ranges[i];
+    largest_ack = min_ack - (int64_t)range->gap - 2;
+    min_ack = largest_ack - (int64_t)range->len;
+    *p++ = ',';
+    *p++ = '[';
+    p = write_number(p, (uint64_t)min_ack);
+    if (largest_ack != min_ack) {
+      *p++ = ',';
+      p = write_number(p, (uint64_t)largest_ack);
+    }
+    *p++ = ']';
+  }
+
+  *p++ = ']';
+
+  if (fr->type == NGTCP2_FRAME_ACK_ECN) {
+    *p++ = ',';
+    p = write_pair_number(p, "ect1", fr->ecn.ect1);
+    *p++ = ',';
+    p = write_pair_number(p, "ect0", fr->ecn.ect0);
+    *p++ = ',';
+    p = write_pair_number(p, "ce", fr->ecn.ce);
+  }
+
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_reset_stream_frame(uint8_t *p,
+                                         const ngtcp2_reset_stream *fr) {
+  /*
+   * {"frame_type":"reset_stream","stream_id":0000000000000000000,"error_code":0000000000000000000,"final_size":0000000000000000000}
+   */
+#define NGTCP2_QLOG_RESET_STREAM_FRAME_OVERHEAD 127
+
+  p = write_verbatim(p, "{\"frame_type\":\"reset_stream\",");
+  p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id);
+  *p++ = ',';
+  p = write_pair_number(p, "error_code", fr->app_error_code);
+  *p++ = ',';
+  p = write_pair_number(p, "final_size", fr->final_size);
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_stop_sending_frame(uint8_t *p,
+                                         const ngtcp2_stop_sending *fr) {
+  /*
+   * {"frame_type":"stop_sending","stream_id":0000000000000000000,"error_code":0000000000000000000}
+   */
+#define NGTCP2_QLOG_STOP_SENDING_FRAME_OVERHEAD 94
+
+  p = write_verbatim(p, "{\"frame_type\":\"stop_sending\",");
+  p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id);
+  *p++ = ',';
+  p = write_pair_number(p, "error_code", fr->app_error_code);
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_crypto_frame(uint8_t *p, const ngtcp2_stream *fr) {
+  /*
+   * {"frame_type":"crypto","offset":0000000000000000000,"length":0000000000000000000}
+   */
+#define NGTCP2_QLOG_CRYPTO_FRAME_OVERHEAD 81
+
+  p = write_verbatim(p, "{\"frame_type\":\"crypto\",");
+  p = write_pair_number(p, "offset", fr->offset);
+  *p++ = ',';
+  p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt));
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_new_token_frame(uint8_t *p, const ngtcp2_new_token *fr) {
+  /*
+   * {"frame_type":"new_token","length":0000000000000000000,"token":{"data":""}}
+   */
+#define NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD 75
+
+  p = write_verbatim(p, "{\"frame_type\":\"new_token\",");
+  p = write_pair_number(p, "length", fr->tokenlen);
+  p = write_verbatim(p, ",\"token\":{");
+  p = write_pair_hex(p, "data", fr->token, fr->tokenlen);
+  *p++ = '}';
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_stream_frame(uint8_t *p, const ngtcp2_stream *fr) {
+  /*
+   * {"frame_type":"stream","stream_id":0000000000000000000,"offset":0000000000000000000,"length":0000000000000000000,"fin":true}
+   */
+#define NGTCP2_QLOG_STREAM_FRAME_OVERHEAD 124
+
+  p = write_verbatim(p, "{\"frame_type\":\"stream\",");
+  p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id);
+  *p++ = ',';
+  p = write_pair_number(p, "offset", fr->offset);
+  *p++ = ',';
+  p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt));
+  if (fr->fin) {
+    *p++ = ',';
+    p = write_pair_bool(p, "fin", 1);
+  }
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_max_data_frame(uint8_t *p, const ngtcp2_max_data *fr) {
+  /*
+   * {"frame_type":"max_data","maximum":0000000000000000000}
+   */
+#define NGTCP2_QLOG_MAX_DATA_FRAME_OVERHEAD 55
+
+  p = write_verbatim(p, "{\"frame_type\":\"max_data\",");
+  p = write_pair_number(p, "maximum", fr->max_data);
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_max_stream_data_frame(uint8_t *p,
+                                            const ngtcp2_max_stream_data *fr) {
+  /*
+   * {"frame_type":"max_stream_data","stream_id":0000000000000000000,"maximum":0000000000000000000}
+   */
+#define NGTCP2_QLOG_MAX_STREAM_DATA_FRAME_OVERHEAD 94
+
+  p = write_verbatim(p, "{\"frame_type\":\"max_stream_data\",");
+  p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id);
+  *p++ = ',';
+  p = write_pair_number(p, "maximum", fr->max_stream_data);
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_max_streams_frame(uint8_t *p,
+                                        const ngtcp2_max_streams *fr) {
+  /*
+   * {"frame_type":"max_streams","stream_type":"unidirectional","maximum":0000000000000000000}
+   */
+#define NGTCP2_QLOG_MAX_STREAMS_FRAME_OVERHEAD 89
+
+  p = write_verbatim(p, "{\"frame_type\":\"max_streams\",\"stream_type\":");
+  if (fr->type == NGTCP2_FRAME_MAX_STREAMS_BIDI) {
+    p = write_string(p, "bidirectional");
+  } else {
+    p = write_string(p, "unidirectional");
+  }
+  *p++ = ',';
+  p = write_pair_number(p, "maximum", fr->max_streams);
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_data_blocked_frame(uint8_t *p,
+                                         const ngtcp2_data_blocked *fr) {
+  /*
+   * {"frame_type":"data_blocked","limit":0000000000000000000}
+   */
+#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++ = '}';
+
+  return p;
+}
+
+static uint8_t *
+write_stream_data_blocked_frame(uint8_t *p,
+                                const ngtcp2_stream_data_blocked *fr) {
+  /*
+   * {"frame_type":"stream_data_blocked","stream_id":0000000000000000000,"limit":0000000000000000000}
+   */
+#define NGTCP2_QLOG_STREAM_DATA_BLOCKED_FRAME_OVERHEAD 96
+
+  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++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_streams_blocked_frame(uint8_t *p,
+                                            const ngtcp2_streams_blocked *fr) {
+  /*
+   * {"frame_type":"streams_blocked","stream_type":"unidirectional","limit":0000000000000000000}
+   */
+#define NGTCP2_QLOG_STREAMS_BLOCKED_FRAME_OVERHEAD 91
+
+  p = write_verbatim(p, "{\"frame_type\":\"streams_blocked\",\"stream_type\":");
+  if (fr->type == NGTCP2_FRAME_STREAMS_BLOCKED_BIDI) {
+    p = write_string(p, "bidirectional");
+  } else {
+    p = write_string(p, "unidirectional");
+  }
+  *p++ = ',';
+  p = write_pair_number(p, "limit", fr->max_streams);
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *
+write_new_connection_id_frame(uint8_t *p, const ngtcp2_new_connection_id *fr) {
+  /*
+   * {"frame_type":"new_connection_id","sequence_number":0000000000000000000,"retire_prior_to":0000000000000000000,"connection_id_length":0000000000000000000,"connection_id":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","stateless_reset_token":{"data":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}}
+   */
+#define NGTCP2_QLOG_NEW_CONNECTION_ID_FRAME_OVERHEAD 280
+
+  p = write_verbatim(p, "{\"frame_type\":\"new_connection_id\",");
+  p = write_pair_number(p, "sequence_number", fr->seq);
+  *p++ = ',';
+  p = write_pair_number(p, "retire_prior_to", fr->retire_prior_to);
+  *p++ = ',';
+  p = write_pair_number(p, "connection_id_length", fr->cid.datalen);
+  *p++ = ',';
+  p = write_pair_cid(p, "connection_id", &fr->cid);
+  p = write_verbatim(p, ",\"stateless_reset_token\":{");
+  p = write_pair_hex(p, "data", fr->stateless_reset_token,
+                     sizeof(fr->stateless_reset_token));
+  *p++ = '}';
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *
+write_retire_connection_id_frame(uint8_t *p,
+                                 const ngtcp2_retire_connection_id *fr) {
+  /*
+   * {"frame_type":"retire_connection_id","sequence_number":0000000000000000000}
+   */
+#define NGTCP2_QLOG_RETIRE_CONNECTION_ID_FRAME_OVERHEAD 75
+
+  p = write_verbatim(p, "{\"frame_type\":\"retire_connection_id\",");
+  p = write_pair_number(p, "sequence_number", fr->seq);
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_path_challenge_frame(uint8_t *p,
+                                           const ngtcp2_path_challenge *fr) {
+  /*
+   * {"frame_type":"path_challenge","data":"xxxxxxxxxxxxxxxx"}
+   */
+#define NGTCP2_QLOG_PATH_CHALLENGE_FRAME_OVERHEAD 57
+
+  p = write_verbatim(p, "{\"frame_type\":\"path_challenge\",");
+  p = write_pair_hex(p, "data", fr->data, sizeof(fr->data));
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_path_response_frame(uint8_t *p,
+                                          const ngtcp2_path_response *fr) {
+  /*
+   * {"frame_type":"path_response","data":"xxxxxxxxxxxxxxxx"}
+   */
+#define NGTCP2_QLOG_PATH_RESPONSE_FRAME_OVERHEAD 56
+
+  p = write_verbatim(p, "{\"frame_type\":\"path_response\",");
+  p = write_pair_hex(p, "data", fr->data, sizeof(fr->data));
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *
+write_connection_close_frame(uint8_t *p, const ngtcp2_connection_close *fr) {
+  /*
+   * {"frame_type":"connection_close","error_space":"application","error_code":0000000000000000000,"raw_error_code":0000000000000000000}
+   */
+#define NGTCP2_QLOG_CONNECTION_CLOSE_FRAME_OVERHEAD 131
+
+  p =
+    write_verbatim(p, "{\"frame_type\":\"connection_close\",\"error_space\":");
+  if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) {
+    p = write_string(p, "transport");
+  } else {
+    p = write_string(p, "application");
+  }
+  *p++ = ',';
+  p = write_pair_number(p, "error_code", fr->error_code);
+  *p++ = ',';
+  p = write_pair_number(p, "raw_error_code", fr->error_code);
+  /* TODO Write reason by escaping non-printables */
+  /* TODO Write trigger_frame_type */
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *write_handshake_done_frame(uint8_t *p,
+                                           const ngtcp2_handshake_done *fr) {
+  (void)fr;
+
+  /*
+   * {"frame_type":"handshake_done"}
+   */
+#define NGTCP2_QLOG_HANDSHAKE_DONE_FRAME_OVERHEAD 31
+
+  return write_verbatim(p, "{\"frame_type\":\"handshake_done\"}");
+}
+
+static uint8_t *write_datagram_frame(uint8_t *p, const ngtcp2_datagram *fr) {
+  /*
+   * {"frame_type":"datagram","length":0000000000000000000}
+   */
+#define NGTCP2_QLOG_DATAGRAM_FRAME_OVERHEAD 54
+
+  p = write_verbatim(p, "{\"frame_type\":\"datagram\",");
+  p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt));
+  *p++ = '}';
+
+  return p;
+}
+
+static uint8_t *qlog_write_time(ngtcp2_qlog *qlog, uint8_t *p) {
+  return write_pair_tstamp(p, "time", qlog->last_ts - qlog->ts);
+}
+
+static void qlog_pkt_write_start(ngtcp2_qlog *qlog, int sent) {
+  uint8_t *p;
+
+  if (!qlog->write) {
+    return;
+  }
+
+  ngtcp2_buf_reset(&qlog->buf);
+  p = qlog->buf.last;
+
+  *p++ = '\x1e';
+  *p++ = '{';
+  p = qlog_write_time(qlog, p);
+  p = write_verbatim(p, ",\"name\":");
+  if (sent) {
+    p = write_string(p, "transport:packet_sent");
+  } else {
+    p = write_string(p, "transport:packet_received");
+  }
+  p = write_verbatim(p, ",\"data\":{\"frames\":[");
+  qlog->buf.last = p;
+}
+
+static void qlog_pkt_write_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd,
+                               size_t pktlen) {
+  uint8_t *p = qlog->buf.last;
+
+  if (!qlog->write) {
+    return;
+  }
+
+  /*
+   * ],"header":,"raw":{"length":0000000000000000000}}}
+   *
+   * plus, terminating LF
+   */
+#define NGTCP2_QLOG_PKT_WRITE_END_OVERHEAD                                     \
+  (1 + 50 + NGTCP2_QLOG_PKT_HD_OVERHEAD)
+
+  if (ngtcp2_buf_left(&qlog->buf) <
+      NGTCP2_QLOG_PKT_WRITE_END_OVERHEAD + hd->tokenlen * 2) {
+    return;
+  }
+
+  assert(ngtcp2_buf_len(&qlog->buf));
+
+  /* Eat last ',' */
+  if (*(p - 1) == ',') {
+    --p;
+  }
+
+  p = write_verbatim(p, "],\"header\":");
+  p = write_pkt_hd(p, hd);
+  p = write_verbatim(p, ",\"raw\":{\"length\":");
+  p = write_number(p, pktlen);
+  p = write_verbatim(p, "}}}\n");
+
+  qlog->buf.last = p;
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, qlog->buf.pos,
+              ngtcp2_buf_len(&qlog->buf));
+}
+
+void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr) {
+  uint8_t *p = qlog->buf.last;
+
+  if (!qlog->write) {
+    return;
+  }
+
+  switch (fr->type) {
+  case NGTCP2_FRAME_PADDING:
+    if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_PADDING_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_padding_frame(p, &fr->padding);
+    break;
+  case NGTCP2_FRAME_PING:
+    if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_PING_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_ping_frame(p, &fr->ping);
+    break;
+  case NGTCP2_FRAME_ACK:
+  case NGTCP2_FRAME_ACK_ECN:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_ACK_FRAME_BASE_OVERHEAD +
+          (size_t)(fr->type == NGTCP2_FRAME_ACK_ECN
+                     ? NGTCP2_QLOG_ACK_FRAME_ECN_OVERHEAD
+                     : 0) +
+          NGTCP2_QLOG_ACK_FRAME_RANGE_OVERHEAD * (1 + fr->ack.rangecnt) + 1) {
+      return;
+    }
+    p = write_ack_frame(p, &fr->ack);
+    break;
+  case NGTCP2_FRAME_RESET_STREAM:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_RESET_STREAM_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_reset_stream_frame(p, &fr->reset_stream);
+    break;
+  case NGTCP2_FRAME_STOP_SENDING:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_STOP_SENDING_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_stop_sending_frame(p, &fr->stop_sending);
+    break;
+  case NGTCP2_FRAME_CRYPTO:
+    if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_CRYPTO_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_crypto_frame(p, &fr->stream);
+    break;
+  case NGTCP2_FRAME_NEW_TOKEN:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD + fr->new_token.tokenlen * 2 + 1) {
+      return;
+    }
+    p = write_new_token_frame(p, &fr->new_token);
+    break;
+  case NGTCP2_FRAME_STREAM:
+    if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_STREAM_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_stream_frame(p, &fr->stream);
+    break;
+  case NGTCP2_FRAME_MAX_DATA:
+    if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_MAX_DATA_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_max_data_frame(p, &fr->max_data);
+    break;
+  case NGTCP2_FRAME_MAX_STREAM_DATA:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_MAX_STREAM_DATA_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_max_stream_data_frame(p, &fr->max_stream_data);
+    break;
+  case NGTCP2_FRAME_MAX_STREAMS_BIDI:
+  case NGTCP2_FRAME_MAX_STREAMS_UNI:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_MAX_STREAMS_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_max_streams_frame(p, &fr->max_streams);
+    break;
+  case NGTCP2_FRAME_DATA_BLOCKED:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_DATA_BLOCKED_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_data_blocked_frame(p, &fr->data_blocked);
+    break;
+  case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_STREAM_DATA_BLOCKED_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_stream_data_blocked_frame(p, &fr->stream_data_blocked);
+    break;
+  case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI:
+  case NGTCP2_FRAME_STREAMS_BLOCKED_UNI:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_STREAMS_BLOCKED_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_streams_blocked_frame(p, &fr->streams_blocked);
+    break;
+  case NGTCP2_FRAME_NEW_CONNECTION_ID:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_NEW_CONNECTION_ID_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_new_connection_id_frame(p, &fr->new_connection_id);
+    break;
+  case NGTCP2_FRAME_RETIRE_CONNECTION_ID:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_RETIRE_CONNECTION_ID_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_retire_connection_id_frame(p, &fr->retire_connection_id);
+    break;
+  case NGTCP2_FRAME_PATH_CHALLENGE:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_PATH_CHALLENGE_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_path_challenge_frame(p, &fr->path_challenge);
+    break;
+  case NGTCP2_FRAME_PATH_RESPONSE:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_PATH_RESPONSE_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_path_response_frame(p, &fr->path_response);
+    break;
+  case NGTCP2_FRAME_CONNECTION_CLOSE:
+  case NGTCP2_FRAME_CONNECTION_CLOSE_APP:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_CONNECTION_CLOSE_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_connection_close_frame(p, &fr->connection_close);
+    break;
+  case NGTCP2_FRAME_HANDSHAKE_DONE:
+    if (ngtcp2_buf_left(&qlog->buf) <
+        NGTCP2_QLOG_HANDSHAKE_DONE_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_handshake_done_frame(p, &fr->handshake_done);
+    break;
+  case NGTCP2_FRAME_DATAGRAM:
+  case NGTCP2_FRAME_DATAGRAM_LEN:
+    if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_DATAGRAM_FRAME_OVERHEAD + 1) {
+      return;
+    }
+    p = write_datagram_frame(p, &fr->datagram);
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  *p++ = ',';
+
+  qlog->buf.last = p;
+}
+
+void ngtcp2_qlog_pkt_received_start(ngtcp2_qlog *qlog) {
+  qlog_pkt_write_start(qlog, /* sent = */ 0);
+}
+
+void ngtcp2_qlog_pkt_received_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd,
+                                  size_t pktlen) {
+  qlog_pkt_write_end(qlog, hd, pktlen);
+}
+
+void ngtcp2_qlog_pkt_sent_start(ngtcp2_qlog *qlog) {
+  qlog_pkt_write_start(qlog, /* sent = */ 1);
+}
+
+void ngtcp2_qlog_pkt_sent_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd,
+                              size_t pktlen) {
+  qlog_pkt_write_end(qlog, hd, pktlen);
+}
+
+void ngtcp2_qlog_parameters_set_transport_params(
+  ngtcp2_qlog *qlog, const ngtcp2_transport_params *params, int server,
+  ngtcp2_qlog_side side) {
+  uint8_t buf[1024];
+  uint8_t *p = buf;
+  const ngtcp2_preferred_addr *paddr;
+  const ngtcp2_sockaddr_in *sa_in;
+  const ngtcp2_sockaddr_in6 *sa_in6;
+
+  if (!qlog->write) {
+    return;
+  }
+
+  *p++ = '\x1e';
+  *p++ = '{';
+  p = qlog_write_time(qlog, p);
+  p = write_verbatim(
+    p, ",\"name\":\"transport:parameters_set\",\"data\":{\"owner\":");
+
+  if (side == NGTCP2_QLOG_SIDE_LOCAL) {
+    p = write_string(p, "local");
+  } else {
+    p = write_string(p, "remote");
+  }
+
+  *p++ = ',';
+  p = write_pair_cid(p, "initial_source_connection_id", &params->initial_scid);
+  *p++ = ',';
+  if (side == (server ? NGTCP2_QLOG_SIDE_LOCAL : NGTCP2_QLOG_SIDE_REMOTE)) {
+    p = write_pair_cid(p, "original_destination_connection_id",
+                       &params->original_dcid);
+    *p++ = ',';
+  }
+  if (params->retry_scid_present) {
+    p = write_pair_cid(p, "retry_source_connection_id", &params->retry_scid);
+    *p++ = ',';
+  }
+  if (params->stateless_reset_token_present) {
+    p = write_verbatim(p, "\"stateless_reset_token\":{");
+    p = write_pair_hex(p, "data", params->stateless_reset_token,
+                       sizeof(params->stateless_reset_token));
+    *p++ = '}';
+    *p++ = ',';
+  }
+  p = write_pair_bool(p, "disable_active_migration",
+                      params->disable_active_migration);
+  *p++ = ',';
+  p = write_pair_duration(p, "max_idle_timeout", params->max_idle_timeout);
+  *p++ = ',';
+  p =
+    write_pair_number(p, "max_udp_payload_size", params->max_udp_payload_size);
+  *p++ = ',';
+  p = write_pair_number(p, "ack_delay_exponent", params->ack_delay_exponent);
+  *p++ = ',';
+  p = write_pair_duration(p, "max_ack_delay", params->max_ack_delay);
+  *p++ = ',';
+  p = write_pair_number(p, "active_connection_id_limit",
+                        params->active_connection_id_limit);
+  *p++ = ',';
+  p = write_pair_number(p, "initial_max_data", params->initial_max_data);
+  *p++ = ',';
+  p = write_pair_number(p, "initial_max_stream_data_bidi_local",
+                        params->initial_max_stream_data_bidi_local);
+  *p++ = ',';
+  p = write_pair_number(p, "initial_max_stream_data_bidi_remote",
+                        params->initial_max_stream_data_bidi_remote);
+  *p++ = ',';
+  p = write_pair_number(p, "initial_max_stream_data_uni",
+                        params->initial_max_stream_data_uni);
+  *p++ = ',';
+  p = write_pair_number(p, "initial_max_streams_bidi",
+                        params->initial_max_streams_bidi);
+  *p++ = ',';
+  p = write_pair_number(p, "initial_max_streams_uni",
+                        params->initial_max_streams_uni);
+  if (params->preferred_addr_present) {
+    *p++ = ',';
+    paddr = &params->preferred_addr;
+    p = write_string(p, "preferred_address");
+    *p++ = ':';
+    *p++ = '{';
+
+    if (paddr->ipv4_present) {
+      sa_in = &paddr->ipv4;
+
+      p = write_pair_hex(p, "ip_v4", (const uint8_t *)&sa_in->sin_addr,
+                         sizeof(sa_in->sin_addr));
+      *p++ = ',';
+      p = write_pair_number(p, "port_v4", ngtcp2_ntohs(sa_in->sin_port));
+      *p++ = ',';
+    }
+
+    if (paddr->ipv6_present) {
+      sa_in6 = &paddr->ipv6;
+
+      p = write_pair_hex(p, "ip_v6", (const uint8_t *)&sa_in6->sin6_addr,
+                         sizeof(sa_in6->sin6_addr));
+      *p++ = ',';
+      p = write_pair_number(p, "port_v6", ngtcp2_ntohs(sa_in6->sin6_port));
+      *p++ = ',';
+    }
+
+    p = write_pair_cid(p, "connection_id", &paddr->cid);
+    p = write_verbatim(p, ",\"stateless_reset_token\":{");
+    p = write_pair_hex(p, "data", paddr->stateless_reset_token,
+                       sizeof(paddr->stateless_reset_token));
+    *p++ = '}';
+    *p++ = '}';
+  }
+  *p++ = ',';
+  p = write_pair_number(p, "max_datagram_frame_size",
+                        params->max_datagram_frame_size);
+  *p++ = ',';
+  p = write_pair_bool(p, "grease_quic_bit", params->grease_quic_bit);
+  p = write_verbatim(p, "}}\n");
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf,
+              (size_t)(p - buf));
+}
+
+void ngtcp2_qlog_metrics_updated(ngtcp2_qlog *qlog,
+                                 const ngtcp2_conn_stat *cstat) {
+  uint8_t buf[1024];
+  uint8_t *p = buf;
+
+  if (!qlog->write) {
+    return;
+  }
+
+  *p++ = '\x1e';
+  *p++ = '{';
+  p = qlog_write_time(qlog, p);
+  p = write_verbatim(p, ",\"name\":\"recovery:metrics_updated\",\"data\":{");
+
+  if (cstat->min_rtt != UINT64_MAX) {
+    p = write_pair_duration(p, "min_rtt", cstat->min_rtt);
+    *p++ = ',';
+  }
+  p = write_pair_duration(p, "smoothed_rtt", cstat->smoothed_rtt);
+  *p++ = ',';
+  p = write_pair_duration(p, "latest_rtt", cstat->latest_rtt);
+  *p++ = ',';
+  p = write_pair_duration(p, "rtt_variance", cstat->rttvar);
+  *p++ = ',';
+  p = write_pair_number(p, "pto_count", cstat->pto_count);
+  *p++ = ',';
+  p = write_pair_number(p, "congestion_window", cstat->cwnd);
+  *p++ = ',';
+  p = write_pair_number(p, "bytes_in_flight", cstat->bytes_in_flight);
+  if (cstat->ssthresh != UINT64_MAX) {
+    *p++ = ',';
+    p = write_pair_number(p, "ssthresh", cstat->ssthresh);
+  }
+
+  p = write_verbatim(p, "}}\n");
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf,
+              (size_t)(p - buf));
+}
+
+void ngtcp2_qlog_pkt_lost(ngtcp2_qlog *qlog, ngtcp2_rtb_entry *ent) {
+  uint8_t buf[256];
+  uint8_t *p = buf;
+  ngtcp2_pkt_hd hd = {0};
+
+  if (!qlog->write) {
+    return;
+  }
+
+  *p++ = '\x1e';
+  *p++ = '{';
+  p = qlog_write_time(qlog, p);
+  p = write_verbatim(
+    p, ",\"name\":\"recovery:packet_lost\",\"data\":{\"header\":");
+
+  hd.type = ent->hd.type;
+  hd.flags = ent->hd.flags;
+  hd.pkt_num = ent->hd.pkt_num;
+
+  p = write_pkt_hd(p, &hd);
+  p = write_verbatim(p, "}}\n");
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf,
+              (size_t)(p - buf));
+}
+
+void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd,
+                                    const ngtcp2_pkt_retry *retry) {
+  uint8_t rawbuf[1024];
+  ngtcp2_buf buf;
+
+  if (!qlog->write) {
+    return;
+  }
+
+  ngtcp2_buf_init(&buf, rawbuf, sizeof(rawbuf));
+
+  *buf.last++ = '\x1e';
+  *buf.last++ = '{';
+  buf.last = qlog_write_time(qlog, buf.last);
+  buf.last = write_verbatim(
+    buf.last, ",\"name\":\"transport:packet_received\",\"data\":{\"header\":");
+
+  if (ngtcp2_buf_left(&buf) < NGTCP2_QLOG_PKT_HD_OVERHEAD + hd->tokenlen * 2 +
+                                sizeof(",\"retry_token\":{\"data\":\"\"}}}\n") -
+                                1 + retry->tokenlen * 2) {
+    return;
+  }
+
+  buf.last = write_pkt_hd(buf.last, hd);
+  buf.last = write_verbatim(buf.last, ",\"retry_token\":{");
+  buf.last = write_pair_hex(buf.last, "data", retry->token, retry->tokenlen);
+  buf.last = write_verbatim(buf.last, "}}}\n");
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf.pos,
+              ngtcp2_buf_len(&buf));
+}
+
+void ngtcp2_qlog_stateless_reset_pkt_received(
+  ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset *sr) {
+  uint8_t buf[256];
+  uint8_t *p = buf;
+  ngtcp2_pkt_hd hd = {0};
+
+  if (!qlog->write) {
+    return;
+  }
+
+  hd.type = NGTCP2_PKT_STATELESS_RESET;
+
+  *p++ = '\x1e';
+  *p++ = '{';
+  p = qlog_write_time(qlog, p);
+  p = write_verbatim(
+    p, ",\"name\":\"transport:packet_received\",\"data\":{\"header\":");
+  p = write_pkt_hd(p, &hd);
+  *p++ = ',';
+  p = write_pair_hex(p, "stateless_reset_token", sr->stateless_reset_token,
+                     NGTCP2_STATELESS_RESET_TOKENLEN);
+  p = write_verbatim(p, "}}\n");
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf,
+              (size_t)(p - buf));
+}
+
+void ngtcp2_qlog_version_negotiation_pkt_received(ngtcp2_qlog *qlog,
+                                                  const ngtcp2_pkt_hd *hd,
+                                                  const uint32_t *sv,
+                                                  size_t nsv) {
+  uint8_t rawbuf[512];
+  ngtcp2_buf buf;
+  size_t i;
+  uint32_t v;
+
+  if (!qlog->write) {
+    return;
+  }
+
+  ngtcp2_buf_init(&buf, rawbuf, sizeof(rawbuf));
+
+  *buf.last++ = '\x1e';
+  *buf.last++ = '{';
+  buf.last = qlog_write_time(qlog, buf.last);
+  buf.last = write_verbatim(
+    buf.last, ",\"name\":\"transport:packet_received\",\"data\":{\"header\":");
+  buf.last = write_pkt_hd(buf.last, hd);
+  buf.last = write_verbatim(buf.last, ",\"supported_versions\":[");
+
+  if (nsv) {
+    if (ngtcp2_buf_left(&buf) <
+        (sizeof("\"xxxxxxxx\",") - 1) * nsv - 1 + sizeof("]}}\n") - 1) {
+      return;
+    }
+
+    v = ngtcp2_htonl(sv[0]);
+    buf.last = write_hex(buf.last, (const uint8_t *)&v, sizeof(v));
+
+    for (i = 1; i < nsv; ++i) {
+      *buf.last++ = ',';
+      v = ngtcp2_htonl(sv[i]);
+      buf.last = write_hex(buf.last, (const uint8_t *)&v, sizeof(v));
+    }
+  }
+
+  buf.last = write_verbatim(buf.last, "]}}\n");
+
+  qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf.pos,
+              ngtcp2_buf_len(&buf));
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_qlog.h b/third_party/ngtcp2/lib/ngtcp2_qlog.h
new file mode 100644 (file)
index 0000000..d2a5f10
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_QLOG_H
+#define NGTCP2_QLOG_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_cc.h"
+#include "ngtcp2_buf.h"
+#include "ngtcp2_rtb.h"
+
+/* NGTCP2_QLOG_BUFLEN is the length of heap allocated buffer for
+   qlog. */
+#define NGTCP2_QLOG_BUFLEN 4096
+
+typedef enum ngtcp2_qlog_side {
+  NGTCP2_QLOG_SIDE_LOCAL,
+  NGTCP2_QLOG_SIDE_REMOTE,
+} ngtcp2_qlog_side;
+
+typedef struct ngtcp2_qlog {
+  /* write is a callback function to write qlog. */
+  ngtcp2_qlog_write write;
+  /* ts is the initial timestamp */
+  ngtcp2_tstamp ts;
+  /* last_ts is the timestamp observed last time. */
+  ngtcp2_tstamp last_ts;
+  /* buf is a heap allocated buffer to write exclusively
+     packet_received and packet_sent. */
+  ngtcp2_buf buf;
+  /* user_data is an opaque pointer which is passed to write
+     callback. */
+  void *user_data;
+} ngtcp2_qlog;
+
+/*
+ * ngtcp2_qlog_init initializes |qlog|.
+ */
+void ngtcp2_qlog_init(ngtcp2_qlog *qlog, ngtcp2_qlog_write write,
+                      ngtcp2_tstamp ts, void *user_data);
+
+/*
+ * ngtcp2_qlog_start writes qlog preamble.
+ */
+void ngtcp2_qlog_start(ngtcp2_qlog *qlog, const ngtcp2_cid *odcid, int server);
+
+/*
+ * ngtcp2_qlog_end writes closing part of qlog.
+ */
+void ngtcp2_qlog_end(ngtcp2_qlog *qlog);
+
+/*
+ * ngtcp2_qlog_write_frame writes |fr| to qlog->buf.
+ * ngtcp2_qlog_pkt_received_start or ngtcp2_qlog_pkt_sent_start must
+ * be called before calling this function.
+ */
+void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr);
+
+/*
+ * ngtcp2_qlog_pkt_received_start starts to write packet_received
+ * event.  It initializes qlog->buf.  It writes qlog to qlog->buf.
+ * ngtcp2_qlog_pkt_received_end will flush the content of qlog->buf to
+ * write callback.
+ */
+void ngtcp2_qlog_pkt_received_start(ngtcp2_qlog *qlog);
+
+/*
+ * ngtcp2_qlog_pkt_received_end ends packet_received event and sends
+ * the content of qlog->buf to qlog->write callback.
+ */
+void ngtcp2_qlog_pkt_received_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd,
+                                  size_t pktlen);
+
+/*
+ * ngtcp2_qlog_pkt_sent_start starts to write packet_sent event.  It
+ * initializes qlog->buf.  It writes qlog to qlog->buf.
+ * ngtcp2_qlog_pkt_sent_end will flush the content of qlog->buf to
+ * write callback.
+ */
+void ngtcp2_qlog_pkt_sent_start(ngtcp2_qlog *qlog);
+
+/*
+ * ngtcp2_qlog_pkt_sent_end ends packet_sent event and sends the
+ * content of qlog->buf to qlog->write callback.
+ */
+void ngtcp2_qlog_pkt_sent_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd,
+                              size_t pktlen);
+
+/*
+ * ngtcp2_qlog_parameters_set_transport_params writes |params| to qlog
+ * as parameters_set event.  |server| is nonzero if the local endpoint
+ * is server.  If |local| is nonzero, it is "owner" field becomes
+ * "local", otherwise "remote".
+ */
+void ngtcp2_qlog_parameters_set_transport_params(
+  ngtcp2_qlog *qlog, const ngtcp2_transport_params *params, int server,
+  ngtcp2_qlog_side side);
+
+/*
+ * ngtcp2_qlog_metrics_updated writes metrics_updated event of
+ * recovery category.
+ */
+void ngtcp2_qlog_metrics_updated(ngtcp2_qlog *qlog,
+                                 const ngtcp2_conn_stat *cstat);
+
+/*
+ * ngtcp2_qlog_pkt_lost writes packet_lost event.
+ */
+void ngtcp2_qlog_pkt_lost(ngtcp2_qlog *qlog, ngtcp2_rtb_entry *ent);
+
+/*
+ * ngtcp2_qlog_retry_pkt_received writes packet_received event for a
+ * received Retry packet.
+ */
+void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd,
+                                    const ngtcp2_pkt_retry *retry);
+
+/*
+ * ngtcp2_qlog_stateless_reset_pkt_received writes packet_received
+ * event for a received Stateless Reset packet.
+ */
+void ngtcp2_qlog_stateless_reset_pkt_received(
+  ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset *sr);
+
+/*
+ * ngtcp2_qlog_version_negotiation_pkt_received writes packet_received
+ * event for a received Version Negotiation packet.
+ */
+void ngtcp2_qlog_version_negotiation_pkt_received(ngtcp2_qlog *qlog,
+                                                  const ngtcp2_pkt_hd *hd,
+                                                  const uint32_t *sv,
+                                                  size_t nsv);
+
+#endif /* !defined(NGTCP2_QLOG_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_range.c b/third_party/ngtcp2/lib/ngtcp2_range.c
new file mode 100644 (file)
index 0000000..e898915
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_range.h"
+#include "ngtcp2_macro.h"
+
+void ngtcp2_range_init(ngtcp2_range *r, uint64_t begin, uint64_t end) {
+  r->begin = begin;
+  r->end = end;
+}
+
+ngtcp2_range ngtcp2_range_intersect(const ngtcp2_range *a,
+                                    const ngtcp2_range *b) {
+  ngtcp2_range r = {0};
+  uint64_t begin = ngtcp2_max_uint64(a->begin, b->begin);
+  uint64_t end = ngtcp2_min_uint64(a->end, b->end);
+
+  if (begin < end) {
+    ngtcp2_range_init(&r, begin, end);
+  }
+
+  return r;
+}
+
+uint64_t ngtcp2_range_len(const ngtcp2_range *r) { return r->end - r->begin; }
+
+int ngtcp2_range_eq(const ngtcp2_range *a, const ngtcp2_range *b) {
+  return a->begin == b->begin && a->end == b->end;
+}
+
+void ngtcp2_range_cut(ngtcp2_range *left, ngtcp2_range *right,
+                      const ngtcp2_range *a, const ngtcp2_range *b) {
+  /* Assume that b is included in a */
+  left->begin = a->begin;
+  left->end = b->begin;
+  right->begin = b->end;
+  right->end = a->end;
+}
+
+int ngtcp2_range_not_after(const ngtcp2_range *a, const ngtcp2_range *b) {
+  return a->end <= b->end;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_range.h b/third_party/ngtcp2/lib/ngtcp2_range.h
new file mode 100644 (file)
index 0000000..22cd295
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_RANGE_H
+#define NGTCP2_RANGE_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/*
+ * ngtcp2_range represents half-closed range [begin, end).
+ */
+typedef struct ngtcp2_range {
+  uint64_t begin;
+  uint64_t end;
+} ngtcp2_range;
+
+/*
+ * ngtcp2_range_init initializes |r| with the range [|begin|, |end|).
+ */
+void ngtcp2_range_init(ngtcp2_range *r, uint64_t begin, uint64_t end);
+
+/*
+ * ngtcp2_range_intersect returns the intersection of |a| and |b|.  If
+ * they do not overlap, it returns empty range.
+ */
+ngtcp2_range ngtcp2_range_intersect(const ngtcp2_range *a,
+                                    const ngtcp2_range *b);
+
+/*
+ * ngtcp2_range_len returns the length of |r|.
+ */
+uint64_t ngtcp2_range_len(const ngtcp2_range *r);
+
+/*
+ * ngtcp2_range_eq returns nonzero if |a| equals |b|, such that
+ * a->begin == b->begin and a->end == b->end hold.
+ */
+int ngtcp2_range_eq(const ngtcp2_range *a, const ngtcp2_range *b);
+
+/*
+ * ngtcp2_range_cut returns the left and right range after removing
+ * |b| from |a|.  This function assumes that |a| completely includes
+ * |b|.  In other words, a->begin <= b->begin and b->end <= a->end
+ * hold.
+ */
+void ngtcp2_range_cut(ngtcp2_range *left, ngtcp2_range *right,
+                      const ngtcp2_range *a, const ngtcp2_range *b);
+
+/*
+ * ngtcp2_range_not_after returns nonzero if the right edge of |a|
+ * does not go beyond of the right edge of |b|.
+ */
+int ngtcp2_range_not_after(const ngtcp2_range *a, const ngtcp2_range *b);
+
+#endif /* !defined(NGTCP2_RANGE_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_rcvry.h b/third_party/ngtcp2/lib/ngtcp2_rcvry.h
new file mode 100644 (file)
index 0000000..e632106
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_RCVRY_H
+#define NGTCP2_RCVRY_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/* NGTCP2_PKT_THRESHOLD is kPacketThreshold described in RFC 9002. */
+#define NGTCP2_PKT_THRESHOLD 3
+
+/* NGTCP2_GRANULARITY is kGranularity described in RFC 9002. */
+#define NGTCP2_GRANULARITY NGTCP2_MILLISECONDS
+
+#endif /* !defined(NGTCP2_RCVRY_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_ringbuf.c b/third_party/ngtcp2/lib/ngtcp2_ringbuf.c
new file mode 100644 (file)
index 0000000..353afca
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_ringbuf.h"
+
+#include <assert.h>
+#ifdef WIN32
+#  include <intrin.h>
+#endif /* defined(WIN32) */
+
+#include "ngtcp2_macro.h"
+
+#ifndef NDEBUG
+static int ispow2(size_t n) {
+#  if defined(_MSC_VER) && !defined(__clang__) &&                              \
+    (defined(_M_ARM) || (defined(_M_ARM64) && _MSC_VER < 1941))
+  return n && !(n & (n - 1));
+#  elif defined(WIN32)
+  return 1 == __popcnt((unsigned int)n);
+#  else  /* !((defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || \
+            (defined(_M_ARM64) && _MSC_VER < 1941))) || defined(WIN32)) */
+  return 1 == __builtin_popcount((unsigned int)n);
+#  endif /* !((defined(_MSC_VER) && !defined(__clang__) && (defined(_M_ARM) || \
+            (defined(_M_ARM64) && _MSC_VER < 1941))) || defined(WIN32)) */
+}
+#endif /* !defined(NDEBUG) */
+
+int ngtcp2_ringbuf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size,
+                        const ngtcp2_mem *mem) {
+  uint8_t *buf = ngtcp2_mem_malloc(mem, nmemb * size);
+
+  if (buf == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_ringbuf_buf_init(rb, nmemb, size, buf, mem);
+
+  return 0;
+}
+
+void ngtcp2_ringbuf_buf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size,
+                             uint8_t *buf, const ngtcp2_mem *mem) {
+  assert(ispow2(nmemb));
+
+  rb->buf = buf;
+  rb->mem = mem;
+  rb->mask = nmemb - 1;
+  rb->size = size;
+  rb->first = 0;
+  rb->len = 0;
+}
+
+void ngtcp2_ringbuf_free(ngtcp2_ringbuf *rb) {
+  if (rb == NULL) {
+    return;
+  }
+
+  ngtcp2_mem_free(rb->mem, rb->buf);
+}
+
+void *ngtcp2_ringbuf_push_front(ngtcp2_ringbuf *rb) {
+  rb->first = (rb->first - 1) & rb->mask;
+  if (rb->len < rb->mask + 1) {
+    ++rb->len;
+  }
+
+  return (void *)&rb->buf[rb->first * rb->size];
+}
+
+void *ngtcp2_ringbuf_push_back(ngtcp2_ringbuf *rb) {
+  size_t offset = (rb->first + rb->len) & rb->mask;
+
+  if (rb->len == rb->mask + 1) {
+    rb->first = (rb->first + 1) & rb->mask;
+  } else {
+    ++rb->len;
+  }
+
+  return (void *)&rb->buf[offset * rb->size];
+}
+
+void ngtcp2_ringbuf_pop_front(ngtcp2_ringbuf *rb) {
+  rb->first = (rb->first + 1) & rb->mask;
+  --rb->len;
+}
+
+void ngtcp2_ringbuf_pop_back(ngtcp2_ringbuf *rb) {
+  assert(rb->len);
+  --rb->len;
+}
+
+void ngtcp2_ringbuf_resize(ngtcp2_ringbuf *rb, size_t len) {
+  assert(len <= rb->mask + 1);
+  rb->len = len;
+}
+
+void *ngtcp2_ringbuf_get(const ngtcp2_ringbuf *rb, size_t offset) {
+  assert(offset < rb->len);
+  offset = (rb->first + offset) & rb->mask;
+
+  return &rb->buf[offset * rb->size];
+}
+
+int ngtcp2_ringbuf_full(const ngtcp2_ringbuf *rb) {
+  return rb->len == rb->mask + 1;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_ringbuf.h b/third_party/ngtcp2/lib/ngtcp2_ringbuf.h
new file mode 100644 (file)
index 0000000..d490524
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_RINGBUF_H
+#define NGTCP2_RINGBUF_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+
+typedef struct ngtcp2_ringbuf {
+  /* buf points to the underlying buffer. */
+  uint8_t *buf;
+  const ngtcp2_mem *mem;
+  /* mask is the bit mask to cover all bits for the maximum number of
+     elements.  The maximum number of elements is mask + 1. */
+  size_t mask;
+  /* size is the size of each element. */
+  size_t size;
+  /* first is the offset to the first element. */
+  size_t first;
+  /* len is the number of elements actually stored. */
+  size_t len;
+} ngtcp2_ringbuf;
+
+/*
+ * ngtcp2_ringbuf_init initializes |rb|.  |nmemb| is the number of
+ * elements that can be stored in this buffer.  |size| is the size of
+ * each element.  |nmemb| must be power of 2.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_ringbuf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size,
+                        const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_ringbuf_buf_init initializes |rb| with given buffer and
+ * size.  Same restrictions are applied as ngtcp2_ringbuf_init.
+ */
+void ngtcp2_ringbuf_buf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size,
+                             uint8_t *buf, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_ringbuf_free frees resources allocated for |rb|.  This
+ * function does not free the memory pointed by |rb|.
+ */
+void ngtcp2_ringbuf_free(ngtcp2_ringbuf *rb);
+
+/* ngtcp2_ringbuf_push_front moves the offset to the first element in
+   the buffer backward, and returns the pointer to the element.
+   Caller can store data to the buffer pointed by the returned
+   pointer.  If this action exceeds the capacity of the ring buffer,
+   this function returns the pointer to the last element, and rb->len
+   remains unchanged. */
+void *ngtcp2_ringbuf_push_front(ngtcp2_ringbuf *rb);
+
+/* ngtcp2_ringbuf_push_back moves the offset to the last element in
+   the buffer forward, and returns the pointer to the element.  Caller
+   can store data to the buffer pointed by the returned pointer.  If
+   this action exceeds the capacity of the ring buffer, this function
+   returns the pointer to the first element, and rb->len remains
+   unchanged. */
+void *ngtcp2_ringbuf_push_back(ngtcp2_ringbuf *rb);
+
+/*
+ * ngtcp2_ringbuf_pop_front removes first element in |rb|.
+ */
+void ngtcp2_ringbuf_pop_front(ngtcp2_ringbuf *rb);
+
+/*
+ * ngtcp2_ringbuf_pop_back removes the last element in |rb|.
+ */
+void ngtcp2_ringbuf_pop_back(ngtcp2_ringbuf *rb);
+
+/* ngtcp2_ringbuf_resize changes the number of elements stored.  This
+   does not change the capacity of the underlying buffer. */
+void ngtcp2_ringbuf_resize(ngtcp2_ringbuf *rb, size_t len);
+
+/* ngtcp2_ringbuf_get returns the pointer to the element at
+   |offset|. */
+void *ngtcp2_ringbuf_get(const ngtcp2_ringbuf *rb, size_t offset);
+
+/* ngtcp2_ringbuf_len returns the number of elements stored. */
+#define ngtcp2_ringbuf_len(RB) ((RB)->len)
+
+/* ngtcp2_ringbuf_full returns nonzero if |rb| is full. */
+int ngtcp2_ringbuf_full(const ngtcp2_ringbuf *rb);
+
+/* ngtcp2_static_ringbuf_def defines ngtcp2_ringbuf struct wrapper
+   which uses a statically allocated buffer.  ngtcp2_ringbuf_free
+   should never be called for rb field. */
+#define ngtcp2_static_ringbuf_def(NAME, NMEMB, SIZE)                           \
+  typedef struct ngtcp2_static_ringbuf_##NAME {                                \
+    ngtcp2_ringbuf rb;                                                         \
+    uint8_t buf[(NMEMB) * (SIZE)];                                             \
+  } ngtcp2_static_ringbuf_##NAME;                                              \
+                                                                               \
+  static inline void ngtcp2_static_ringbuf_##NAME##_init(                      \
+    ngtcp2_static_ringbuf_##NAME *srb) {                                       \
+    ngtcp2_ringbuf_buf_init(&srb->rb, (NMEMB), (SIZE), srb->buf, NULL);        \
+  }
+
+#endif /* !defined(NGTCP2_RINGBUF_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_rob.c b/third_party/ngtcp2/lib/ngtcp2_rob.c
new file mode 100644 (file)
index 0000000..853f1d6
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_rob.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_macro.h"
+
+int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end,
+                       const ngtcp2_mem *mem) {
+  *pg = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_rob_gap));
+  if (*pg == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  (*pg)->range.begin = begin;
+  (*pg)->range.end = end;
+
+  return 0;
+}
+
+void ngtcp2_rob_gap_del(ngtcp2_rob_gap *g, const ngtcp2_mem *mem) {
+  ngtcp2_mem_free(mem, g);
+}
+
+int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk,
+                        const ngtcp2_mem *mem) {
+  *pd = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_rob_data) + chunk);
+  if (*pd == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  (*pd)->range.begin = offset;
+  (*pd)->range.end = offset + chunk;
+  (*pd)->begin = (uint8_t *)(*pd) + sizeof(ngtcp2_rob_data);
+
+  return 0;
+}
+
+void ngtcp2_rob_data_del(ngtcp2_rob_data *d, const ngtcp2_mem *mem) {
+  ngtcp2_mem_free(mem, d);
+}
+
+int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem) {
+  int rv;
+  ngtcp2_rob_gap *g;
+
+  ngtcp2_ksl_init(&rob->gapksl, ngtcp2_ksl_range_compar,
+                  ngtcp2_ksl_range_search, sizeof(ngtcp2_range), mem);
+
+  rv = ngtcp2_rob_gap_new(&g, 0, UINT64_MAX, mem);
+  if (rv != 0) {
+    goto fail_rob_gap_new;
+  }
+
+  rv = ngtcp2_ksl_insert(&rob->gapksl, NULL, &g->range, g);
+  if (rv != 0) {
+    goto fail_gapksl_ksl_insert;
+  }
+
+  ngtcp2_ksl_init(&rob->dataksl, ngtcp2_ksl_range_compar,
+                  ngtcp2_ksl_range_search, sizeof(ngtcp2_range), mem);
+
+  rob->chunk = chunk;
+  rob->mem = mem;
+
+  return 0;
+
+fail_gapksl_ksl_insert:
+  ngtcp2_rob_gap_del(g, mem);
+fail_rob_gap_new:
+  ngtcp2_ksl_free(&rob->gapksl);
+  return rv;
+}
+
+void ngtcp2_rob_free(ngtcp2_rob *rob) {
+  ngtcp2_ksl_it it;
+
+  if (rob == NULL) {
+    return;
+  }
+
+  for (it = ngtcp2_ksl_begin(&rob->dataksl); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    ngtcp2_rob_data_del(ngtcp2_ksl_it_get(&it), rob->mem);
+  }
+
+  for (it = ngtcp2_ksl_begin(&rob->gapksl); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    ngtcp2_rob_gap_del(ngtcp2_ksl_it_get(&it), rob->mem);
+  }
+
+  ngtcp2_ksl_free(&rob->dataksl);
+  ngtcp2_ksl_free(&rob->gapksl);
+}
+
+static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data,
+                          size_t len) {
+  size_t n;
+  int rv;
+  ngtcp2_rob_data *d;
+  ngtcp2_range range = {
+    .begin = offset,
+    .end = offset + len,
+  };
+  ngtcp2_ksl_it it;
+
+  for (it = ngtcp2_ksl_lower_bound_search(&rob->dataksl, &range,
+                                          ngtcp2_ksl_range_exclusive_search);
+       len; ngtcp2_ksl_it_next(&it)) {
+    if (ngtcp2_ksl_it_end(&it)) {
+      d = NULL;
+    } else {
+      d = ngtcp2_ksl_it_get(&it);
+    }
+
+    if (d == NULL || offset < d->range.begin) {
+      rv = ngtcp2_rob_data_new(&d, (offset / rob->chunk) * rob->chunk,
+                               rob->chunk, rob->mem);
+      if (rv != 0) {
+        return rv;
+      }
+
+      rv = ngtcp2_ksl_insert(&rob->dataksl, &it, &d->range, d);
+      if (rv != 0) {
+        ngtcp2_rob_data_del(d, rob->mem);
+        return rv;
+      }
+    }
+
+    n = (size_t)ngtcp2_min_uint64((uint64_t)len,
+                                  d->range.begin + rob->chunk - offset);
+    memcpy(d->begin + (offset - d->range.begin), data, n);
+    offset += n;
+    data += n;
+    len -= n;
+  }
+
+  return 0;
+}
+
+int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data,
+                    size_t datalen) {
+  int rv;
+  ngtcp2_rob_gap *g;
+  ngtcp2_range m, l, r;
+  ngtcp2_range q = {
+    .begin = offset,
+    .end = offset + datalen,
+  };
+  ngtcp2_ksl_it it;
+
+  it = ngtcp2_ksl_lower_bound_search(&rob->gapksl, &q,
+                                     ngtcp2_ksl_range_exclusive_search);
+
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    g = ngtcp2_ksl_it_get(&it);
+
+    m = ngtcp2_range_intersect(&q, &g->range);
+    if (!ngtcp2_range_len(&m)) {
+      break;
+    }
+
+    if (ngtcp2_range_eq(&g->range, &m)) {
+      ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g->range);
+      ngtcp2_rob_gap_del(g, rob->mem);
+
+      rv = rob_write_data(rob, m.begin, data + (m.begin - offset),
+                          (size_t)ngtcp2_range_len(&m));
+      if (rv != 0) {
+        return rv;
+      }
+
+      continue;
+    }
+
+    ngtcp2_range_cut(&l, &r, &g->range, &m);
+
+    if (ngtcp2_range_len(&l)) {
+      ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &l);
+      g->range = l;
+
+      if (ngtcp2_range_len(&r)) {
+        ngtcp2_rob_gap *ng;
+
+        rv = ngtcp2_rob_gap_new(&ng, r.begin, r.end, rob->mem);
+        if (rv != 0) {
+          return rv;
+        }
+
+        rv = ngtcp2_ksl_insert(&rob->gapksl, &it, &ng->range, ng);
+        if (rv != 0) {
+          ngtcp2_rob_gap_del(ng, rob->mem);
+          return rv;
+        }
+      }
+    } else if (ngtcp2_range_len(&r)) {
+      ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &r);
+      g->range = r;
+    }
+
+    rv = rob_write_data(rob, m.begin, data + (m.begin - offset),
+                        (size_t)ngtcp2_range_len(&m));
+    if (rv != 0) {
+      return rv;
+    }
+
+    ngtcp2_ksl_it_next(&it);
+  }
+
+  return 0;
+}
+
+void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) {
+  ngtcp2_rob_gap *g;
+  ngtcp2_rob_data *d;
+  ngtcp2_ksl_it it;
+
+  it = ngtcp2_ksl_begin(&rob->gapksl);
+
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    g = ngtcp2_ksl_it_get(&it);
+    if (offset <= g->range.begin) {
+      break;
+    }
+
+    if (offset < g->range.end) {
+      ngtcp2_range r = {offset, g->range.end};
+
+      ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &r);
+      g->range.begin = offset;
+
+      break;
+    }
+
+    ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g->range);
+    ngtcp2_rob_gap_del(g, rob->mem);
+  }
+
+  it = ngtcp2_ksl_begin(&rob->dataksl);
+
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    d = ngtcp2_ksl_it_get(&it);
+    if (offset < d->range.begin + rob->chunk) {
+      return;
+    }
+
+    ngtcp2_ksl_remove_hint(&rob->dataksl, &it, &it, &d->range);
+    ngtcp2_rob_data_del(d, rob->mem);
+  }
+}
+
+size_t ngtcp2_rob_data_at(const ngtcp2_rob *rob, const uint8_t **pdest,
+                          uint64_t offset) {
+  ngtcp2_rob_gap *g;
+  ngtcp2_rob_data *d;
+  ngtcp2_ksl_it it;
+
+  it = ngtcp2_ksl_begin(&rob->gapksl);
+  if (ngtcp2_ksl_it_end(&it)) {
+    return 0;
+  }
+
+  g = ngtcp2_ksl_it_get(&it);
+
+  if (g->range.begin <= offset) {
+    return 0;
+  }
+
+  it = ngtcp2_ksl_begin(&rob->dataksl);
+  d = ngtcp2_ksl_it_get(&it);
+
+  assert(d);
+  assert(d->range.begin <= offset);
+  assert(offset < d->range.begin + rob->chunk);
+
+  *pdest = d->begin + (offset - d->range.begin);
+
+  return (
+    size_t)(ngtcp2_min_uint64(g->range.begin, d->range.begin + rob->chunk) -
+            offset);
+}
+
+void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len) {
+  ngtcp2_ksl_it it;
+  ngtcp2_rob_data *d;
+
+  it = ngtcp2_ksl_begin(&rob->dataksl);
+  d = ngtcp2_ksl_it_get(&it);
+
+  assert(d);
+
+  if (offset + len < d->range.begin + rob->chunk) {
+    return;
+  }
+
+  ngtcp2_ksl_remove_hint(&rob->dataksl, NULL, &it, &d->range);
+  ngtcp2_rob_data_del(d, rob->mem);
+}
+
+uint64_t ngtcp2_rob_first_gap_offset(const ngtcp2_rob *rob) {
+  ngtcp2_ksl_it it = ngtcp2_ksl_begin(&rob->gapksl);
+  ngtcp2_rob_gap *g;
+
+  if (ngtcp2_ksl_it_end(&it)) {
+    return UINT64_MAX;
+  }
+
+  g = ngtcp2_ksl_it_get(&it);
+
+  return g->range.begin;
+}
+
+int ngtcp2_rob_data_buffered(const ngtcp2_rob *rob) {
+  return ngtcp2_ksl_len(&rob->dataksl) != 0;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_rob.h b/third_party/ngtcp2/lib/ngtcp2_rob.h
new file mode 100644 (file)
index 0000000..d53b516
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_ROB_H
+#define NGTCP2_ROB_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+#include "ngtcp2_range.h"
+#include "ngtcp2_ksl.h"
+
+/*
+ * ngtcp2_rob_gap represents the gap, which is the range of stream
+ * data that is not received yet.
+ */
+typedef struct ngtcp2_rob_gap {
+  /* range is the range of this gap. */
+  ngtcp2_range range;
+} ngtcp2_rob_gap;
+
+/*
+ * ngtcp2_rob_gap_new allocates new ngtcp2_rob_gap object, and assigns
+ * its pointer to |*pg|.  The caller should call ngtcp2_rob_gap_del to
+ * delete it when it is no longer used.  The range of the gap is
+ * [begin, end).  |mem| is custom memory allocator to allocate memory.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end,
+                       const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_rob_gap_del deallocates |g|.  It deallocates the memory
+ * pointed by |g| it self.  |mem| is custom memory allocator to
+ * deallocate memory.
+ */
+void ngtcp2_rob_gap_del(ngtcp2_rob_gap *g, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_rob_data holds the buffered stream data.
+ */
+typedef struct ngtcp2_rob_data {
+  /* range is the range of this data. */
+  ngtcp2_range range;
+  /* begin points to the buffer. */
+  uint8_t *begin;
+} ngtcp2_rob_data;
+
+/*
+ * ngtcp2_rob_data_new allocates new ngtcp2_rob_data object, and
+ * assigns its pointer to |*pd|.  The caller should call
+ * ngtcp2_rob_data_del to delete it when it is no longer used.
+ * |offset| is the stream offset of the first byte of this data.
+ * |chunk| is the size of the buffer.  |offset| must be multiple of
+ * |chunk|.  |mem| is custom memory allocator to allocate memory.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk,
+                        const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_rob_data_del deallocates |d|.  It deallocates the memory
+ * pointed by |d| itself.  |mem| is custom memory allocator to
+ * deallocate memory.
+ */
+void ngtcp2_rob_data_del(ngtcp2_rob_data *d, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_rob is the reorder buffer which reassembles stream data
+ * received in out of order.
+ */
+typedef struct ngtcp2_rob {
+  /* gapksl maintains the range of offset which is not received
+     yet. Initially, its range is [0, UINT64_MAX). */
+  ngtcp2_ksl gapksl;
+  /* dataksl maintains the buffers which store received out-of-order
+     data ordered by stream offset. */
+  ngtcp2_ksl dataksl;
+  /* mem is custom memory allocator */
+  const ngtcp2_mem *mem;
+  /* chunk is the size of each buffer in data field */
+  size_t chunk;
+} ngtcp2_rob;
+
+/*
+ * ngtcp2_rob_init initializes |rob|.  |chunk| is the size of buffer
+ * per chunk.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_rob_free frees resources allocated for |rob|.
+ */
+void ngtcp2_rob_free(ngtcp2_rob *rob);
+
+/*
+ * ngtcp2_rob_push adds new data pointed by |data| of length |datalen|
+ * at the stream offset |offset|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data,
+                    size_t datalen);
+
+/*
+ * ngtcp2_rob_remove_prefix removes gap up to |offset|, exclusive.  It
+ * also removes buffered data if it is completely included in
+ * |offset|.
+ */
+void ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset);
+
+/*
+ * ngtcp2_rob_data_at stores the pointer to the buffer of stream
+ * offset |offset| to |*pdest| if it is available, and returns the
+ * 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.
+ */
+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
+ * |len|.
+ *
+ * |offset| must be the offset given in ngtcp2_rob_data_at.  |len|
+ * must be the return value of ngtcp2_rob_data_at when |offset| is
+ * passed.
+ *
+ * Caller should call this function from offset 0 in non-decreasing
+ * order.
+ */
+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.
+ * If there is no gap, it returns UINT64_MAX.
+ */
+uint64_t ngtcp2_rob_first_gap_offset(const ngtcp2_rob *rob);
+
+/*
+ * ngtcp2_rob_data_buffered returns nonzero if any data is buffered.
+ */
+int ngtcp2_rob_data_buffered(const ngtcp2_rob *rob);
+
+#endif /* !defined(NGTCP2_ROB_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_rst.c b/third_party/ngtcp2/lib/ngtcp2_rst.c
new file mode 100644 (file)
index 0000000..181691f
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_rst.h"
+
+#include <assert.h>
+
+#include "ngtcp2_rtb.h"
+#include "ngtcp2_cc.h"
+#include "ngtcp2_macro.h"
+#include "ngtcp2_conn_stat.h"
+
+void ngtcp2_rs_init(ngtcp2_rs *rs) {
+  rs->interval = UINT64_MAX;
+  rs->delivered = 0;
+  rs->prior_delivered = 0;
+  rs->prior_ts = UINT64_MAX;
+  rs->tx_in_flight = 0;
+  rs->lost = 0;
+  rs->prior_lost = 0;
+  rs->send_elapsed = 0;
+  rs->ack_elapsed = 0;
+  rs->last_end_seq = -1;
+  rs->is_app_limited = 0;
+}
+
+void ngtcp2_rst_init(ngtcp2_rst *rst) {
+  rst->last_seq = -1;
+  ngtcp2_rst_reset(rst);
+}
+
+void ngtcp2_rst_reset(ngtcp2_rst *rst) {
+  ngtcp2_rs_init(&rst->rs);
+  rst->delivered = 0;
+  rst->delivered_ts = 0;
+  rst->first_sent_ts = 0;
+  rst->app_limited = 0;
+  rst->is_cwnd_limited = 0;
+  rst->lost = 0;
+  rst->valid_after_seq = rst->last_seq;
+}
+
+void ngtcp2_rst_on_pkt_sent(ngtcp2_rst *rst, ngtcp2_rtb_entry *ent,
+                            const ngtcp2_conn_stat *cstat) {
+  if (cstat->bytes_in_flight == 0) {
+    rst->first_sent_ts = rst->delivered_ts = ent->ts;
+  }
+  ent->rst.first_sent_ts = rst->first_sent_ts;
+  ent->rst.delivered_ts = rst->delivered_ts;
+  ent->rst.delivered = rst->delivered;
+  ent->rst.is_app_limited = rst->app_limited != 0;
+  ent->rst.tx_in_flight = cstat->bytes_in_flight + ent->pktlen;
+  ent->rst.lost = rst->lost;
+  ent->rst.end_seq = ++rst->last_seq;
+}
+
+void ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat) {
+  ngtcp2_rs *rs = &rst->rs;
+
+  if (rst->app_limited && rst->delivered > rst->app_limited) {
+    rst->app_limited = 0;
+  }
+
+  if (rs->prior_ts == UINT64_MAX) {
+    return;
+  }
+
+  rs->interval = ngtcp2_max_uint64(rs->send_elapsed, rs->ack_elapsed);
+
+  rs->delivered = rst->delivered - rs->prior_delivered;
+  rs->lost = rst->lost - rs->prior_lost;
+
+  if (rs->interval < cstat->min_rtt) {
+    rs->interval = UINT64_MAX;
+    return;
+  }
+
+  if (!rs->interval) {
+    return;
+  }
+
+  cstat->delivery_rate_sec = rs->delivered * NGTCP2_SECONDS / rs->interval;
+}
+
+static int rst_is_newest_pkt(const ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent,
+                             const ngtcp2_rs *rs) {
+  return ent->ts > rst->first_sent_ts ||
+         (ent->ts == rst->first_sent_ts && ent->rst.end_seq > rs->last_end_seq);
+}
+
+void ngtcp2_rst_update_rate_sample(ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent,
+                                   ngtcp2_tstamp ts) {
+  ngtcp2_rs *rs = &rst->rs;
+
+  if (ent->rst.end_seq <= rst->valid_after_seq) {
+    return;
+  }
+
+  rst->delivered += ent->pktlen;
+  rst->delivered_ts = ts;
+
+  if (rs->prior_ts == UINT64_MAX || rst_is_newest_pkt(rst, ent, rs)) {
+    rs->prior_delivered = ent->rst.delivered;
+    rs->prior_ts = ent->rst.delivered_ts;
+    rs->is_app_limited = ent->rst.is_app_limited;
+    rs->send_elapsed = ent->ts - ent->rst.first_sent_ts;
+    rs->ack_elapsed = rst->delivered_ts - ent->rst.delivered_ts;
+    rs->tx_in_flight = ent->rst.tx_in_flight;
+    rs->prior_lost = ent->rst.lost;
+    rs->last_end_seq = ent->rst.end_seq;
+    rst->first_sent_ts = ent->ts;
+  }
+}
+
+void ngtcp2_rst_update_app_limited(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat) {
+  (void)rst;
+  (void)cstat;
+  /* TODO Not implemented */
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_rst.h b/third_party/ngtcp2/lib/ngtcp2_rst.h
new file mode 100644 (file)
index 0000000..c258030
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_RST_H
+#define NGTCP2_RST_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_window_filter.h"
+
+typedef struct ngtcp2_rtb_entry ngtcp2_rtb_entry;
+typedef struct ngtcp2_conn_stat ngtcp2_conn_stat;
+
+/**
+ * @struct
+ *
+ * ngtcp2_rs contains connection state for delivery rate estimation.
+ */
+typedef struct ngtcp2_rs {
+  ngtcp2_duration interval;
+  uint64_t delivered;
+  uint64_t prior_delivered;
+  ngtcp2_tstamp prior_ts;
+  uint64_t tx_in_flight;
+  uint64_t lost;
+  uint64_t prior_lost;
+  ngtcp2_duration send_elapsed;
+  ngtcp2_duration ack_elapsed;
+  int64_t last_end_seq;
+  int is_app_limited;
+} ngtcp2_rs;
+
+void ngtcp2_rs_init(ngtcp2_rs *rs);
+
+/*
+ * ngtcp2_rst implements delivery rate estimation described in
+ * https://ietf-wg-ccwg.github.io/draft-cardwell-ccwg-bbr/draft-cardwell-ccwg-bbr.html
+ */
+typedef struct ngtcp2_rst {
+  ngtcp2_rs rs;
+  uint64_t delivered;
+  ngtcp2_tstamp delivered_ts;
+  ngtcp2_tstamp first_sent_ts;
+  uint64_t app_limited;
+  uint64_t lost;
+  /* last_seq is the sequence number of packets across all packet
+     number spaces.  If we would adopt single packet number sequence
+     across all packet number spaces, we can replace this with a
+     packet number. */
+  int64_t last_seq;
+  /* valid_after_seq is the sequence number, and ignore a packet if
+     the sequence number of the packet is less than or equal to this
+     number. */
+  int64_t valid_after_seq;
+  int is_cwnd_limited;
+} ngtcp2_rst;
+
+void ngtcp2_rst_init(ngtcp2_rst *rst);
+
+void ngtcp2_rst_reset(ngtcp2_rst *rst);
+
+void ngtcp2_rst_on_pkt_sent(ngtcp2_rst *rst, ngtcp2_rtb_entry *ent,
+                            const ngtcp2_conn_stat *cstat);
+void ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat);
+void ngtcp2_rst_update_rate_sample(ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent,
+                                   ngtcp2_tstamp ts);
+void ngtcp2_rst_update_app_limited(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat);
+
+#endif /* !defined(NGTCP2_RST_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_rtb.c b/third_party/ngtcp2/lib/ngtcp2_rtb.c
new file mode 100644 (file)
index 0000000..101dcaa
--- /dev/null
@@ -0,0 +1,1495 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_rtb.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "ngtcp2_macro.h"
+#include "ngtcp2_conn.h"
+#include "ngtcp2_log.h"
+#include "ngtcp2_vec.h"
+#include "ngtcp2_cc.h"
+#include "ngtcp2_rcvry.h"
+#include "ngtcp2_rst.h"
+#include "ngtcp2_unreachable.h"
+#include "ngtcp2_tstamp.h"
+#include "ngtcp2_frame_chain.h"
+
+ngtcp2_objalloc_def(rtb_entry, ngtcp2_rtb_entry, oplent)
+
+static void rtb_entry_init(ngtcp2_rtb_entry *ent, const ngtcp2_pkt_hd *hd,
+                           ngtcp2_frame_chain *frc, ngtcp2_tstamp ts,
+                           size_t pktlen, uint16_t flags) {
+  memset(ent, 0, sizeof(*ent));
+
+  ent->hd.pkt_num = hd->pkt_num;
+  ent->hd.type = hd->type;
+  ent->hd.flags = hd->flags;
+  ent->frc = frc;
+  ent->ts = ts;
+  ent->lost_ts = UINT64_MAX;
+  ent->pktlen = pktlen;
+  ent->flags = flags;
+}
+
+int ngtcp2_rtb_entry_objalloc_new(ngtcp2_rtb_entry **pent,
+                                  const ngtcp2_pkt_hd *hd,
+                                  ngtcp2_frame_chain *frc, ngtcp2_tstamp ts,
+                                  size_t pktlen, uint16_t flags,
+                                  ngtcp2_objalloc *objalloc) {
+  *pent = ngtcp2_objalloc_rtb_entry_get(objalloc);
+  if (*pent == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  rtb_entry_init(*pent, hd, frc, ts, pktlen, flags);
+
+  return 0;
+}
+
+void ngtcp2_rtb_entry_objalloc_del(ngtcp2_rtb_entry *ent,
+                                   ngtcp2_objalloc *objalloc,
+                                   ngtcp2_objalloc *frc_objalloc,
+                                   const ngtcp2_mem *mem) {
+  ngtcp2_frame_chain_list_objalloc_del(ent->frc, frc_objalloc, mem);
+
+  ent->frc = NULL;
+
+  ngtcp2_objalloc_rtb_entry_release(objalloc, ent);
+}
+
+void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_rst *rst, ngtcp2_cc *cc,
+                     int64_t cc_pkt_num, ngtcp2_log *log, ngtcp2_qlog *qlog,
+                     ngtcp2_objalloc *rtb_entry_objalloc,
+                     ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) {
+  rtb->rtb_entry_objalloc = rtb_entry_objalloc;
+  rtb->frc_objalloc = frc_objalloc;
+  ngtcp2_ksl_init(&rtb->ents, ngtcp2_ksl_int64_greater,
+                  ngtcp2_ksl_int64_greater_search, sizeof(int64_t), mem);
+  rtb->rst = rst;
+  rtb->cc = cc;
+  rtb->log = log;
+  rtb->qlog = qlog;
+  rtb->mem = mem;
+  rtb->largest_acked_tx_pkt_num = -1;
+  rtb->num_ack_eliciting = 0;
+  rtb->num_retransmittable = 0;
+  rtb->num_pto_eliciting = 0;
+  rtb->probe_pkt_left = 0;
+  rtb->cc_pkt_num = cc_pkt_num;
+  rtb->cc_bytes_in_flight = 0;
+  rtb->num_lost_pkts = 0;
+  rtb->num_lost_ignore_pkts = 0;
+}
+
+void ngtcp2_rtb_free(ngtcp2_rtb *rtb) {
+  ngtcp2_ksl_it it;
+
+  if (rtb == NULL) {
+    return;
+  }
+
+  it = ngtcp2_ksl_begin(&rtb->ents);
+
+  for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) {
+    ngtcp2_rtb_entry_objalloc_del(ngtcp2_ksl_it_get(&it),
+                                  rtb->rtb_entry_objalloc, rtb->frc_objalloc,
+                                  rtb->mem);
+  }
+
+  ngtcp2_ksl_free(&rtb->ents);
+}
+
+static void rtb_on_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
+                       ngtcp2_conn_stat *cstat) {
+  ngtcp2_rst_on_pkt_sent(rtb->rst, ent, cstat);
+
+  assert(rtb->cc_pkt_num <= ent->hd.pkt_num);
+
+  cstat->bytes_in_flight += ent->pktlen;
+  rtb->cc_bytes_in_flight += ent->pktlen;
+
+  ngtcp2_rst_update_app_limited(rtb->rst, cstat);
+
+  if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
+    ++rtb->num_ack_eliciting;
+  }
+
+  if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) {
+    ++rtb->num_retransmittable;
+  }
+
+  if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) {
+    ++rtb->num_pto_eliciting;
+  }
+}
+
+static size_t rtb_on_remove(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
+                            ngtcp2_conn_stat *cstat) {
+  if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) {
+    assert(rtb->num_lost_pkts);
+    --rtb->num_lost_pkts;
+
+    if (ent->flags &
+        (NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE | NGTCP2_RTB_ENTRY_FLAG_SKIP)) {
+      assert(rtb->num_lost_ignore_pkts);
+      --rtb->num_lost_ignore_pkts;
+    }
+
+    return 0;
+  }
+
+  if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
+    assert(rtb->num_ack_eliciting);
+    --rtb->num_ack_eliciting;
+  }
+
+  if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) &&
+      !(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED)) {
+    assert(rtb->num_retransmittable);
+    --rtb->num_retransmittable;
+  }
+
+  if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) {
+    assert(rtb->num_pto_eliciting);
+    --rtb->num_pto_eliciting;
+  }
+
+  if (rtb->cc_pkt_num <= ent->hd.pkt_num) {
+    assert(cstat->bytes_in_flight >= ent->pktlen);
+    cstat->bytes_in_flight -= ent->pktlen;
+
+    assert(rtb->cc_bytes_in_flight >= ent->pktlen);
+    rtb->cc_bytes_in_flight -= ent->pktlen;
+
+    /* If PMTUD packet is lost, we do not report the lost bytes to the
+       caller in order to ignore loss of PMTUD packet. */
+    if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) {
+      return 0;
+    }
+
+    return ent->pktlen;
+  }
+
+  return 0;
+}
+
+/* NGTCP2_RECLAIM_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_RECLAIM_FLAG_NONE 0x00u
+/* NGTCP2_RECLAIM_FLAG_ON_LOSS indicates that frames are reclaimed
+   because of the packet loss.*/
+#define NGTCP2_RECLAIM_FLAG_ON_LOSS 0x01u
+
+/*
+ * rtb_reclaim_frame copies and queues frames included in |ent| for
+ * retransmission.  The frames are not deleted from |ent|.  It returns
+ * the number of frames queued.  |flags| is bitwise OR of 0 or more of
+ * NGTCP2_RECLAIM_FLAG_*.
+ */
+static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags,
+                                      ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                                      ngtcp2_rtb_entry *ent) {
+  ngtcp2_frame_chain *frc, *nfrc, **pfrc = &pktns->tx.frq;
+  ngtcp2_frame *fr;
+  ngtcp2_strm *strm;
+  ngtcp2_range gap, range;
+  size_t num_reclaimed = 0;
+  int rv;
+
+  assert(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE);
+
+  /* TODO Reconsider the order of pfrc */
+  for (frc = ent->frc; frc; frc = frc->next) {
+    fr = &frc->fr;
+
+    /* Check that a late ACK acknowledged this frame. */
+    if (frc->binder &&
+        (frc->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK)) {
+      continue;
+    }
+
+    switch (frc->fr.type) {
+    case NGTCP2_FRAME_STREAM:
+      strm = ngtcp2_conn_find_stream(conn, fr->stream.stream_id);
+      if (strm == NULL || (strm->flags & NGTCP2_STRM_FLAG_RESET_STREAM)) {
+        continue;
+      }
+
+      gap = ngtcp2_strm_get_unacked_range_after(strm, fr->stream.offset);
+
+      range.begin = fr->stream.offset;
+      range.end =
+        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 &&
+          /* 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
+             are acknowledged. */
+          (fr->stream.offset != 0 || fr->stream.datacnt != 0 ||
+           strm->tx.offset ||
+           (strm->flags &
+            (NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_ANY_ACKED)))) {
+        continue;
+      }
+
+      if ((flags & NGTCP2_RECLAIM_FLAG_ON_LOSS) &&
+          ent->hd.pkt_num != strm->tx.last_lost_pkt_num) {
+        strm->tx.last_lost_pkt_num = ent->hd.pkt_num;
+        ++strm->tx.loss_count;
+      }
+
+      rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
+        &nfrc, fr->stream.datacnt, rtb->frc_objalloc, rtb->mem);
+      if (rv != 0) {
+        return rv;
+      }
+
+      nfrc->fr = *fr;
+      ngtcp2_vec_copy(nfrc->fr.stream.data, fr->stream.data,
+                      fr->stream.datacnt);
+
+      rv = ngtcp2_strm_streamfrq_push(strm, nfrc);
+      if (rv != 0) {
+        ngtcp2_frame_chain_objalloc_del(nfrc, 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;
+        }
+      }
+
+      ++num_reclaimed;
+
+      continue;
+    case NGTCP2_FRAME_CRYPTO:
+      /* Do not resend CRYPTO frame if the whole region it contains
+         has been acknowledged */
+      gap = ngtcp2_strm_get_unacked_range_after(&pktns->crypto.strm,
+                                                fr->stream.offset);
+
+      range.begin = fr->stream.offset;
+      range.end =
+        fr->stream.offset + ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt);
+      range = ngtcp2_range_intersect(&range, &gap);
+
+      if (ngtcp2_range_len(&range) == 0) {
+        continue;
+      }
+
+      rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
+        &nfrc, fr->stream.datacnt, rtb->frc_objalloc, rtb->mem);
+      if (rv != 0) {
+        return rv;
+      }
+
+      nfrc->fr = *fr;
+      ngtcp2_vec_copy(nfrc->fr.stream.data, fr->stream.data,
+                      fr->stream.datacnt);
+
+      rv = ngtcp2_strm_streamfrq_push(&pktns->crypto.strm, nfrc);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        ngtcp2_frame_chain_objalloc_del(nfrc, rtb->frc_objalloc, rtb->mem);
+        return rv;
+      }
+
+      ++num_reclaimed;
+
+      continue;
+    case NGTCP2_FRAME_NEW_TOKEN:
+      rv = ngtcp2_frame_chain_new_token_objalloc_new(
+        &nfrc, fr->new_token.token, fr->new_token.tokenlen, rtb->frc_objalloc,
+        rtb->mem);
+      if (rv != 0) {
+        return rv;
+      }
+
+      rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem);
+      if (rv != 0) {
+        return rv;
+      }
+
+      ++num_reclaimed;
+
+      nfrc->next = *pfrc;
+      *pfrc = nfrc;
+      pfrc = &nfrc->next;
+
+      continue;
+    case NGTCP2_FRAME_DATAGRAM:
+    case NGTCP2_FRAME_DATAGRAM_LEN:
+      continue;
+    case NGTCP2_FRAME_RESET_STREAM:
+      strm = ngtcp2_conn_find_stream(conn, fr->reset_stream.stream_id);
+      if (strm == NULL || !ngtcp2_strm_require_retransmit_reset_stream(strm)) {
+        continue;
+      }
+
+      break;
+    case NGTCP2_FRAME_STOP_SENDING:
+      strm = ngtcp2_conn_find_stream(conn, fr->stop_sending.stream_id);
+      if (strm == NULL || !ngtcp2_strm_require_retransmit_stop_sending(strm)) {
+        continue;
+      }
+
+      break;
+    case NGTCP2_FRAME_MAX_STREAM_DATA:
+      strm = ngtcp2_conn_find_stream(conn, fr->max_stream_data.stream_id);
+      if (strm == NULL || !ngtcp2_strm_require_retransmit_max_stream_data(
+                            strm, &fr->max_stream_data)) {
+        continue;
+      }
+
+      break;
+    case NGTCP2_FRAME_STREAM_DATA_BLOCKED:
+      strm = ngtcp2_conn_find_stream(conn, fr->stream_data_blocked.stream_id);
+      if (strm == NULL || !ngtcp2_strm_require_retransmit_stream_data_blocked(
+                            strm, &fr->stream_data_blocked)) {
+        continue;
+      }
+
+      break;
+    }
+
+    rv = ngtcp2_frame_chain_objalloc_new(&nfrc, rtb->frc_objalloc);
+    if (rv != 0) {
+      return rv;
+    }
+
+    nfrc->fr = *fr;
+
+    rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem);
+    if (rv != 0) {
+      return rv;
+    }
+
+    ++num_reclaimed;
+
+    nfrc->next = *pfrc;
+    *pfrc = nfrc;
+    pfrc = &nfrc->next;
+  }
+
+  return (ngtcp2_ssize)num_reclaimed;
+}
+
+/*
+ * conn_process_lost_datagram calls ngtcp2_lost_datagram callback for
+ * lost DATAGRAM frames.
+ */
+static int conn_process_lost_datagram(ngtcp2_conn *conn,
+                                      ngtcp2_rtb_entry *ent) {
+  ngtcp2_frame_chain *frc;
+  int rv;
+
+  for (frc = ent->frc; frc; frc = frc->next) {
+    switch (frc->fr.type) {
+    case NGTCP2_FRAME_DATAGRAM:
+    case NGTCP2_FRAME_DATAGRAM_LEN:
+      assert(conn->callbacks.lost_datagram);
+
+      rv = conn->callbacks.lost_datagram(conn, frc->fr.datagram.dgram_id,
+                                         conn->user_data);
+      if (rv != 0) {
+        return NGTCP2_ERR_CALLBACK_FAILURE;
+      }
+
+      break;
+    }
+  }
+
+  return 0;
+}
+
+static int rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
+                           ngtcp2_conn_stat *cstat, ngtcp2_conn *conn,
+                           ngtcp2_pktns *pktns, ngtcp2_tstamp ts) {
+  int rv;
+  ngtcp2_ssize reclaimed;
+  ngtcp2_cc *cc = rtb->cc;
+  ngtcp2_cc_pkt pkt;
+
+  if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_SKIP)) {
+    ngtcp2_log_pkt_lost(rtb->log, ent->hd.pkt_num, ent->hd.type, ent->hd.flags,
+                        ent->ts);
+
+    if (rtb->qlog) {
+      ngtcp2_qlog_pkt_lost(rtb->qlog, ent);
+    }
+  }
+
+  if (ent->flags &
+      (NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE | NGTCP2_RTB_ENTRY_FLAG_SKIP)) {
+    ++rtb->num_lost_ignore_pkts;
+  } else if (rtb->cc->on_pkt_lost) {
+    cc->on_pkt_lost(cc, cstat,
+                    ngtcp2_cc_pkt_init(&pkt, ent->hd.pkt_num, ent->pktlen,
+                                       pktns->id, ent->ts, ent->rst.lost,
+                                       ent->rst.tx_in_flight,
+                                       ent->rst.is_app_limited),
+                    ts);
+  }
+
+  if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) {
+    ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC,
+                    "pkn=%" PRId64 " has already been reclaimed on PTO",
+                    ent->hd.pkt_num);
+    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);
+
+      reclaimed =
+        rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_ON_LOSS, conn, pktns, ent);
+      if (reclaimed < 0) {
+        return (int)reclaimed;
+      }
+    }
+  }
+
+  ent->flags |= NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED;
+  ent->lost_ts = ts;
+
+  ++rtb->num_lost_pkts;
+
+  return 0;
+}
+
+int ngtcp2_rtb_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
+                   ngtcp2_conn_stat *cstat) {
+  int rv;
+
+  rv = ngtcp2_ksl_insert(&rtb->ents, NULL, &ent->hd.pkt_num, ent);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rtb_on_add(rtb, ent, cstat);
+
+  return 0;
+}
+
+ngtcp2_ksl_it ngtcp2_rtb_head(const ngtcp2_rtb *rtb) {
+  return ngtcp2_ksl_begin(&rtb->ents);
+}
+
+static void rtb_remove(ngtcp2_rtb *rtb, ngtcp2_ksl_it *it,
+                       ngtcp2_rtb_entry **pent, ngtcp2_rtb_entry *ent,
+                       ngtcp2_conn_stat *cstat) {
+  int rv;
+  (void)rv;
+
+  rv = ngtcp2_ksl_remove_hint(&rtb->ents, it, it, &ent->hd.pkt_num);
+  assert(0 == rv);
+  rtb_on_remove(rtb, ent, cstat);
+
+  assert(ent->next == NULL);
+
+  ngtcp2_list_insert(ent, pent);
+}
+
+static void conn_ack_crypto_data(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                                 uint64_t datalen) {
+  ngtcp2_buf_chain **pbufchain, *bufchain;
+  size_t left;
+
+  for (pbufchain = &pktns->crypto.tx.data; *pbufchain;) {
+    left = ngtcp2_buf_len(&(*pbufchain)->buf);
+    if (left > datalen) {
+      (*pbufchain)->buf.pos += datalen;
+      return;
+    }
+
+    bufchain = *pbufchain;
+    *pbufchain = bufchain->next;
+
+    ngtcp2_mem_free(conn->mem, bufchain);
+
+    datalen -= left;
+
+    if (datalen == 0) {
+      return;
+    }
+  }
+
+  assert(datalen == 0);
+
+  return;
+}
+
+static int process_acked_pkt(ngtcp2_rtb_entry *ent, ngtcp2_conn *conn,
+                             ngtcp2_pktns *pktns) {
+  ngtcp2_frame_chain *frc;
+  uint64_t prev_stream_offset, stream_offset;
+  ngtcp2_strm *strm;
+  int rv;
+  uint64_t datalen;
+  ngtcp2_strm *crypto = &pktns->crypto.strm;
+
+  if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) && conn->pmtud &&
+      conn->pmtud->tx_pkt_num <= ent->hd.pkt_num) {
+    ngtcp2_pmtud_probe_success(conn->pmtud, ent->pktlen);
+
+    if (conn->dcid.current.max_udp_payload_size < ent->pktlen) {
+      conn->dcid.current.max_udp_payload_size = ent->pktlen;
+      conn->cstat.max_tx_udp_payload_size =
+        ngtcp2_conn_get_path_max_tx_udp_payload_size(conn);
+    }
+
+    if (ngtcp2_pmtud_finished(conn->pmtud)) {
+      ngtcp2_conn_stop_pmtud(conn);
+    }
+  }
+
+  for (frc = ent->frc; frc; frc = frc->next) {
+    if (frc->binder) {
+      if (frc->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK) {
+        continue;
+      }
+
+      frc->binder->flags |= NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK;
+    }
+
+    switch (frc->fr.type) {
+    case NGTCP2_FRAME_STREAM:
+      strm = ngtcp2_conn_find_stream(conn, frc->fr.stream.stream_id);
+      if (strm == NULL) {
+        break;
+      }
+
+      strm->flags |= NGTCP2_STRM_FLAG_ANY_ACKED;
+
+      if (frc->fr.stream.fin) {
+        strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED;
+      }
+
+      prev_stream_offset = ngtcp2_strm_get_acked_offset(strm);
+
+      rv = ngtcp2_strm_ack_data(
+        strm, frc->fr.stream.offset,
+        ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt));
+      if (rv != 0) {
+        return rv;
+      }
+
+      if (conn->callbacks.acked_stream_data_offset) {
+        stream_offset = ngtcp2_strm_get_acked_offset(strm);
+
+        datalen = stream_offset - prev_stream_offset;
+        if (datalen == 0 && !frc->fr.stream.fin) {
+          break;
+        }
+
+        rv = conn->callbacks.acked_stream_data_offset(
+          conn, strm->stream_id, prev_stream_offset, datalen, conn->user_data,
+          strm->stream_user_data);
+        if (rv != 0) {
+          return NGTCP2_ERR_CALLBACK_FAILURE;
+        }
+      }
+
+      rv = ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm);
+      if (rv != 0) {
+        return rv;
+      }
+
+      break;
+    case NGTCP2_FRAME_CRYPTO:
+      prev_stream_offset = ngtcp2_strm_get_acked_offset(crypto);
+
+      rv = ngtcp2_strm_ack_data(
+        crypto, frc->fr.stream.offset,
+        ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt));
+      if (rv != 0) {
+        return rv;
+      }
+
+      stream_offset = ngtcp2_strm_get_acked_offset(crypto);
+
+      datalen = stream_offset - prev_stream_offset;
+      if (datalen == 0) {
+        break;
+      }
+
+      conn_ack_crypto_data(conn, pktns, datalen);
+
+      break;
+    case NGTCP2_FRAME_RESET_STREAM:
+      strm = ngtcp2_conn_find_stream(conn, frc->fr.reset_stream.stream_id);
+      if (strm == NULL) {
+        break;
+      }
+
+      strm->flags |= NGTCP2_STRM_FLAG_RESET_STREAM_ACKED;
+
+      rv = ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm);
+      if (rv != 0) {
+        return rv;
+      }
+
+      break;
+    case NGTCP2_FRAME_RETIRE_CONNECTION_ID:
+      ngtcp2_dcidtr_untrack_retired_seq(&conn->dcid.dtr,
+                                        frc->fr.retire_connection_id.seq);
+      break;
+    case NGTCP2_FRAME_NEW_CONNECTION_ID:
+      assert(conn->scid.num_in_flight);
+
+      --conn->scid.num_in_flight;
+
+      break;
+    case NGTCP2_FRAME_DATAGRAM:
+    case NGTCP2_FRAME_DATAGRAM_LEN:
+      if (!conn->callbacks.ack_datagram) {
+        break;
+      }
+
+      rv = conn->callbacks.ack_datagram(conn, frc->fr.datagram.dgram_id,
+                                        conn->user_data);
+      if (rv != 0) {
+        return NGTCP2_ERR_CALLBACK_FAILURE;
+      }
+
+      break;
+    }
+  }
+
+  return 0;
+}
+
+static void rtb_on_pkt_acked(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
+                             ngtcp2_conn_stat *cstat, const ngtcp2_pktns *pktns,
+                             ngtcp2_tstamp ts) {
+  ngtcp2_cc *cc = rtb->cc;
+  ngtcp2_cc_pkt pkt;
+
+  ngtcp2_rst_update_rate_sample(rtb->rst, ent, ts);
+
+  if (cc->on_pkt_acked) {
+    cc->on_pkt_acked(cc, cstat,
+                     ngtcp2_cc_pkt_init(&pkt, ent->hd.pkt_num, ent->pktlen,
+                                        pktns->id, ent->ts, ent->rst.lost,
+                                        ent->rst.tx_in_flight,
+                                        ent->rst.is_app_limited),
+                     ts);
+  }
+
+  if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PROBE) &&
+      (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) {
+    cstat->pto_count = 0;
+  }
+}
+
+static void conn_verify_ecn(ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                            ngtcp2_cc *cc, ngtcp2_conn_stat *cstat,
+                            const ngtcp2_ack *fr, size_t ecn_acked,
+                            ngtcp2_tstamp largest_pkt_sent_ts,
+                            ngtcp2_tstamp ts) {
+  if (conn->tx.ecn.state == NGTCP2_ECN_STATE_FAILED) {
+    return;
+  }
+
+  if ((ecn_acked && fr->type == NGTCP2_FRAME_ACK) ||
+      (fr->type == NGTCP2_FRAME_ACK_ECN &&
+       (pktns->acktr.ecn.ack.ect0 > fr->ecn.ect0 ||
+        pktns->acktr.ecn.ack.ect1 > fr->ecn.ect1 ||
+        pktns->acktr.ecn.ack.ce > fr->ecn.ce ||
+        (fr->ecn.ect0 - pktns->acktr.ecn.ack.ect0) +
+            (fr->ecn.ce - pktns->acktr.ecn.ack.ce) <
+          ecn_acked ||
+        fr->ecn.ect0 > pktns->tx.ecn.ect0 || fr->ecn.ect1))) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON,
+                    "path is not ECN capable");
+    conn->tx.ecn.state = NGTCP2_ECN_STATE_FAILED;
+
+    return;
+  }
+
+  if (conn->tx.ecn.state != NGTCP2_ECN_STATE_CAPABLE && ecn_acked) {
+    ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "path is ECN capable");
+    conn->tx.ecn.state = NGTCP2_ECN_STATE_CAPABLE;
+  }
+
+  if (fr->type == NGTCP2_FRAME_ACK_ECN) {
+    if (cc->congestion_event && largest_pkt_sent_ts != UINT64_MAX &&
+        fr->ecn.ce > pktns->acktr.ecn.ack.ce) {
+      cc->congestion_event(cc, cstat, largest_pkt_sent_ts, 0, ts);
+    }
+
+    pktns->acktr.ecn.ack.ect0 = fr->ecn.ect0;
+    pktns->acktr.ecn.ack.ect1 = fr->ecn.ect1;
+    pktns->acktr.ecn.ack.ce = fr->ecn.ce;
+  }
+}
+
+static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost,
+                               ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                               ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts);
+
+ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,
+                                 ngtcp2_conn_stat *cstat, ngtcp2_conn *conn,
+                                 ngtcp2_pktns *pktns, ngtcp2_tstamp pkt_ts,
+                                 ngtcp2_tstamp ts) {
+  ngtcp2_rtb_entry *ent;
+  int64_t largest_ack = fr->largest_ack, min_ack;
+  size_t i;
+  int rv;
+  ngtcp2_ksl_it it;
+  size_t num_acked = 0;
+  ngtcp2_tstamp largest_pkt_sent_ts = UINT64_MAX;
+  int64_t pkt_num;
+  ngtcp2_cc *cc = rtb->cc;
+  ngtcp2_rtb_entry *acked_ent = NULL;
+  int ack_eliciting_pkt_acked = 0;
+  size_t ecn_acked = 0;
+  int verify_ecn = 0;
+  ngtcp2_cc_ack cc_ack = {0};
+  size_t num_lost_pkts = rtb->num_lost_pkts - rtb->num_lost_ignore_pkts;
+
+  cc_ack.prior_bytes_in_flight = cstat->bytes_in_flight;
+  cc_ack.rtt = UINT64_MAX;
+
+  if (conn && (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) &&
+      (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR) &&
+      largest_ack >= conn->pktns.crypto.tx.ckm->pkt_num) {
+    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(rtb->log, NGTCP2_LOG_EVENT_CRY, "key update confirmed");
+  }
+
+  if (rtb->largest_acked_tx_pkt_num < largest_ack) {
+    rtb->largest_acked_tx_pkt_num = largest_ack;
+    verify_ecn = 1;
+  }
+
+  /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */
+  it = ngtcp2_ksl_lower_bound(&rtb->ents, &largest_ack);
+  if (ngtcp2_ksl_it_end(&it)) {
+    if (conn && verify_ecn) {
+      conn_verify_ecn(conn, pktns, rtb->cc, cstat, fr, ecn_acked,
+                      largest_pkt_sent_ts, ts);
+    }
+
+    return 0;
+  }
+
+  min_ack = largest_ack - (int64_t)fr->first_ack_range;
+
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    pkt_num = *(int64_t *)ngtcp2_ksl_it_key(&it);
+
+    assert(pkt_num <= largest_ack);
+
+    if (pkt_num < min_ack) {
+      break;
+    }
+
+    ent = ngtcp2_ksl_it_get(&it);
+
+    if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_SKIP) {
+      rv = NGTCP2_ERR_PROTO;
+      goto fail;
+    }
+
+    if (largest_ack == pkt_num) {
+      largest_pkt_sent_ts = ent->ts;
+    }
+
+    if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
+      ack_eliciting_pkt_acked = 1;
+    }
+
+    rtb_remove(rtb, &it, &acked_ent, ent, cstat);
+    ++num_acked;
+  }
+
+  for (i = 0; i < fr->rangecnt; ++i) {
+    largest_ack = min_ack - (int64_t)fr->ranges[i].gap - 2;
+    min_ack = largest_ack - (int64_t)fr->ranges[i].len;
+
+    it = ngtcp2_ksl_lower_bound(&rtb->ents, &largest_ack);
+    if (ngtcp2_ksl_it_end(&it)) {
+      break;
+    }
+
+    for (; !ngtcp2_ksl_it_end(&it);) {
+      pkt_num = *(int64_t *)ngtcp2_ksl_it_key(&it);
+      if (pkt_num < min_ack) {
+        break;
+      }
+
+      ent = ngtcp2_ksl_it_get(&it);
+
+      if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_SKIP) {
+        rv = NGTCP2_ERR_PROTO;
+        goto fail;
+      }
+
+      if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) {
+        ack_eliciting_pkt_acked = 1;
+      }
+
+      rtb_remove(rtb, &it, &acked_ent, ent, cstat);
+      ++num_acked;
+    }
+  }
+
+  if (largest_pkt_sent_ts != UINT64_MAX && ack_eliciting_pkt_acked) {
+    cc_ack.rtt =
+      ngtcp2_max_uint64(pkt_ts - largest_pkt_sent_ts, NGTCP2_NANOSECONDS);
+
+    rv = ngtcp2_conn_update_rtt(conn, cc_ack.rtt, fr->ack_delay_unscaled, ts);
+    if (rv == 0 && cc->new_rtt_sample) {
+      cc->new_rtt_sample(cc, cstat, ts);
+    }
+  }
+
+  if (conn) {
+    for (ent = acked_ent; ent; ent = acked_ent) {
+      if (ent->hd.pkt_num >= pktns->tx.ecn.start_pkt_num &&
+          (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ECN)) {
+        ++ecn_acked;
+      }
+
+      rv = process_acked_pkt(ent, conn, pktns);
+      if (rv != 0) {
+        goto fail;
+      }
+
+      if (ent->hd.pkt_num >= rtb->cc_pkt_num) {
+        assert(cc_ack.pkt_delivered <= ent->rst.delivered);
+
+        cc_ack.bytes_delivered += ent->pktlen;
+        cc_ack.pkt_delivered = ent->rst.delivered;
+      }
+
+      rtb_on_pkt_acked(rtb, ent, cstat, pktns, ts);
+      acked_ent = ent->next;
+      ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
+                                    rtb->frc_objalloc, rtb->mem);
+    }
+
+    if (verify_ecn) {
+      conn_verify_ecn(conn, pktns, rtb->cc, cstat, fr, ecn_acked,
+                      largest_pkt_sent_ts, ts);
+    }
+  } else {
+    /* For unit tests */
+    for (ent = acked_ent; ent; ent = acked_ent) {
+      rtb_on_pkt_acked(rtb, ent, cstat, pktns, ts);
+      acked_ent = ent->next;
+      ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
+                                    rtb->frc_objalloc, rtb->mem);
+    }
+  }
+
+  if (rtb->cc->on_spurious_congestion && num_lost_pkts &&
+      rtb->num_lost_pkts == rtb->num_lost_ignore_pkts) {
+    rtb->cc->on_spurious_congestion(cc, cstat, ts);
+  }
+
+  if (num_acked) {
+    ngtcp2_rst_on_ack_recv(rtb->rst, cstat);
+
+    if (conn) {
+      rv = rtb_detect_lost_pkt(rtb, &cc_ack.bytes_lost, conn, pktns, cstat, ts);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+  }
+
+  rtb->rst->lost += cc_ack.bytes_lost;
+
+  cc_ack.largest_pkt_sent_ts = largest_pkt_sent_ts;
+  if (num_acked && cc->on_ack_recv) {
+    cc->on_ack_recv(cc, cstat, &cc_ack, ts);
+  }
+
+  return (ngtcp2_ssize)num_acked;
+
+fail:
+  for (ent = acked_ent; ent; ent = acked_ent) {
+    acked_ent = ent->next;
+    ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
+                                  rtb->frc_objalloc, rtb->mem);
+  }
+
+  return rv;
+}
+
+static int rtb_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat,
+                        const ngtcp2_rtb_entry *ent, ngtcp2_duration loss_delay,
+                        size_t pkt_thres, const ngtcp2_pktns *pktns,
+                        ngtcp2_tstamp ts) {
+  ngtcp2_tstamp loss_time;
+
+  if (ngtcp2_tstamp_elapsed(ent->ts, loss_delay, ts) ||
+      rtb->largest_acked_tx_pkt_num >= ent->hd.pkt_num + (int64_t)pkt_thres) {
+    return 1;
+  }
+
+  loss_time = cstat->loss_time[pktns->id];
+
+  if (loss_time == UINT64_MAX) {
+    loss_time = ent->ts + loss_delay;
+  } else {
+    loss_time = ngtcp2_min_uint64(loss_time, ent->ts + loss_delay);
+  }
+
+  cstat->loss_time[pktns->id] = loss_time;
+
+  return 0;
+}
+
+/*
+ * compute_pkt_loss_delay computes loss delay.
+ */
+static ngtcp2_duration compute_pkt_loss_delay(const ngtcp2_conn_stat *cstat) {
+  /* 9/8 is kTimeThreshold */
+  ngtcp2_duration loss_delay =
+    ngtcp2_max_uint64(cstat->latest_rtt, cstat->smoothed_rtt) * 9 / 8;
+  return ngtcp2_max_uint64(loss_delay, NGTCP2_GRANULARITY);
+}
+
+/*
+ * conn_all_ecn_pkt_lost returns nonzero if all ECN QUIC packets are
+ * lost during validation period.
+ */
+static int conn_all_ecn_pkt_lost(ngtcp2_conn *conn) {
+  ngtcp2_pktns *in_pktns = conn->in_pktns;
+  ngtcp2_pktns *hs_pktns = conn->hs_pktns;
+  ngtcp2_pktns *pktns = &conn->pktns;
+
+  return (!in_pktns || in_pktns->tx.ecn.validation_pkt_sent ==
+                         in_pktns->tx.ecn.validation_pkt_lost) &&
+         (!hs_pktns || hs_pktns->tx.ecn.validation_pkt_sent ==
+                         hs_pktns->tx.ecn.validation_pkt_lost) &&
+         pktns->tx.ecn.validation_pkt_sent == pktns->tx.ecn.validation_pkt_lost;
+}
+
+static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost,
+                               ngtcp2_conn *conn, ngtcp2_pktns *pktns,
+                               ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) {
+  ngtcp2_rtb_entry *ent;
+  ngtcp2_duration loss_delay;
+  ngtcp2_ksl_it it;
+  ngtcp2_tstamp latest_ts, oldest_ts;
+  int64_t last_lost_pkt_num;
+  ngtcp2_duration loss_window, congestion_period;
+  ngtcp2_cc *cc = rtb->cc;
+  int rv;
+  uint64_t pkt_thres =
+    rtb->cc_bytes_in_flight / cstat->max_tx_udp_payload_size / 2;
+  size_t ecn_pkt_lost = 0;
+  ngtcp2_tstamp start_ts;
+  ngtcp2_duration pto = ngtcp2_conn_compute_pto(conn, pktns);
+  uint64_t bytes_lost = 0;
+  ngtcp2_duration max_ack_delay;
+
+  pkt_thres = ngtcp2_max_uint64(pkt_thres, NGTCP2_PKT_THRESHOLD);
+  pkt_thres = ngtcp2_min_uint64(pkt_thres, 256);
+  cstat->loss_time[pktns->id] = UINT64_MAX;
+  loss_delay = compute_pkt_loss_delay(cstat);
+
+  it = ngtcp2_ksl_lower_bound(&rtb->ents, &rtb->largest_acked_tx_pkt_num);
+  for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) {
+    ent = ngtcp2_ksl_it_get(&it);
+
+    if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) {
+      break;
+    }
+
+    if (rtb_pkt_lost(rtb, cstat, ent, loss_delay, (size_t)pkt_thres, pktns,
+                     ts)) {
+      /* All entries from ent are considered to be lost. */
+      latest_ts = oldest_ts = ent->ts;
+      /* +1 to pick this packet for persistent congestion in the
+         following loop. */
+      last_lost_pkt_num = ent->hd.pkt_num + 1;
+      max_ack_delay = conn->remote.transport_params
+                        ? conn->remote.transport_params->max_ack_delay
+                        : 0;
+
+      congestion_period =
+        (cstat->smoothed_rtt +
+         ngtcp2_max_uint64(4 * cstat->rttvar, NGTCP2_GRANULARITY) +
+         max_ack_delay) *
+        NGTCP2_PERSISTENT_CONGESTION_THRESHOLD;
+
+      start_ts = ngtcp2_max_uint64(conn->handshake_confirmed_ts,
+                                   cstat->first_rtt_sample_ts);
+
+      for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) {
+        ent = ngtcp2_ksl_it_get(&it);
+
+        if (last_lost_pkt_num == ent->hd.pkt_num + 1 && ent->ts >= start_ts) {
+          last_lost_pkt_num = ent->hd.pkt_num;
+          oldest_ts = ent->ts;
+        } else {
+          last_lost_pkt_num = -1;
+        }
+
+        if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)) {
+          if (pktns->id != NGTCP2_PKTNS_ID_APPLICATION ||
+              last_lost_pkt_num == -1 ||
+              latest_ts - oldest_ts >= congestion_period) {
+            break;
+          }
+
+          continue;
+        }
+
+        if (ent->hd.pkt_num >= pktns->tx.ecn.start_pkt_num &&
+            (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ECN)) {
+          ++ecn_pkt_lost;
+        }
+
+        bytes_lost += rtb_on_remove(rtb, ent, cstat);
+        rv = rtb_on_pkt_lost(rtb, ent, cstat, conn, pktns, ts);
+        if (rv != 0) {
+          return rv;
+        }
+      }
+
+      /* If only PMTUD packets are lost, do not trigger congestion
+         event. */
+      if (bytes_lost == 0) {
+        break;
+      }
+
+      switch (conn->tx.ecn.state) {
+      case NGTCP2_ECN_STATE_TESTING:
+        if (conn->tx.ecn.validation_start_ts == UINT64_MAX) {
+          break;
+        }
+
+        if (ts - conn->tx.ecn.validation_start_ts < 3 * pto) {
+          pktns->tx.ecn.validation_pkt_lost += ecn_pkt_lost;
+          assert(pktns->tx.ecn.validation_pkt_sent >=
+                 pktns->tx.ecn.validation_pkt_lost);
+          break;
+        }
+
+        conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN;
+
+        /* fall through */
+      case NGTCP2_ECN_STATE_UNKNOWN:
+        pktns->tx.ecn.validation_pkt_lost += ecn_pkt_lost;
+        assert(pktns->tx.ecn.validation_pkt_sent >=
+               pktns->tx.ecn.validation_pkt_lost);
+        if (conn_all_ecn_pkt_lost(conn)) {
+          conn->tx.ecn.state = NGTCP2_ECN_STATE_FAILED;
+        }
+        break;
+      default:
+        break;
+      }
+
+      if (cc->congestion_event) {
+        cc->congestion_event(cc, cstat, latest_ts, bytes_lost, ts);
+      }
+
+      loss_window = latest_ts - oldest_ts;
+      /* Persistent congestion situation is only evaluated for app
+       * packet number space and for the packets sent after handshake
+       * is confirmed.  During handshake, there is not much packets
+       * sent and also people seem to do lots of effort not to trigger
+       * persistent congestion there, then it is a lot easier to just
+       * not enable it during handshake.
+       */
+      if (pktns->id == NGTCP2_PKTNS_ID_APPLICATION && loss_window &&
+          loss_window >= congestion_period) {
+        ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC,
+                        "persistent congestion loss_window=%" PRIu64
+                        " congestion_period=%" PRIu64,
+                        loss_window, congestion_period);
+
+        /* Reset min_rtt, srtt, and rttvar here.  Next new RTT
+           sample will be used to recalculate these values. */
+        cstat->min_rtt = UINT64_MAX;
+        cstat->smoothed_rtt = conn->local.settings.initial_rtt;
+        cstat->rttvar = conn->local.settings.initial_rtt / 2;
+        cstat->first_rtt_sample_ts = UINT64_MAX;
+
+        if (cc->on_persistent_congestion) {
+          cc->on_persistent_congestion(cc, cstat, ts);
+        }
+      }
+
+      break;
+    }
+  }
+
+  ngtcp2_rtb_remove_excessive_lost_pkt(rtb, (size_t)pkt_thres);
+
+  if (ppkt_lost) {
+    *ppkt_lost = bytes_lost;
+  }
+
+  return 0;
+}
+
+int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
+                               ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat,
+                               ngtcp2_tstamp ts) {
+  return rtb_detect_lost_pkt(rtb, /* ppkt_lost = */ NULL, conn, pktns, cstat,
+                             ts);
+}
+
+void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) {
+  ngtcp2_ksl_it it = ngtcp2_ksl_end(&rtb->ents);
+  ngtcp2_rtb_entry *ent;
+  int rv;
+  (void)rv;
+
+  for (; rtb->num_lost_pkts > n;) {
+    assert(ngtcp2_ksl_it_end(&it));
+    ngtcp2_ksl_it_prev(&it);
+    ent = ngtcp2_ksl_it_get(&it);
+
+    assert(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED);
+
+    ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC,
+                    "removing stale lost pkn=%" PRId64, 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;
+    }
+
+    rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num);
+    assert(0 == rv);
+    ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
+                                  rtb->frc_objalloc, rtb->mem);
+  }
+}
+
+void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb,
+                                        ngtcp2_duration timeout,
+                                        ngtcp2_tstamp ts) {
+  ngtcp2_ksl_it it;
+  ngtcp2_rtb_entry *ent;
+  int rv;
+  (void)rv;
+
+  if (rtb->num_lost_pkts == 0) {
+    return;
+  }
+
+  it = ngtcp2_ksl_end(&rtb->ents);
+
+  for (; rtb->num_lost_pkts;) {
+    assert(ngtcp2_ksl_it_end(&it));
+
+    ngtcp2_ksl_it_prev(&it);
+    ent = ngtcp2_ksl_it_get(&it);
+
+    if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) ||
+        ts - ent->lost_ts < timeout) {
+      return;
+    }
+
+    ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC,
+                    "removing stale lost pkn=%" PRId64, 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;
+    }
+
+    rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num);
+    assert(0 == rv);
+    ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
+                                  rtb->frc_objalloc, rtb->mem);
+  }
+}
+
+ngtcp2_tstamp ngtcp2_rtb_lost_pkt_ts(const ngtcp2_rtb *rtb) {
+  ngtcp2_ksl_it it;
+  ngtcp2_rtb_entry *ent;
+
+  if (ngtcp2_ksl_len(&rtb->ents) == 0) {
+    return UINT64_MAX;
+  }
+
+  it = ngtcp2_ksl_end(&rtb->ents);
+  ngtcp2_ksl_it_prev(&it);
+  ent = ngtcp2_ksl_it_get(&it);
+
+  if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)) {
+    return UINT64_MAX;
+  }
+
+  return ent->lost_ts;
+}
+
+static int rtb_reclaim_frame_on_retry(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
+                                      ngtcp2_pktns *pktns,
+                                      ngtcp2_rtb_entry *ent) {
+  ngtcp2_frame_chain **pfrc = &ent->frc, *frc;
+  ngtcp2_stream *sfr;
+  ngtcp2_strm *strm;
+  int rv;
+
+  for (; *pfrc;) {
+    switch ((*pfrc)->fr.type) {
+    case NGTCP2_FRAME_STREAM:
+      frc = *pfrc;
+
+      *pfrc = frc->next;
+      frc->next = NULL;
+      sfr = &frc->fr.stream;
+
+      strm = ngtcp2_conn_find_stream(conn, sfr->stream_id);
+      if (!strm) {
+        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;
+        }
+      }
+
+      break;
+    case NGTCP2_FRAME_CRYPTO:
+      frc = *pfrc;
+
+      *pfrc = frc->next;
+      frc->next = NULL;
+
+      rv = ngtcp2_strm_streamfrq_push(&pktns->crypto.strm, frc);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem);
+        return rv;
+      }
+
+      break;
+    case NGTCP2_FRAME_DATAGRAM:
+    case NGTCP2_FRAME_DATAGRAM_LEN:
+      frc = *pfrc;
+
+      if (conn->callbacks.lost_datagram) {
+        rv = conn->callbacks.lost_datagram(conn, frc->fr.datagram.dgram_id,
+                                           conn->user_data);
+        if (rv != 0) {
+          return NGTCP2_ERR_CALLBACK_FAILURE;
+        }
+      }
+
+      *pfrc = (*pfrc)->next;
+
+      ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem);
+
+      break;
+    default:
+      pfrc = &(*pfrc)->next;
+    }
+  }
+
+  *pfrc = pktns->tx.frq;
+  pktns->tx.frq = ent->frc;
+  ent->frc = NULL;
+
+  return 0;
+}
+
+int ngtcp2_rtb_reclaim_on_retry(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
+                                ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat) {
+  ngtcp2_rtb_entry *ent;
+  ngtcp2_ksl_it it = ngtcp2_ksl_begin(&rtb->ents);
+  int rv;
+
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    ent = ngtcp2_ksl_it_get(&it);
+
+    rtb_on_remove(rtb, ent, cstat);
+    rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num);
+    assert(0 == rv);
+
+    if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_SKIP)) {
+      ngtcp2_log_pkt_lost(rtb->log, ent->hd.pkt_num, ent->hd.type,
+                          ent->hd.flags, ent->ts);
+
+      if (rtb->qlog) {
+        ngtcp2_qlog_pkt_lost(rtb->qlog, ent);
+      }
+    }
+
+    /* 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);
+    assert(0 == rtb->num_lost_ignore_pkts);
+
+    /* PMTUD probe must not be sent before handshake completion. */
+    assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE));
+
+    if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) {
+      ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_LDC,
+                      "pkn=%" PRId64 " has already been reclaimed on PTO",
+                      ent->hd.pkt_num);
+      continue;
+    }
+
+    if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) &&
+        (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_DATAGRAM) ||
+         !conn->callbacks.lost_datagram)) {
+      continue;
+    }
+
+    rv = rtb_reclaim_frame_on_retry(rtb, conn, pktns, ent);
+
+    ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
+                                  rtb->frc_objalloc, rtb->mem);
+
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+void ngtcp2_rtb_remove_early_data(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat) {
+  ngtcp2_rtb_entry *ent;
+  ngtcp2_ksl_it it;
+  int rv;
+  (void)rv;
+
+  it = ngtcp2_ksl_begin(&rtb->ents);
+
+  for (; !ngtcp2_ksl_it_end(&it);) {
+    ent = ngtcp2_ksl_it_get(&it);
+
+    if (ent->hd.type != NGTCP2_PKT_0RTT) {
+      ngtcp2_ksl_it_next(&it);
+      continue;
+    }
+
+    rtb_on_remove(rtb, ent, cstat);
+    rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num);
+    assert(0 == rv);
+
+    ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc,
+                                  rtb->frc_objalloc, rtb->mem);
+  }
+}
+
+int ngtcp2_rtb_empty(const ngtcp2_rtb *rtb) {
+  return ngtcp2_ksl_len(&rtb->ents) == 0;
+}
+
+void ngtcp2_rtb_reset_cc_state(ngtcp2_rtb *rtb, int64_t cc_pkt_num) {
+  rtb->cc_pkt_num = cc_pkt_num;
+  rtb->cc_bytes_in_flight = 0;
+}
+
+ngtcp2_ssize ngtcp2_rtb_reclaim_on_pto(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
+                                       ngtcp2_pktns *pktns, size_t num_pkts) {
+  ngtcp2_ksl_it it;
+  ngtcp2_rtb_entry *ent;
+  ngtcp2_ssize reclaimed;
+  size_t atmost = num_pkts;
+
+  it = ngtcp2_ksl_end(&rtb->ents);
+  for (; !ngtcp2_ksl_it_begin(&it) && num_pkts;) {
+    ngtcp2_ksl_it_prev(&it);
+    ent = ngtcp2_ksl_it_get(&it);
+
+    if ((ent->flags & (NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED |
+                       NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED)) ||
+        !(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE)) {
+      continue;
+    }
+
+    assert(ent->frc);
+
+    reclaimed =
+      rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_NONE, conn, pktns, ent);
+    if (reclaimed < 0) {
+      return reclaimed;
+    }
+
+    /* Mark ent reclaimed even if reclaimed == 0 so that we can skip
+       it in the next run. */
+    ent->flags |= NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED;
+
+    assert(rtb->num_retransmittable);
+    --rtb->num_retransmittable;
+
+    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;
+    }
+  }
+
+  return (ngtcp2_ssize)(atmost - num_pkts);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_rtb.h b/third_party/ngtcp2/lib/ngtcp2_rtb.h
new file mode 100644 (file)
index 0000000..14684a4
--- /dev/null
@@ -0,0 +1,321 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_RTB_H
+#define NGTCP2_RTB_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_ksl.h"
+#include "ngtcp2_pq.h"
+#include "ngtcp2_objalloc.h"
+#include "ngtcp2_pktns_id.h"
+
+typedef struct ngtcp2_conn ngtcp2_conn;
+typedef struct ngtcp2_pktns ngtcp2_pktns;
+typedef struct ngtcp2_log ngtcp2_log;
+typedef struct ngtcp2_qlog ngtcp2_qlog;
+typedef struct ngtcp2_strm ngtcp2_strm;
+typedef struct ngtcp2_rst ngtcp2_rst;
+typedef struct ngtcp2_cc ngtcp2_cc;
+typedef struct ngtcp2_conn_stat ngtcp2_conn_stat;
+typedef struct ngtcp2_frame_chain ngtcp2_frame_chain;
+
+/* NGTCP2_RTB_ENTRY_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_RTB_ENTRY_FLAG_NONE 0x00u
+/* NGTCP2_RTB_ENTRY_FLAG_PROBE indicates that the entry includes a
+   probe packet. */
+#define NGTCP2_RTB_ENTRY_FLAG_PROBE 0x01u
+/* NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE indicates that the entry
+   includes a frame which must be retransmitted until it is
+   acknowledged.  In most cases, this flag is used along with
+   NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING and
+   NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING. */
+#define NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE 0x02u
+/* NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING indicates that the entry
+   elicits acknowledgement. */
+#define NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING 0x04u
+/* NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED indicates that the packet has
+   been reclaimed on PTO.  It is not marked lost yet and still
+   consumes congestion window. */
+#define NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED 0x08u
+/* NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED indicates that the entry
+   has been marked lost and, optionally, scheduled to retransmit. */
+#define NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED 0x10u
+/* NGTCP2_RTB_ENTRY_FLAG_ECN indicates that the entry is included in a
+   UDP datagram with ECN marking. */
+#define NGTCP2_RTB_ENTRY_FLAG_ECN 0x20u
+/* NGTCP2_RTB_ENTRY_FLAG_DATAGRAM indicates that the entry includes
+   DATAGRAM frame. */
+#define NGTCP2_RTB_ENTRY_FLAG_DATAGRAM 0x40u
+/* NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE indicates that the entry includes
+   a PMTUD probe packet. */
+#define NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE 0x80u
+/* NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING indicates that the entry
+   includes a packet which elicits PTO probe packets. */
+#define NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING 0x100u
+/* NGTCP2_RTB_ENTRY_FLAG_SKIP indicates that the entry has the skipped
+   packet number. */
+#define NGTCP2_RTB_ENTRY_FLAG_SKIP 0x200u
+
+typedef struct ngtcp2_rtb_entry ngtcp2_rtb_entry;
+
+/*
+ * ngtcp2_rtb_entry is an object stored in ngtcp2_rtb.  It corresponds
+ * to the one packet which is waiting for its acknowledgement.
+ */
+struct ngtcp2_rtb_entry {
+  union {
+    struct {
+      ngtcp2_rtb_entry *next;
+
+      struct {
+        int64_t pkt_num;
+        uint8_t type;
+        uint8_t flags;
+      } hd;
+      ngtcp2_frame_chain *frc;
+      /* ts is the time point when a packet included in this entry is
+         sent to a remote endpoint. */
+      ngtcp2_tstamp ts;
+      /* lost_ts is the time when this entry is declared to be
+         lost. */
+      ngtcp2_tstamp lost_ts;
+      /* pktlen is the length of QUIC packet */
+      size_t pktlen;
+      struct {
+        uint64_t delivered;
+        ngtcp2_tstamp delivered_ts;
+        ngtcp2_tstamp first_sent_ts;
+        uint64_t tx_in_flight;
+        uint64_t lost;
+        int64_t end_seq;
+        int is_app_limited;
+      } rst;
+      /* flags is bitwise-OR of zero or more of
+         NGTCP2_RTB_ENTRY_FLAG_*. */
+      uint16_t flags;
+    };
+
+    ngtcp2_opl_entry oplent;
+  };
+};
+
+ngtcp2_objalloc_decl(rtb_entry, ngtcp2_rtb_entry, oplent)
+
+/*
+ * ngtcp2_rtb_entry_objalloc_new allocates ngtcp2_rtb_entry object via
+ * |objalloc|, and assigns its pointer to |*pent|.
+ */
+int ngtcp2_rtb_entry_objalloc_new(ngtcp2_rtb_entry **pent,
+                                  const ngtcp2_pkt_hd *hd,
+                                  ngtcp2_frame_chain *frc, ngtcp2_tstamp ts,
+                                  size_t pktlen, uint16_t flags,
+                                  ngtcp2_objalloc *objalloc);
+
+/*
+ * ngtcp2_rtb_entry_objalloc_del adds |ent| to |objalloc| for reuse.
+ * ngtcp2_frame_chain linked from ent->frc are also added to
+ * |frc_objalloc| depending on their frame type and size.
+ */
+void ngtcp2_rtb_entry_objalloc_del(ngtcp2_rtb_entry *ent,
+                                   ngtcp2_objalloc *objalloc,
+                                   ngtcp2_objalloc *frc_objalloc,
+                                   const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_rtb tracks sent packets, and its acknowledgement timeout for
+ * retransmission.
+ */
+typedef struct ngtcp2_rtb {
+  ngtcp2_objalloc *frc_objalloc;
+  ngtcp2_objalloc *rtb_entry_objalloc;
+  /* ents includes ngtcp2_rtb_entry sorted by decreasing order of
+     packet number. */
+  ngtcp2_ksl ents;
+  ngtcp2_rst *rst;
+  ngtcp2_cc *cc;
+  ngtcp2_log *log;
+  ngtcp2_qlog *qlog;
+  const ngtcp2_mem *mem;
+  /* largest_acked_tx_pkt_num is the largest packet number
+     acknowledged by a remote endpoint. */
+  int64_t largest_acked_tx_pkt_num;
+  /* num_ack_eliciting is the number of ACK eliciting entries in
+     ents. */
+  size_t num_ack_eliciting;
+  /* num_retransmittable is the number of packets which contain frames
+     that must be retransmitted on loss in ents. */
+  size_t num_retransmittable;
+  /* num_pto_eliciting is the number of packets that elicit PTO probe
+     packets in ents. */
+  size_t num_pto_eliciting;
+  /* probe_pkt_left is the number of probe packet to send */
+  size_t probe_pkt_left;
+  /* cc_pkt_num is the smallest packet number that is contributed to
+     ngtcp2_conn_stat.bytes_in_flight. */
+  int64_t cc_pkt_num;
+  /* cc_bytes_in_flight is the number of in-flight bytes that is
+     contributed to ngtcp2_conn_stat.bytes_in_flight.  It only
+     includes the bytes after congestion state is reset, that is only
+     count a packet whose packet number is greater than or equals to
+     cc_pkt_num. */
+  uint64_t cc_bytes_in_flight;
+  /* num_lost_pkts is the number entries in ents which has
+     NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED flag set. */
+  size_t num_lost_pkts;
+  /* num_lost_ignore_pkts is the number of entries in ents which have
+     NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED flag set, and should be
+     excluded from lost byte count.  If only those packets are lost,
+     congestion event is not triggered. */
+  size_t num_lost_ignore_pkts;
+} ngtcp2_rtb;
+
+/*
+ * ngtcp2_rtb_init initializes |rtb|.
+ */
+void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_rst *rst, ngtcp2_cc *cc,
+                     int64_t cc_pkt_num, ngtcp2_log *log, ngtcp2_qlog *qlog,
+                     ngtcp2_objalloc *rtb_entry_objalloc,
+                     ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_rtb_free deallocates resources allocated for |rtb|.
+ */
+void ngtcp2_rtb_free(ngtcp2_rtb *rtb);
+
+/*
+ * ngtcp2_rtb_add adds |ent| to |rtb|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_rtb_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent,
+                   ngtcp2_conn_stat *cstat);
+
+/*
+ * ngtcp2_rtb_head returns the iterator which points to the entry
+ * which has the largest packet number.  If there is no entry,
+ * returned value satisfies ngtcp2_ksl_it_end(&it) != 0.
+ */
+ngtcp2_ksl_it ngtcp2_rtb_head(const ngtcp2_rtb *rtb);
+
+/*
+ * ngtcp2_rtb_recv_ack removes an acknowledged ngtcp2_rtb_entry from
+ * |rtb|.  |pkt_num| is a packet number which includes |fr|.  |pkt_ts|
+ * is the timestamp when packet is received.  |ts| should be the
+ * current time.  Usually they are the same, but for buffered packets,
+ * |pkt_ts| would be earlier than |ts|.
+ *
+ * This function returns the number of newly acknowledged packets if
+ * it succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_CALLBACK_FAILURE
+ *     User callback failed
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr,
+                                 ngtcp2_conn_stat *cstat, ngtcp2_conn *conn,
+                                 ngtcp2_pktns *pktns, ngtcp2_tstamp pkt_ts,
+                                 ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_rtb_detect_lost_pkt detects lost packets and prepends the
+ * frames contained them to |*pfrc|.  Even when this function fails,
+ * some frames might be prepended to |*pfrc|, and the caller should
+ * handle them.
+ */
+int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
+                               ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat,
+                               ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_rtb_remove_expired_lost_pkt removes expired lost packet.
+ */
+void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb,
+                                        ngtcp2_duration timeout,
+                                        ngtcp2_tstamp ts);
+
+/*
+ * ngtcp2_rtb_lost_pkt_ts returns the timestamp when an oldest lost
+ * packet tracked by |rtb| was declared lost.  It returns UINT64_MAX
+ * if no such packet exists.
+ */
+ngtcp2_tstamp ngtcp2_rtb_lost_pkt_ts(const ngtcp2_rtb *rtb);
+
+/*
+ * ngtcp2_rtb_reclaim_on_retry is called when Retry packet is
+ * received.  It removes all packets from |rtb|, and retransmittable
+ * frames are reclaimed for retransmission.
+ */
+int ngtcp2_rtb_reclaim_on_retry(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
+                                ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat);
+
+/*
+ * ngtcp2_rtb_remove_early_data removes all entries for 0RTT packets.
+ */
+void ngtcp2_rtb_remove_early_data(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat);
+
+/*
+ * ngtcp2_rtb_empty returns nonzero if |rtb| has no entry.
+ */
+int ngtcp2_rtb_empty(const ngtcp2_rtb *rtb);
+
+/*
+ * ngtcp2_rtb_reset_cc_state resets congestion state in |rtb|.
+ * |cc_pkt_num| is the next outbound packet number which is sent under
+ * new congestion state.
+ */
+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);
+
+/*
+ * ngtcp2_rtb_reclaim_on_pto reclaims up to |num_pkts| packets which
+ * are in-flight and not marked lost.  The reclaimed frames may be
+ * sent in a PTO probe packet.
+ *
+ * This function returns the number of packets reclaimed if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+ngtcp2_ssize ngtcp2_rtb_reclaim_on_pto(ngtcp2_rtb *rtb, ngtcp2_conn *conn,
+                                       ngtcp2_pktns *pktns, size_t num_pkts);
+
+#endif /* !defined(NGTCP2_RTB_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_settings.c b/third_party/ngtcp2/lib/ngtcp2_settings.c
new file mode 100644 (file)
index 0000000..77a68bd
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2024 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_settings.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_unreachable.h"
+
+void ngtcp2_settings_default_versioned(int settings_version,
+                                       ngtcp2_settings *settings) {
+  size_t len = ngtcp2_settingslen_version(settings_version);
+
+  memset(settings, 0, len);
+
+  switch (settings_version) {
+  case NGTCP2_SETTINGS_VERSION:
+  case NGTCP2_SETTINGS_V1:
+    settings->cc_algo = NGTCP2_CC_ALGO_CUBIC;
+    settings->initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT;
+    settings->ack_thresh = 2;
+    settings->max_tx_udp_payload_size = 1500 - 48;
+    settings->handshake_timeout = UINT64_MAX;
+
+    break;
+  }
+}
+
+static void settings_copy(ngtcp2_settings *dest, const ngtcp2_settings *src,
+                          int settings_version) {
+  assert(settings_version != NGTCP2_SETTINGS_VERSION);
+
+  memcpy(dest, src, ngtcp2_settingslen_version(settings_version));
+}
+
+const ngtcp2_settings *
+ngtcp2_settings_convert_to_latest(ngtcp2_settings *dest, int settings_version,
+                                  const ngtcp2_settings *src) {
+  if (settings_version == NGTCP2_SETTINGS_VERSION) {
+    return src;
+  }
+
+  ngtcp2_settings_default(dest);
+
+  settings_copy(dest, src, settings_version);
+
+  return dest;
+}
+
+void ngtcp2_settings_convert_to_old(int settings_version, ngtcp2_settings *dest,
+                                    const ngtcp2_settings *src) {
+  assert(settings_version != NGTCP2_SETTINGS_VERSION);
+
+  settings_copy(dest, src, settings_version);
+}
+
+size_t ngtcp2_settingslen_version(int settings_version) {
+  ngtcp2_settings settings;
+
+  switch (settings_version) {
+  case NGTCP2_SETTINGS_VERSION:
+    return sizeof(settings);
+  case NGTCP2_SETTINGS_V1:
+    return offsetof(ngtcp2_settings, initial_pkt_num) +
+           sizeof(settings.initial_pkt_num);
+  default:
+    ngtcp2_unreachable();
+  }
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_settings.h b/third_party/ngtcp2/lib/ngtcp2_settings.h
new file mode 100644 (file)
index 0000000..80466d4
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2024 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_SETTINGS_H
+#define NGTCP2_SETTINGS_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/*
+ * ngtcp2_settings_convert_to_latest converts |src| of version
+ * |settings_version| to the latest version NGTCP2_SETTINGS_VERSION.
+ *
+ * |dest| must point to the latest version.  |src| may be the older
+ * version, and if so, it may have fewer fields.  Accessing those
+ * fields causes undefined behavior.
+ *
+ * If |settings_version| == NGTCP2_SETTINGS_VERSION, no conversion is
+ * made, and |src| is returned.  Otherwise, first |dest| is
+ * initialized via ngtcp2_settings_default, and then all valid fields
+ * in |src| are copied into |dest|.  Finally, |dest| is returned.
+ */
+const ngtcp2_settings *
+ngtcp2_settings_convert_to_latest(ngtcp2_settings *dest, int settings_version,
+                                  const ngtcp2_settings *src);
+
+/*
+ * ngtcp2_settings_convert_to_old converts |src| of the latest version
+ * to |dest| of version |settings_version|.
+ *
+ * |settings_version| must not be the latest version
+ *  NGTCP2_SETTINGS_VERSION.
+ *
+ * |dest| points to the older version, and it may have fewer fields.
+ * Accessing those fields causes undefined behavior.
+ *
+ * This function copies all valid fields in version |settings_version|
+ * from |src| to |dest|.
+ */
+void ngtcp2_settings_convert_to_old(int settings_version, ngtcp2_settings *dest,
+                                    const ngtcp2_settings *src);
+
+/*
+ * ngtcp2_settingslen_version returns the effective length of
+ * ngtcp2_settings at the version |settings_version|.
+ */
+size_t ngtcp2_settingslen_version(int settings_version);
+
+#endif /* !defined(NGTCP2_SETTINGS_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_str.c b/third_party/ngtcp2/lib/ngtcp2_str.c
new file mode 100644 (file)
index 0000000..a61636d
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_str.h"
+
+#include <string.h>
+
+#include "ngtcp2_macro.h"
+
+void *ngtcp2_cpymem(void *dest, const void *src, size_t n) {
+  memcpy(dest, src, n);
+  return (uint8_t *)dest + n;
+}
+
+uint8_t *ngtcp2_setmem(uint8_t *dest, uint8_t b, size_t n) {
+  memset(dest, b, n);
+  return dest + n;
+}
+
+const void *ngtcp2_get_bytes(void *dest, const void *src, size_t n) {
+  memcpy(dest, src, n);
+  return (uint8_t *)src + n;
+}
+
+#define LOWER_XDIGITS "0123456789abcdef"
+
+uint8_t *ngtcp2_encode_hex(uint8_t *dest, const uint8_t *data, size_t len) {
+  size_t i;
+  uint8_t *p = dest;
+
+  for (i = 0; i < len; ++i) {
+    *p++ = (uint8_t)LOWER_XDIGITS[data[i] >> 4];
+    *p++ = (uint8_t)LOWER_XDIGITS[data[i] & 0xf];
+  }
+
+  *p = '\0';
+
+  return dest;
+}
+
+char *ngtcp2_encode_printable_ascii(char *dest, const uint8_t *data,
+                                    size_t len) {
+  size_t i;
+  char *p = dest;
+  uint8_t c;
+
+  for (i = 0; i < len; ++i) {
+    c = data[i];
+    if (0x20 <= c && c <= 0x7e) {
+      *p++ = (char)c;
+    } else {
+      *p++ = '.';
+    }
+  }
+
+  *p = '\0';
+
+  return dest;
+}
+
+/*
+ * write_uint writes |n| to the buffer pointed by |p| in decimal
+ * representation.  It returns |p| plus the number of bytes written.
+ * The function assumes that the buffer has enough capacity to contain
+ * a string.
+ */
+static uint8_t *write_uint(uint8_t *p, uint64_t n) {
+  size_t nlen = 0;
+  uint64_t t;
+  uint8_t *res;
+
+  if (n == 0) {
+    *p++ = '0';
+    return p;
+  }
+  for (t = n; t; t /= 10, ++nlen)
+    ;
+  p += nlen;
+  res = p;
+  for (; n; n /= 10) {
+    *--p = (uint8_t)((n % 10) + '0');
+  }
+  return res;
+}
+
+uint8_t *ngtcp2_encode_ipv4(uint8_t *dest, const uint8_t *addr) {
+  size_t i;
+  uint8_t *p = dest;
+
+  p = write_uint(p, addr[0]);
+
+  for (i = 1; i < 4; ++i) {
+    *p++ = '.';
+    p = write_uint(p, addr[i]);
+  }
+
+  *p = '\0';
+
+  return dest;
+}
+
+/*
+ * write_hex_zsup writes the content of buffer pointed by |data| of
+ * length |len| to |dest| in hex string.  Any leading zeros are
+ * suppressed.  It returns |dest| plus the number of bytes written.
+ */
+static uint8_t *write_hex_zsup(uint8_t *dest, const uint8_t *data, size_t len) {
+  size_t i;
+  uint8_t *p = dest;
+  uint8_t d;
+
+  for (i = 0; i < len; ++i) {
+    d = data[i];
+    if (d >> 4) {
+      break;
+    }
+
+    d &= 0xf;
+
+    if (d) {
+      *p++ = (uint8_t)LOWER_XDIGITS[d];
+      ++i;
+      break;
+    }
+  }
+
+  if (p == dest && i == len) {
+    *p++ = '0';
+    return p;
+  }
+
+  for (; i < len; ++i) {
+    d = data[i];
+    *p++ = (uint8_t)LOWER_XDIGITS[d >> 4];
+    *p++ = (uint8_t)LOWER_XDIGITS[d & 0xf];
+  }
+
+  return p;
+}
+
+uint8_t *ngtcp2_encode_ipv6(uint8_t *dest, const uint8_t *addr) {
+  uint16_t blks[8];
+  size_t i;
+  size_t zlen, zoff;
+  size_t max_zlen = 0, max_zoff = 8;
+  uint8_t *p = dest;
+
+  for (i = 0; i < 16; i += sizeof(uint16_t)) {
+    /* Copy in network byte order. */
+    memcpy(&blks[i / sizeof(uint16_t)], addr + i, sizeof(uint16_t));
+  }
+
+  for (i = 0; i < 8;) {
+    if (blks[i]) {
+      ++i;
+      continue;
+    }
+
+    zlen = 1;
+    zoff = i;
+
+    ++i;
+    for (; i < 8 && blks[i] == 0; ++i, ++zlen)
+      ;
+    if (zlen > max_zlen) {
+      max_zlen = zlen;
+      max_zoff = zoff;
+    }
+  }
+
+  /* Do not suppress a single '0' block */
+  if (max_zlen == 1) {
+    max_zoff = 8;
+  }
+
+  if (max_zoff != 0) {
+    p = write_hex_zsup(p, (const uint8_t *)blks, sizeof(uint16_t));
+
+    for (i = 1; i < max_zoff; ++i) {
+      *p++ = ':';
+      p = write_hex_zsup(p, (const uint8_t *)(blks + i), sizeof(uint16_t));
+    }
+  }
+
+  if (max_zoff != 8) {
+    *p++ = ':';
+
+    if (max_zoff + max_zlen == 8) {
+      *p++ = ':';
+    } else {
+      for (i = max_zoff + max_zlen; i < 8; ++i) {
+        *p++ = ':';
+        p = write_hex_zsup(p, (const uint8_t *)(blks + i), sizeof(uint16_t));
+      }
+    }
+  }
+
+  *p = '\0';
+
+  return dest;
+}
+
+int ngtcp2_cmemeq(const uint8_t *a, const uint8_t *b, size_t n) {
+  size_t i;
+  int rv = 0;
+
+  for (i = 0; i < n; ++i) {
+    rv |= a[i] ^ b[i];
+  }
+
+  return rv == 0;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_str.h b/third_party/ngtcp2/lib/ngtcp2_str.h
new file mode 100644 (file)
index 0000000..f970c15
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_STR_H
+#define NGTCP2_STR_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+void *ngtcp2_cpymem(void *dest, const void *src, size_t n);
+
+/*
+ * ngtcp2_setmem writes a string of length |n| consisting only |b| to
+ * the buffer pointed by |dest|.  It returns dest + n;
+ */
+uint8_t *ngtcp2_setmem(uint8_t *dest, uint8_t b, size_t n);
+
+/*
+ * ngtcp2_get_bytes copies |n| bytes from |src| to |dest|, and returns
+ * |src| + |n|.
+ */
+const void *ngtcp2_get_bytes(void *dest, const void *src, size_t n);
+
+/*
+ * ngtcp2_encode_hex encodes |data| of length |len| in hex string.  It
+ * writes additional NULL bytes at the end of the buffer.  The buffer
+ * pointed by |dest| must have at least |len| * 2 + 1 bytes space.
+ * This function returns |dest|.
+ */
+uint8_t *ngtcp2_encode_hex(uint8_t *dest, const uint8_t *data, size_t len);
+
+/*
+ * ngtcp2_encode_ipv4 encodes binary form IPv4 address stored in
+ * |addr| to human readable text form in the buffer pointed by |dest|.
+ * The capacity of buffer must have enough length to store a text form
+ * plus a terminating NULL byte.  The resulting text form ends with
+ * NULL byte.  The function returns |dest|.
+ */
+uint8_t *ngtcp2_encode_ipv4(uint8_t *dest, const uint8_t *addr);
+
+/*
+ * ngtcp2_encode_ipv6 encodes binary form IPv6 address stored in
+ * |addr| to human readable text form in the buffer pointed by |dest|.
+ * The capacity of buffer must have enough length to store a text form
+ * plus a terminating NULL byte.  The resulting text form ends with
+ * NULL byte.  The function produces the canonical form of IPv6 text
+ * representation described in
+ * https://tools.ietf.org/html/rfc5952#section-4.  The function
+ * returns |dest|.
+ */
+uint8_t *ngtcp2_encode_ipv6(uint8_t *dest, const uint8_t *addr);
+
+/*
+ * ngtcp2_encode_printable_ascii encodes |data| of length |len| in
+ * |dest| in the following manner: printable ascii characters are
+ * copied as is.  The other characters are converted to ".".  It
+ * writes additional NULL bytes at the end of the buffer.  |dest| must
+ * have at least |len| + 1 bytes.  This function returns |dest|.
+ */
+char *ngtcp2_encode_printable_ascii(char *dest, const uint8_t *data,
+                                    size_t len);
+
+/*
+ * ngtcp2_cmemeq returns nonzero if the first |n| bytes of the buffers
+ * pointed by |a| and |b| are equal.  The comparison is done in a
+ * constant time manner.
+ */
+int ngtcp2_cmemeq(const uint8_t *a, const uint8_t *b, size_t n);
+
+#endif /* !defined(NGTCP2_STR_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_strm.c b/third_party/ngtcp2/lib/ngtcp2_strm.c
new file mode 100644 (file)
index 0000000..8ea969c
--- /dev/null
@@ -0,0 +1,775 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_strm.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_rtb.h"
+#include "ngtcp2_pkt.h"
+#include "ngtcp2_vec.h"
+#include "ngtcp2_frame_chain.h"
+
+void ngtcp2_strm_init(ngtcp2_strm *strm, int64_t stream_id, uint32_t flags,
+                      uint64_t max_rx_offset, uint64_t max_tx_offset,
+                      void *stream_user_data, ngtcp2_objalloc *frc_objalloc,
+                      const ngtcp2_mem *mem) {
+  strm->pe.index = NGTCP2_PQ_BAD_INDEX;
+  strm->cycle = 0;
+  strm->frc_objalloc = frc_objalloc;
+  strm->tx.acked_offset = NULL;
+  strm->tx.cont_acked_offset = 0;
+  strm->tx.streamfrq = NULL;
+  strm->tx.offset = 0;
+  strm->tx.max_offset = max_tx_offset;
+  strm->tx.last_blocked_offset = UINT64_MAX;
+  strm->tx.last_max_stream_data_ts = UINT64_MAX;
+  strm->tx.loss_count = 0;
+  strm->tx.last_lost_pkt_num = -1;
+  strm->tx.stop_sending_app_error_code = 0;
+  strm->tx.reset_stream_app_error_code = 0;
+  strm->rx.rob = NULL;
+  strm->rx.cont_offset = 0;
+  strm->rx.last_offset = 0;
+  strm->rx.max_offset = strm->rx.unsent_max_offset = strm->rx.window =
+    max_rx_offset;
+  strm->mem = mem;
+  strm->stream_id = stream_id;
+  strm->stream_user_data = stream_user_data;
+  strm->flags = flags;
+  strm->app_error_code = 0;
+}
+
+void ngtcp2_strm_free(ngtcp2_strm *strm) {
+  ngtcp2_ksl_it it;
+
+  if (strm == NULL) {
+    return;
+  }
+
+  if (strm->tx.streamfrq) {
+    for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it);
+         ngtcp2_ksl_it_next(&it)) {
+      ngtcp2_frame_chain_objalloc_del(ngtcp2_ksl_it_get(&it),
+                                      strm->frc_objalloc, strm->mem);
+    }
+
+    ngtcp2_ksl_free(strm->tx.streamfrq);
+    ngtcp2_mem_free(strm->mem, strm->tx.streamfrq);
+  }
+
+  if (strm->rx.rob) {
+    ngtcp2_rob_free(strm->rx.rob);
+    ngtcp2_mem_free(strm->mem, strm->rx.rob);
+  }
+
+  if (strm->tx.acked_offset) {
+    ngtcp2_gaptr_free(strm->tx.acked_offset);
+    ngtcp2_mem_free(strm->mem, strm->tx.acked_offset);
+  }
+}
+
+static int strm_rob_init(ngtcp2_strm *strm) {
+  int rv;
+  ngtcp2_rob *rob = ngtcp2_mem_malloc(strm->mem, sizeof(*rob));
+
+  if (rob == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  rv = ngtcp2_rob_init(rob, 8 * 1024, strm->mem);
+  if (rv != 0) {
+    ngtcp2_mem_free(strm->mem, rob);
+    return rv;
+  }
+
+  strm->rx.rob = rob;
+
+  return 0;
+}
+
+uint64_t ngtcp2_strm_rx_offset(const ngtcp2_strm *strm) {
+  if (strm->rx.rob == NULL) {
+    return strm->rx.cont_offset;
+  }
+  return ngtcp2_rob_first_gap_offset(strm->rx.rob);
+}
+
+/* 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) >= 5000;
+}
+
+int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data,
+                                size_t datalen, uint64_t offset) {
+  int rv;
+
+  if (strm->rx.rob == NULL) {
+    rv = strm_rob_init(strm);
+    if (rv != 0) {
+      return rv;
+    }
+
+    if (strm->rx.cont_offset) {
+      ngtcp2_rob_remove_prefix(strm->rx.rob, strm->rx.cont_offset);
+    }
+  }
+
+  if (strm_rob_heavily_fragmented(strm->rx.rob)) {
+    return NGTCP2_ERR_INTERNAL;
+  }
+
+  return ngtcp2_rob_push(strm->rx.rob, offset, data, datalen);
+}
+
+void ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset) {
+  if (strm->rx.rob == NULL) {
+    strm->rx.cont_offset = offset;
+    return;
+  }
+
+  ngtcp2_rob_remove_prefix(strm->rx.rob, offset);
+}
+
+void ngtcp2_strm_discard_reordered_data(ngtcp2_strm *strm) {
+  if (strm->rx.rob == NULL) {
+    return;
+  }
+
+  strm->rx.cont_offset = ngtcp2_strm_rx_offset(strm);
+
+  ngtcp2_rob_free(strm->rx.rob);
+  ngtcp2_mem_free(strm->mem, strm->rx.rob);
+  strm->rx.rob = NULL;
+}
+
+void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags) {
+  strm->flags |= flags & NGTCP2_STRM_FLAG_SHUT_RDWR;
+}
+
+static int strm_streamfrq_init(ngtcp2_strm *strm) {
+  ngtcp2_ksl *streamfrq = ngtcp2_mem_malloc(strm->mem, sizeof(*streamfrq));
+  if (streamfrq == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_ksl_init(streamfrq, ngtcp2_ksl_uint64_less,
+                  ngtcp2_ksl_uint64_less_search, sizeof(uint64_t), strm->mem);
+
+  strm->tx.streamfrq = streamfrq;
+
+  return 0;
+}
+
+int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc) {
+  int rv;
+
+  assert(frc->fr.type == NGTCP2_FRAME_STREAM ||
+         frc->fr.type == NGTCP2_FRAME_CRYPTO);
+  assert(frc->next == NULL);
+
+  if (strm->tx.streamfrq == NULL) {
+    rv = strm_streamfrq_init(strm);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &frc->fr.stream.offset,
+                           frc);
+}
+
+static int strm_streamfrq_unacked_pop(ngtcp2_strm *strm,
+                                      ngtcp2_frame_chain **pfrc) {
+  ngtcp2_frame_chain *frc, *nfrc;
+  ngtcp2_stream *fr, *nfr;
+  uint64_t offset, end_offset;
+  size_t idx, end_idx;
+  uint64_t base_offset, end_base_offset;
+  ngtcp2_range gap;
+  ngtcp2_vec *v;
+  int rv;
+  ngtcp2_ksl_it it;
+
+  *pfrc = NULL;
+
+  assert(strm->tx.streamfrq);
+  assert(ngtcp2_ksl_len(strm->tx.streamfrq));
+
+  for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it);) {
+    frc = ngtcp2_ksl_it_get(&it);
+    fr = &frc->fr.stream;
+
+    ngtcp2_ksl_remove_hint(strm->tx.streamfrq, &it, &it, &fr->offset);
+
+    idx = 0;
+    offset = fr->offset;
+    base_offset = 0;
+
+    gap = ngtcp2_strm_get_unacked_range_after(strm, offset);
+    if (gap.begin < offset) {
+      gap.begin = offset;
+    }
+
+    for (; idx < fr->datacnt && offset < gap.begin; ++idx) {
+      v = &fr->data[idx];
+      if (offset + v->len > gap.begin) {
+        base_offset = gap.begin - offset;
+        break;
+      }
+
+      offset += v->len;
+    }
+
+    if (idx == fr->datacnt) {
+      if (fr->fin) {
+        if (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) {
+          ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+          assert(ngtcp2_ksl_len(strm->tx.streamfrq) == 0);
+          return 0;
+        }
+
+        fr->offset += ngtcp2_vec_len(fr->data, fr->datacnt);
+        fr->datacnt = 0;
+
+        *pfrc = frc;
+
+        return 0;
+      }
+
+      if (fr->offset == 0 && fr->datacnt == 0 && strm->tx.offset == 0 &&
+          !(strm->flags & NGTCP2_STRM_FLAG_ANY_ACKED)) {
+        *pfrc = frc;
+
+        return 0;
+      }
+
+      ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+
+      continue;
+    }
+
+    assert(gap.begin == offset + base_offset);
+
+    end_idx = idx;
+    end_offset = offset;
+    end_base_offset = 0;
+
+    for (; end_idx < fr->datacnt; ++end_idx) {
+      v = &fr->data[end_idx];
+      if (end_offset + v->len > gap.end) {
+        end_base_offset = gap.end - end_offset;
+        break;
+      }
+
+      end_offset += v->len;
+    }
+
+    if (fr->offset == offset && base_offset == 0 && fr->datacnt == end_idx) {
+      *pfrc = frc;
+      return 0;
+    }
+
+    if (fr->datacnt == end_idx) {
+      memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx));
+
+      assert(fr->data[0].len > base_offset);
+
+      fr->offset = offset + base_offset;
+      fr->datacnt = end_idx - idx;
+      fr->data[0].base += base_offset;
+      fr->data[0].len -= (size_t)base_offset;
+
+      *pfrc = frc;
+
+      return 0;
+    }
+
+    rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
+      &nfrc, fr->datacnt - end_idx, strm->frc_objalloc, strm->mem);
+    if (rv != 0) {
+      ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+      return rv;
+    }
+
+    nfr = &nfrc->fr.stream;
+    memcpy(nfr->data, fr->data + end_idx,
+           sizeof(nfr->data[0]) * (fr->datacnt - end_idx));
+
+    assert(nfr->data[0].len > end_base_offset);
+
+    nfr->type = fr->type;
+    nfr->flags = 0;
+    nfr->fin = fr->fin;
+    nfr->stream_id = fr->stream_id;
+    nfr->offset = end_offset + end_base_offset;
+    nfr->datacnt = fr->datacnt - end_idx;
+    nfr->data[0].base += end_base_offset;
+    nfr->data[0].len -= (size_t)end_base_offset;
+
+    rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem);
+      ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+
+      return rv;
+    }
+
+    if (end_base_offset) {
+      ++end_idx;
+    }
+
+    memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx));
+
+    assert(fr->data[0].len > base_offset);
+
+    fr->fin = 0;
+    fr->offset = offset + base_offset;
+    fr->datacnt = end_idx - idx;
+
+    if (end_base_offset) {
+      assert(fr->data[fr->datacnt - 1].len > end_base_offset);
+      fr->data[fr->datacnt - 1].len = (size_t)end_base_offset;
+    }
+
+    fr->data[0].base += base_offset;
+    fr->data[0].len -= (size_t)base_offset;
+
+    *pfrc = frc;
+
+    return 0;
+  }
+
+  return 0;
+}
+
+int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc,
+                              size_t left) {
+  ngtcp2_stream *fr, *nfr;
+  ngtcp2_frame_chain *frc, *nfrc, *sfrc;
+  int rv;
+  size_t nmerged;
+  uint64_t datalen;
+  ngtcp2_vec a[NGTCP2_MAX_STREAM_DATACNT];
+  ngtcp2_vec b[NGTCP2_MAX_STREAM_DATACNT];
+  size_t acnt, bcnt;
+  uint64_t unacked_offset;
+
+  if (strm->tx.streamfrq == NULL || ngtcp2_ksl_len(strm->tx.streamfrq) == 0) {
+    *pfrc = NULL;
+    return 0;
+  }
+
+  rv = strm_streamfrq_unacked_pop(strm, &frc);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (frc == NULL) {
+    *pfrc = NULL;
+    return 0;
+  }
+
+  fr = &frc->fr.stream;
+  datalen = ngtcp2_vec_len(fr->data, fr->datacnt);
+
+  /* datalen could be zero if 0 length STREAM has been sent */
+  if (left == 0 && datalen) {
+    rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &fr->offset, frc);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+      return rv;
+    }
+
+    *pfrc = NULL;
+
+    return 0;
+  }
+
+  if (datalen > left) {
+    ngtcp2_vec_copy(a, fr->data, fr->datacnt);
+    acnt = fr->datacnt;
+
+    bcnt = 0;
+    ngtcp2_vec_split(b, &bcnt, a, &acnt, left, NGTCP2_MAX_STREAM_DATACNT);
+
+    assert(acnt > 0);
+    assert(bcnt > 0);
+
+    rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
+      &nfrc, bcnt, strm->frc_objalloc, strm->mem);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+      return rv;
+    }
+
+    nfr = &nfrc->fr.stream;
+    nfr->type = fr->type;
+    nfr->flags = 0;
+    nfr->fin = fr->fin;
+    nfr->stream_id = fr->stream_id;
+    nfr->offset = fr->offset + left;
+    nfr->datacnt = bcnt;
+    ngtcp2_vec_copy(nfr->data, b, bcnt);
+
+    rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem);
+      ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+
+      return rv;
+    }
+
+    rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
+      &nfrc, acnt, strm->frc_objalloc, strm->mem);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+      return rv;
+    }
+
+    nfr = &nfrc->fr.stream;
+    *nfr = *fr;
+    nfr->fin = 0;
+    nfr->datacnt = acnt;
+    ngtcp2_vec_copy(nfr->data, a, acnt);
+
+    ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+
+    *pfrc = nfrc;
+
+    return 0;
+  }
+
+  left -= (size_t)datalen;
+
+  ngtcp2_vec_copy(a, fr->data, fr->datacnt);
+  acnt = fr->datacnt;
+
+  for (; left && ngtcp2_ksl_len(strm->tx.streamfrq);) {
+    unacked_offset = ngtcp2_strm_streamfrq_unacked_offset(strm);
+    if (unacked_offset != fr->offset + datalen) {
+      assert(fr->offset + datalen < unacked_offset);
+      break;
+    }
+
+    rv = strm_streamfrq_unacked_pop(strm, &nfrc);
+    if (rv != 0) {
+      assert(ngtcp2_err_is_fatal(rv));
+      ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+      return rv;
+    }
+    if (nfrc == NULL) {
+      break;
+    }
+
+    nfr = &nfrc->fr.stream;
+
+    if (nfr->fin && nfr->datacnt == 0) {
+      fr->fin = 1;
+      ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem);
+      break;
+    }
+
+    bcnt = nfr->datacnt;
+
+    nmerged = ngtcp2_vec_merge(a, &acnt, nfr->data, &bcnt, left,
+                               NGTCP2_MAX_STREAM_DATACNT);
+    if (nmerged == 0) {
+      rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc);
+      if (rv != 0) {
+        assert(ngtcp2_err_is_fatal(rv));
+        ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem);
+        ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+
+        return rv;
+      }
+
+      break;
+    }
+
+    datalen += nmerged;
+    left -= nmerged;
+
+    if (bcnt == 0) {
+      fr->fin = nfr->fin;
+      ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem);
+      continue;
+    }
+
+    if (nfr->datacnt <= NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES ||
+        bcnt > NGTCP2_FRAME_CHAIN_STREAM_DATACNT_THRES) {
+      nfr->offset += nmerged;
+      nfr->datacnt = bcnt;
+
+      rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc);
+      if (rv != 0) {
+        ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem);
+        ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+        return rv;
+      }
+    } else {
+      rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
+        &sfrc, bcnt, strm->frc_objalloc, strm->mem);
+      if (rv != 0) {
+        ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem);
+        ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+        return rv;
+      }
+
+      sfrc->fr.stream = nfrc->fr.stream;
+      sfrc->fr.stream.offset += nmerged;
+      sfrc->fr.stream.datacnt = bcnt;
+      ngtcp2_vec_copy(sfrc->fr.stream.data, nfrc->fr.stream.data, bcnt);
+
+      ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem);
+
+      rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &sfrc->fr.stream.offset,
+                             sfrc);
+      if (rv != 0) {
+        ngtcp2_frame_chain_objalloc_del(sfrc, strm->frc_objalloc, strm->mem);
+        ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+        return rv;
+      }
+    }
+
+    break;
+  }
+
+  if (acnt == fr->datacnt) {
+    if (acnt > 0) {
+      fr->data[acnt - 1] = a[acnt - 1];
+    }
+
+    *pfrc = frc;
+
+    return 0;
+  }
+
+  assert(acnt > fr->datacnt);
+
+  rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new(
+    &nfrc, acnt, strm->frc_objalloc, strm->mem);
+  if (rv != 0) {
+    ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+    return rv;
+  }
+
+  nfr = &nfrc->fr.stream;
+  *nfr = *fr;
+  nfr->datacnt = acnt;
+  ngtcp2_vec_copy(nfr->data, a, acnt);
+
+  ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+
+  *pfrc = nfrc;
+
+  return 0;
+}
+
+uint64_t ngtcp2_strm_streamfrq_unacked_offset(const ngtcp2_strm *strm) {
+  ngtcp2_frame_chain *frc;
+  ngtcp2_stream *fr;
+  ngtcp2_range gap;
+  ngtcp2_ksl_it it;
+  uint64_t datalen;
+
+  assert(strm->tx.streamfrq);
+  assert(ngtcp2_ksl_len(strm->tx.streamfrq));
+
+  for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    frc = ngtcp2_ksl_it_get(&it);
+    fr = &frc->fr.stream;
+
+    gap = ngtcp2_strm_get_unacked_range_after(strm, fr->offset);
+
+    datalen = ngtcp2_vec_len(fr->data, fr->datacnt);
+
+    if (gap.begin <= fr->offset) {
+      return fr->offset;
+    }
+
+    if (gap.begin < fr->offset + datalen) {
+      return gap.begin;
+    }
+
+    if (fr->offset + datalen == gap.begin && fr->fin &&
+        !(strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED)) {
+      return fr->offset + datalen;
+    }
+  }
+
+  return (uint64_t)-1;
+}
+
+ngtcp2_frame_chain *ngtcp2_strm_streamfrq_top(const ngtcp2_strm *strm) {
+  ngtcp2_ksl_it it;
+
+  assert(strm->tx.streamfrq);
+  assert(ngtcp2_ksl_len(strm->tx.streamfrq));
+
+  it = ngtcp2_ksl_begin(strm->tx.streamfrq);
+
+  return ngtcp2_ksl_it_get(&it);
+}
+
+int ngtcp2_strm_streamfrq_empty(const ngtcp2_strm *strm) {
+  return strm->tx.streamfrq == NULL || ngtcp2_ksl_len(strm->tx.streamfrq) == 0;
+}
+
+void ngtcp2_strm_streamfrq_clear(ngtcp2_strm *strm) {
+  ngtcp2_frame_chain *frc;
+  ngtcp2_ksl_it it;
+
+  if (strm->tx.streamfrq == NULL) {
+    return;
+  }
+
+  for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it);
+       ngtcp2_ksl_it_next(&it)) {
+    frc = ngtcp2_ksl_it_get(&it);
+    ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem);
+  }
+
+  ngtcp2_ksl_clear(strm->tx.streamfrq);
+}
+
+int ngtcp2_strm_is_tx_queued(const ngtcp2_strm *strm) {
+  return strm->pe.index != NGTCP2_PQ_BAD_INDEX;
+}
+
+int ngtcp2_strm_is_all_tx_data_acked(const ngtcp2_strm *strm) {
+  if (strm->tx.acked_offset == NULL) {
+    return strm->tx.cont_acked_offset == strm->tx.offset;
+  }
+
+  return ngtcp2_gaptr_first_gap_offset(strm->tx.acked_offset) ==
+         strm->tx.offset;
+}
+
+int ngtcp2_strm_is_all_tx_data_fin_acked(const ngtcp2_strm *strm) {
+  return (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) &&
+         ngtcp2_strm_is_all_tx_data_acked(strm);
+}
+
+ngtcp2_range ngtcp2_strm_get_unacked_range_after(const ngtcp2_strm *strm,
+                                                 uint64_t offset) {
+  ngtcp2_range gap;
+
+  if (strm->tx.acked_offset == NULL) {
+    gap.begin = strm->tx.cont_acked_offset;
+    gap.end = UINT64_MAX;
+    return gap;
+  }
+
+  return ngtcp2_gaptr_get_first_gap_after(strm->tx.acked_offset, offset);
+}
+
+uint64_t ngtcp2_strm_get_acked_offset(const ngtcp2_strm *strm) {
+  if (strm->tx.acked_offset == NULL) {
+    return strm->tx.cont_acked_offset;
+  }
+
+  return ngtcp2_gaptr_first_gap_offset(strm->tx.acked_offset);
+}
+
+static int strm_acked_offset_init(ngtcp2_strm *strm) {
+  ngtcp2_gaptr *acked_offset =
+    ngtcp2_mem_malloc(strm->mem, sizeof(*acked_offset));
+
+  if (acked_offset == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  ngtcp2_gaptr_init(acked_offset, strm->mem);
+
+  strm->tx.acked_offset = acked_offset;
+
+  return 0;
+}
+
+int ngtcp2_strm_ack_data(ngtcp2_strm *strm, uint64_t offset, uint64_t len) {
+  int rv;
+
+  if (strm->tx.acked_offset == NULL) {
+    if (strm->tx.cont_acked_offset == offset) {
+      strm->tx.cont_acked_offset += len;
+      return 0;
+    }
+
+    rv = strm_acked_offset_init(strm);
+    if (rv != 0) {
+      return rv;
+    }
+
+    rv =
+      ngtcp2_gaptr_push(strm->tx.acked_offset, 0, strm->tx.cont_acked_offset);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return ngtcp2_gaptr_push(strm->tx.acked_offset, offset, len);
+}
+
+void ngtcp2_strm_set_app_error_code(ngtcp2_strm *strm,
+                                    uint64_t app_error_code) {
+  if (strm->flags & NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET) {
+    return;
+  }
+
+  assert(0 == strm->app_error_code);
+
+  strm->flags |= NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET;
+  strm->app_error_code = app_error_code;
+}
+
+int ngtcp2_strm_require_retransmit_reset_stream(const ngtcp2_strm *strm) {
+  return !ngtcp2_strm_is_all_tx_data_fin_acked(strm);
+}
+
+int ngtcp2_strm_require_retransmit_stop_sending(const ngtcp2_strm *strm) {
+  return !(strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) ||
+         ngtcp2_strm_rx_offset(strm) != strm->rx.last_offset;
+}
+
+int ngtcp2_strm_require_retransmit_max_stream_data(
+  const ngtcp2_strm *strm, const ngtcp2_max_stream_data *fr) {
+  return fr->max_stream_data == strm->rx.max_offset &&
+         !(strm->flags &
+           (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING));
+}
+
+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 &&
+         !(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_strm.h b/third_party/ngtcp2/lib/ngtcp2_strm.h
new file mode 100644 (file)
index 0000000..c72f8b9
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_STRM_H
+#define NGTCP2_STRM_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_rob.h"
+#include "ngtcp2_map.h"
+#include "ngtcp2_gaptr.h"
+#include "ngtcp2_ksl.h"
+#include "ngtcp2_pq.h"
+#include "ngtcp2_pkt.h"
+
+typedef struct ngtcp2_frame_chain ngtcp2_frame_chain;
+
+/* NGTCP2_STRM_FLAG_NONE indicates that no flag is set. */
+#define NGTCP2_STRM_FLAG_NONE 0x00u
+/* NGTCP2_STRM_FLAG_SHUT_RD indicates that further reception of stream
+   data is not allowed. */
+#define NGTCP2_STRM_FLAG_SHUT_RD 0x01u
+/* NGTCP2_STRM_FLAG_SHUT_WR indicates that further transmission of
+   stream data is not allowed. */
+#define NGTCP2_STRM_FLAG_SHUT_WR 0x02u
+#define NGTCP2_STRM_FLAG_SHUT_RDWR                                             \
+  (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_SHUT_WR)
+/* NGTCP2_STRM_FLAG_RESET_STREAM indicates that RESET_STREAM is sent
+   from the local endpoint.  In this case, NGTCP2_STRM_FLAG_SHUT_WR is
+   also set. */
+#define NGTCP2_STRM_FLAG_RESET_STREAM 0x04u
+/* NGTCP2_STRM_FLAG_RESET_STREAM_RECVED indicates that RESET_STREAM is
+   received from the remote endpoint.  In this case,
+   NGTCP2_STRM_FLAG_SHUT_RD is also set. */
+#define NGTCP2_STRM_FLAG_RESET_STREAM_RECVED 0x08u
+/* NGTCP2_STRM_FLAG_STOP_SENDING indicates that STOP_SENDING is sent
+   from the local endpoint. */
+#define NGTCP2_STRM_FLAG_STOP_SENDING 0x10u
+/* NGTCP2_STRM_FLAG_RESET_STREAM_ACKED indicates that the outgoing
+   RESET_STREAM is acknowledged by peer. */
+#define NGTCP2_STRM_FLAG_RESET_STREAM_ACKED 0x20u
+/* NGTCP2_STRM_FLAG_FIN_ACKED indicates that a STREAM with FIN bit set
+   is acknowledged by a remote endpoint. */
+#define NGTCP2_STRM_FLAG_FIN_ACKED 0x40u
+/* NGTCP2_STRM_FLAG_ANY_ACKED indicates that any portion of stream
+   data, including 0 length segment, is acknowledged. */
+#define NGTCP2_STRM_FLAG_ANY_ACKED 0x80u
+/* NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET indicates that app_error_code
+   field is set.  This resolves the ambiguity that the initial
+   app_error_code value 0 might be a proper application error code.
+   In this case, without this flag, we are unable to distinguish
+   assigned value from unassigned one.  */
+#define NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET 0x100u
+/* NGTCP2_STRM_FLAG_SEND_STOP_SENDING is set when STOP_SENDING frame
+   should be sent. */
+#define NGTCP2_STRM_FLAG_SEND_STOP_SENDING 0x200u
+/* NGTCP2_STRM_FLAG_SEND_RESET_STREAM is set when RESET_STREAM frame
+   should be sent. */
+#define NGTCP2_STRM_FLAG_SEND_RESET_STREAM 0x400u
+/* NGTCP2_STRM_FLAG_STOP_SENDING_RECVED indicates that STOP_SENDING is
+   received from the remote endpoint.  In this case,
+   NGTCP2_STRM_FLAG_SHUT_WR is also set. */
+#define NGTCP2_STRM_FLAG_STOP_SENDING_RECVED 0x800u
+/* NGTCP2_STRM_FLAG_ANY_SENT indicates that any STREAM frame,
+   including empty one, has been sent. */
+#define NGTCP2_STRM_FLAG_ANY_SENT 0x1000u
+
+typedef struct ngtcp2_strm ngtcp2_strm;
+
+struct ngtcp2_strm {
+  union {
+    struct {
+      ngtcp2_pq_entry pe;
+      uint64_t cycle;
+      ngtcp2_objalloc *frc_objalloc;
+
+      struct {
+        /* acked_offset tracks acknowledged outgoing data. */
+        ngtcp2_gaptr *acked_offset;
+        /* cont_acked_offset is the offset that all data up to this offset
+           is acknowledged by a remote endpoint.  It is used until the
+           remote endpoint acknowledges data in out-of-order.  After that,
+           acked_offset is used instead. */
+        uint64_t cont_acked_offset;
+        /* streamfrq contains STREAM or CRYPTO frame for
+           retransmission.  The flow control credits have already been
+           paid when they are transmitted first time.  There are no
+           restriction regarding flow control for retransmission. */
+        ngtcp2_ksl *streamfrq;
+        /* offset is the next offset of new outgoing data.  In other
+           words, it is the number of bytes sent in this stream
+           without duplication. */
+        uint64_t offset;
+        /* max_tx_offset is the maximum offset that local endpoint can
+           send for this stream. */
+        uint64_t max_offset;
+        /* last_blocked_offset is the largest offset where the
+           transmission of stream data is blocked. */
+        uint64_t last_blocked_offset;
+        /* last_max_stream_data_ts is the timestamp when last
+           MAX_STREAM_DATA frame is sent. */
+        ngtcp2_tstamp last_max_stream_data_ts;
+        /* loss_count is the number of packets that contain STREAM
+           frame for this stream and are declared to be lost.  It may
+           include the spurious losses.  It does not include a packet
+           whose contents have been reclaimed for PTO and which is
+           later declared to be lost.  Those data are not blocked by
+           the flow control and will be sent immediately if no other
+           restrictions are applied. */
+        size_t loss_count;
+        /* last_lost_pkt_num is the packet number of the packet that
+           is counted to loss_count.  It is used to avoid to count
+           multiple STREAM frames in one lost packet. */
+        int64_t last_lost_pkt_num;
+        /* stop_sending_app_error_code is the application specific
+           error code that is sent along with STOP_SENDING. */
+        uint64_t stop_sending_app_error_code;
+        /* reset_stream_app_error_code is the application specific
+           error code that is sent along with RESET_STREAM. */
+        uint64_t reset_stream_app_error_code;
+      } tx;
+
+      struct {
+        /* rob is the reorder buffer for incoming stream data.  The data
+           received in out of order is buffered and sorted by its offset
+           in this object. */
+        ngtcp2_rob *rob;
+        /* cont_offset is the largest offset of consecutive data.  It is
+           used until the endpoint receives out-of-order data.  After
+           that, rob is used to track the offset and data. */
+        uint64_t cont_offset;
+        /* last_offset is the largest offset of stream data received for
+           this stream. */
+        uint64_t last_offset;
+        /* max_offset is the maximum offset that remote endpoint can send
+           to this stream. */
+        uint64_t max_offset;
+        /* unsent_max_offset is the maximum offset that remote endpoint
+           can send to this stream, and it is not notified to the remote
+           endpoint.  unsent_max_offset >= max_offset must be hold. */
+        uint64_t unsent_max_offset;
+        /* window is the stream-level flow control window size. */
+        uint64_t window;
+      } rx;
+
+      const ngtcp2_mem *mem;
+      int64_t stream_id;
+      void *stream_user_data;
+      /* flags is bit-wise OR of zero or more of NGTCP2_STRM_FLAG_*. */
+      uint32_t flags;
+      /* app_error_code is an error code the local endpoint sent in
+         RESET_STREAM or STOP_SENDING, or received from a remote endpoint
+         in RESET_STREAM or STOP_SENDING.  First application error code is
+         chosen and when set, NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET flag is
+         set in flags field. */
+      uint64_t app_error_code;
+    };
+
+    ngtcp2_opl_entry oplent;
+  };
+};
+
+/*
+ * ngtcp2_strm_init initializes |strm|.
+ */
+void ngtcp2_strm_init(ngtcp2_strm *strm, int64_t stream_id, uint32_t flags,
+                      uint64_t max_rx_offset, uint64_t max_tx_offset,
+                      void *stream_user_data, ngtcp2_objalloc *frc_objalloc,
+                      const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_strm_free deallocates memory allocated for |strm|.  This
+ * function does not free the memory pointed by |strm| itself.
+ */
+void ngtcp2_strm_free(ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_rx_offset returns the minimum offset of stream data
+ * which is not received yet.
+ */
+uint64_t ngtcp2_strm_rx_offset(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_recv_reordering handles reordered data.
+ *
+ * It returns 0 if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data,
+                                size_t datalen, uint64_t offset);
+
+/*
+ * ngtcp2_strm_update_rx_offset tells that data up to |offset| bytes
+ * are received in order.
+ */
+void ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset);
+
+/*
+ * ngtcp2_strm_discard_reordered_data discards all buffered reordered
+ * data.
+ */
+void ngtcp2_strm_discard_reordered_data(ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_shutdown shutdowns |strm|.  |flags| should be one of
+ * NGTCP2_STRM_FLAG_SHUT_RD, NGTCP2_STRM_FLAG_SHUT_WR, and
+ * NGTCP2_STRM_FLAG_SHUT_RDWR.
+ */
+void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags);
+
+/*
+ * ngtcp2_strm_streamfrq_push pushes |frc| to strm->tx.streamfrq for
+ * retransmission.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc);
+
+/*
+ * ngtcp2_strm_streamfrq_pop assigns a ngtcp2_frame_chain that only
+ * contains unacknowledged stream data with smallest offset to |*pfrc|
+ * for retransmission.  The assigned ngtcp2_frame_chain has stream
+ * data at most |left| bytes.  strm->tx.streamfrq is adjusted to
+ * exclude the portion of data included in it.  If there is no stream
+ * data to send, this function returns 0 and |*pfrc| is NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory
+ */
+int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc,
+                              size_t left);
+
+/*
+ * ngtcp2_strm_streamfrq_unacked_offset returns the smallest offset of
+ * unacknowledged stream data held in strm->tx.streamfrq.
+ */
+uint64_t ngtcp2_strm_streamfrq_unacked_offset(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_streamfrq_top returns the first ngtcp2_frame_chain.
+ * The queue must not be empty.
+ */
+ngtcp2_frame_chain *ngtcp2_strm_streamfrq_top(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_streamfrq_empty returns nonzero if streamfrq is empty.
+ */
+int ngtcp2_strm_streamfrq_empty(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_streamfrq_clear removes all frames from streamfrq.
+ */
+void ngtcp2_strm_streamfrq_clear(ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_is_tx_queued returns nonzero if |strm| is queued.
+ */
+int ngtcp2_strm_is_tx_queued(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_is_all_tx_data_acked returns nonzero if all outgoing
+ * data for |strm| which have sent so far have been acknowledged.
+ */
+int ngtcp2_strm_is_all_tx_data_acked(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_is_all_tx_data_fin_acked behaves like
+ * ngtcp2_strm_is_all_tx_data_acked, but it also requires that STREAM
+ * frame with fin bit set is acknowledged.
+ */
+int ngtcp2_strm_is_all_tx_data_fin_acked(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_get_unacked_range_after returns the range that is not
+ * acknowledged yet and includes or comes after |offset|.
+ */
+ngtcp2_range ngtcp2_strm_get_unacked_range_after(const ngtcp2_strm *strm,
+                                                 uint64_t offset);
+
+/*
+ * ngtcp2_strm_get_acked_offset returns offset, that is the data up to
+ * this offset have been acknowledged by a remote endpoint.  It
+ * returns 0 if no data is acknowledged.
+ */
+uint64_t ngtcp2_strm_get_acked_offset(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_ack_data tells |strm| that the data [|offset|, |offset|
+ * + |len|) is acknowledged by a remote endpoint.
+ */
+int ngtcp2_strm_ack_data(ngtcp2_strm *strm, uint64_t offset, uint64_t len);
+
+/*
+ * ngtcp2_strm_set_app_error_code sets |app_error_code| to |strm| and
+ * set NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET flag.  If the flag is
+ * already set, this function does nothing.
+ */
+void ngtcp2_strm_set_app_error_code(ngtcp2_strm *strm, uint64_t app_error_code);
+
+/*
+ * ngtcp2_strm_require_retransmit_reset_stream returns nonzero if
+ * RESET_STREAM frame should be retransmitted.
+ */
+int ngtcp2_strm_require_retransmit_reset_stream(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_require_retransmit_stop_sending returns nonzero if
+ * STOP_SENDING frame should be retransmitted.
+ */
+int ngtcp2_strm_require_retransmit_stop_sending(const ngtcp2_strm *strm);
+
+/*
+ * ngtcp2_strm_require_retransmit_max_stream_data returns nonzero if
+ * MAX_STREAM_DATA frame should be retransmitted.
+ */
+int ngtcp2_strm_require_retransmit_max_stream_data(
+  const ngtcp2_strm *strm, const ngtcp2_max_stream_data *fr);
+
+/*
+ * ngtcp2_strm_require_retransmit_stream_data_blocked returns nonzero
+ * if STREAM_DATA_BLOCKED frame frame should be retransmitted.
+ */
+int ngtcp2_strm_require_retransmit_stream_data_blocked(
+  const ngtcp2_strm *strm, const ngtcp2_stream_data_blocked *fr);
+
+#endif /* !defined(NGTCP2_STRM_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_transport_params.c b/third_party/ngtcp2/lib/ngtcp2_transport_params.c
new file mode 100644 (file)
index 0000000..ca51753
--- /dev/null
@@ -0,0 +1,889 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2023 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_transport_params.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_conv.h"
+#include "ngtcp2_str.h"
+#include "ngtcp2_mem.h"
+#include "ngtcp2_unreachable.h"
+
+void ngtcp2_transport_params_default_versioned(
+  int transport_params_version, ngtcp2_transport_params *params) {
+  size_t len;
+
+  switch (transport_params_version) {
+  case NGTCP2_TRANSPORT_PARAMS_VERSION:
+    len = sizeof(*params);
+
+    break;
+  default:
+    ngtcp2_unreachable();
+  }
+
+  memset(params, 0, len);
+
+  switch (transport_params_version) {
+  case NGTCP2_TRANSPORT_PARAMS_VERSION:
+    params->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE;
+    params->active_connection_id_limit =
+      NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT;
+    params->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT;
+    params->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY;
+
+    break;
+  }
+}
+
+/*
+ * varint_paramlen returns the length of a single transport parameter
+ * which has variable integer in its parameter.
+ */
+static size_t varint_paramlen(ngtcp2_transport_param_id id, uint64_t param) {
+  size_t valuelen = ngtcp2_put_uvarintlen(param);
+  return ngtcp2_put_uvarintlen(id) + ngtcp2_put_uvarintlen(valuelen) + valuelen;
+}
+
+/*
+ * write_varint_param writes parameter |id| of the given |value| in
+ * varint encoding.  It returns p + the number of bytes written.
+ */
+static uint8_t *write_varint_param(uint8_t *p, ngtcp2_transport_param_id id,
+                                   uint64_t value) {
+  p = ngtcp2_put_uvarint(p, id);
+  p = ngtcp2_put_uvarint(p, ngtcp2_put_uvarintlen(value));
+  return ngtcp2_put_uvarint(p, value);
+}
+
+/*
+ * zero_paramlen returns the length of a single transport parameter
+ * which has zero length value in its parameter.
+ */
+static size_t zero_paramlen(ngtcp2_transport_param_id id) {
+  return ngtcp2_put_uvarintlen(id) + 1;
+}
+
+/*
+ * write_zero_param writes parameter |id| that has zero length value.
+ * It returns p + the number of bytes written.
+ */
+static uint8_t *write_zero_param(uint8_t *p, ngtcp2_transport_param_id id) {
+  p = ngtcp2_put_uvarint(p, id);
+  *p++ = 0;
+
+  return p;
+}
+
+/*
+ * cid_paramlen returns the length of a single transport parameter
+ * which has |cid| as value.
+ */
+static size_t cid_paramlen(ngtcp2_transport_param_id id,
+                           const ngtcp2_cid *cid) {
+  return ngtcp2_put_uvarintlen(id) + ngtcp2_put_uvarintlen(cid->datalen) +
+         cid->datalen;
+}
+
+/*
+ * write_cid_param writes parameter |id| of the given |cid|.  It
+ * returns p + the number of bytes written.
+ */
+static uint8_t *write_cid_param(uint8_t *p, ngtcp2_transport_param_id id,
+                                const ngtcp2_cid *cid) {
+  assert(cid->datalen == 0 || cid->datalen >= NGTCP2_MIN_CIDLEN);
+  assert(cid->datalen <= NGTCP2_MAX_CIDLEN);
+
+  p = ngtcp2_put_uvarint(p, id);
+  p = ngtcp2_put_uvarint(p, cid->datalen);
+  if (cid->datalen) {
+    p = ngtcp2_cpymem(p, cid->data, cid->datalen);
+  }
+  return p;
+}
+
+static const uint8_t empty_address[16];
+
+ngtcp2_ssize ngtcp2_transport_params_encode_versioned(
+  uint8_t *dest, size_t destlen, int transport_params_version,
+  const ngtcp2_transport_params *params) {
+  uint8_t *p;
+  size_t len = 0;
+  /* For some reason, gcc 7.3.0 requires this initialization. */
+  size_t preferred_addrlen = 0;
+  size_t version_infolen = 0;
+  const ngtcp2_sockaddr_in *sa_in;
+  const ngtcp2_sockaddr_in6 *sa_in6;
+  ngtcp2_transport_params paramsbuf;
+
+  params = ngtcp2_transport_params_convert_to_latest(
+    &paramsbuf, transport_params_version, params);
+
+  if (params->original_dcid_present) {
+    len +=
+      cid_paramlen(NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID,
+                   &params->original_dcid);
+  }
+
+  if (params->stateless_reset_token_present) {
+    len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN) +
+           ngtcp2_put_uvarintlen(NGTCP2_STATELESS_RESET_TOKENLEN) +
+           NGTCP2_STATELESS_RESET_TOKENLEN;
+  }
+
+  if (params->preferred_addr_present) {
+    assert(params->preferred_addr.cid.datalen >= NGTCP2_MIN_CIDLEN);
+    assert(params->preferred_addr.cid.datalen <= NGTCP2_MAX_CIDLEN);
+    preferred_addrlen = 4 /* ipv4Address */ + 2 /* ipv4Port */ +
+                        16 /* ipv6Address */ + 2 /* ipv6Port */
+                        + 1 + params->preferred_addr.cid.datalen /* CID */ +
+                        NGTCP2_STATELESS_RESET_TOKENLEN;
+    len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS) +
+           ngtcp2_put_uvarintlen(preferred_addrlen) + preferred_addrlen;
+  }
+  if (params->retry_scid_present) {
+    len += cid_paramlen(NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID,
+                        &params->retry_scid);
+  }
+
+  if (params->initial_scid_present) {
+    len += cid_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID,
+                        &params->initial_scid);
+  }
+
+  if (params->initial_max_stream_data_bidi_local) {
+    len +=
+      varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
+                      params->initial_max_stream_data_bidi_local);
+  }
+  if (params->initial_max_stream_data_bidi_remote) {
+    len += varint_paramlen(
+      NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
+      params->initial_max_stream_data_bidi_remote);
+  }
+  if (params->initial_max_stream_data_uni) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI,
+                           params->initial_max_stream_data_uni);
+  }
+  if (params->initial_max_data) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA,
+                           params->initial_max_data);
+  }
+  if (params->initial_max_streams_bidi) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI,
+                           params->initial_max_streams_bidi);
+  }
+  if (params->initial_max_streams_uni) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI,
+                           params->initial_max_streams_uni);
+  }
+  if (params->max_udp_payload_size !=
+      NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE,
+                           params->max_udp_payload_size);
+  }
+  if (params->ack_delay_exponent != NGTCP2_DEFAULT_ACK_DELAY_EXPONENT) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT,
+                           params->ack_delay_exponent);
+  }
+  if (params->disable_active_migration) {
+    len += zero_paramlen(NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION);
+  }
+  if (params->max_ack_delay != NGTCP2_DEFAULT_MAX_ACK_DELAY) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY,
+                           params->max_ack_delay / NGTCP2_MILLISECONDS);
+  }
+  if (params->max_idle_timeout) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT,
+                           params->max_idle_timeout / NGTCP2_MILLISECONDS);
+  }
+  if (params->active_connection_id_limit &&
+      params->active_connection_id_limit !=
+        NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT,
+                           params->active_connection_id_limit);
+  }
+  if (params->max_datagram_frame_size) {
+    len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE,
+                           params->max_datagram_frame_size);
+  }
+  if (params->grease_quic_bit) {
+    len += zero_paramlen(NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT);
+  }
+  if (params->version_info_present) {
+    version_infolen =
+      sizeof(uint32_t) + params->version_info.available_versionslen;
+    len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION) +
+           ngtcp2_put_uvarintlen(version_infolen) + version_infolen;
+  }
+
+  if (dest == NULL && destlen == 0) {
+    return (ngtcp2_ssize)len;
+  }
+
+  if (destlen < len) {
+    return NGTCP2_ERR_NOBUF;
+  }
+
+  p = dest;
+
+  if (params->original_dcid_present) {
+    p = write_cid_param(
+      p, NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID,
+      &params->original_dcid);
+  }
+
+  if (params->stateless_reset_token_present) {
+    p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN);
+    p = ngtcp2_put_uvarint(p, sizeof(params->stateless_reset_token));
+    p = ngtcp2_cpymem(p, params->stateless_reset_token,
+                      sizeof(params->stateless_reset_token));
+  }
+
+  if (params->preferred_addr_present) {
+    p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS);
+    p = ngtcp2_put_uvarint(p, preferred_addrlen);
+
+    if (params->preferred_addr.ipv4_present) {
+      sa_in = &params->preferred_addr.ipv4;
+      p = ngtcp2_cpymem(p, &sa_in->sin_addr, sizeof(sa_in->sin_addr));
+      p = ngtcp2_put_uint16(p, sa_in->sin_port);
+    } else {
+      p = ngtcp2_cpymem(p, empty_address, sizeof(sa_in->sin_addr));
+      p = ngtcp2_put_uint16(p, 0);
+    }
+
+    if (params->preferred_addr.ipv6_present) {
+      sa_in6 = &params->preferred_addr.ipv6;
+      p = ngtcp2_cpymem(p, &sa_in6->sin6_addr, sizeof(sa_in6->sin6_addr));
+      p = ngtcp2_put_uint16(p, sa_in6->sin6_port);
+    } else {
+      p = ngtcp2_cpymem(p, empty_address, sizeof(sa_in6->sin6_addr));
+      p = ngtcp2_put_uint16(p, 0);
+    }
+
+    *p++ = (uint8_t)params->preferred_addr.cid.datalen;
+    if (params->preferred_addr.cid.datalen) {
+      p = ngtcp2_cpymem(p, params->preferred_addr.cid.data,
+                        params->preferred_addr.cid.datalen);
+    }
+    p = ngtcp2_cpymem(p, params->preferred_addr.stateless_reset_token,
+                      sizeof(params->preferred_addr.stateless_reset_token));
+  }
+
+  if (params->retry_scid_present) {
+    p = write_cid_param(p, NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID,
+                        &params->retry_scid);
+  }
+
+  if (params->initial_scid_present) {
+    p = write_cid_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID,
+                        &params->initial_scid);
+  }
+
+  if (params->initial_max_stream_data_bidi_local) {
+    p = write_varint_param(
+      p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
+      params->initial_max_stream_data_bidi_local);
+  }
+
+  if (params->initial_max_stream_data_bidi_remote) {
+    p = write_varint_param(
+      p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
+      params->initial_max_stream_data_bidi_remote);
+  }
+
+  if (params->initial_max_stream_data_uni) {
+    p =
+      write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI,
+                         params->initial_max_stream_data_uni);
+  }
+
+  if (params->initial_max_data) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA,
+                           params->initial_max_data);
+  }
+
+  if (params->initial_max_streams_bidi) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI,
+                           params->initial_max_streams_bidi);
+  }
+
+  if (params->initial_max_streams_uni) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI,
+                           params->initial_max_streams_uni);
+  }
+
+  if (params->max_udp_payload_size !=
+      NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE,
+                           params->max_udp_payload_size);
+  }
+
+  if (params->ack_delay_exponent != NGTCP2_DEFAULT_ACK_DELAY_EXPONENT) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT,
+                           params->ack_delay_exponent);
+  }
+
+  if (params->disable_active_migration) {
+    p = write_zero_param(p, NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION);
+  }
+
+  if (params->max_ack_delay != NGTCP2_DEFAULT_MAX_ACK_DELAY) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY,
+                           params->max_ack_delay / NGTCP2_MILLISECONDS);
+  }
+
+  if (params->max_idle_timeout) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT,
+                           params->max_idle_timeout / NGTCP2_MILLISECONDS);
+  }
+
+  if (params->active_connection_id_limit &&
+      params->active_connection_id_limit !=
+        NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT,
+                           params->active_connection_id_limit);
+  }
+
+  if (params->max_datagram_frame_size) {
+    p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE,
+                           params->max_datagram_frame_size);
+  }
+
+  if (params->grease_quic_bit) {
+    p = write_zero_param(p, NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT);
+  }
+
+  if (params->version_info_present) {
+    p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION);
+    p = ngtcp2_put_uvarint(p, version_infolen);
+    p = ngtcp2_put_uint32be(p, params->version_info.chosen_version);
+    if (params->version_info.available_versionslen) {
+      p = ngtcp2_cpymem(p, params->version_info.available_versions,
+                        params->version_info.available_versionslen);
+    }
+  }
+
+  assert((size_t)(p - dest) == len);
+
+  return (ngtcp2_ssize)len;
+}
+
+/*
+ * decode_varint decodes a single varint from the buffer pointed by
+ * |*pp| of length |end - *pp|.  If it decodes an integer
+ * successfully, it stores the integer in |*pdest|, increment |*pp| by
+ * the number of bytes read from |*pp|, and returns 0.  Otherwise it
+ * returns -1.
+ */
+static int decode_varint(uint64_t *pdest, const uint8_t **pp,
+                         const uint8_t *end) {
+  const uint8_t *p = *pp;
+  size_t len;
+
+  if (p == end) {
+    return -1;
+  }
+
+  len = ngtcp2_get_uvarintlen(p);
+  if ((size_t)(end - p) < len) {
+    return -1;
+  }
+
+  *pp = ngtcp2_get_uvarint(pdest, p);
+
+  return 0;
+}
+
+/*
+ * decode_varint_param decodes length prefixed value from the buffer
+ * pointed by |*pp| of length |end - *pp|.  The length and value are
+ * encoded in varint form.  If it decodes a value successfully, it
+ * stores the value in |*pdest|, increment |*pp| by the number of
+ * bytes read from |*pp|, and returns 0.  Otherwise it returns -1.
+ */
+static int decode_varint_param(uint64_t *pdest, const uint8_t **pp,
+                               const uint8_t *end) {
+  const uint8_t *p = *pp;
+  uint64_t valuelen;
+
+  if (decode_varint(&valuelen, &p, end) != 0) {
+    return -1;
+  }
+
+  if (p == end) {
+    return -1;
+  }
+
+  if ((uint64_t)(end - p) < valuelen) {
+    return -1;
+  }
+
+  if (ngtcp2_get_uvarintlen(p) != valuelen) {
+    return -1;
+  }
+
+  *pp = ngtcp2_get_uvarint(pdest, p);
+
+  return 0;
+}
+
+/*
+ * decode_zero_param decodes zero length value from the buffer pointed
+ * by |*pp| of length |end - *pp|.  The length is encoded in varint
+ * form.  If it decodes zero length value successfully, it increments
+ * |*pp| by 1, and returns 0.  Otherwise it returns -1.
+ */
+static int decode_zero_param(const uint8_t **pp, const uint8_t *end) {
+  if (*pp == end || **pp != 0) {
+    return -1;
+  }
+
+  ++*pp;
+
+  return 0;
+}
+
+/*
+ * decode_cid_param decodes length prefixed ngtcp2_cid from the buffer
+ * pointed by |*pp| of length |end - *pp|.  The length is encoded in
+ * varint form.  If it decodes a value successfully, it stores the
+ * value in |*pdest|, increment |*pp| by the number of read from
+ * |*pp|, and returns the number of bytes read.  Otherwise it returns
+ * the one of the negative error code:
+ *
+ * NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM
+ *     Could not decode Connection ID.
+ */
+static int decode_cid_param(ngtcp2_cid *pdest, const uint8_t **pp,
+                            const uint8_t *end) {
+  const uint8_t *p = *pp;
+  uint64_t valuelen;
+
+  if (decode_varint(&valuelen, &p, end) != 0) {
+    return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+  }
+
+  if ((valuelen != 0 && valuelen < NGTCP2_MIN_CIDLEN) ||
+      valuelen > NGTCP2_MAX_CIDLEN || (size_t)(end - p) < valuelen) {
+    return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+  }
+
+  ngtcp2_cid_init(pdest, p, (size_t)valuelen);
+
+  p += valuelen;
+
+  *pp = p;
+
+  return 0;
+}
+
+int ngtcp2_transport_params_decode_versioned(int transport_params_version,
+                                             ngtcp2_transport_params *dest,
+                                             const uint8_t *data,
+                                             size_t datalen) {
+  const uint8_t *p, *end, *lend;
+  size_t len;
+  uint64_t param_type;
+  uint64_t valuelen;
+  int rv;
+  ngtcp2_sockaddr_in *sa_in;
+  ngtcp2_sockaddr_in6 *sa_in6;
+  uint32_t version;
+  ngtcp2_transport_params *params, paramsbuf;
+
+  if (transport_params_version == NGTCP2_TRANSPORT_PARAMS_VERSION) {
+    params = dest;
+  } else {
+    params = &paramsbuf;
+  }
+
+  /* Set default values */
+  memset(params, 0, sizeof(*params));
+  params->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE;
+  params->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT;
+  params->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY;
+  params->active_connection_id_limit =
+    NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT;
+
+  p = end = data;
+
+  if (datalen) {
+    end += datalen;
+  }
+
+  for (; (size_t)(end - p) >= 2;) {
+    if (decode_varint(&param_type, &p, end) != 0) {
+      return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+    }
+
+    switch (param_type) {
+    case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL:
+      if (decode_varint_param(&params->initial_max_stream_data_bidi_local, &p,
+                              end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE:
+      if (decode_varint_param(&params->initial_max_stream_data_bidi_remote, &p,
+                              end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI:
+      if (decode_varint_param(&params->initial_max_stream_data_uni, &p, end) !=
+          0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA:
+      if (decode_varint_param(&params->initial_max_data, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI:
+      if (decode_varint_param(&params->initial_max_streams_bidi, &p, end) !=
+          0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if (params->initial_max_streams_bidi > NGTCP2_MAX_STREAMS) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI:
+      if (decode_varint_param(&params->initial_max_streams_uni, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if (params->initial_max_streams_uni > NGTCP2_MAX_STREAMS) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT:
+      if (decode_varint_param(&params->max_idle_timeout, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      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) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN:
+      if (decode_varint(&valuelen, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if ((size_t)valuelen != sizeof(params->stateless_reset_token)) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if ((size_t)(end - p) < sizeof(params->stateless_reset_token)) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+
+      p = ngtcp2_get_bytes(params->stateless_reset_token, p,
+                           sizeof(params->stateless_reset_token));
+      params->stateless_reset_token_present = 1;
+
+      break;
+    case NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT:
+      if (decode_varint_param(&params->ack_delay_exponent, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if (params->ack_delay_exponent > 20) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS:
+      if (decode_varint(&valuelen, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if ((size_t)(end - p) < valuelen) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      len = 4 /* ipv4Address */ + 2 /* ipv4Port */ + 16 /* ipv6Address */ +
+            2 /* ipv6Port */
+            + 1 /* cid length */ + NGTCP2_STATELESS_RESET_TOKENLEN;
+      if (valuelen < len) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+
+      sa_in = &params->preferred_addr.ipv4;
+
+      p = ngtcp2_get_bytes(&sa_in->sin_addr, p, sizeof(sa_in->sin_addr));
+      p = ngtcp2_get_uint16(&sa_in->sin_port, p);
+
+      if (sa_in->sin_port || memcmp(empty_address, &sa_in->sin_addr,
+                                    sizeof(sa_in->sin_addr)) != 0) {
+        sa_in->sin_family = NGTCP2_AF_INET;
+        params->preferred_addr.ipv4_present = 1;
+      }
+
+      sa_in6 = &params->preferred_addr.ipv6;
+
+      p = ngtcp2_get_bytes(&sa_in6->sin6_addr, p, sizeof(sa_in6->sin6_addr));
+      p = ngtcp2_get_uint16(&sa_in6->sin6_port, p);
+
+      if (sa_in6->sin6_port || memcmp(empty_address, &sa_in6->sin6_addr,
+                                      sizeof(sa_in6->sin6_addr)) != 0) {
+        sa_in6->sin6_family = NGTCP2_AF_INET6;
+        params->preferred_addr.ipv6_present = 1;
+      }
+
+      /* cid */
+      params->preferred_addr.cid.datalen = *p++;
+      len += params->preferred_addr.cid.datalen;
+      if (valuelen != len ||
+          params->preferred_addr.cid.datalen > NGTCP2_MAX_CIDLEN ||
+          params->preferred_addr.cid.datalen < NGTCP2_MIN_CIDLEN) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if (params->preferred_addr.cid.datalen) {
+        p = ngtcp2_get_bytes(params->preferred_addr.cid.data, p,
+                             params->preferred_addr.cid.datalen);
+      }
+
+      /* stateless reset token */
+      p =
+        ngtcp2_get_bytes(params->preferred_addr.stateless_reset_token, p,
+                         sizeof(params->preferred_addr.stateless_reset_token));
+      params->preferred_addr_present = 1;
+      break;
+    case NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION:
+      if (decode_zero_param(&p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      params->disable_active_migration = 1;
+      break;
+    case NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID:
+      rv = decode_cid_param(&params->original_dcid, &p, end);
+      if (rv != 0) {
+        return rv;
+      }
+      params->original_dcid_present = 1;
+      break;
+    case NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID:
+      rv = decode_cid_param(&params->retry_scid, &p, end);
+      if (rv != 0) {
+        return rv;
+      }
+      params->retry_scid_present = 1;
+      break;
+    case NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID:
+      rv = decode_cid_param(&params->initial_scid, &p, end);
+      if (rv != 0) {
+        return rv;
+      }
+      params->initial_scid_present = 1;
+      break;
+    case NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY:
+      if (decode_varint_param(&params->max_ack_delay, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if (params->max_ack_delay >= 16384) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      params->max_ack_delay *= NGTCP2_MILLISECONDS;
+      break;
+    case NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT:
+      if (decode_varint_param(&params->active_connection_id_limit, &p, end) !=
+          0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE:
+      if (decode_varint_param(&params->max_datagram_frame_size, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      break;
+    case NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT:
+      if (decode_zero_param(&p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      params->grease_quic_bit = 1;
+      break;
+    case NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION:
+      if (decode_varint(&valuelen, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if ((size_t)(end - p) < valuelen) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if (valuelen < sizeof(uint32_t) || (valuelen & 0x3)) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      p = ngtcp2_get_uint32be(&params->version_info.chosen_version, p);
+      if (params->version_info.chosen_version == 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if (valuelen > sizeof(uint32_t)) {
+        params->version_info.available_versions = (uint8_t *)p;
+        params->version_info.available_versionslen =
+          (size_t)valuelen - sizeof(uint32_t);
+
+        for (lend = p + (valuelen - sizeof(uint32_t)); p != lend;) {
+          p = ngtcp2_get_uint32be(&version, p);
+          if (version == 0) {
+            return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+          }
+        }
+      }
+      params->version_info_present = 1;
+      break;
+    default:
+      /* Ignore unknown parameter */
+      if (decode_varint(&valuelen, &p, end) != 0) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      if ((size_t)(end - p) < valuelen) {
+        return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+      }
+      p += valuelen;
+      break;
+    }
+  }
+
+  if (end - p != 0) {
+    return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM;
+  }
+
+  if (transport_params_version != NGTCP2_TRANSPORT_PARAMS_VERSION) {
+    ngtcp2_transport_params_convert_to_old(transport_params_version, dest,
+                                           params);
+  }
+
+  return 0;
+}
+
+static int transport_params_copy_new(ngtcp2_transport_params **pdest,
+                                     const ngtcp2_transport_params *src,
+                                     const ngtcp2_mem *mem) {
+  size_t len = sizeof(**pdest);
+  ngtcp2_transport_params *dest;
+  uint8_t *p;
+
+  if (src->version_info_present) {
+    len += src->version_info.available_versionslen;
+  }
+
+  dest = ngtcp2_mem_malloc(mem, len);
+  if (dest == NULL) {
+    return NGTCP2_ERR_NOMEM;
+  }
+
+  *dest = *src;
+
+  if (src->version_info_present && src->version_info.available_versionslen) {
+    p = (uint8_t *)dest + sizeof(*dest);
+    memcpy(p, src->version_info.available_versions,
+           src->version_info.available_versionslen);
+    dest->version_info.available_versions = p;
+  }
+
+  *pdest = dest;
+
+  return 0;
+}
+
+int ngtcp2_transport_params_decode_new(ngtcp2_transport_params **pparams,
+                                       const uint8_t *data, size_t datalen,
+                                       const ngtcp2_mem *mem) {
+  int rv;
+  ngtcp2_transport_params params;
+
+  rv = ngtcp2_transport_params_decode(&params, data, datalen);
+  if (rv < 0) {
+    return rv;
+  }
+
+  if (mem == NULL) {
+    mem = ngtcp2_mem_default();
+  }
+
+  return transport_params_copy_new(pparams, &params, mem);
+}
+
+void ngtcp2_transport_params_del(ngtcp2_transport_params *params,
+                                 const ngtcp2_mem *mem) {
+  if (params == NULL) {
+    return;
+  }
+
+  if (mem == NULL) {
+    mem = ngtcp2_mem_default();
+  }
+
+  ngtcp2_mem_free(mem, params);
+}
+
+int ngtcp2_transport_params_copy_new(ngtcp2_transport_params **pdest,
+                                     const ngtcp2_transport_params *src,
+                                     const ngtcp2_mem *mem) {
+  if (src == NULL) {
+    *pdest = NULL;
+    return 0;
+  }
+
+  return transport_params_copy_new(pdest, src, mem);
+}
+
+static void transport_params_copy(ngtcp2_transport_params *dest,
+                                  const ngtcp2_transport_params *src,
+                                  int transport_params_version) {
+  assert(transport_params_version != NGTCP2_TRANSPORT_PARAMS_VERSION);
+
+  switch (transport_params_version) {
+  case NGTCP2_TRANSPORT_PARAMS_V1:
+    memcpy(dest, src,
+           offsetof(ngtcp2_transport_params, version_info_present) +
+             sizeof(src->version_info_present));
+
+    break;
+  }
+}
+
+const ngtcp2_transport_params *
+ngtcp2_transport_params_convert_to_latest(ngtcp2_transport_params *dest,
+                                          int transport_params_version,
+                                          const ngtcp2_transport_params *src) {
+  if (transport_params_version == NGTCP2_TRANSPORT_PARAMS_VERSION) {
+    return src;
+  }
+
+  ngtcp2_transport_params_default(dest);
+
+  transport_params_copy(dest, src, transport_params_version);
+
+  return dest;
+}
+
+void ngtcp2_transport_params_convert_to_old(
+  int transport_params_version, ngtcp2_transport_params *dest,
+  const ngtcp2_transport_params *src) {
+  assert(transport_params_version != NGTCP2_TRANSPORT_PARAMS_VERSION);
+
+  transport_params_copy(dest, src, transport_params_version);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_transport_params.h b/third_party/ngtcp2/lib/ngtcp2_transport_params.h
new file mode 100644 (file)
index 0000000..c077f06
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2023 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_TRANSPORT_PARAMS_H
+#define NGTCP2_TRANSPORT_PARAMS_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/* ngtcp2_transport_param_id is the registry of QUIC transport
+   parameter ID. */
+typedef uint64_t ngtcp2_transport_param_id;
+
+#define NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID 0x00
+#define NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT 0x01
+#define NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN 0x02
+#define NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE 0x03
+#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA 0x04
+#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05
+#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06
+#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI 0x07
+#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI 0x08
+#define NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI 0x09
+#define NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT 0x0a
+#define NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY 0x0b
+#define NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION 0x0c
+#define NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS 0x0d
+#define NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT 0x0e
+#define NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID 0x0f
+#define NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID 0x10
+/* https://datatracker.ietf.org/doc/html/rfc9221 */
+#define NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE 0x20
+#define NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT 0x2ab2
+/* https://datatracker.ietf.org/doc/html/rfc9368 */
+#define NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION 0x11
+
+/* NGTCP2_MAX_STREAMS is the maximum number of streams. */
+#define NGTCP2_MAX_STREAMS (1LL << 60)
+
+/*
+ * ngtcp2_transport_params_copy_new makes a copy of |src|, and assigns
+ * it to |*pdest|.  If |src| is NULL, NULL is assigned to |*pdest|.
+ *
+ * Caller is responsible to call ngtcp2_transport_params_del to free
+ * the memory assigned to |*pdest|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGTCP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int ngtcp2_transport_params_copy_new(ngtcp2_transport_params **pdest,
+                                     const ngtcp2_transport_params *src,
+                                     const ngtcp2_mem *mem);
+
+/*
+ * ngtcp2_transport_params_convert_to_latest converts |src| of version
+ * |transport_params_version| to the latest version
+ * NGTCP2_TRANSPORT_PARAMS_VERSION.
+ *
+ * |dest| must point to the latest version.  |src| may be the older
+ * version, and if so, it may have fewer fields.  Accessing those
+ * fields causes undefined behavior.
+ *
+ * If |transport_params_version| == NGTCP2_TRANSPORT_PARAMS_VERSION,
+ * no conversion is made, and |src| is returned.  Otherwise, first
+ * |dest| is initialized via ngtcp2_transport_params_default, and then
+ * all valid fields in |src| are copied into |dest|.  Finally, |dest|
+ * is returned.
+ */
+const ngtcp2_transport_params *
+ngtcp2_transport_params_convert_to_latest(ngtcp2_transport_params *dest,
+                                          int transport_params_version,
+                                          const ngtcp2_transport_params *src);
+
+/*
+ * ngtcp2_transport_params_convert_to_old converts |src| of the latest
+ * version to |dest| of version |transport_params_version|.
+ *
+ * |transport_params_version| must not be the latest version
+ *  NGTCP2_TRANSPORT_PARAMS_VERSION.
+ *
+ * |dest| points to the older version, and it may have fewer fields.
+ * Accessing those fields causes undefined behavior.
+ *
+ * This function copies all valid fields in version
+ * |transport_params_version| from |src| to |dest|.
+ */
+void ngtcp2_transport_params_convert_to_old(int transport_params_version,
+                                            ngtcp2_transport_params *dest,
+                                            const ngtcp2_transport_params *src);
+
+#endif /* !defined(NGTCP2_TRANSPORT_PARAMS_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_tstamp.h b/third_party/ngtcp2/lib/ngtcp2_tstamp.h
new file mode 100644 (file)
index 0000000..2b1bb51
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2023 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_TSTAMP_H
+#define NGTCP2_TSTAMP_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+/*
+ * ngtcp2_tstamp_elapsed returns nonzero if at least |d| has passed
+ * since |base|.  |ts| expresses a current time, and must not be
+ * UINT64_MAX.
+ *
+ * If |base| is UINT64_MAX, this function returns 0 because UINT64_MAX
+ * is an invalid timestamp.  Otherwise, if |base| + |d| >= UINT64_MAX,
+ * this function returns 0.
+ *
+ * !ngtcp2_tstamp_elapsed() == ngtcp2_tstamp_not_elapsed() does not
+ * hold when |base| is UINT64_MAX.  If you need nonzero if |base| is
+ * UINT64_MAX, use !ngtcp2_tstamp_elapsed.  Otherwise, use
+ * ngtcp2_tstamp_not_elapsed.
+ */
+static inline int ngtcp2_tstamp_elapsed(ngtcp2_tstamp base, ngtcp2_duration d,
+                                        ngtcp2_tstamp ts) {
+  return base != UINT64_MAX && base < UINT64_MAX - d && base + d <= ts;
+}
+
+/*
+ * ngtcp2_tstamp_not_elapsed returns nonzero if |d| has not passed
+ * since |base|.  |ts| expresses a current time, and must not be
+ * UINT64_MAX.
+ *
+ * If |base| is UINT64_MAX, this function returns 0 because UINT64_MAX
+ * is an invalid timestamp.  Otherwise, if |base| + |d| >= UINT64_MAX,
+ * this function returns nonzero.
+ */
+static inline int ngtcp2_tstamp_not_elapsed(ngtcp2_tstamp base,
+                                            ngtcp2_duration d,
+                                            ngtcp2_tstamp ts) {
+  return base != UINT64_MAX && (base >= UINT64_MAX - d || base + d > ts);
+}
+
+#endif /* !defined(NGTCP2_TSTAMP_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_unreachable.c b/third_party/ngtcp2/lib/ngtcp2_unreachable.c
new file mode 100644 (file)
index 0000000..5ab1db7
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_unreachable.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#  define NGTCP2_UNREACHABLE_LOG
+#  include <unistd.h>
+#elif defined(WIN32)
+#  define NGTCP2_UNREACHABLE_LOG
+#  include <io.h>
+#endif /* defined(WIN32) */
+
+void ngtcp2_unreachable_fail(const char *file, int line, const char *func) {
+#ifdef NGTCP2_UNREACHABLE_LOG
+  char *buf;
+  size_t buflen;
+  int rv;
+
+#  define NGTCP2_UNREACHABLE_TEMPLATE "%s:%d %s: Unreachable.\n"
+
+  rv = snprintf(NULL, 0, NGTCP2_UNREACHABLE_TEMPLATE, file, line, func);
+  if (rv < 0) {
+    abort();
+  }
+
+  /* here we explicitly use system malloc */
+  buflen = (size_t)rv + 1;
+  buf = malloc(buflen);
+  if (buf == NULL) {
+    abort();
+  }
+
+  rv = snprintf(buf, buflen, NGTCP2_UNREACHABLE_TEMPLATE, file, line, func);
+  if (rv < 0) {
+    abort();
+  }
+
+#  ifndef WIN32
+  while (write(STDERR_FILENO, buf, (size_t)rv) == -1 && errno == EINTR)
+    ;
+#  else  /* defined(WIN32) */
+  _write(_fileno(stderr), buf, (unsigned int)rv);
+#  endif /* defined(WIN32) */
+
+  free(buf);
+#endif /* defined(NGTCP2_UNREACHABLE_LOG) */
+
+  abort();
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_unreachable.h b/third_party/ngtcp2/lib/ngtcp2_unreachable.h
new file mode 100644 (file)
index 0000000..ca71238
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2022 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_UNREACHABLE_H
+#define NGTCP2_UNREACHABLE_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#ifdef __FILE_NAME__
+#  define NGTCP2_FILE_NAME __FILE_NAME__
+#else /* !defined(__FILE_NAME__) */
+#  define NGTCP2_FILE_NAME "(file)"
+#endif /* !defined(__FILE_NAME__) */
+
+#define ngtcp2_unreachable()                                                   \
+  ngtcp2_unreachable_fail(NGTCP2_FILE_NAME, __LINE__, __func__)
+
+#ifdef _MSC_VER
+__declspec(noreturn)
+#endif /* defined(_MSC_VER) */
+    void ngtcp2_unreachable_fail(const char *file, int line, const char *func)
+#ifndef _MSC_VER
+        __attribute__((noreturn))
+#endif /* !defined(_MSC_VER) */
+        ;
+
+#endif /* !defined(NGTCP2_UNREACHABLE_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_vec.c b/third_party/ngtcp2/lib/ngtcp2_vec.c
new file mode 100644 (file)
index 0000000..dbca869
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ngtcp2_vec.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "ngtcp2_str.h"
+
+ngtcp2_vec *ngtcp2_vec_init(ngtcp2_vec *vec, const uint8_t *base, size_t len) {
+  vec->base = (uint8_t *)base;
+  vec->len = len;
+  return vec;
+}
+
+uint64_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n) {
+  size_t i;
+  size_t res = 0;
+
+  for (i = 0; i < n; ++i) {
+    res += vec[i].len;
+  }
+
+  return res;
+}
+
+int64_t ngtcp2_vec_len_varint(const ngtcp2_vec *vec, size_t n) {
+  uint64_t res = 0;
+  size_t len;
+  size_t i;
+
+  for (i = 0; i < n; ++i) {
+    len = vec[i].len;
+    if (len > NGTCP2_MAX_VARINT - res) {
+      return -1;
+    }
+
+    res += len;
+  }
+
+  return (int64_t)res;
+}
+
+ngtcp2_ssize ngtcp2_vec_split(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src,
+                              size_t *psrccnt, size_t left, size_t maxcnt) {
+  size_t i;
+  size_t srccnt = *psrccnt;
+  size_t nmove;
+  size_t extra = 0;
+
+  for (i = 0; i < srccnt; ++i) {
+    if (left >= src[i].len) {
+      left -= src[i].len;
+      continue;
+    }
+
+    if (*pdstcnt && src[srccnt - 1].base + src[srccnt - 1].len == dst[0].base) {
+      if (*pdstcnt + srccnt - i - 1 > maxcnt) {
+        return -1;
+      }
+
+      dst[0].len += src[srccnt - 1].len;
+      dst[0].base = src[srccnt - 1].base;
+      extra = src[srccnt - 1].len;
+      --srccnt;
+    } else if (*pdstcnt + srccnt - i > maxcnt) {
+      return -1;
+    }
+
+    if (left == 0) {
+      *psrccnt = i;
+    } else {
+      *psrccnt = i + 1;
+    }
+
+    nmove = srccnt - i;
+    if (nmove) {
+      memmove(dst + nmove, dst, sizeof(ngtcp2_vec) * (*pdstcnt));
+      *pdstcnt += nmove;
+      memcpy(dst, src + i, sizeof(ngtcp2_vec) * nmove);
+    }
+
+    dst[0].len -= left;
+    dst[0].base += left;
+    src[i].len = left;
+
+    if (nmove == 0) {
+      extra -= left;
+    }
+
+    return (ngtcp2_ssize)(ngtcp2_vec_len(dst, nmove) + extra);
+  }
+
+  return 0;
+}
+
+size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src,
+                        size_t *psrccnt, size_t left, size_t maxcnt) {
+  size_t orig_left = left;
+  size_t i = 0;
+  ngtcp2_vec *a, *b;
+
+  assert(maxcnt);
+
+  if (*pdstcnt == 0) {
+    if (*psrccnt == 0) {
+      return 0;
+    }
+
+    a = &dst[0];
+    b = &src[0];
+
+    if (left < b->len) {
+      a->len = left;
+      a->base = b->base;
+
+      b->len -= left;
+      b->base += left;
+
+      return left;
+    }
+
+    *a = *b;
+    ++*pdstcnt;
+    left -= b->len;
+    i = 1;
+  }
+
+  for (; left && i < *psrccnt; ++i) {
+    a = &dst[*pdstcnt - 1];
+    b = &src[i];
+
+    if (left < b->len) {
+      if (a->base + a->len == b->base) {
+        a->len += left;
+      } else if (*pdstcnt == maxcnt) {
+        break;
+      } else {
+        dst[*pdstcnt].len = left;
+        dst[*pdstcnt].base = b->base;
+        ++*pdstcnt;
+      }
+
+      b->len -= left;
+      b->base += left;
+      left = 0;
+
+      break;
+    }
+
+    if (a->base + a->len == b->base) {
+      a->len += b->len;
+    } else if (*pdstcnt == maxcnt) {
+      break;
+    } else {
+      dst[(*pdstcnt)++] = *b;
+    }
+
+    left -= b->len;
+  }
+
+  memmove(src, src + i, sizeof(ngtcp2_vec) * (*psrccnt - i));
+  *psrccnt -= i;
+
+  return orig_left - left;
+}
+
+size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt,
+                               const ngtcp2_vec *src, size_t srccnt,
+                               size_t left) {
+  size_t i, j;
+
+  for (i = 0, j = 0; left > 0 && i < srccnt && j < dstcnt;) {
+    if (src[i].len == 0) {
+      ++i;
+      continue;
+    }
+
+    if (src[i].len > left) {
+      dst[j].base = src[i].base;
+      dst[j].len = left;
+
+      return j + 1;
+    }
+
+    dst[j] = src[i];
+    left -= dst[j].len;
+    ++i;
+    ++j;
+  }
+
+  return j;
+}
+
+void ngtcp2_vec_copy(ngtcp2_vec *dst, const ngtcp2_vec *src, size_t cnt) {
+  memcpy(dst, src, sizeof(ngtcp2_vec) * cnt);
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_vec.h b/third_party/ngtcp2/lib/ngtcp2_vec.h
new file mode 100644 (file)
index 0000000..55e735d
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGTCP2_VEC_H
+#define NGTCP2_VEC_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "ngtcp2_mem.h"
+
+/*
+ * ngtcp2_vec_lit is a convenient macro to fill the object pointed by
+ * |DEST| with the literal string |LIT|.
+ */
+#define ngtcp2_vec_lit(DEST, LIT)                                              \
+  ((DEST)->base = (uint8_t *)(LIT), (DEST)->len = sizeof(LIT) - 1, (DEST))
+
+/*
+ * ngtcp2_vec_init initializes |vec| with the given parameters.  It
+ * returns |vec|.
+ */
+ngtcp2_vec *ngtcp2_vec_init(ngtcp2_vec *vec, const uint8_t *base, size_t len);
+
+/*
+ * ngtcp2_vec_len returns the sum of length in |vec| of |n| elements.
+ */
+uint64_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n);
+
+/*
+ * ngtcp2_vec_len_varint is similar to ngtcp2_vec_len, but it returns
+ * -1 if the sum of the length exceeds NGTCP2_MAX_VARINT.
+ */
+int64_t ngtcp2_vec_len_varint(const ngtcp2_vec *vec, size_t n);
+
+/*
+ * ngtcp2_vec_split splits |src| at the data position where
+ * ngtcp2_vec_len(|src|) does not exceed |left| bytes.  The removed
+ * vectors are moved to |dst|.  The existing elements in |dst| are
+ * moved forward to make up a space.  The |maxcnt| is the maximum
+ * number of elements which |dst| array can contain.  The caller must
+ * set |*psrccnt| to the number of elements of |src|.  Similarly, the
+ * caller must set |*pdstcnt| to the number of elements of |dst|.  The
+ * split does not necessarily occur at the boundary of ngtcp2_vec
+ * object.  After split has done, this function updates |*psrccnt| and
+ * |*pdstcnt|.  This function returns the number of bytes moved from
+ * |src| to |dst|.  If split cannot be made because doing so exceeds
+ * |maxcnt|, this function returns -1.
+ */
+ngtcp2_ssize ngtcp2_vec_split(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src,
+                              size_t *psrccnt, size_t left, size_t maxcnt);
+
+/*
+ * ngtcp2_vec_merge merges |src| into |dst| by moving at most |left|
+ * bytes from |src|.  The |maxcnt| is the maximum number of elements
+ * which |dst| array can contain.  The caller must set |*pdstcnt| to
+ * the number of elements of |dst|.  Similarly, the caller must set
+ * |*psrccnt| to the number of elements of |src|.  After merge has
+ * done, this function updates |*psrccnt| and |*pdstcnt|.  This
+ * function returns the number of bytes moved from |src| to |dst|.
+ */
+size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src,
+                        size_t *psrccnt, size_t left, size_t maxcnt);
+
+/*
+ * ngtcp2_vec_copy_at_most copies |src| of length |srccnt| to |dst| of
+ * length |dstcnt|.  The total number of bytes which the copied
+ * ngtcp2_vec refers to is at most |left|.  The empty elements in
+ * |src| are ignored.  This function returns the number of elements
+ * copied.
+ */
+size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt,
+                               const ngtcp2_vec *src, size_t srccnt,
+                               size_t left);
+
+/*
+ * ngtcp2_vec_copy copies |src| of length |cnt| to |dst|.  |dst| must
+ * have sufficient capacity.
+ */
+void ngtcp2_vec_copy(ngtcp2_vec *dst, const ngtcp2_vec *src, size_t cnt);
+
+#endif /* !defined(NGTCP2_VEC_H) */
diff --git a/third_party/ngtcp2/lib/ngtcp2_version.c b/third_party/ngtcp2/lib/ngtcp2_version.c
new file mode 100644 (file)
index 0000000..d2557e9
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2019 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+static ngtcp2_info version = {NGTCP2_VERSION_AGE, NGTCP2_VERSION_NUM,
+                              NGTCP2_VERSION};
+
+const ngtcp2_info *ngtcp2_version(int least_version) {
+  if (least_version > NGTCP2_VERSION_NUM) {
+    return NULL;
+  }
+  return &version;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_window_filter.c b/third_party/ngtcp2/lib/ngtcp2_window_filter.c
new file mode 100644 (file)
index 0000000..39f3d40
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2021 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Translated to C from the original C++ code
+ * https://quiche.googlesource.com/quiche/+/5be974e29f7e71a196e726d6e2272676d33ab77d/quic/core/congestion_control/windowed_filter.h
+ * with the following license:
+ *
+ * // Copyright (c) 2016 The Chromium Authors. All rights reserved.
+ * // Use of this source code is governed by a BSD-style license that can be
+ * // found in the LICENSE file.
+ */
+#include "ngtcp2_window_filter.h"
+
+#include <string.h>
+
+void ngtcp2_window_filter_init(ngtcp2_window_filter *wf,
+                               uint64_t window_length) {
+  wf->window_length = window_length;
+  memset(wf->estimates, 0xff, sizeof(wf->estimates));
+}
+
+void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample,
+                                 uint64_t new_time) {
+  /* Reset all estimates if they have not yet been initialized, if new
+     sample is a new best, or if the newest recorded estimate is too
+     old. */
+  if (wf->estimates[0].sample == UINT64_MAX ||
+      new_sample > wf->estimates[0].sample ||
+      new_time - wf->estimates[2].time > wf->window_length) {
+    ngtcp2_window_filter_reset(wf, new_sample, new_time);
+    return;
+  }
+
+  if (new_sample > wf->estimates[1].sample) {
+    wf->estimates[1].sample = new_sample;
+    wf->estimates[1].time = new_time;
+    wf->estimates[2] = wf->estimates[1];
+  } else if (new_sample > wf->estimates[2].sample) {
+    wf->estimates[2].sample = new_sample;
+    wf->estimates[2].time = new_time;
+  }
+
+  /* Expire and update estimates as necessary. */
+  if (new_time - wf->estimates[0].time > wf->window_length) {
+    /* The best estimate hasn't been updated for an entire window, so
+       promote second and third best estimates. */
+    wf->estimates[0] = wf->estimates[1];
+    wf->estimates[1] = wf->estimates[2];
+    wf->estimates[2].sample = new_sample;
+    wf->estimates[2].time = new_time;
+
+    /* Need to iterate one more time.  Check if the new best estimate
+       is outside the window as well, since it may also have been
+       recorded a long time ago.  Don't need to iterate once more
+       since we cover that case at the beginning of the method. */
+    if (new_time - wf->estimates[0].time > wf->window_length) {
+      wf->estimates[0] = wf->estimates[1];
+      wf->estimates[1] = wf->estimates[2];
+    }
+    return;
+  }
+
+  if (wf->estimates[1].sample == wf->estimates[0].sample &&
+      new_time - wf->estimates[1].time > wf->window_length >> 2) {
+    /* A quarter of the window has passed without a better sample, so
+       the second-best estimate is taken from the second quarter of
+       the window. */
+    wf->estimates[2].sample = new_sample;
+    wf->estimates[2].time = new_time;
+    wf->estimates[1] = wf->estimates[2];
+    return;
+  }
+
+  if (wf->estimates[2].sample == wf->estimates[1].sample &&
+      new_time - wf->estimates[2].time > wf->window_length >> 1) {
+    /* We've passed a half of the window without a better estimate, so
+       take a third-best estimate from the second half of the
+       window. */
+    wf->estimates[2].sample = new_sample;
+    wf->estimates[2].time = new_time;
+  }
+}
+
+void ngtcp2_window_filter_reset(ngtcp2_window_filter *wf, uint64_t new_sample,
+                                uint64_t new_time) {
+  wf->estimates[0].sample = new_sample;
+  wf->estimates[0].time = new_time;
+  wf->estimates[1] = wf->estimates[2] = wf->estimates[0];
+}
+
+uint64_t ngtcp2_window_filter_get_best(ngtcp2_window_filter *wf) {
+  return wf->estimates[0].sample;
+}
diff --git a/third_party/ngtcp2/lib/ngtcp2_window_filter.h b/third_party/ngtcp2/lib/ngtcp2_window_filter.h
new file mode 100644 (file)
index 0000000..c90a9fd
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2021 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Translated to C from the original C++ code
+ * https://quiche.googlesource.com/quiche/+/5be974e29f7e71a196e726d6e2272676d33ab77d/quic/core/congestion_control/windowed_filter.h
+ * with the following license:
+ *
+ * // Copyright (c) 2016 The Chromium Authors. All rights reserved.
+ * // Use of this source code is governed by a BSD-style license that can be
+ * // found in the LICENSE file.
+ */
+#ifndef NGTCP2_WINDOW_FILTER_H
+#define NGTCP2_WINDOW_FILTER_H
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif /* defined(HAVE_CONFIG_H) */
+
+#include <ngtcp2/ngtcp2.h>
+
+typedef struct ngtcp2_window_filter_sample {
+  uint64_t sample;
+  uint64_t time;
+} ngtcp2_window_filter_sample;
+
+typedef struct ngtcp2_window_filter {
+  uint64_t window_length;
+  ngtcp2_window_filter_sample estimates[3];
+} ngtcp2_window_filter;
+
+void ngtcp2_window_filter_init(ngtcp2_window_filter *wf,
+                               uint64_t window_length);
+
+void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample,
+                                 uint64_t new_time);
+
+void ngtcp2_window_filter_reset(ngtcp2_window_filter *wf, uint64_t new_sample,
+                                uint64_t new_time);
+
+uint64_t ngtcp2_window_filter_get_best(ngtcp2_window_filter *wf);
+
+#endif /* !defined(NGTCP2_WINDOW_FILTER_H) */
diff --git a/third_party/ngtcp2/update.sh b/third_party/ngtcp2/update.sh
new file mode 100755 (executable)
index 0000000..e8c1844
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/bash
+
+if [[ $# -lt 1 ]]; then
+    echo "Usage: update.sh VERSION"
+    exit 1
+fi
+
+NGTCP2_VERSION="${1}"
+NGTCP2_GIT="https://github.com/ngtcp2/ngtcp2.git"
+NGTCP2_UPDATE_SCRIPT="$(readlink -f "$0")"
+NGTCP2_SAMBA_DIR="$(dirname "${NGTCP2_UPDATE_SCRIPT}")"
+NGTCP2_TMPDIR=$(mktemp --tmpdir -d ngtcp2-XXXXXXXX)
+
+echo "VERSION:          ${NGTCP2_VERSION}"
+echo "GIT URL:          ${NGTCP2_GIT}"
+echo "NGTCP2 SAMBA DIR: ${NGTCP2_SAMBA_DIR}"
+echo "UIC TMP DIR:      ${NGTCP2_TMPDIR}"
+
+cleanup_tmpdir() {
+    popd 2>/dev/null || true
+    rm -rf "$NGTCP2_TMPDIR"
+}
+trap cleanup_tmpdir SIGINT
+
+cleanup_and_exit() {
+    cleanup_tmpdir
+    if test "$1" = 0 -o -z "$1" ; then
+        exit 0
+    else
+        exit "$1"
+    fi
+}
+
+# Checkout the git tree
+mkdir -p "${NGTCP2_TMPDIR}"
+pushd "${NGTCP2_TMPDIR}" || cleanup_and_exit 1
+
+git clone "${NGTCP2_GIT}"
+ret=$?
+if [ $ret -ne 0 ]; then
+    echo "ERROR: Failed to clone repository"
+    cleanup_and_exit 1
+fi
+
+pushd ngtcp2 || cleanup_and_exit 1
+git checkout -b "ngtcp2-${NGTCP2_VERSION}" "v${NGTCP2_VERSION}"
+ret=$?
+if [ $ret -ne 0 ]; then
+    echo "ERROR: Failed to checkout v${NGTCP2_VERSION} repository"
+    cleanup_and_exit 1
+fi
+PAGER= git log --pretty=oneline -1
+popd || cleanup_and_exit 1
+
+popd || cleanup_and_exit 1
+
+# Update src
+pushd "${NGTCP2_SAMBA_DIR}" || cleanup_and_exit 1
+pwd
+
+rm -rf crypto/ lib/
+rsync -av "${NGTCP2_TMPDIR}/ngtcp2/crypto/" crypto/
+rsync -av "${NGTCP2_TMPDIR}/ngtcp2/lib/" lib/
+ret=$?
+if [ $ret -ne 0 ]; then
+    echo "ERROR: Failed copy src"
+    cleanup_and_exit 1
+fi
+
+git add lib crypto
+
+popd || cleanup_and_exit 1
+
+echo
+echo "Now please change VERSION in buildtools/wafsamba/samba_third_party.py"
+echo
+
+cleanup_and_exit 0
diff --git a/third_party/ngtcp2/wscript b/third_party/ngtcp2/wscript
new file mode 100644 (file)
index 0000000..f4cfd1c
--- /dev/null
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+
+import sys
+from waflib import Logs
+from samba_utils import save_file
+
+def configure(conf):
+    if not sys.platform.startswith('linux'):
+        Logs.warn('libngtcp2 requires linux')
+        conf.SET_TARGET_TYPE('ngtcp2', 'EMPTY')
+        return
+
+    if not conf.CONFIG_GET('HAVE_GNUTLS_CB_TLS_SERVER_END_POINT'):
+        Logs.warn('libngtcp2 requires gnutls 3.7.2')
+        conf.SET_TARGET_TYPE('ngtcp2', 'EMPTY')
+        return
+
+    if conf.CHECK_LIBNGTCP2():
+        conf.DEFINE('HAVE_LIBNGTCP2', '1')
+        conf.define('USING_SYSTEM_LIBNGTCP2', 1)
+        return
+
+    conf.ADD_NAMED_CFLAGS('LIBNGTCP2_UNPICKY_CFLAGS',
+                          '-Wno-cast-qual',
+                          testflags=True)
+    conf.ADD_NAMED_CFLAGS('LIBNGTCP2_UNPICKY_CFLAGS',
+                          '-Wno-error=cast-qual',
+                          testflags=True)
+    conf.ADD_NAMED_CFLAGS('LIBNGTCP2_UNPICKY_CFLAGS',
+                          '-Wno-strict-aliasing',
+                          testflags=True)
+    conf.ADD_NAMED_CFLAGS('LIBNGTCP2_UNPICKY_CFLAGS',
+                          '-Wno-error=strict-aliasing',
+                          testflags=True)
+    conf.ADD_NAMED_CFLAGS('LIBNGTCP2_UNPICKY_CFLAGS',
+                          '-Wno-strict-overflow',
+                          testflags=True)
+    conf.ADD_NAMED_CFLAGS('LIBNGTCP2_UNPICKY_CFLAGS',
+                          '-Wno-error=strict-overflow',
+                          testflags=True)
+
+    conf.DEFINE('HAVE_LIBNGTCP2', '1')
+    return
+
+def build(bld):
+    if bld.CONFIG_SET('USING_SYSTEM_LIBNGTCP2'):
+        return
+
+    def generate_ngtcp2_version_h(task):
+        ngtcp2_version_h = task.outputs[0].bldpath(task.env)
+        h = '''
+        #ifndef NGTCP2_VERSION_H
+        #define NGTCP2_VERSION_H
+        #define NGTCP2_VERSION "1.11.0"
+        #define NGTCP2_VERSION_NUM 0x010b00
+        #endif
+        '''
+
+        save_file(ngtcp2_version_h, h, create_dir=True)
+        return 0
+
+    ngtcp2_version_h = 'lib/includes/ngtcp2/version.h'
+    bld.SAMBA_GENERATOR(ngtcp2_version_h,
+                        target=ngtcp2_version_h,
+                        rule=generate_ngtcp2_version_h,
+                        enabled=bld.CONFIG_SET('HAVE_LIBNGTCP2'))
+
+    bld.SAMBA_LIBRARY('ngtcp2',
+                      source='''
+                      lib/ngtcp2_acktr.c
+                      lib/ngtcp2_addr.c
+                      lib/ngtcp2_balloc.c
+                      lib/ngtcp2_bbr.c
+                      lib/ngtcp2_buf.c
+                      lib/ngtcp2_cc.c
+                      lib/ngtcp2_cid.c
+                      lib/ngtcp2_conn.c
+                      lib/ngtcp2_conv.c
+                      lib/ngtcp2_crypto.c
+                      lib/ngtcp2_dcidtr.c
+                      lib/ngtcp2_err.c
+                      lib/ngtcp2_frame_chain.c
+                      lib/ngtcp2_gaptr.c
+                      lib/ngtcp2_idtr.c
+                      lib/ngtcp2_ksl.c
+                      lib/ngtcp2_log.c
+                      lib/ngtcp2_map.c
+                      lib/ngtcp2_mem.c
+                      lib/ngtcp2_objalloc.c
+                      lib/ngtcp2_opl.c
+                      lib/ngtcp2_path.c
+                      lib/ngtcp2_pkt.c
+                      lib/ngtcp2_pmtud.c
+                      lib/ngtcp2_ppe.c
+                      lib/ngtcp2_pq.c
+                      lib/ngtcp2_pv.c
+                      lib/ngtcp2_qlog.c
+                      lib/ngtcp2_range.c
+                      lib/ngtcp2_ringbuf.c
+                      lib/ngtcp2_rob.c
+                      lib/ngtcp2_rst.c
+                      lib/ngtcp2_rtb.c
+                      lib/ngtcp2_settings.c
+                      lib/ngtcp2_str.c
+                      lib/ngtcp2_strm.c
+                      lib/ngtcp2_transport_params.c
+                      lib/ngtcp2_unreachable.c
+                      lib/ngtcp2_vec.c
+                      lib/ngtcp2_version.c
+                      lib/ngtcp2_window_filter.c
+                      ''',
+                      includes='crypto/includes lib/includes crypto lib',
+                      deps='replace',
+                      cflags_end=bld.env.LIBNGTCP2_UNPICKY_CFLAGS,
+                      private_library=True,
+                      enabled=bld.CONFIG_SET('HAVE_LIBNGTCP2'))
+    bld.SAMBA_SUBSYSTEM('libngtcp2', source='', public_deps='ngtcp2',
+                        enabled=bld.CONFIG_SET('HAVE_LIBNGTCP2'))
+    bld.SAMBA_LIBRARY('ngtcp2_crypto_gnutls',
+                      source='''
+                      crypto/shared.c
+                      crypto/gnutls/gnutls.c
+                      ''',
+                      includes='crypto/includes lib/includes crypto lib',
+                      deps='replace',
+                      public_deps='libngtcp2 gnutls',
+                      cflags_end=bld.env.LIBNGTCP2_UNPICKY_CFLAGS,
+                      private_library=True,
+                      enabled=bld.CONFIG_SET('HAVE_LIBNGTCP2'))
+    bld.SAMBA_SUBSYSTEM('libngtcp2_crypto_gnutls', source='', public_deps='ngtcp2_crypto_gnutls',
+                        enabled=bld.CONFIG_SET('HAVE_LIBNGTCP2'))
index 1345d77844ef95e8d96a463cc07d7f5399e652c1..fa72b1b462d2af16182cb64a0b082630c6e65d1c 100644 (file)
@@ -6,6 +6,7 @@ def configure(conf):
     conf.RECURSE('cmocka')
     conf.RECURSE('popt')
     conf.RECURSE('quic')
+    conf.RECURSE('ngtcp2')
     if conf.CONFIG_GET('ENABLE_SELFTEST'):
         conf.RECURSE('socket_wrapper')
         conf.RECURSE('nss_wrapper')
@@ -19,6 +20,7 @@ def build(bld):
     bld.RECURSE('cmocka')
     bld.RECURSE('popt')
     bld.RECURSE('quic')
+    bld.RECURSE('ngtcp2')
     if bld.CONFIG_GET('SOCKET_WRAPPER'):
         bld.RECURSE('socket_wrapper')
     if bld.CONFIG_GET('NSS_WRAPPER'):