summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:30:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:30:55 +0000
commit17e81f2cd1843f01838245eae7b5ed5edf83d6be (patch)
treea0f685dff11ce5a2dc546a7b46a48bae5d1c0140 /examples
parentInitial commit. (diff)
downloadngtcp2-17e81f2cd1843f01838245eae7b5ed5edf83d6be.tar.xz
ngtcp2-17e81f2cd1843f01838245eae7b5ed5edf83d6be.zip
Adding upstream version 0.12.1+dfsg.upstream/0.12.1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'examples')
-rw-r--r--examples/.gitignore15
-rw-r--r--examples/CMakeLists.txt359
-rw-r--r--examples/Makefile.am226
-rw-r--r--examples/client.cc3052
-rw-r--r--examples/client.h192
-rw-r--r--examples/client_base.cc202
-rw-r--r--examples/client_base.h212
-rw-r--r--examples/debug.cc298
-rw-r--r--examples/debug.h124
-rw-r--r--examples/examplestest.cc84
-rw-r--r--examples/gtlssimpleclient.c720
-rw-r--r--examples/h09client.cc2604
-rw-r--r--examples/h09client.h196
-rw-r--r--examples/h09server.cc3034
-rw-r--r--examples/h09server.h237
-rw-r--r--examples/http.cc138
-rw-r--r--examples/http.h44
-rw-r--r--examples/network.h80
-rw-r--r--examples/server.cc3741
-rw-r--r--examples/server.h256
-rw-r--r--examples/server_base.cc58
-rw-r--r--examples/server_base.h194
-rw-r--r--examples/shared.cc385
-rw-r--r--examples/shared.h96
-rw-r--r--examples/simpleclient.c683
-rw-r--r--examples/template.h71
-rw-r--r--examples/tests/.gitignore3
-rw-r--r--examples/tests/README.rst60
-rw-r--r--examples/tests/__init__.py0
-rw-r--r--examples/tests/config.ini.in32
-rw-r--r--examples/tests/conftest.py28
-rw-r--r--examples/tests/ngtcp2test/__init__.py6
-rw-r--r--examples/tests/ngtcp2test/certs.py476
-rw-r--r--examples/tests/ngtcp2test/client.py187
-rw-r--r--examples/tests/ngtcp2test/env.py191
-rw-r--r--examples/tests/ngtcp2test/log.py101
-rw-r--r--examples/tests/ngtcp2test/server.py137
-rw-r--r--examples/tests/ngtcp2test/tls.py983
-rw-r--r--examples/tests/test_01_handshake.py30
-rw-r--r--examples/tests/test_02_resume.py46
-rw-r--r--examples/tests/test_03_earlydata.py56
-rw-r--r--examples/tests/test_04_clientcert.py57
-rw-r--r--examples/tests/test_05_ciphers.py46
-rw-r--r--examples/tls_client_context.h52
-rw-r--r--examples/tls_client_context_boringssl.cc126
-rw-r--r--examples/tls_client_context_boringssl.h49
-rw-r--r--examples/tls_client_context_gnutls.cc74
-rw-r--r--examples/tls_client_context_gnutls.h50
-rw-r--r--examples/tls_client_context_openssl.cc137
-rw-r--r--examples/tls_client_context_openssl.h49
-rw-r--r--examples/tls_client_context_picotls.cc155
-rw-r--r--examples/tls_client_context_picotls.h54
-rw-r--r--examples/tls_client_context_wolfssl.cc177
-rw-r--r--examples/tls_client_context_wolfssl.h51
-rw-r--r--examples/tls_client_session.h52
-rw-r--r--examples/tls_client_session_boringssl.cc110
-rw-r--r--examples/tls_client_session_boringssl.h52
-rw-r--r--examples/tls_client_session_gnutls.cc190
-rw-r--r--examples/tls_client_session_gnutls.h52
-rw-r--r--examples/tls_client_session_openssl.cc113
-rw-r--r--examples/tls_client_session_openssl.h52
-rw-r--r--examples/tls_client_session_picotls.cc147
-rw-r--r--examples/tls_client_session_picotls.h52
-rw-r--r--examples/tls_client_session_wolfssl.cc160
-rw-r--r--examples/tls_client_session_wolfssl.h52
-rw-r--r--examples/tls_server_context.h52
-rw-r--r--examples/tls_server_context_boringssl.cc257
-rw-r--r--examples/tls_server_context_boringssl.h54
-rw-r--r--examples/tls_server_context_gnutls.cc99
-rw-r--r--examples/tls_server_context_gnutls.h59
-rw-r--r--examples/tls_server_context_openssl.cc338
-rw-r--r--examples/tls_server_context_openssl.h54
-rw-r--r--examples/tls_server_context_picotls.cc318
-rw-r--r--examples/tls_server_context_picotls.h59
-rw-r--r--examples/tls_server_context_wolfssl.cc284
-rw-r--r--examples/tls_server_context_wolfssl.h55
-rw-r--r--examples/tls_server_session.h52
-rw-r--r--examples/tls_server_session_boringssl.cc84
-rw-r--r--examples/tls_server_session_boringssl.h47
-rw-r--r--examples/tls_server_session_gnutls.cc155
-rw-r--r--examples/tls_server_session_gnutls.h46
-rw-r--r--examples/tls_server_session_openssl.cc54
-rw-r--r--examples/tls_server_session_openssl.h47
-rw-r--r--examples/tls_server_session_picotls.cc70
-rw-r--r--examples/tls_server_session_picotls.h47
-rw-r--r--examples/tls_server_session_wolfssl.cc55
-rw-r--r--examples/tls_server_session_wolfssl.h47
-rw-r--r--examples/tls_session_base_gnutls.cc87
-rw-r--r--examples/tls_session_base_gnutls.h51
-rw-r--r--examples/tls_session_base_openssl.cc54
-rw-r--r--examples/tls_session_base_openssl.h52
-rw-r--r--examples/tls_session_base_picotls.cc56
-rw-r--r--examples/tls_session_base_picotls.h54
-rw-r--r--examples/tls_session_base_wolfssl.cc54
-rw-r--r--examples/tls_session_base_wolfssl.h54
-rw-r--r--examples/util.cc646
-rw-r--r--examples/util.h361
-rw-r--r--examples/util_gnutls.cc136
-rw-r--r--examples/util_openssl.cc131
-rw-r--r--examples/util_test.cc237
-rw-r--r--examples/util_test.h45
-rw-r--r--examples/util_wolfssl.cc130
102 files changed, 26227 insertions, 0 deletions
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000..b2e3553
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1,15 @@
+client
+server
+examplestest
+h09client
+h09server
+gtlsclient
+gtlsserver
+bsslclient
+bsslserver
+ptlsclient
+ptlsserver
+simpleclient
+wsslclient
+wsslserver
+gtlssimpleclient
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..09701b8
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,359 @@
+# 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.
+
+if(LIBEV_FOUND AND HAVE_OPENSSL AND LIBNGHTTP3_FOUND)
+ set(client_SOURCES
+ client.cc
+ client_base.cc
+ debug.cc
+ util.cc
+ shared.cc
+ tls_client_context_openssl.cc
+ tls_client_session_openssl.cc
+ tls_session_base_openssl.cc
+ util_openssl.cc
+ )
+
+ set(server_SOURCES
+ server.cc
+ server_base.cc
+ debug.cc
+ util.cc
+ http.cc
+ shared.cc
+ tls_server_context_openssl.cc
+ tls_server_session_openssl.cc
+ tls_session_base_openssl.cc
+ util_openssl.cc
+ )
+
+ set(ossl_INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}/lib/includes
+ ${CMAKE_BINARY_DIR}/lib/includes
+ ${CMAKE_SOURCE_DIR}/third-party
+ ${CMAKE_SOURCE_DIR}/crypto/includes
+
+ ${JEMALLOC_INCLUDE_DIRS}
+ ${OPENSSL_INCLUDE_DIRS}
+ ${LIBEV_INCLUDE_DIRS}
+ ${LIBNGHTTP3_INCLUDE_DIRS}
+ )
+
+ set(ossl_LIBS
+ ngtcp2_crypto_openssl
+ ngtcp2
+ ${JEMALLOC_LIBRARIES}
+ ${OPENSSL_LIBRARIES}
+ ${LIBEV_LIBRARIES}
+ ${LIBNGHTTP3_LIBRARIES}
+ )
+
+ add_executable(client ${client_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ add_executable(server ${server_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ set_target_properties(client PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_OPENSSL -DWITH_EXAMPLE_OPENSSL"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ set_target_properties(server PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_OPENSSL -DWITH_EXAMPLE_OPENSSL"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ target_include_directories(client PUBLIC ${ossl_INCLUDE_DIRS})
+ target_include_directories(server PUBLIC ${ossl_INCLUDE_DIRS})
+ target_link_libraries(client ${ossl_LIBS})
+ target_link_libraries(server ${ossl_LIBS})
+
+ # TODO prevent client and example servers from being installed?
+endif()
+
+if(LIBEV_FOUND AND HAVE_GNUTLS AND LIBNGHTTP3_FOUND)
+ set(gtlsclient_SOURCES
+ client.cc
+ client_base.cc
+ debug.cc
+ util.cc
+ shared.cc
+ tls_client_context_gnutls.cc
+ tls_client_session_gnutls.cc
+ tls_session_base_gnutls.cc
+ util_gnutls.cc
+ )
+
+ set(gtlsserver_SOURCES
+ server.cc
+ server_base.cc
+ debug.cc
+ util.cc
+ http.cc
+ shared.cc
+ tls_server_context_gnutls.cc
+ tls_server_session_gnutls.cc
+ tls_session_base_gnutls.cc
+ util_gnutls.cc
+ )
+
+ set(gtls_INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}/lib/includes
+ ${CMAKE_BINARY_DIR}/lib/includes
+ ${CMAKE_SOURCE_DIR}/third-party
+ ${CMAKE_SOURCE_DIR}/crypto/includes
+
+ ${JEMALLOC_INCLUDE_DIRS}
+ ${GNUTLS_INCLUDE_DIRS}
+ ${LIBEV_INCLUDE_DIRS}
+ ${LIBNGHTTP3_INCLUDE_DIRS}
+ )
+
+ set(gtls_LIBS
+ ngtcp2_crypto_gnutls
+ ngtcp2
+ ${JEMALLOC_LIBRARIES}
+ ${GNUTLS_LIBRARIES}
+ ${LIBEV_LIBRARIES}
+ ${LIBNGHTTP3_LIBRARIES}
+ )
+
+ add_executable(gtlsclient ${gtlsclient_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ add_executable(gtlsserver ${gtlsserver_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ set_target_properties(gtlsclient PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_GNUTLS -DWITH_EXAMPLE_GNUTLS"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ set_target_properties(gtlsserver PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_GNUTLS -DWITH_EXAMPLE_GNUTLS"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ target_include_directories(gtlsclient PUBLIC ${gtls_INCLUDE_DIRS})
+ target_include_directories(gtlsserver PUBLIC ${gtls_INCLUDE_DIRS})
+ target_link_libraries(gtlsclient ${gtls_LIBS})
+ target_link_libraries(gtlsserver ${gtls_LIBS})
+
+ # TODO prevent gtlsclient and example gtlsservers from being installed?
+endif()
+
+if(LIBEV_FOUND AND HAVE_BORINGSSL AND LIBNGHTTP3_FOUND)
+ set(bsslclient_SOURCES
+ client.cc
+ client_base.cc
+ debug.cc
+ util.cc
+ shared.cc
+ tls_client_context_boringssl.cc
+ tls_client_session_boringssl.cc
+ tls_session_base_openssl.cc
+ util_openssl.cc
+ )
+
+ set(bsslserver_SOURCES
+ server.cc
+ server_base.cc
+ debug.cc
+ util.cc
+ http.cc
+ shared.cc
+ tls_server_context_boringssl.cc
+ tls_server_session_boringssl.cc
+ tls_session_base_openssl.cc
+ util_openssl.cc
+ )
+
+ set(bssl_INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}/lib/includes
+ ${CMAKE_BINARY_DIR}/lib/includes
+ ${CMAKE_SOURCE_DIR}/third-party
+ ${CMAKE_SOURCE_DIR}/crypto/includes
+
+ ${JEMALLOC_INCLUDE_DIRS}
+ ${BORINGSSL_INCLUDE_DIRS}
+ ${LIBEV_INCLUDE_DIRS}
+ ${LIBNGHTTP3_INCLUDE_DIRS}
+ )
+
+ set(bssl_LIBS
+ ngtcp2_crypto_boringssl_static
+ ngtcp2
+ ${JEMALLOC_LIBRARIES}
+ ${BORINGSSL_LIBRARIES}
+ ${LIBEV_LIBRARIES}
+ ${LIBNGHTTP3_LIBRARIES}
+ )
+
+ add_executable(bsslclient ${bsslclient_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ add_executable(bsslserver ${bsslserver_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ set_target_properties(bsslclient PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_BORINGSSL -DWITH_EXAMPLE_BORINGSSL"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ set_target_properties(bsslserver PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_BORINGSSL -DWITH_EXAMPLE_BORINGSSL"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ target_include_directories(bsslclient PUBLIC ${bssl_INCLUDE_DIRS})
+ target_include_directories(bsslserver PUBLIC ${bssl_INCLUDE_DIRS})
+ target_link_libraries(bsslclient ${bssl_LIBS})
+ target_link_libraries(bsslserver ${bssl_LIBS})
+
+ # TODO prevent bsslclient and example bsslservers from being installed?
+endif()
+
+if(LIBEV_FOUND AND HAVE_PICOTLS AND LIBNGHTTP3_FOUND)
+ set(ptlsclient_SOURCES
+ client.cc
+ client_base.cc
+ debug.cc
+ util.cc
+ shared.cc
+ tls_client_context_picotls.cc
+ tls_client_session_picotls.cc
+ tls_session_base_picotls.cc
+ util_openssl.cc
+ )
+
+ set(ptlsserver_SOURCES
+ server.cc
+ server_base.cc
+ debug.cc
+ util.cc
+ http.cc
+ shared.cc
+ tls_server_context_picotls.cc
+ tls_server_session_picotls.cc
+ tls_session_base_picotls.cc
+ util_openssl.cc
+ )
+
+ set(ptls_INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}/lib/includes
+ ${CMAKE_BINARY_DIR}/lib/includes
+ ${CMAKE_SOURCE_DIR}/third-party
+ ${CMAKE_SOURCE_DIR}/crypto/includes
+
+ ${JEMALLOC_INCLUDE_DIRS}
+ ${PICOTLS_INCLUDE_DIRS}
+ ${VANILLA_OPENSSL_INCLUDE_DIRS}
+ ${LIBEV_INCLUDE_DIRS}
+ ${LIBNGHTTP3_INCLUDE_DIRS}
+ )
+
+ set(ptls_LIBS
+ ngtcp2_crypto_picotls_static
+ ngtcp2
+ ${JEMALLOC_LIBRARIES}
+ ${PICOTLS_LIBRARIES}
+ ${VANILLA_OPENSSL_LIBRARIES}
+ ${LIBEV_LIBRARIES}
+ ${LIBNGHTTP3_LIBRARIES}
+ )
+
+ add_executable(ptlsclient ${ptlsclient_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ add_executable(ptlsserver ${ptlsserver_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ set_target_properties(ptlsclient PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_PICOTLS -DWITH_EXAMPLE_PICOTLS"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ set_target_properties(ptlsserver PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_PICOTLS -DWITH_EXAMPLE_PICOTLS"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ target_include_directories(ptlsclient PUBLIC ${ptls_INCLUDE_DIRS})
+ target_include_directories(ptlsserver PUBLIC ${ptls_INCLUDE_DIRS})
+ target_link_libraries(ptlsclient ${ptls_LIBS})
+ target_link_libraries(ptlsserver ${ptls_LIBS})
+
+ # TODO prevent ptlsclient and example ptlsservers from being installed?
+endif()
+
+if(LIBEV_FOUND AND HAVE_WOLFSSL AND LIBNGHTTP3_FOUND)
+ set(wsslclient_SOURCES
+ client.cc
+ client_base.cc
+ debug.cc
+ util.cc
+ shared.cc
+ tls_client_context_wolfssl.cc
+ tls_client_session_wolfssl.cc
+ tls_session_base_wolfssl.cc
+ util_wolfssl.cc
+ )
+
+ set(wsslserver_SOURCES
+ server.cc
+ server_base.cc
+ debug.cc
+ util.cc
+ http.cc
+ shared.cc
+ tls_server_context_wolfssl.cc
+ tls_server_session_wolfssl.cc
+ tls_session_base_wolfssl.cc
+ util_wolfssl.cc
+ )
+
+ set(wolfssl_INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}/lib/includes
+ ${CMAKE_BINARY_DIR}/lib/includes
+ ${CMAKE_SOURCE_DIR}/third-party
+ ${CMAKE_SOURCE_DIR}/crypto/includes
+
+ ${JEMALLOC_INCLUDE_DIRS}
+ ${WOLFSSL_INCLUDE_DIRS}
+ ${LIBEV_INCLUDE_DIRS}
+ ${LIBNGHTTP3_INCLUDE_DIRS}
+ )
+
+ set(wolfssl_LIBS
+ ngtcp2_crypto_wolfssl_static
+ ngtcp2
+ ${JEMALLOC_LIBRARIES}
+ ${WOLFSSL_LIBRARIES}
+ ${LIBEV_LIBRARIES}
+ ${LIBNGHTTP3_LIBRARIES}
+ )
+
+ add_executable(wsslclient ${wsslclient_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ add_executable(wsslserver ${wsslserver_SOURCES} $<TARGET_OBJECTS:http-parser>)
+ set_target_properties(wsslclient PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_WOLFSSL -DWITH_EXAMPLE_WOLFSSL"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ set_target_properties(wsslserver PROPERTIES
+ COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_WOLFSSL -DWITH_EXAMPLE_WOLFSSL"
+ CXX_STANDARD 20
+ CXX_STANDARD_REQUIRED ON
+ )
+ target_include_directories(wsslclient PUBLIC ${wolfssl_INCLUDE_DIRS})
+ target_include_directories(wsslserver PUBLIC ${wolfssl_INCLUDE_DIRS})
+ target_link_libraries(wsslclient ${wolfssl_LIBS})
+ target_link_libraries(wsslserver ${wolfssl_LIBS})
+
+ # TODO prevent wsslclient and example wsslserver from being installed?
+endif()
diff --git a/examples/Makefile.am b/examples/Makefile.am
new file mode 100644
index 0000000..66bfbe5
--- /dev/null
+++ b/examples/Makefile.am
@@ -0,0 +1,226 @@
+# 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.
+EXTRA_DIST = CMakeLists.txt
+
+AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS)
+AM_CXXFLAGS = $(WARNCXXFLAGS) $(DEBUGCFLAGS)
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/lib/includes \
+ -I$(top_builddir)/lib/includes \
+ -I$(top_srcdir)/crypto/includes \
+ -I$(top_srcdir)/third-party \
+ @LIBEV_CFLAGS@ \
+ @LIBNGHTTP3_CFLAGS@ \
+ @DEFS@ \
+ @EXTRA_DEFS@
+AM_LDFLAGS = -no-install \
+ @LIBTOOL_LDFLAGS@
+LDADD = $(top_builddir)/lib/libngtcp2.la \
+ $(top_builddir)/third-party/libhttp-parser.la \
+ @LIBEV_LIBS@ \
+ @LIBNGHTTP3_LIBS@
+
+SERVER_SRCS = \
+ server_base.cc server_base.h \
+ tls_server_context.h \
+ tls_server_session.h \
+ template.h \
+ debug.cc debug.h \
+ util.cc util.h \
+ shared.cc shared.h \
+ http.cc http.h \
+ network.h
+
+CLIENT_SRCS = \
+ client_base.cc client_base.h \
+ tls_client_context.h \
+ tls_client_session.h \
+ template.h \
+ debug.cc debug.h \
+ util.cc util.h \
+ shared.cc shared.h \
+ network.h
+
+noinst_PROGRAMS =
+
+if ENABLE_EXAMPLE_OPENSSL
+noinst_PROGRAMS += client server h09client h09server simpleclient
+
+simpleclient_CPPFLAGS = ${AM_CPPFLAGS} \
+ @JEMALLOC_CFLAGS@ @OPENSSL_CFLAGS@ -DWITH_EXAMPLE_OPENSSL
+simpleclient_LDADD = ${LDADD} \
+ $(top_builddir)/crypto/openssl/libngtcp2_crypto_openssl.la \
+ @OPENSSL_LIBS@ \
+ @JEMALLOC_LIBS@
+simpleclient_SOURCES = simpleclient.c
+
+client_CPPFLAGS = ${AM_CPPFLAGS} \
+ @JEMALLOC_CFLAGS@ @OPENSSL_CFLAGS@ -DWITH_EXAMPLE_OPENSSL
+client_LDADD = ${LDADD} \
+ $(top_builddir)/crypto/openssl/libngtcp2_crypto_openssl.la \
+ @OPENSSL_LIBS@ \
+ @JEMALLOC_LIBS@
+client_SOURCES = client.cc client.h ${CLIENT_SRCS} \
+ tls_client_context_openssl.cc tls_client_context_openssl.h \
+ tls_client_session_openssl.cc tls_client_session_openssl.h \
+ tls_session_base_openssl.cc tls_session_base_openssl.h \
+ util_openssl.cc
+
+server_CPPFLAGS = ${client_CPPFLAGS}
+server_LDADD = ${client_LDADD}
+server_SOURCES = server.cc server.h ${SERVER_SRCS} \
+ tls_server_context_openssl.cc tls_server_context_openssl.h \
+ tls_server_session_openssl.cc tls_server_session_openssl.h \
+ tls_session_base_openssl.cc tls_session_base_openssl.h \
+ util_openssl.cc
+
+h09client_CPPFLAGS = ${client_CPPFLAGS}
+h09client_LDADD = ${client_LDADD}
+h09client_SOURCES = h09client.cc h09client.h ${CLIENT_SRCS} \
+ tls_client_context_openssl.cc tls_client_context_openssl.h \
+ tls_client_session_openssl.cc tls_client_session_openssl.h \
+ tls_session_base_openssl.cc tls_session_base_openssl.h \
+ util_openssl.cc
+
+h09server_CPPFLAGS = ${client_CPPFLAGS}
+h09server_LDADD = ${client_LDADD}
+h09server_SOURCES = h09server.cc h09server.h ${SERVER_SRCS} \
+ tls_server_context_openssl.cc tls_server_context_openssl.h \
+ tls_server_session_openssl.cc tls_server_session_openssl.h \
+ tls_session_base_openssl.cc tls_session_base_openssl.h \
+ util_openssl.cc
+endif # ENABLE_EXAMPLE_OPENSSL
+
+if ENABLE_EXAMPLE_GNUTLS
+noinst_PROGRAMS += gtlsclient gtlsserver gtlssimpleclient
+
+gtlssimpleclient_CPPFLAGS = ${AM_CPPFLAGS} \
+ @JEMALLOC_CFLAGS@ @GNUTLS_CFLAGS@ -DWITH_EXAMPLE_OPENSSL
+gtlssimpleclient_LDADD = ${LDADD} \
+ $(top_builddir)/crypto/gnutls/libngtcp2_crypto_gnutls.la \
+ @GNUTLS_LIBS@ \
+ @JEMALLOC_LIBS@
+gtlssimpleclient_SOURCES = gtlssimpleclient.c
+
+gtlsclient_CPPFLAGS = ${AM_CPPFLAGS} \
+ @JEMALLOC_CFLAGS@ @GNUTLS_CFLAGS@ -DWITH_EXAMPLE_GNUTLS
+gtlsclient_LDADD = ${LDADD} \
+ $(top_builddir)/crypto/gnutls/libngtcp2_crypto_gnutls.la \
+ @GNUTLS_LIBS@ \
+ @JEMALLOC_LIBS@
+gtlsclient_SOURCES = client.cc client.h ${CLIENT_SRCS} \
+ tls_client_context_gnutls.cc tls_client_context_gnutls.h \
+ tls_client_session_gnutls.cc tls_client_session_gnutls.h \
+ tls_session_base_gnutls.cc tls_session_base_gnutls.h \
+ util_gnutls.cc
+
+gtlsserver_CPPFLAGS = ${gtlsclient_CPPFLAGS}
+gtlsserver_LDADD = ${gtlsclient_LDADD} \
+ $(top_builddir)/crypto/gnutls/libngtcp2_crypto_gnutls.la \
+ @GNUTLS_LIBS@
+gtlsserver_SOURCES = server.cc server.h ${SERVER_SRCS} \
+ tls_server_context_gnutls.cc tls_server_context_gnutls.h \
+ tls_server_session_gnutls.cc tls_server_session_gnutls.h \
+ tls_session_base_gnutls.cc tls_session_base_gnutls.h \
+ util_gnutls.cc
+endif # ENABLE_EXAMPLE_GNUTLS
+
+if ENABLE_EXAMPLE_BORINGSSL
+noinst_PROGRAMS += bsslclient bsslserver
+
+bsslclient_CPPFLAGS = ${AM_CPPFLAGS} @BORINGSSL_CFLAGS@ -DWITH_EXAMPLE_BORINGSSL
+bsslclient_LDADD = ${LDADD} \
+ $(top_builddir)/crypto/boringssl/libngtcp2_crypto_boringssl.a \
+ @BORINGSSL_LIBS@ \
+ @JEMALLOC_LIBS@
+bsslclient_SOURCES = client.cc client.h ${CLIENT_SRCS} \
+ tls_client_context_boringssl.cc tls_client_context_boringssl.h \
+ tls_client_session_boringssl.cc tls_client_session_boringssl.h \
+ tls_session_base_openssl.cc tls_session_base_openssl.h \
+ util_openssl.cc
+
+bsslserver_CPPFLAGS = ${bsslclient_CPPFLAGS}
+bsslserver_LDADD = ${bsslclient_LDADD}
+bsslserver_SOURCES = server.cc server.h ${SERVER_SRCS} \
+ tls_server_context_boringssl.cc tls_server_context_boringssl.h \
+ tls_server_session_boringssl.cc tls_server_session_boringssl.h \
+ tls_session_base_openssl.cc tls_session_base_openssl.h \
+ util_openssl.cc
+endif # ENABLE_EXAMPLE_BORINGSSL
+
+if ENABLE_EXAMPLE_PICOTLS
+noinst_PROGRAMS += ptlsclient ptlsserver
+
+ptlsclient_CPPFLAGS = ${AM_CPPFLAGS} @PICOTLS_CFLAGS@ @VANILLA_OPENSSL_CFLAGS@ \
+ -DWITH_EXAMPLE_PICOTLS
+ptlsclient_LDADD = ${LDADD} \
+ $(top_builddir)/crypto/picotls/libngtcp2_crypto_picotls.a \
+ @PICOTLS_LIBS@ @VANILLA_OPENSSL_LIBS@ \
+ @JEMALLOC_LIBS@
+ptlsclient_SOURCES = client.cc client.h ${CLIENT_SRCS} \
+ tls_client_context_picotls.cc tls_client_context_picotls.h \
+ tls_client_session_picotls.cc tls_client_session_picotls.h \
+ tls_session_base_picotls.cc tls_session_base_picotls.h \
+ util_openssl.cc
+
+ptlsserver_CPPFLAGS = ${ptlsclient_CPPFLAGS}
+ptlsserver_LDADD = ${ptlsclient_LDADD}
+ptlsserver_SOURCES = server.cc server.h ${SERVER_SRCS} \
+ tls_server_context_picotls.cc tls_server_context_picotls.h \
+ tls_server_session_picotls.cc tls_server_session_picotls.h \
+ tls_session_base_picotls.cc tls_session_base_picotls.h \
+ util_openssl.cc
+endif # ENABLE_EXAMPLE_PICOTLS
+
+if ENABLE_EXAMPLE_WOLFSSL
+noinst_PROGRAMS += wsslclient wsslserver
+
+wsslclient_CPPFLAGS = ${AM_CPPFLAGS} @WOLFSSL_CFLAGS@ -DWITH_EXAMPLE_WOLFSSL
+wsslclient_LDADD = ${LDADD} \
+ $(top_builddir)/crypto/wolfssl/libngtcp2_crypto_wolfssl.la \
+ @WOLFSSL_LIBS@ \
+ @JEMALLOC_LIBS@
+wsslclient_SOURCES = client.cc client.h ${CLIENT_SRCS} \
+ tls_client_context_wolfssl.cc tls_client_context_wolfssl.h \
+ tls_client_session_wolfssl.cc tls_client_session_wolfssl.h \
+ tls_session_base_wolfssl.cc tls_session_base_wolfssl.h \
+ util_wolfssl.cc
+
+wsslserver_CPPFLAGS = ${wsslclient_CPPFLAGS}
+wsslserver_LDADD = ${wsslclient_LDADD}
+wsslserver_SOURCES = server.cc server.h ${SERVER_SRCS} \
+ tls_server_context_wolfssl.cc tls_server_context_wolfssl.h \
+ tls_server_session_wolfssl.cc tls_server_session_wolfssl.h \
+ tls_session_base_wolfssl.cc tls_session_base_wolfssl.h \
+ util_wolfssl.cc
+endif # ENABLE_EXAMPLE_WOLFSSL
+
+if HAVE_CUNIT
+check_PROGRAMS = examplestest
+examplestest_SOURCES = examplestest.cc \
+ util_test.cc util_test.h util.cc util.h
+examplestest_CPPFLAGS = ${AM_CPPFLAGS} @JEMALLOC_CFLAGS@
+examplestest_LDADD = ${LDADD} @CUNIT_LIBS@ @JEMALLOC_LIBS@
+
+TESTS = examplestest
+endif # HAVE_CUNIT
diff --git a/examples/client.cc b/examples/client.cc
new file mode 100644
index 0000000..2a2e2b3
--- /dev/null
+++ b/examples/client.cc
@@ -0,0 +1,3052 @@
+/*
+ * 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 <cstdlib>
+#include <cassert>
+#include <cerrno>
+#include <iostream>
+#include <algorithm>
+#include <memory>
+#include <fstream>
+#include <iomanip>
+
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/mman.h>
+
+#include <http-parser/http_parser.h>
+
+#include "client.h"
+#include "network.h"
+#include "debug.h"
+#include "util.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+using namespace std::literals;
+
+namespace {
+auto randgen = util::make_mt19937();
+} // namespace
+
+namespace {
+constexpr size_t max_preferred_versionslen = 4;
+} // namespace
+
+Config config{};
+
+Stream::Stream(const Request &req, int64_t stream_id)
+ : req(req), stream_id(stream_id), fd(-1) {}
+
+Stream::~Stream() {
+ if (fd != -1) {
+ close(fd);
+ }
+}
+
+int Stream::open_file(const std::string_view &path) {
+ assert(fd == -1);
+
+ std::string_view filename;
+
+ auto it = std::find(std::rbegin(path), std::rend(path), '/').base();
+ if (it == std::end(path)) {
+ filename = "index.html"sv;
+ } else {
+ filename = std::string_view{it, static_cast<size_t>(std::end(path) - it)};
+ if (filename == ".."sv || filename == "."sv) {
+ std::cerr << "Invalid file name: " << filename << std::endl;
+ return -1;
+ }
+ }
+
+ auto fname = std::string{config.download};
+ fname += '/';
+ fname += filename;
+
+ fd = open(fname.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd == -1) {
+ std::cerr << "open: Could not open file " << fname << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ c->on_write();
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto ep = static_cast<Endpoint *>(w->data);
+ auto c = ep->client;
+
+ if (c->on_read(*ep) != 0) {
+ return;
+ }
+
+ c->on_write();
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ int rv;
+ auto c = static_cast<Client *>(w->data);
+
+ rv = c->handle_expiry();
+ if (rv != 0) {
+ return;
+ }
+
+ c->on_write();
+}
+} // namespace
+
+namespace {
+void change_local_addrcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ c->change_local_addr();
+}
+} // namespace
+
+namespace {
+void key_updatecb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ if (c->initiate_key_update() != 0) {
+ c->disconnect();
+ }
+}
+} // namespace
+
+namespace {
+void delay_streamcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ ev_timer_stop(loop, w);
+ c->on_extend_max_streams();
+ c->on_write();
+}
+} // namespace
+
+namespace {
+void siginthandler(struct ev_loop *loop, ev_signal *w, int revents) {
+ ev_break(loop, EVBREAK_ALL);
+}
+} // namespace
+
+Client::Client(struct ev_loop *loop, uint32_t client_chosen_version,
+ uint32_t original_version)
+ : remote_addr_{},
+ loop_(loop),
+ httpconn_(nullptr),
+ addr_(nullptr),
+ port_(nullptr),
+ nstreams_done_(0),
+ nstreams_closed_(0),
+ nkey_update_(0),
+ client_chosen_version_(client_chosen_version),
+ original_version_(original_version),
+ early_data_(false),
+ should_exit_(false),
+ should_exit_on_handshake_confirmed_(false),
+ handshake_confirmed_(false),
+ tx_{} {
+ ev_io_init(&wev_, writecb, 0, EV_WRITE);
+ wev_.data = this;
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+ ev_timer_init(&change_local_addr_timer_, change_local_addrcb,
+ static_cast<double>(config.change_local_addr) / NGTCP2_SECONDS,
+ 0.);
+ change_local_addr_timer_.data = this;
+ ev_timer_init(&key_update_timer_, key_updatecb,
+ static_cast<double>(config.key_update) / NGTCP2_SECONDS, 0.);
+ key_update_timer_.data = this;
+ ev_timer_init(&delay_stream_timer_, delay_streamcb,
+ static_cast<double>(config.delay_stream) / NGTCP2_SECONDS, 0.);
+ delay_stream_timer_.data = this;
+ ev_signal_init(&sigintev_, siginthandler, SIGINT);
+}
+
+Client::~Client() {
+ disconnect();
+
+ if (httpconn_) {
+ nghttp3_conn_del(httpconn_);
+ httpconn_ = nullptr;
+ }
+}
+
+void Client::disconnect() {
+ tx_.send_blocked = false;
+
+ handle_error();
+
+ config.tx_loss_prob = 0;
+
+ ev_timer_stop(loop_, &delay_stream_timer_);
+ ev_timer_stop(loop_, &key_update_timer_);
+ ev_timer_stop(loop_, &change_local_addr_timer_);
+ ev_timer_stop(loop_, &timer_);
+
+ ev_io_stop(loop_, &wev_);
+
+ for (auto &ep : endpoints_) {
+ ev_io_stop(loop_, &ep.rev);
+ close(ep.fd);
+ }
+
+ endpoints_.clear();
+
+ ev_signal_stop(loop_, &sigintev_);
+}
+
+namespace {
+int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data) {
+ if (!config.quiet && !config.no_quic_dump) {
+ debug::print_crypto_data(crypto_level, data, datalen);
+ }
+
+ return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data,
+ datalen, user_data);
+}
+} // namespace
+
+namespace {
+int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet && !config.no_quic_dump) {
+ debug::print_stream_data(stream_id, data, datalen);
+ }
+
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->recv_stream_data(flags, stream_id, data, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->acked_stream_data_offset(stream_id, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (!config.quiet) {
+ debug::handshake_completed(conn, user_data);
+ }
+
+ if (c->handshake_completed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::handshake_completed() {
+ if (early_data_ && !tls_session_.get_early_data_accepted()) {
+ if (!config.quiet) {
+ std::cerr << "Early data was rejected by server" << std::endl;
+ }
+
+ // Some TLS backends only report early data rejection after
+ // handshake completion (e.g., OpenSSL). For TLS backends which
+ // report it early (e.g., BoringSSL and PicoTLS), the following
+ // functions are noop.
+ if (auto rv = ngtcp2_conn_early_data_rejected(conn_); rv != 0) {
+ std::cerr << "ngtcp2_conn_early_data_rejected: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (setup_httpconn() != 0) {
+ return -1;
+ }
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name()
+ << std::endl;
+ std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn()
+ << std::endl;
+ }
+
+ if (config.tp_file) {
+ auto params = ngtcp2_conn_get_remote_transport_params(conn_);
+
+ if (write_transport_params(config.tp_file, params) != 0) {
+ std::cerr << "Could not write transport parameters in " << config.tp_file
+ << std::endl;
+ }
+ }
+
+ return 0;
+}
+
+namespace {
+int handshake_confirmed(ngtcp2_conn *conn, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (!config.quiet) {
+ debug::handshake_confirmed(conn, user_data);
+ }
+
+ if (c->handshake_confirmed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::handshake_confirmed() {
+ handshake_confirmed_ = true;
+
+ if (config.change_local_addr) {
+ start_change_local_addr_timer();
+ }
+ if (config.key_update) {
+ start_key_update_timer();
+ }
+ if (config.delay_stream) {
+ start_delay_stream_timer();
+ }
+
+ if (should_exit_on_handshake_confirmed_) {
+ should_exit_ = true;
+ }
+
+ return 0;
+}
+
+namespace {
+int recv_version_negotiation(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
+ const uint32_t *sv, size_t nsv, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ c->recv_version_negotiation(sv, nsv);
+
+ return 0;
+}
+} // namespace
+
+void Client::recv_version_negotiation(const uint32_t *sv, size_t nsv) {
+ offered_versions_.resize(nsv);
+ std::copy_n(sv, nsv, std::begin(offered_versions_));
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+
+ if (c->on_stream_close(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->on_stream_reset(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->on_stream_stop_sending(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int extend_max_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
+ void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->on_extend_max_streams() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
+ std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); });
+}
+} // namespace
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ if (util::generate_secure_random(cid->data, cidlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+ if (ngtcp2_crypto_generate_stateless_reset_token(
+ token, config.static_secret.data(), config.static_secret.size(),
+ cid) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+ const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) {
+ if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (!config.quiet && config.show_secret) {
+ debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen,
+ void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx,
+ tx_iv, current_rx_secret, current_tx_secret,
+ secretlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path,
+ ngtcp2_path_validation_result res, void *user_data) {
+ if (!config.quiet) {
+ debug::path_validation(path, res);
+ }
+
+ if (flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR) {
+ auto c = static_cast<Client *>(user_data);
+
+ c->set_remote_addr(path->remote);
+ }
+
+ return 0;
+}
+} // namespace
+
+void Client::set_remote_addr(const ngtcp2_addr &remote_addr) {
+ memcpy(&remote_addr_.su, remote_addr.addr, remote_addr.addrlen);
+ remote_addr_.len = remote_addr.addrlen;
+}
+
+namespace {
+int select_preferred_address(ngtcp2_conn *conn, ngtcp2_path *dest,
+ const ngtcp2_preferred_addr *paddr,
+ void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+ Address remote_addr;
+
+ if (config.no_preferred_addr) {
+ return 0;
+ }
+
+ if (c->select_preferred_address(remote_addr, paddr) != 0) {
+ return 0;
+ }
+
+ auto ep = c->endpoint_for(remote_addr);
+ if (!ep) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ ngtcp2_addr_copy_byte(&dest->local, &(*ep)->addr.su.sa, (*ep)->addr.len);
+ ngtcp2_addr_copy_byte(&dest->remote, &remote_addr.su.sa, remote_addr.len);
+ dest->user_data = *ep;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->extend_max_stream_data(stream_id, max_data) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::extend_max_stream_data(int64_t stream_id, uint64_t max_data) {
+ if (auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); rv != 0) {
+ std::cerr << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int recv_new_token(ngtcp2_conn *conn, const ngtcp2_vec *token,
+ void *user_data) {
+ if (config.token_file.empty()) {
+ return 0;
+ }
+
+ util::write_token(config.token_file, token->base, token->len);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int recv_rx_key(ngtcp2_conn *conn, ngtcp2_crypto_level level, void *user_data) {
+ if (level != NGTCP2_CRYPTO_LEVEL_APPLICATION) {
+ return 0;
+ }
+
+ auto c = static_cast<Client *>(user_data);
+ if ((!c->get_early_data() || ngtcp2_conn_get_early_data_rejected(conn)) &&
+ c->setup_httpconn() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int early_data_rejected(ngtcp2_conn *conn, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ c->early_data_rejected();
+
+ return 0;
+}
+} // namespace
+
+void Client::early_data_rejected() {
+ nghttp3_conn_del(httpconn_);
+ httpconn_ = nullptr;
+
+ nstreams_done_ = 0;
+ streams_.clear();
+}
+
+int Client::init(int fd, const Address &local_addr, const Address &remote_addr,
+ const char *addr, const char *port,
+ TLSClientContext &tls_ctx) {
+ endpoints_.reserve(4);
+
+ endpoints_.emplace_back();
+ auto &ep = endpoints_.back();
+ ep.addr = local_addr;
+ ep.client = this;
+ ep.fd = fd;
+ ev_io_init(&ep.rev, readcb, fd, EV_READ);
+ ep.rev.data = &ep;
+
+ remote_addr_ = remote_addr;
+ addr_ = addr;
+ port_ = port;
+
+ auto callbacks = ngtcp2_callbacks{
+ ngtcp2_crypto_client_initial_cb,
+ nullptr, // recv_client_initial
+ ::recv_crypto_data,
+ ::handshake_completed,
+ ::recv_version_negotiation,
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ do_hp_mask,
+ ::recv_stream_data,
+ ::acked_stream_data_offset,
+ nullptr, // stream_open
+ stream_close,
+ nullptr, // recv_stateless_reset
+ ngtcp2_crypto_recv_retry_cb,
+ extend_max_streams_bidi,
+ nullptr, // extend_max_streams_uni
+ rand,
+ get_new_connection_id,
+ nullptr, // remove_connection_id
+ ::update_key,
+ path_validation,
+ ::select_preferred_address,
+ stream_reset,
+ nullptr, // extend_max_remote_streams_bidi,
+ nullptr, // extend_max_remote_streams_uni,
+ ::extend_max_stream_data,
+ nullptr, // dcid_status
+ ::handshake_confirmed,
+ ::recv_new_token,
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ stream_stop_sending,
+ ngtcp2_crypto_version_negotiation_cb,
+ ::recv_rx_key,
+ nullptr, // recv_tx_key
+ ::early_data_rejected,
+ };
+
+ ngtcp2_cid scid, dcid;
+ if (config.scid_present) {
+ scid = config.scid;
+ } else {
+ scid.datalen = 17;
+ if (util::generate_secure_random(scid.data, scid.datalen) != 0) {
+ std::cerr << "Could not generate source connection ID" << std::endl;
+ return -1;
+ }
+ }
+ if (config.dcid.datalen == 0) {
+ dcid.datalen = 18;
+ if (util::generate_secure_random(dcid.data, dcid.datalen) != 0) {
+ std::cerr << "Could not generate destination connection ID" << std::endl;
+ return -1;
+ }
+ } else {
+ dcid = config.dcid;
+ }
+
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ settings.log_printf = config.quiet ? nullptr : debug::log_printf;
+ if (!config.qlog_file.empty() || !config.qlog_dir.empty()) {
+ std::string path;
+ if (!config.qlog_file.empty()) {
+ path = config.qlog_file;
+ } else {
+ path = std::string{config.qlog_dir};
+ path += '/';
+ path += util::format_hex(scid.data, scid.datalen);
+ path += ".sqlog";
+ }
+ qlog_ = fopen(path.c_str(), "w");
+ if (qlog_ == nullptr) {
+ std::cerr << "Could not open qlog file " << std::quoted(path) << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+ settings.qlog.write = qlog_write_cb;
+ }
+
+ settings.cc_algo = config.cc_algo;
+ settings.initial_ts = util::timestamp(loop_);
+ settings.initial_rtt = config.initial_rtt;
+ settings.max_window = config.max_window;
+ settings.max_stream_window = config.max_stream_window;
+ if (config.max_udp_payload_size) {
+ settings.max_tx_udp_payload_size = config.max_udp_payload_size;
+ settings.no_tx_udp_payload_size_shaping = 1;
+ }
+ settings.handshake_timeout = config.handshake_timeout;
+ settings.no_pmtud = config.no_pmtud;
+ settings.ack_thresh = config.ack_thresh;
+
+ std::string token;
+
+ if (!config.token_file.empty()) {
+ std::cerr << "Reading token file " << config.token_file << std::endl;
+
+ auto t = util::read_token(config.token_file);
+ if (t) {
+ token = std::move(*t);
+ settings.token.base = reinterpret_cast<uint8_t *>(token.data());
+ settings.token.len = token.size();
+ }
+ }
+
+ if (!config.other_versions.empty()) {
+ settings.other_versions = config.other_versions.data();
+ settings.other_versionslen = config.other_versions.size();
+ }
+
+ if (!config.preferred_versions.empty()) {
+ settings.preferred_versions = config.preferred_versions.data();
+ settings.preferred_versionslen = config.preferred_versions.size();
+ }
+
+ settings.original_version = original_version_;
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+ params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
+ params.initial_max_stream_data_bidi_remote =
+ config.max_stream_data_bidi_remote;
+ params.initial_max_stream_data_uni = config.max_stream_data_uni;
+ params.initial_max_data = config.max_data;
+ params.initial_max_streams_bidi = config.max_streams_bidi;
+ params.initial_max_streams_uni = config.max_streams_uni;
+ params.max_idle_timeout = config.timeout;
+ params.active_connection_id_limit = 7;
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&ep.addr.su.sa),
+ ep.addr.len,
+ },
+ {
+ const_cast<sockaddr *>(&remote_addr.su.sa),
+ remote_addr.len,
+ },
+ &ep,
+ };
+ auto rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path,
+ client_chosen_version_, &callbacks,
+ &settings, &params, nullptr, this);
+
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_client_new: " << ngtcp2_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (tls_session_.init(early_data_, tls_ctx, addr_, this,
+ client_chosen_version_, AppProtocol::H3) != 0) {
+ return -1;
+ }
+
+ ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle());
+
+ if (early_data_ && config.tp_file) {
+ ngtcp2_transport_params params;
+ if (read_transport_params(config.tp_file, &params) != 0) {
+ std::cerr << "Could not read transport parameters from " << config.tp_file
+ << std::endl;
+ early_data_ = false;
+ } else {
+ ngtcp2_conn_set_early_remote_transport_params(conn_, &params);
+ if (make_stream_early() != 0) {
+ return -1;
+ }
+ }
+ }
+
+ ev_io_start(loop_, &ep.rev);
+
+ ev_signal_start(loop_, &sigintev_);
+
+ return 0;
+}
+
+int Client::feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data,
+ size_t datalen) {
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&ep.addr.su.sa),
+ ep.addr.len,
+ },
+ {
+ const_cast<sockaddr *>(sa),
+ salen,
+ },
+ const_cast<Endpoint *>(&ep),
+ };
+ if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen,
+ util::timestamp(loop_));
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl;
+ if (!last_error_.error_code) {
+ if (rv == NGTCP2_ERR_CRYPTO) {
+ ngtcp2_connection_close_error_set_transport_error_tls_alert(
+ &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0);
+ } else {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, rv, nullptr, 0);
+ }
+ }
+ disconnect();
+ return -1;
+ }
+ return 0;
+}
+
+int Client::on_read(const Endpoint &ep) {
+ std::array<uint8_t, 64_k> buf;
+ sockaddr_union su;
+ size_t pktcnt = 0;
+ ngtcp2_pkt_info pi;
+
+ iovec msg_iov;
+ msg_iov.iov_base = buf.data();
+ msg_iov.iov_len = buf.size();
+
+ msghdr msg{};
+ msg.msg_name = &su;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint8_t))];
+ msg.msg_control = msg_ctrl;
+
+ for (;;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(ep.fd, &msg, 0);
+
+ if (nread == -1) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ std::cerr << "recvmsg: " << strerror(errno) << std::endl;
+ }
+ break;
+ }
+
+ pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family);
+
+ if (!config.quiet) {
+ std::cerr << "Received packet: local="
+ << util::straddr(&ep.addr.su.sa, ep.addr.len)
+ << " remote=" << util::straddr(&su.sa, msg.msg_namelen)
+ << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread
+ << " bytes" << std::endl;
+ }
+
+ if (debug::packet_lost(config.rx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated incoming packet loss **" << std::endl;
+ }
+ break;
+ }
+
+ if (feed_data(ep, &su.sa, msg.msg_namelen, &pi, buf.data(), nread) != 0) {
+ return -1;
+ }
+
+ if (++pktcnt >= 10) {
+ break;
+ }
+ }
+
+ if (should_exit_) {
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(0), nullptr, 0);
+ disconnect();
+ return -1;
+ }
+
+ update_timer();
+
+ return 0;
+}
+
+int Client::handle_expiry() {
+ auto now = util::timestamp(loop_);
+ if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) {
+ std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv,
+ nullptr, 0);
+ disconnect();
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::on_write() {
+ if (tx_.send_blocked) {
+ if (auto rv = send_blocked_packet(); rv != 0) {
+ return rv;
+ }
+
+ if (tx_.send_blocked) {
+ return 0;
+ }
+ }
+
+ if (auto rv = write_streams(); rv != 0) {
+ return rv;
+ }
+
+ if (should_exit_) {
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(0), nullptr, 0);
+ disconnect();
+ return -1;
+ }
+
+ update_timer();
+ return 0;
+}
+
+int Client::write_streams() {
+ std::array<nghttp3_vec, 16> vec;
+ ngtcp2_path_storage ps;
+ size_t pktcnt = 0;
+ auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_);
+ auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size;
+ auto ts = util::timestamp(loop_);
+
+ ngtcp2_path_storage_zero(&ps);
+
+ for (;;) {
+ int64_t stream_id = -1;
+ int fin = 0;
+ nghttp3_ssize sveccnt = 0;
+
+ if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) {
+ sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin,
+ vec.data(), vec.size());
+ if (sveccnt < 0) {
+ std::cerr << "nghttp3_conn_writev_stream: " << nghttp3_strerror(sveccnt)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(sveccnt),
+ nullptr, 0);
+ disconnect();
+ return -1;
+ }
+ }
+
+ ngtcp2_ssize ndatalen;
+ auto v = vec.data();
+ auto vcnt = static_cast<size_t>(sveccnt);
+
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ if (fin) {
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ ngtcp2_pkt_info pi;
+
+ auto nwrite = ngtcp2_conn_writev_stream(
+ conn_, &ps.path, &pi, tx_.data.data(), max_udp_payload_size, &ndatalen,
+ flags, stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ assert(ndatalen == -1);
+ nghttp3_conn_block_stream(httpconn_, stream_id);
+ continue;
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ nghttp3_conn_shutdown_stream_write(httpconn_, stream_id);
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ if (auto rv =
+ nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr,
+ 0);
+ disconnect();
+ return -1;
+ }
+ continue;
+ }
+
+ assert(ndatalen == -1);
+
+ std::cerr << "ngtcp2_conn_write_stream: " << ngtcp2_strerror(nwrite)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, nwrite, nullptr, 0);
+ disconnect();
+ return -1;
+ } else if (ndatalen >= 0) {
+ if (auto rv =
+ nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr,
+ 0);
+ disconnect();
+ return -1;
+ }
+ }
+
+ if (nwrite == 0) {
+ // We are congestion limited.
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ ev_io_stop(loop_, &wev_);
+ return 0;
+ }
+
+ auto &ep = *static_cast<Endpoint *>(ps.path.user_data);
+
+ if (auto rv =
+ send_packet(ep, ps.path.remote, pi.ecn, tx_.data.data(), nwrite);
+ rv != NETWORK_ERR_OK) {
+ if (rv != NETWORK_ERR_SEND_BLOCKED) {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0);
+ disconnect();
+
+ return rv;
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ on_send_blocked(ep, ps.path.remote, pi.ecn, nwrite);
+
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt) {
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ start_wev_endpoint(ep);
+ return 0;
+ }
+ }
+}
+
+void Client::update_timer() {
+ auto expiry = ngtcp2_conn_get_expiry(conn_);
+ auto now = util::timestamp(loop_);
+
+ if (expiry <= now) {
+ if (!config.quiet) {
+ auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS;
+ std::cerr << "Timer has already expired: " << t << "s" << std::endl;
+ }
+
+ ev_feed_event(loop_, &timer_, EV_TIMER);
+
+ return;
+ }
+
+ auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS;
+ if (!config.quiet) {
+ std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat
+ << std::endl;
+ }
+ timer_.repeat = t;
+ ev_timer_again(loop_, &timer_);
+}
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+namespace {
+int bind_addr(Address &local_addr, int fd, const in_addr_union *iau,
+ int family) {
+ addrinfo hints{};
+ addrinfo *res, *rp;
+
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ char *node;
+ std::array<char, NI_MAXHOST> nodebuf;
+
+ if (iau) {
+ if (inet_ntop(family, iau, nodebuf.data(), nodebuf.size()) == nullptr) {
+ std::cerr << "inet_ntop: " << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ node = nodebuf.data();
+ } else {
+ node = nullptr;
+ }
+
+ if (auto rv = getaddrinfo(node, "0", &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
+ break;
+ }
+ }
+
+ if (!rp) {
+ std::cerr << "Could not bind" << std::endl;
+ return -1;
+ }
+
+ socklen_t len = sizeof(local_addr.su.storage);
+ if (getsockname(fd, &local_addr.su.sa, &len) == -1) {
+ std::cerr << "getsockname: " << strerror(errno) << std::endl;
+ return -1;
+ }
+ local_addr.len = len;
+ local_addr.ifindex = 0;
+
+ return 0;
+}
+} // namespace
+#endif // HAVE_LINUX_RTNETLINK_H
+
+#ifndef HAVE_LINUX_RTNETLINK_H
+namespace {
+int connect_sock(Address &local_addr, int fd, const Address &remote_addr) {
+ if (connect(fd, &remote_addr.su.sa, remote_addr.len) != 0) {
+ std::cerr << "connect: " << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ socklen_t len = sizeof(local_addr.su.storage);
+ if (getsockname(fd, &local_addr.su.sa, &len) == -1) {
+ std::cerr << "getsockname: " << strerror(errno) << std::endl;
+ return -1;
+ }
+ local_addr.len = len;
+ local_addr.ifindex = 0;
+
+ return 0;
+}
+} // namespace
+#endif // !HAVE_LINUX_RTNETLINK_H
+
+namespace {
+int udp_sock(int family) {
+ auto fd = util::create_nonblock_socket(family, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd == -1) {
+ return -1;
+ }
+
+ fd_set_recv_ecn(fd, family);
+ fd_set_ip_mtu_discover(fd, family);
+ fd_set_ip_dontfrag(fd, family);
+
+ return fd;
+}
+} // namespace
+
+namespace {
+int create_sock(Address &remote_addr, const char *addr, const char *port) {
+ addrinfo hints{};
+ addrinfo *res, *rp;
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ int fd = -1;
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ fd = udp_sock(rp->ai_family);
+ if (fd == -1) {
+ continue;
+ }
+
+ break;
+ }
+
+ if (!rp) {
+ std::cerr << "Could not create socket" << std::endl;
+ return -1;
+ }
+
+ remote_addr.len = rp->ai_addrlen;
+ memcpy(&remote_addr.su, rp->ai_addr, rp->ai_addrlen);
+
+ return fd;
+}
+} // namespace
+
+std::optional<Endpoint *> Client::endpoint_for(const Address &remote_addr) {
+#ifdef HAVE_LINUX_RTNETLINK_H
+ in_addr_union iau;
+
+ if (get_local_addr(iau, remote_addr) != 0) {
+ std::cerr << "Could not get local address for a selected preferred address"
+ << std::endl;
+ return nullptr;
+ }
+
+ auto current_path = ngtcp2_conn_get_path(conn_);
+ auto current_ep = static_cast<Endpoint *>(current_path->user_data);
+ if (addreq(&current_ep->addr.su.sa, iau)) {
+ return current_ep;
+ }
+#endif // HAVE_LINUX_RTNETLINK_H
+
+ auto fd = udp_sock(remote_addr.su.sa.sa_family);
+ if (fd == -1) {
+ return nullptr;
+ }
+
+ Address local_addr;
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+ if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) {
+ close(fd);
+ return nullptr;
+ }
+#else // !HAVE_LINUX_RTNETLINK_H
+ if (connect_sock(local_addr, fd, remote_addr) != 0) {
+ close(fd);
+ return nullptr;
+ }
+#endif // !HAVE_LINUX_RTNETLINK_H
+
+ endpoints_.emplace_back();
+ auto &ep = endpoints_.back();
+ ep.addr = local_addr;
+ ep.client = this;
+ ep.fd = fd;
+ ev_io_init(&ep.rev, readcb, fd, EV_READ);
+ ep.rev.data = &ep;
+
+ ev_io_start(loop_, &ep.rev);
+
+ return &ep;
+}
+
+void Client::start_change_local_addr_timer() {
+ ev_timer_start(loop_, &change_local_addr_timer_);
+}
+
+int Client::change_local_addr() {
+ Address local_addr;
+
+ if (!config.quiet) {
+ std::cerr << "Changing local address" << std::endl;
+ }
+
+ auto nfd = udp_sock(remote_addr_.su.sa.sa_family);
+ if (nfd == -1) {
+ return -1;
+ }
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+ in_addr_union iau;
+
+ if (get_local_addr(iau, remote_addr_) != 0) {
+ std::cerr << "Could not get local address" << std::endl;
+ close(nfd);
+ return -1;
+ }
+
+ if (bind_addr(local_addr, nfd, &iau, remote_addr_.su.sa.sa_family) != 0) {
+ close(nfd);
+ return -1;
+ }
+#else // !HAVE_LINUX_RTNETLINK_H
+ if (connect_sock(local_addr, nfd, remote_addr_) != 0) {
+ close(nfd);
+ return -1;
+ }
+#endif // !HAVE_LINUX_RTNETLINK_H
+
+ if (!config.quiet) {
+ std::cerr << "Local address is now "
+ << util::straddr(&local_addr.su.sa, local_addr.len) << std::endl;
+ }
+
+ endpoints_.emplace_back();
+ auto &ep = endpoints_.back();
+ ep.addr = local_addr;
+ ep.client = this;
+ ep.fd = nfd;
+ ev_io_init(&ep.rev, readcb, nfd, EV_READ);
+ ep.rev.data = &ep;
+
+ ngtcp2_addr addr;
+ ngtcp2_addr_init(&addr, &local_addr.su.sa, local_addr.len);
+
+ if (config.nat_rebinding) {
+ ngtcp2_conn_set_local_addr(conn_, &addr);
+ ngtcp2_conn_set_path_user_data(conn_, &ep);
+ } else {
+ auto path = ngtcp2_path{
+ addr,
+ {
+ const_cast<sockaddr *>(&remote_addr_.su.sa),
+ remote_addr_.len,
+ },
+ &ep,
+ };
+ if (auto rv = ngtcp2_conn_initiate_immediate_migration(
+ conn_, &path, util::timestamp(loop_));
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_initiate_immediate_migration: "
+ << ngtcp2_strerror(rv) << std::endl;
+ }
+ }
+
+ ev_io_start(loop_, &ep.rev);
+
+ return 0;
+}
+
+void Client::start_key_update_timer() {
+ ev_timer_start(loop_, &key_update_timer_);
+}
+
+int Client::update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen) {
+ if (!config.quiet) {
+ std::cerr << "Updating traffic key" << std::endl;
+ }
+
+ auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_);
+ auto aead = &crypto_ctx->aead;
+ auto keylen = ngtcp2_crypto_aead_keylen(aead);
+ auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
+
+ ++nkey_update_;
+
+ std::array<uint8_t, 64> rx_key, tx_key;
+
+ if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx,
+ rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(),
+ tx_iv, current_rx_secret, current_tx_secret,
+ secretlen) != 0) {
+ return -1;
+ }
+
+ if (!config.quiet && config.show_secret) {
+ std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl;
+ debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv,
+ ivlen);
+ std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl;
+ debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv,
+ ivlen);
+ }
+
+ return 0;
+}
+
+int Client::initiate_key_update() {
+ if (!config.quiet) {
+ std::cerr << "Initiate key update" << std::endl;
+ }
+
+ if (auto rv = ngtcp2_conn_initiate_key_update(conn_, util::timestamp(loop_));
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_initiate_key_update: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+void Client::start_delay_stream_timer() {
+ ev_timer_start(loop_, &delay_stream_timer_);
+}
+
+int Client::send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr,
+ unsigned int ecn, const uint8_t *data, size_t datalen) {
+ if (debug::packet_lost(config.tx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated outgoing packet loss **" << std::endl;
+ }
+ return NETWORK_ERR_OK;
+ }
+
+ iovec msg_iov;
+ msg_iov.iov_base = const_cast<uint8_t *>(data);
+ msg_iov.iov_len = datalen;
+
+ msghdr msg{};
+#ifdef HAVE_LINUX_RTNETLINK_H
+ msg.msg_name = const_cast<sockaddr *>(remote_addr.addr);
+ msg.msg_namelen = remote_addr.addrlen;
+#endif // HAVE_LINUX_RTNETLINK_H
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ fd_set_ecn(ep.fd, remote_addr.addr->sa_family, ecn);
+
+ ssize_t nwrite = 0;
+
+ do {
+ nwrite = sendmsg(ep.fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return NETWORK_ERR_SEND_BLOCKED;
+ }
+ std::cerr << "sendmsg: " << strerror(errno) << std::endl;
+ if (errno == EMSGSIZE) {
+ return 0;
+ }
+ return NETWORK_ERR_FATAL;
+ }
+
+ assert(static_cast<size_t>(nwrite) == datalen);
+
+ if (!config.quiet) {
+ std::cerr << "Sent packet: local="
+ << util::straddr(&ep.addr.su.sa, ep.addr.len) << " remote="
+ << util::straddr(remote_addr.addr, remote_addr.addrlen)
+ << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite
+ << " bytes" << std::endl;
+ }
+
+ return NETWORK_ERR_OK;
+}
+
+void Client::on_send_blocked(const Endpoint &ep, const ngtcp2_addr &remote_addr,
+ unsigned int ecn, size_t datalen) {
+ assert(!tx_.send_blocked);
+
+ tx_.send_blocked = true;
+
+ memcpy(&tx_.blocked.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+ tx_.blocked.remote_addr.len = remote_addr.addrlen;
+ tx_.blocked.ecn = ecn;
+ tx_.blocked.datalen = datalen;
+ tx_.blocked.endpoint = &ep;
+
+ start_wev_endpoint(ep);
+}
+
+void Client::start_wev_endpoint(const Endpoint &ep) {
+ // We do not close ep.fd, so we can expect that each Endpoint has
+ // unique fd.
+ if (ep.fd != wev_.fd) {
+ if (ev_is_active(&wev_)) {
+ ev_io_stop(loop_, &wev_);
+ }
+
+ ev_io_set(&wev_, ep.fd, EV_WRITE);
+ }
+
+ ev_io_start(loop_, &wev_);
+}
+
+int Client::send_blocked_packet() {
+ assert(tx_.send_blocked);
+
+ ngtcp2_addr remote_addr{
+ .addr = &tx_.blocked.remote_addr.su.sa,
+ .addrlen = tx_.blocked.remote_addr.len,
+ };
+
+ auto rv = send_packet(*tx_.blocked.endpoint, remote_addr, tx_.blocked.ecn,
+ tx_.data.data(), tx_.blocked.datalen);
+ if (rv != 0) {
+ if (rv == NETWORK_ERR_SEND_BLOCKED) {
+ assert(wev_.fd == tx_.blocked.endpoint->fd);
+
+ ev_io_start(loop_, &wev_);
+
+ return 0;
+ }
+
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0);
+ disconnect();
+
+ return rv;
+ }
+
+ tx_.send_blocked = false;
+
+ return 0;
+}
+
+int Client::handle_error() {
+ if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_) ||
+ ngtcp2_conn_is_in_draining_period(conn_)) {
+ return 0;
+ }
+
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+
+ ngtcp2_path_storage ps;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ ngtcp2_pkt_info pi;
+
+ auto nwrite = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, buf.data(), buf.size(), &last_error_,
+ util::timestamp(loop_));
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_conn_write_connection_close: "
+ << ngtcp2_strerror(nwrite) << std::endl;
+ return -1;
+ }
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ return send_packet(*static_cast<Endpoint *>(ps.path.user_data),
+ ps.path.remote, pi.ecn, buf.data(), nwrite);
+}
+
+int Client::on_stream_close(int64_t stream_id, uint64_t app_error_code) {
+ if (httpconn_) {
+ if (app_error_code == 0) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+ auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code);
+ switch (rv) {
+ case 0:
+ break;
+ case NGHTTP3_ERR_STREAM_NOT_FOUND:
+ // We have to handle the case when stream opened but no data is
+ // transferred. In this case, nghttp3_conn_close_stream might
+ // return error.
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
+ ngtcp2_conn_extend_max_streams_uni(conn_, 1);
+ }
+ break;
+ default:
+ std::cerr << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int Client::on_stream_reset(int64_t stream_id) {
+ if (httpconn_) {
+ if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int Client::on_stream_stop_sending(int64_t stream_id) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::make_stream_early() {
+ if (setup_httpconn() != 0) {
+ return -1;
+ }
+
+ return on_extend_max_streams();
+}
+
+int Client::on_extend_max_streams() {
+ int64_t stream_id;
+
+ if ((config.delay_stream && !handshake_confirmed_) ||
+ ev_is_active(&delay_stream_timer_)) {
+ return 0;
+ }
+
+ for (; nstreams_done_ < config.nstreams; ++nstreams_done_) {
+ if (auto rv = ngtcp2_conn_open_bidi_stream(conn_, &stream_id, nullptr);
+ rv != 0) {
+ assert(NGTCP2_ERR_STREAM_ID_BLOCKED == rv);
+ break;
+ }
+
+ auto stream = std::make_unique<Stream>(
+ config.requests[nstreams_done_ % config.requests.size()], stream_id);
+
+ if (submit_http_request(stream.get()) != 0) {
+ break;
+ }
+
+ if (!config.download.empty()) {
+ stream->open_file(stream->req.path);
+ }
+ streams_.emplace(stream_id, std::move(stream));
+ }
+ return 0;
+}
+
+namespace {
+nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
+ size_t veccnt, uint32_t *pflags, void *user_data,
+ void *stream_user_data) {
+ vec[0].base = config.data;
+ vec[0].len = config.datalen;
+ *pflags |= NGHTTP3_DATA_FLAG_EOF;
+
+ return 1;
+}
+} // namespace
+
+int Client::submit_http_request(const Stream *stream) {
+ std::string content_length_str;
+
+ const auto &req = stream->req;
+
+ std::array<nghttp3_nv, 6> nva{
+ util::make_nv_nn(":method", config.http_method),
+ util::make_nv_nn(":scheme", req.scheme),
+ util::make_nv_nn(":authority", req.authority),
+ util::make_nv_nn(":path", req.path),
+ util::make_nv_nn("user-agent", "nghttp3/ngtcp2 client"),
+ };
+ size_t nvlen = 5;
+ if (config.fd != -1) {
+ content_length_str = util::format_uint(config.datalen);
+ nva[nvlen++] = util::make_nv_nc("content-length", content_length_str);
+ }
+
+ if (!config.quiet) {
+ debug::print_http_request_headers(stream->stream_id, nva.data(), nvlen);
+ }
+
+ nghttp3_data_reader dr{};
+ dr.read_data = read_data;
+
+ if (auto rv = nghttp3_conn_submit_request(
+ httpconn_, stream->stream_id, nva.data(), nvlen,
+ config.fd == -1 ? nullptr : &dr, nullptr);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_submit_request: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ auto nconsumed = nghttp3_conn_read_stream(
+ httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
+ if (nconsumed < 0) {
+ std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr,
+ 0);
+ return -1;
+ }
+
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(conn_, nconsumed);
+
+ return 0;
+}
+
+int Client::acked_stream_data_offset(int64_t stream_id, uint64_t datalen) {
+ if (auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::select_preferred_address(Address &selected_addr,
+ const ngtcp2_preferred_addr *paddr) {
+ auto path = ngtcp2_conn_get_path(conn_);
+
+ switch (path->local.addr->sa_family) {
+ case AF_INET:
+ if (!paddr->ipv4_present) {
+ return -1;
+ }
+ selected_addr.su.in = paddr->ipv4;
+ selected_addr.len = sizeof(paddr->ipv4);
+ break;
+ case AF_INET6:
+ if (!paddr->ipv6_present) {
+ return -1;
+ }
+ selected_addr.su.in6 = paddr->ipv6;
+ selected_addr.len = sizeof(paddr->ipv6);
+ break;
+ default:
+ return -1;
+ }
+
+ char host[NI_MAXHOST], service[NI_MAXSERV];
+ if (auto rv = getnameinfo(&selected_addr.su.sa, selected_addr.len, host,
+ sizeof(host), service, sizeof(service),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "selected server preferred_address is [" << host
+ << "]:" << service << std::endl;
+ }
+
+ return 0;
+}
+
+namespace {
+int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
+ size_t datalen, void *user_data, void *stream_user_data) {
+ if (!config.quiet && !config.no_http_dump) {
+ debug::print_http_data(stream_id, data, datalen);
+ }
+ auto c = static_cast<Client *>(user_data);
+ c->http_consume(stream_id, datalen);
+ c->http_write_data(stream_id, data, datalen);
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
+ size_t nconsumed, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ c->http_consume(stream_id, nconsumed);
+ return 0;
+}
+} // namespace
+
+void Client::http_consume(int64_t stream_id, size_t nconsumed) {
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(conn_, nconsumed);
+}
+
+void Client::http_write_data(int64_t stream_id, const uint8_t *data,
+ size_t datalen) {
+ auto it = streams_.find(stream_id);
+ if (it == std::end(streams_)) {
+ return;
+ }
+
+ auto &stream = (*it).second;
+
+ if (stream->fd == -1) {
+ return;
+ }
+
+ ssize_t nwrite;
+ do {
+ nwrite = write(stream->fd, data, datalen);
+ } while (nwrite == -1 && errno == EINTR);
+}
+
+namespace {
+int http_begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_begin_response_headers(stream_id);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token,
+ nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_header(stream_id, name, value, flags);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_end_headers(stream_id);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_begin_trailers(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_begin_trailers(stream_id);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_recv_trailer(nghttp3_conn *conn, int64_t stream_id, int32_t token,
+ nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_header(stream_id, name, value, flags);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_end_trailers(nghttp3_conn *conn, int64_t stream_id, int fin,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_end_trailers(stream_id);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_stop_sending(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->stop_sending(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::stop_sending(int64_t stream_id, uint64_t app_error_code) {
+ if (auto rv =
+ ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int http_reset_stream(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->reset_stream(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::reset_stream(int64_t stream_id, uint64_t app_error_code) {
+ if (auto rv =
+ ngtcp2_conn_shutdown_stream_write(conn_, stream_id, app_error_code);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int http_stream_close(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *conn_user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(conn_user_data);
+ if (c->http_stream_close(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::http_stream_close(int64_t stream_id, uint64_t app_error_code) {
+ if (ngtcp2_is_bidi_stream(stream_id)) {
+ assert(ngtcp2_conn_is_local_stream(conn_, stream_id));
+
+ ++nstreams_closed_;
+
+ if (config.exit_on_first_stream_close ||
+ (config.exit_on_all_streams_close &&
+ config.nstreams == nstreams_done_ &&
+ nstreams_closed_ == nstreams_done_)) {
+ if (handshake_confirmed_) {
+ should_exit_ = true;
+ } else {
+ should_exit_on_handshake_confirmed_ = true;
+ }
+ }
+ } else {
+ assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
+ ngtcp2_conn_extend_max_streams_uni(conn_, 1);
+ }
+
+ if (auto it = streams_.find(stream_id); it != std::end(streams_)) {
+ if (!config.quiet) {
+ std::cerr << "HTTP stream " << stream_id << " closed with error code "
+ << app_error_code << std::endl;
+ }
+ streams_.erase(it);
+ }
+
+ return 0;
+}
+
+int Client::setup_httpconn() {
+ if (httpconn_) {
+ return 0;
+ }
+
+ if (ngtcp2_conn_get_max_local_streams_uni(conn_) < 3) {
+ std::cerr << "peer does not allow at least 3 unidirectional streams."
+ << std::endl;
+ return -1;
+ }
+
+ nghttp3_callbacks callbacks{
+ nullptr, // acked_stream_data
+ ::http_stream_close,
+ ::http_recv_data,
+ ::http_deferred_consume,
+ ::http_begin_headers,
+ ::http_recv_header,
+ ::http_end_headers,
+ ::http_begin_trailers,
+ ::http_recv_trailer,
+ ::http_end_trailers,
+ ::http_stop_sending,
+ nullptr, // end_stream
+ ::http_reset_stream,
+ nullptr, // shutdown
+ };
+ nghttp3_settings settings;
+ nghttp3_settings_default(&settings);
+ settings.qpack_max_dtable_capacity = 4_k;
+ settings.qpack_blocked_streams = 100;
+
+ auto mem = nghttp3_mem_default();
+
+ if (auto rv =
+ nghttp3_conn_client_new(&httpconn_, &callbacks, &settings, mem, this);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ int64_t ctrl_stream_id;
+
+ if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (auto rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ fprintf(stderr, "http: control stream=%" PRIx64 "\n", ctrl_stream_id);
+ }
+
+ int64_t qpack_enc_stream_id, qpack_dec_stream_id;
+
+ if (auto rv =
+ ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (auto rv =
+ ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (auto rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id,
+ qpack_dec_stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ fprintf(stderr,
+ "http: QPACK streams encoder=%" PRIx64 " decoder=%" PRIx64 "\n",
+ qpack_enc_stream_id, qpack_dec_stream_id);
+ }
+
+ return 0;
+}
+
+const std::vector<uint32_t> &Client::get_offered_versions() const {
+ return offered_versions_;
+}
+
+bool Client::get_early_data() const { return early_data_; };
+
+namespace {
+int run(Client &c, const char *addr, const char *port,
+ TLSClientContext &tls_ctx) {
+ Address remote_addr, local_addr;
+
+ auto fd = create_sock(remote_addr, addr, port);
+ if (fd == -1) {
+ return -1;
+ }
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+ in_addr_union iau;
+
+ if (get_local_addr(iau, remote_addr) != 0) {
+ std::cerr << "Could not get local address" << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) {
+ close(fd);
+ return -1;
+ }
+#else // !HAVE_LINUX_RTNETLINK_H
+ if (connect_sock(local_addr, fd, remote_addr) != 0) {
+ close(fd);
+ return -1;
+ }
+#endif // !HAVE_LINUX_RTNETLINK_H
+
+ if (c.init(fd, local_addr, remote_addr, addr, port, tls_ctx) != 0) {
+ return -1;
+ }
+
+ // TODO Do we need this ?
+ if (auto rv = c.on_write(); rv != 0) {
+ return rv;
+ }
+
+ ev_run(EV_DEFAULT, 0);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+std::string_view get_string(const char *uri, const http_parser_url &u,
+ http_parser_url_fields f) {
+ auto p = &u.field_data[f];
+ return {uri + p->off, p->len};
+}
+} // namespace
+
+namespace {
+int parse_uri(Request &req, const char *uri) {
+ http_parser_url u;
+
+ http_parser_url_init(&u);
+ if (http_parser_parse_url(uri, strlen(uri), /* is_connect = */ 0, &u) != 0) {
+ return -1;
+ }
+
+ if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) {
+ return -1;
+ }
+
+ req.scheme = get_string(uri, u, UF_SCHEMA);
+
+ req.authority = get_string(uri, u, UF_HOST);
+ if (util::numeric_host(req.authority.c_str(), AF_INET6)) {
+ req.authority = '[' + req.authority + ']';
+ }
+ if (u.field_set & (1 << UF_PORT)) {
+ req.authority += ':';
+ req.authority += get_string(uri, u, UF_PORT);
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ req.path = get_string(uri, u, UF_PATH);
+ } else {
+ req.path = "/";
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ req.path += '?';
+ req.path += get_string(uri, u, UF_QUERY);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int parse_requests(char **argv, size_t argvlen) {
+ for (size_t i = 0; i < argvlen; ++i) {
+ auto uri = argv[i];
+ Request req;
+ if (parse_uri(req, uri) != 0) {
+ std::cerr << "Could not parse URI: " << uri << std::endl;
+ return -1;
+ }
+ config.requests.emplace_back(std::move(req));
+ }
+ return 0;
+}
+} // namespace
+
+std::ofstream keylog_file;
+
+namespace {
+void print_usage() {
+ std::cerr << "Usage: client [OPTIONS] <HOST> <PORT> [<URI>...]" << std::endl;
+}
+} // namespace
+
+namespace {
+void config_set_default(Config &config) {
+ config = Config{};
+ config.tx_loss_prob = 0.;
+ config.rx_loss_prob = 0.;
+ config.fd = -1;
+ config.ciphers = util::crypto_default_ciphers();
+ config.groups = util::crypto_default_groups();
+ config.nstreams = 0;
+ config.data = nullptr;
+ config.datalen = 0;
+ config.version = NGTCP2_PROTO_VER_V1;
+ config.timeout = 30 * NGTCP2_SECONDS;
+ config.http_method = "GET"sv;
+ config.max_data = 15_m;
+ config.max_stream_data_bidi_local = 6_m;
+ config.max_stream_data_bidi_remote = 6_m;
+ config.max_stream_data_uni = 6_m;
+ config.max_window = 24_m;
+ config.max_stream_window = 16_m;
+ config.max_streams_uni = 100;
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT;
+ config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT;
+ config.ack_thresh = 2;
+}
+} // namespace
+
+namespace {
+void print_help() {
+ print_usage();
+
+ config_set_default(config);
+
+ std::cout << R"(
+ <HOST> Remote server host (DNS name or IP address). In case of
+ DNS name, it will be sent in TLS SNI extension.
+ <PORT> Remote server port
+ <URI> Remote URI
+Options:
+ -t, --tx-loss=<P>
+ The probability of losing outgoing packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ -r, --rx-loss=<P>
+ The probability of losing incoming packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ -d, --data=<PATH>
+ Read data from <PATH>, and send them as STREAM data.
+ -n, --nstreams=<N>
+ The number of requests. <URI>s are used in the order of
+ appearance in the command-line. If the number of <URI>
+ list is less than <N>, <URI> list is wrapped. It
+ defaults to 0 which means the number of <URI> specified.
+ -v, --version=<HEX>
+ Specify QUIC version to use in hex string. If the given
+ version is not supported by libngtcp2, client will use
+ QUIC v1 long packet types. Instead of specifying hex
+ string, there are special aliases available: "v1"
+ indicates QUIC v1, and "v2draft" indicates QUIC v2
+ draft.
+ Default: )"
+ << std::hex << "0x" << config.version << std::dec << R"(
+ --preferred-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string in the order of
+ preference. Client chooses one of those versions if
+ client received Version Negotiation packet from server.
+ These versions must be supported by libngtcp2. Instead
+ of specifying hex string, there are special aliases
+ available: "v1" indicates QUIC v1, and "v2draft"
+ indicates QUIC v2 draft.
+ --other-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string that are sent in
+ other_versions field of version_information transport
+ parameter. This list can include a version which is not
+ supported by libngtcp2. Instead of specifying hex
+ string, there are special aliases available: "v1"
+ indicates QUIC v1, and "v2draft" indicates QUIC v2
+ draft.
+ -q, --quiet Suppress debug output.
+ -s, --show-secret
+ Print out secrets unless --quiet is used.
+ --timeout=<DURATION>
+ Specify idle timeout.
+ Default: )"
+ << util::format_duration(config.timeout) << R"(
+ --ciphers=<CIPHERS>
+ Specify the cipher suite list to enable.
+ Default: )"
+ << config.ciphers << R"(
+ --groups=<GROUPS>
+ Specify the supported groups.
+ Default: )"
+ << config.groups << R"(
+ --session-file=<PATH>
+ Read/write TLS session from/to <PATH>. To resume a
+ session, the previous session must be supplied with this
+ option.
+ --tp-file=<PATH>
+ Read/write QUIC transport parameters from/to <PATH>. To
+ send 0-RTT data, the transport parameters received from
+ the previous session must be supplied with this option.
+ --dcid=<DCID>
+ Specify initial DCID. <DCID> is hex string. When
+ decoded as binary, it should be at least 8 bytes and at
+ most 18 bytes long.
+ --scid=<SCID>
+ Specify source connection ID. <SCID> is hex string. If
+ an empty string is given, zero length connection ID is
+ assumed.
+ --change-local-addr=<DURATION>
+ Client changes local address when <DURATION> elapse
+ after handshake completes.
+ --nat-rebinding
+ When used with --change-local-addr, simulate NAT
+ rebinding. In other words, client changes local
+ address, but it does not start path validation.
+ --key-update=<DURATION>
+ Client initiates key update when <DURATION> elapse after
+ handshake completes.
+ -m, --http-method=<METHOD>
+ Specify HTTP method. Default: )"
+ << config.http_method << R"(
+ --delay-stream=<DURATION>
+ Delay sending STREAM data in 1-RTT for <DURATION> after
+ handshake completes.
+ --no-preferred-addr
+ Do not try to use preferred address offered by server.
+ --key=<PATH>
+ The path to client private key PEM file.
+ --cert=<PATH>
+ The path to client certificate PEM file.
+ --download=<PATH>
+ The path to the directory to save a downloaded content.
+ It is undefined if 2 concurrent requests write to the
+ same file. If a request path does not contain a path
+ component usable as a file name, it defaults to
+ "index.html".
+ --no-quic-dump
+ Disables printing QUIC STREAM and CRYPTO frame data out.
+ --no-http-dump
+ Disables printing HTTP response body out.
+ --qlog-file=<PATH>
+ The path to write qlog. This option and --qlog-dir are
+ mutually exclusive.
+ --qlog-dir=<PATH>
+ Path to the directory where qlog file is stored. The
+ file name of each qlog is the Source Connection ID of
+ client. This option and --qlog-file are mutually
+ exclusive.
+ --max-data=<SIZE>
+ The initial connection-level flow control window.
+ Default: )"
+ << util::format_uint_iec(config.max_data) << R"(
+ --max-stream-data-bidi-local=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the local endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_local) << R"(
+ --max-stream-data-bidi-remote=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the remote endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"(
+ --max-stream-data-uni=<SIZE>
+ The initial stream-level flow control window for a
+ unidirectional stream.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_uni) << R"(
+ --max-streams-bidi=<N>
+ The number of the concurrent bidirectional streams.
+ Default: )"
+ << config.max_streams_bidi << R"(
+ --max-streams-uni=<N>
+ The number of the concurrent unidirectional streams.
+ Default: )"
+ << config.max_streams_uni << R"(
+ --exit-on-first-stream-close
+ Exit when a first client initialted HTTP stream is
+ closed.
+ --exit-on-all-streams-close
+ Exit when all client initiated HTTP streams are closed.
+ --disable-early-data
+ Disable early data.
+ --cc=(cubic|reno|bbr|bbr2)
+ The name of congestion controller algorithm.
+ Default: )"
+ << util::strccalgo(config.cc_algo) << R"(
+ --token-file=<PATH>
+ Read/write token from/to <PATH>. Token is obtained from
+ NEW_TOKEN frame from server.
+ --sni=<DNSNAME>
+ Send <DNSNAME> in TLS SNI, overriding the DNS name
+ specified in <HOST>.
+ --initial-rtt=<DURATION>
+ Set an initial RTT.
+ Default: )"
+ << util::format_duration(config.initial_rtt) << R"(
+ --max-window=<SIZE>
+ Maximum connection-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_window) << R"(
+ --max-stream-window=<SIZE>
+ Maximum stream-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_window) << R"(
+ --max-udp-payload-size=<SIZE>
+ Override maximum UDP payload size that client transmits.
+ --handshake-timeout=<DURATION>
+ Set the QUIC handshake timeout.
+ Default: )"
+ << util::format_duration(config.handshake_timeout) << R"(
+ --no-pmtud Disables Path MTU Discovery.
+ --ack-thresh=<N>
+ The minimum number of the received ACK eliciting packets
+ that triggers immediate acknowledgement.
+ Default: )"
+ << config.ack_thresh << R"(
+ -h, --help Display this help and exit.
+
+---
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms,
+ us, or ns (hours, minutes, seconds, milliseconds, microseconds, and
+ nanoseconds respectively). If a unit is omitted, a second is used
+ as unit.
+
+ The <HEX> argument is an hex string which must start with "0x"
+ (e.g., 0x00000001).)"
+ << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ config_set_default(config);
+ char *data_path = nullptr;
+ const char *private_key_file = nullptr;
+ const char *cert_file = nullptr;
+
+ for (;;) {
+ static int flag = 0;
+ constexpr static option long_opts[] = {
+ {"help", no_argument, nullptr, 'h'},
+ {"tx-loss", required_argument, nullptr, 't'},
+ {"rx-loss", required_argument, nullptr, 'r'},
+ {"data", required_argument, nullptr, 'd'},
+ {"http-method", required_argument, nullptr, 'm'},
+ {"nstreams", required_argument, nullptr, 'n'},
+ {"version", required_argument, nullptr, 'v'},
+ {"quiet", no_argument, nullptr, 'q'},
+ {"show-secret", no_argument, nullptr, 's'},
+ {"ciphers", required_argument, &flag, 1},
+ {"groups", required_argument, &flag, 2},
+ {"timeout", required_argument, &flag, 3},
+ {"session-file", required_argument, &flag, 4},
+ {"tp-file", required_argument, &flag, 5},
+ {"dcid", required_argument, &flag, 6},
+ {"change-local-addr", required_argument, &flag, 7},
+ {"key-update", required_argument, &flag, 8},
+ {"nat-rebinding", no_argument, &flag, 9},
+ {"delay-stream", required_argument, &flag, 10},
+ {"no-preferred-addr", no_argument, &flag, 11},
+ {"key", required_argument, &flag, 12},
+ {"cert", required_argument, &flag, 13},
+ {"download", required_argument, &flag, 14},
+ {"no-quic-dump", no_argument, &flag, 15},
+ {"no-http-dump", no_argument, &flag, 16},
+ {"qlog-file", required_argument, &flag, 17},
+ {"max-data", required_argument, &flag, 18},
+ {"max-stream-data-bidi-local", required_argument, &flag, 19},
+ {"max-stream-data-bidi-remote", required_argument, &flag, 20},
+ {"max-stream-data-uni", required_argument, &flag, 21},
+ {"max-streams-bidi", required_argument, &flag, 22},
+ {"max-streams-uni", required_argument, &flag, 23},
+ {"exit-on-first-stream-close", no_argument, &flag, 24},
+ {"disable-early-data", no_argument, &flag, 25},
+ {"qlog-dir", required_argument, &flag, 26},
+ {"cc", required_argument, &flag, 27},
+ {"exit-on-all-streams-close", no_argument, &flag, 28},
+ {"token-file", required_argument, &flag, 29},
+ {"sni", required_argument, &flag, 30},
+ {"initial-rtt", required_argument, &flag, 31},
+ {"max-window", required_argument, &flag, 32},
+ {"max-stream-window", required_argument, &flag, 33},
+ {"scid", required_argument, &flag, 34},
+ {"max-udp-payload-size", required_argument, &flag, 35},
+ {"handshake-timeout", required_argument, &flag, 36},
+ {"other-versions", required_argument, &flag, 37},
+ {"no-pmtud", no_argument, &flag, 38},
+ {"preferred-versions", required_argument, &flag, 39},
+ {"ack-thresh", required_argument, &flag, 40},
+ {nullptr, 0, nullptr, 0},
+ };
+
+ auto optidx = 0;
+ auto c = getopt_long(argc, argv, "d:him:n:qr:st:v:", long_opts, &optidx);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'd':
+ // --data
+ data_path = optarg;
+ break;
+ case 'h':
+ // --help
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'm':
+ // --http-method
+ config.http_method = optarg;
+ break;
+ case 'n':
+ // --streams
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "streams: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > NGTCP2_MAX_VARINT) {
+ std::cerr << "streams: must not exceed " << NGTCP2_MAX_VARINT
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.nstreams = *n;
+ }
+ break;
+ case 'q':
+ // --quiet
+ config.quiet = true;
+ break;
+ case 'r':
+ // --rx-loss
+ config.rx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 's':
+ // --show-secret
+ config.show_secret = true;
+ break;
+ case 't':
+ // --tx-loss
+ config.tx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 'v': {
+ // --version
+ if (optarg == "v1"sv) {
+ config.version = NGTCP2_PROTO_VER_V1;
+ break;
+ }
+ if (optarg == "v2draft"sv) {
+ config.version = NGTCP2_PROTO_VER_V2_DRAFT;
+ break;
+ }
+ auto rv = util::parse_version(optarg);
+ if (!rv) {
+ std::cerr << "version: invalid version " << std::quoted(optarg)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.version = *rv;
+ break;
+ }
+ case '?':
+ print_usage();
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // --ciphers
+ config.ciphers = optarg;
+ break;
+ case 2:
+ // --groups
+ config.groups = optarg;
+ break;
+ case 3:
+ // --timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.timeout = *t;
+ }
+ break;
+ case 4:
+ // --session-file
+ config.session_file = optarg;
+ break;
+ case 5:
+ // --tp-file
+ config.tp_file = optarg;
+ break;
+ case 6: {
+ // --dcid
+ auto dcidlen2 = strlen(optarg);
+ if (dcidlen2 % 2 || dcidlen2 / 2 < 8 || dcidlen2 / 2 > 18) {
+ std::cerr << "dcid: wrong length" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ auto dcid = util::decode_hex(optarg);
+ ngtcp2_cid_init(&config.dcid,
+ reinterpret_cast<const uint8_t *>(dcid.c_str()),
+ dcid.size());
+ break;
+ }
+ case 7:
+ // --change-local-addr
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "change-local-addr: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.change_local_addr = *t;
+ }
+ break;
+ case 8:
+ // --key-update
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "key-update: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.key_update = *t;
+ }
+ break;
+ case 9:
+ // --nat-rebinding
+ config.nat_rebinding = true;
+ break;
+ case 10:
+ // --delay-stream
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "delay-stream: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.delay_stream = *t;
+ }
+ break;
+ case 11:
+ // --no-preferred-addr
+ config.no_preferred_addr = true;
+ break;
+ case 12:
+ // --key
+ private_key_file = optarg;
+ break;
+ case 13:
+ // --cert
+ cert_file = optarg;
+ break;
+ case 14:
+ // --download
+ config.download = optarg;
+ break;
+ case 15:
+ // --no-quic-dump
+ config.no_quic_dump = true;
+ break;
+ case 16:
+ // --no-http-dump
+ config.no_http_dump = true;
+ break;
+ case 17:
+ // --qlog-file
+ config.qlog_file = optarg;
+ break;
+ case 18:
+ // --max-data
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-data: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_data = *n;
+ }
+ break;
+ case 19:
+ // --max-stream-data-bidi-local
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-local: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_local = *n;
+ }
+ break;
+ case 20:
+ // --max-stream-data-bidi-remote
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-remote: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_remote = *n;
+ }
+ break;
+ case 21:
+ // --max-stream-data-uni
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_uni = *n;
+ }
+ break;
+ case 22:
+ // --max-streams-bidi
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-bidi: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_bidi = *n;
+ }
+ break;
+ case 23:
+ // --max-streams-uni
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_uni = *n;
+ }
+ break;
+ case 24:
+ // --exit-on-first-stream-close
+ config.exit_on_first_stream_close = true;
+ break;
+ case 25:
+ // --disable-early-data
+ config.disable_early_data = true;
+ break;
+ case 26:
+ // --qlog-dir
+ config.qlog_dir = optarg;
+ break;
+ case 27:
+ // --cc
+ if (strcmp("cubic", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ break;
+ }
+ if (strcmp("reno", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_RENO;
+ break;
+ }
+ if (strcmp("bbr", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR;
+ break;
+ }
+ if (strcmp("bbr2", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR2;
+ break;
+ }
+ std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl;
+ exit(EXIT_FAILURE);
+ case 28:
+ // --exit-on-all-streams-close
+ config.exit_on_all_streams_close = true;
+ break;
+ case 29:
+ // --token-file
+ config.token_file = optarg;
+ break;
+ case 30:
+ // --sni
+ config.sni = optarg;
+ break;
+ case 31:
+ // --initial-rtt
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "initial-rtt: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.initial_rtt = *t;
+ }
+ break;
+ case 32:
+ // --max-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_window = *n;
+ }
+ break;
+ case 33:
+ // --max-stream-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_window = *n;
+ }
+ break;
+ case 34: {
+ // --scid
+ auto scid = util::decode_hex(optarg);
+ ngtcp2_cid_init(&config.scid,
+ reinterpret_cast<const uint8_t *>(scid.c_str()),
+ scid.size());
+ config.scid_present = true;
+ break;
+ }
+ case 35:
+ // --max-udp-payload-size
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-udp-payload-size: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 64_k) {
+ std::cerr << "max-udp-payload-size: must not exceed 65536"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n == 0) {
+ std::cerr << "max-udp-payload-size: must not be 0" << std::endl;
+ } else {
+ config.max_udp_payload_size = *n;
+ }
+ break;
+ case 36:
+ // --handshake-timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "handshake-timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.handshake_timeout = *t;
+ }
+ break;
+ case 37: {
+ // --other-versions
+ if (strlen(optarg) == 0) {
+ config.other_versions.resize(0);
+ break;
+ }
+ auto l = util::split_str(optarg);
+ config.other_versions.resize(l.size());
+ auto it = std::begin(config.other_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "other-versions: invalid version " << std::quoted(k)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 38:
+ // --no-pmtud
+ config.no_pmtud = true;
+ break;
+ case 39: {
+ // --preferred-versions
+ auto l = util::split_str(optarg);
+ if (l.size() > max_preferred_versionslen) {
+ std::cerr << "preferred-versions: too many versions > "
+ << max_preferred_versionslen << std::endl;
+ }
+ config.preferred_versions.resize(l.size());
+ auto it = std::begin(config.preferred_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "preferred-versions: invalid version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (!ngtcp2_is_supported_version(*rv)) {
+ std::cerr << "preferred-versions: unsupported version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 40:
+ // --ack-thresh
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "ack-thresh: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 100) {
+ std::cerr << "ack-thresh: must not exceed 100" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.ack_thresh = *n;
+ }
+ break;
+ }
+ break;
+ default:
+ break;
+ };
+ }
+
+ if (argc - optind < 2) {
+ std::cerr << "Too few arguments" << std::endl;
+ print_usage();
+ exit(EXIT_FAILURE);
+ }
+
+ if (!config.qlog_file.empty() && !config.qlog_dir.empty()) {
+ std::cerr << "qlog-file and qlog-dir are mutually exclusive" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.exit_on_first_stream_close && config.exit_on_all_streams_close) {
+ std::cerr << "exit-on-first-stream-close and exit-on-all-streams-close are "
+ "mutually exclusive"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (data_path) {
+ auto fd = open(data_path, O_RDONLY);
+ if (fd == -1) {
+ std::cerr << "data: Could not open file " << data_path << ": "
+ << strerror(errno) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ struct stat st;
+ if (fstat(fd, &st) != 0) {
+ std::cerr << "data: Could not stat file " << data_path << ": "
+ << strerror(errno) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.fd = fd;
+ config.datalen = st.st_size;
+ auto addr = mmap(nullptr, config.datalen, PROT_READ, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED) {
+ std::cerr << "data: Could not mmap file " << data_path << ": "
+ << strerror(errno) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.data = static_cast<uint8_t *>(addr);
+ }
+
+ auto addr = argv[optind++];
+ auto port = argv[optind++];
+
+ if (parse_requests(&argv[optind], argc - optind) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (!ngtcp2_is_reserved_version(config.version)) {
+ if (!config.preferred_versions.empty() &&
+ std::find(std::begin(config.preferred_versions),
+ std::end(config.preferred_versions),
+ config.version) == std::end(config.preferred_versions)) {
+ std::cerr << "preferred-version: must include version "
+ << "0x" << config.version << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (!config.other_versions.empty() &&
+ std::find(std::begin(config.other_versions),
+ std::end(config.other_versions),
+ config.version) == std::end(config.other_versions)) {
+ std::cerr << "other-versions: must include version "
+ << "0x" << config.version << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (config.nstreams == 0) {
+ config.nstreams = config.requests.size();
+ }
+
+ TLSClientContext tls_ctx;
+ if (tls_ctx.init(private_key_file, cert_file) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT);
+
+ auto keylog_filename = getenv("SSLKEYLOGFILE");
+ if (keylog_filename) {
+ keylog_file.open(keylog_filename, std::ios_base::app);
+ if (keylog_file) {
+ tls_ctx.enable_keylog();
+ }
+ }
+
+ if (util::generate_secret(config.static_secret.data(),
+ config.static_secret.size()) != 0) {
+ std::cerr << "Unable to generate static secret" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ auto client_chosen_version = config.version;
+
+ for (;;) {
+ Client c(EV_DEFAULT, client_chosen_version, config.version);
+
+ if (run(c, addr, port, tls_ctx) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.preferred_versions.empty()) {
+ break;
+ }
+
+ auto &offered_versions = c.get_offered_versions();
+ if (offered_versions.empty()) {
+ break;
+ }
+
+ client_chosen_version = ngtcp2_select_version(
+ config.preferred_versions.data(), config.preferred_versions.size(),
+ offered_versions.data(), offered_versions.size());
+
+ if (client_chosen_version == 0) {
+ std::cerr << "Unable to select a version" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Client selected version " << std::hex << "0x"
+ << client_chosen_version << std::dec << std::endl;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/client.h b/examples/client.h
new file mode 100644
index 0000000..d861917
--- /dev/null
+++ b/examples/client.h
@@ -0,0 +1,192 @@
+/*
+ * 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 CLIENT_H
+#define CLIENT_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <vector>
+#include <deque>
+#include <map>
+#include <string_view>
+#include <memory>
+
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <nghttp3/nghttp3.h>
+
+#include <ev.h>
+
+#include "client_base.h"
+#include "tls_client_context.h"
+#include "tls_client_session.h"
+#include "network.h"
+#include "shared.h"
+#include "template.h"
+
+using namespace ngtcp2;
+
+struct Stream {
+ Stream(const Request &req, int64_t stream_id);
+ ~Stream();
+
+ int open_file(const std::string_view &path);
+
+ Request req;
+ int64_t stream_id;
+ int fd;
+};
+
+class Client;
+
+struct Endpoint {
+ Address addr;
+ ev_io rev;
+ Client *client;
+ int fd;
+};
+
+class Client : public ClientBase {
+public:
+ Client(struct ev_loop *loop, uint32_t client_chosen_version,
+ uint32_t original_version);
+ ~Client();
+
+ int init(int fd, const Address &local_addr, const Address &remote_addr,
+ const char *addr, const char *port, TLSClientContext &tls_ctx);
+ void disconnect();
+
+ int on_read(const Endpoint &ep);
+ int on_write();
+ int write_streams();
+ int feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen);
+ int handle_expiry();
+ void update_timer();
+ int handshake_completed();
+ int handshake_confirmed();
+ void recv_version_negotiation(const uint32_t *sv, size_t nsv);
+
+ int send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr,
+ unsigned int ecn, const uint8_t *data, size_t datalen);
+ int on_stream_close(int64_t stream_id, uint64_t app_error_code);
+ int on_extend_max_streams();
+ int handle_error();
+ int make_stream_early();
+ int change_local_addr();
+ void start_change_local_addr_timer();
+ int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen);
+ int initiate_key_update();
+ void start_key_update_timer();
+ void start_delay_stream_timer();
+
+ int select_preferred_address(Address &selected_addr,
+ const ngtcp2_preferred_addr *paddr);
+
+ std::optional<Endpoint *> endpoint_for(const Address &remote_addr);
+
+ void set_remote_addr(const ngtcp2_addr &remote_addr);
+
+ int setup_httpconn();
+ int submit_http_request(const Stream *stream);
+ int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data,
+ size_t datalen);
+ int acked_stream_data_offset(int64_t stream_id, uint64_t datalen);
+ void http_consume(int64_t stream_id, size_t nconsumed);
+ void http_write_data(int64_t stream_id, const uint8_t *data, size_t datalen);
+ int on_stream_reset(int64_t stream_id);
+ int on_stream_stop_sending(int64_t stream_id);
+ int extend_max_stream_data(int64_t stream_id, uint64_t max_data);
+ int stop_sending(int64_t stream_id, uint64_t app_error_code);
+ int reset_stream(int64_t stream_id, uint64_t app_error_code);
+ int http_stream_close(int64_t stream_id, uint64_t app_error_code);
+
+ void on_send_blocked(const Endpoint &ep, const ngtcp2_addr &remote_addr,
+ unsigned int ecn, size_t datalen);
+ void start_wev_endpoint(const Endpoint &ep);
+ int send_blocked_packet();
+
+ const std::vector<uint32_t> &get_offered_versions() const;
+
+ bool get_early_data() const;
+ void early_data_rejected();
+
+private:
+ std::vector<Endpoint> endpoints_;
+ Address remote_addr_;
+ ev_io wev_;
+ ev_timer timer_;
+ ev_timer change_local_addr_timer_;
+ ev_timer key_update_timer_;
+ ev_timer delay_stream_timer_;
+ ev_signal sigintev_;
+ struct ev_loop *loop_;
+ std::map<int64_t, std::unique_ptr<Stream>> streams_;
+ std::vector<uint32_t> offered_versions_;
+ nghttp3_conn *httpconn_;
+ // addr_ is the server host address.
+ const char *addr_;
+ // port_ is the server port.
+ const char *port_;
+ // nstreams_done_ is the number of streams opened.
+ size_t nstreams_done_;
+ // nstreams_closed_ is the number of streams get closed.
+ size_t nstreams_closed_;
+ // nkey_update_ is the number of key update occurred.
+ size_t nkey_update_;
+ uint32_t client_chosen_version_;
+ uint32_t original_version_;
+ // early_data_ is true if client attempts to do 0RTT data transfer.
+ bool early_data_;
+ // should_exit_ is true if client should exit rather than waiting
+ // for timeout.
+ bool should_exit_;
+ // should_exit_on_handshake_confirmed_ is true if client should exit
+ // when handshake confirmed.
+ bool should_exit_on_handshake_confirmed_;
+ // handshake_confirmed_ gets true after handshake has been
+ // confirmed.
+ bool handshake_confirmed_;
+
+ struct {
+ bool send_blocked;
+ // blocked field is effective only when send_blocked is true.
+ struct {
+ const Endpoint *endpoint;
+ Address remote_addr;
+ unsigned int ecn;
+ size_t datalen;
+ } blocked;
+ std::array<uint8_t, 64_k> data;
+ } tx_;
+};
+
+#endif // CLIENT_H
diff --git a/examples/client_base.cc b/examples/client_base.cc
new file mode 100644
index 0000000..aa35b4b
--- /dev/null
+++ b/examples/client_base.cc
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+#include "client_base.h"
+
+#include <cassert>
+#include <array>
+#include <iostream>
+#include <fstream>
+
+#include "debug.h"
+#include "template.h"
+#include "util.h"
+
+using namespace ngtcp2;
+using namespace std::literals;
+
+extern Config config;
+
+static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
+ auto c = static_cast<ClientBase *>(conn_ref->user_data);
+ return c->conn();
+}
+
+ClientBase::ClientBase()
+ : conn_ref_{get_conn, this}, qlog_(nullptr), conn_(nullptr) {
+ ngtcp2_connection_close_error_default(&last_error_);
+}
+
+ClientBase::~ClientBase() {
+ if (conn_) {
+ ngtcp2_conn_del(conn_);
+ }
+
+ if (qlog_) {
+ fclose(qlog_);
+ }
+}
+
+int ClientBase::write_transport_params(const char *path,
+ const ngtcp2_transport_params *params) {
+ auto f = std::ofstream(path);
+ if (!f) {
+ return -1;
+ }
+
+ f << "initial_max_streams_bidi=" << params->initial_max_streams_bidi << '\n'
+ << "initial_max_streams_uni=" << params->initial_max_streams_uni << '\n'
+ << "initial_max_stream_data_bidi_local="
+ << params->initial_max_stream_data_bidi_local << '\n'
+ << "initial_max_stream_data_bidi_remote="
+ << params->initial_max_stream_data_bidi_remote << '\n'
+ << "initial_max_stream_data_uni=" << params->initial_max_stream_data_uni
+ << '\n'
+ << "initial_max_data=" << params->initial_max_data << '\n'
+ << "active_connection_id_limit=" << params->active_connection_id_limit
+ << '\n'
+ << "max_datagram_frame_size=" << params->max_datagram_frame_size << '\n';
+
+ f.close();
+ if (!f) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int ClientBase::read_transport_params(const char *path,
+ ngtcp2_transport_params *params) {
+ auto f = std::ifstream(path);
+ if (!f) {
+ return -1;
+ }
+
+ for (std::string line; std::getline(f, line);) {
+ if (util::istarts_with(line, "initial_max_streams_bidi="sv)) {
+ if (auto n = util::parse_uint(line.c_str() +
+ "initial_max_streams_bidi="sv.size());
+ !n) {
+ return -1;
+ } else {
+ params->initial_max_streams_bidi = *n;
+ }
+ continue;
+ }
+
+ if (util::istarts_with(line, "initial_max_streams_uni="sv)) {
+ if (auto n = util::parse_uint(line.c_str() +
+ "initial_max_streams_uni="sv.size());
+ !n) {
+ return -1;
+ } else {
+ params->initial_max_streams_uni = *n;
+ }
+ continue;
+ }
+
+ if (util::istarts_with(line, "initial_max_stream_data_bidi_local="sv)) {
+ if (auto n = util::parse_uint(
+ line.c_str() + "initial_max_stream_data_bidi_local="sv.size());
+ !n) {
+ return -1;
+ } else {
+ params->initial_max_stream_data_bidi_local = *n;
+ }
+ continue;
+ }
+
+ if (util::istarts_with(line, "initial_max_stream_data_bidi_remote="sv)) {
+ if (auto n = util::parse_uint(
+ line.c_str() + "initial_max_stream_data_bidi_remote="sv.size());
+ !n) {
+ return -1;
+ } else {
+ params->initial_max_stream_data_bidi_remote = *n;
+ }
+ continue;
+ }
+
+ if (util::istarts_with(line, "initial_max_stream_data_uni="sv)) {
+ if (auto n = util::parse_uint(line.c_str() +
+ "initial_max_stream_data_uni="sv.size());
+ !n) {
+ return -1;
+ } else {
+ params->initial_max_stream_data_uni = *n;
+ }
+ continue;
+ }
+
+ if (util::istarts_with(line, "initial_max_data="sv)) {
+ if (auto n =
+ util::parse_uint(line.c_str() + "initial_max_data="sv.size());
+ !n) {
+ return -1;
+ } else {
+ params->initial_max_data = *n;
+ }
+ continue;
+ }
+
+ if (util::istarts_with(line, "active_connection_id_limit="sv)) {
+ if (auto n = util::parse_uint(line.c_str() +
+ "active_connection_id_limit="sv.size());
+ !n) {
+ return -1;
+ } else {
+ params->active_connection_id_limit = *n;
+ }
+ continue;
+ }
+
+ if (util::istarts_with(line, "max_datagram_frame_size="sv)) {
+ if (auto n = util::parse_uint(line.c_str() +
+ "max_datagram_frame_size="sv.size());
+ !n) {
+ return -1;
+ } else {
+ params->max_datagram_frame_size = *n;
+ }
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+ngtcp2_conn *ClientBase::conn() const { return conn_; }
+
+void qlog_write_cb(void *user_data, uint32_t flags, const void *data,
+ size_t datalen) {
+ auto c = static_cast<ClientBase *>(user_data);
+ c->write_qlog(data, datalen);
+}
+
+void ClientBase::write_qlog(const void *data, size_t datalen) {
+ assert(qlog_);
+ fwrite(data, 1, datalen, qlog_);
+}
+
+ngtcp2_crypto_conn_ref *ClientBase::conn_ref() { return &conn_ref_; }
diff --git a/examples/client_base.h b/examples/client_base.h
new file mode 100644
index 0000000..ef364ea
--- /dev/null
+++ b/examples/client_base.h
@@ -0,0 +1,212 @@
+/*
+ * 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 CLIENT_BASE_H
+#define CLIENT_BASE_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <vector>
+#include <deque>
+#include <string>
+#include <string_view>
+#include <functional>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include "tls_client_session.h"
+#include "network.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+struct Request {
+ std::string_view scheme;
+ std::string authority;
+ std::string path;
+};
+
+struct Config {
+ ngtcp2_cid dcid;
+ ngtcp2_cid scid;
+ bool scid_present;
+ // tx_loss_prob is probability of losing outgoing packet.
+ double tx_loss_prob;
+ // rx_loss_prob is probability of losing incoming packet.
+ double rx_loss_prob;
+ // fd is a file descriptor to read input for streams.
+ int fd;
+ // ciphers is the list of enabled ciphers.
+ const char *ciphers;
+ // groups is the list of supported groups.
+ const char *groups;
+ // nstreams is the number of streams to open.
+ size_t nstreams;
+ // data is the pointer to memory region which maps file denoted by
+ // fd.
+ uint8_t *data;
+ // datalen is the length of file denoted by fd.
+ size_t datalen;
+ // version is a QUIC version to use.
+ uint32_t version;
+ // quiet suppresses the output normally shown except for the error
+ // messages.
+ bool quiet;
+ // timeout is an idle timeout for QUIC connection.
+ ngtcp2_duration timeout;
+ // session_file is a path to a file to write, and read TLS session.
+ const char *session_file;
+ // tp_file is a path to a file to write, and read QUIC transport
+ // parameters.
+ const char *tp_file;
+ // show_secret is true if transport secrets should be printed out.
+ bool show_secret;
+ // change_local_addr is the duration after which client changes
+ // local address.
+ ngtcp2_duration change_local_addr;
+ // key_update is the duration after which client initiates key
+ // update.
+ ngtcp2_duration key_update;
+ // delay_stream is the duration after which client sends the first
+ // 1-RTT stream.
+ ngtcp2_duration delay_stream;
+ // nat_rebinding is true if simulated NAT rebinding is enabled.
+ bool nat_rebinding;
+ // no_preferred_addr is true if client do not follow preferred
+ // address offered by server.
+ bool no_preferred_addr;
+ std::string_view http_method;
+ // download is a path to a directory where a downloaded file is
+ // saved. If it is empty, no file is saved.
+ std::string_view download;
+ // requests contains URIs to request.
+ std::vector<Request> requests;
+ // no_quic_dump is true if hexdump of QUIC STREAM and CRYPTO data
+ // should be disabled.
+ bool no_quic_dump;
+ // no_http_dump is true if hexdump of HTTP response body should be
+ // disabled.
+ bool no_http_dump;
+ // qlog_file is the path to write qlog.
+ std::string_view qlog_file;
+ // qlog_dir is the path to directory where qlog is stored. qlog_dir
+ // and qlog_file are mutually exclusive.
+ std::string_view qlog_dir;
+ // max_data is the initial connection-level flow control window.
+ uint64_t max_data;
+ // max_stream_data_bidi_local is the initial stream-level flow
+ // control window for a bidirectional stream that the local endpoint
+ // initiates.
+ uint64_t max_stream_data_bidi_local;
+ // max_stream_data_bidi_remote is the initial stream-level flow
+ // control window for a bidirectional stream that the remote
+ // endpoint initiates.
+ uint64_t max_stream_data_bidi_remote;
+ // max_stream_data_uni is the initial stream-level flow control
+ // window for a unidirectional stream.
+ uint64_t max_stream_data_uni;
+ // max_streams_bidi is the number of the concurrent bidirectional
+ // streams.
+ uint64_t max_streams_bidi;
+ // max_streams_uni is the number of the concurrent unidirectional
+ // streams.
+ uint64_t max_streams_uni;
+ // max_window is the maximum connection-level flow control window
+ // size if auto-tuning is enabled.
+ uint64_t max_window;
+ // max_stream_window is the maximum stream-level flow control window
+ // size if auto-tuning is enabled.
+ uint64_t max_stream_window;
+ // exit_on_first_stream_close is the flag that if it is true, client
+ // exits when a first HTTP stream gets closed. It is not
+ // necessarily the same time when the underlying QUIC stream closes
+ // due to the QPACK synchronization.
+ bool exit_on_first_stream_close;
+ // exit_on_all_streams_close is the flag that if it is true, client
+ // exits when all HTTP streams get closed.
+ bool exit_on_all_streams_close;
+ // disable_early_data disables early data.
+ bool disable_early_data;
+ // static_secret is used to derive keying materials for Stateless
+ // Retry token.
+ std::array<uint8_t, 32> static_secret;
+ // cc_algo is the congestion controller algorithm.
+ ngtcp2_cc_algo cc_algo;
+ // token_file is a path to file to read or write token from
+ // NEW_TOKEN frame.
+ std::string_view token_file;
+ // sni is the value sent in TLS SNI, overriding DNS name of the
+ // remote host.
+ std::string_view sni;
+ // initial_rtt is an initial RTT.
+ ngtcp2_duration initial_rtt;
+ // max_udp_payload_size is the maximum UDP payload size that client
+ // transmits.
+ size_t max_udp_payload_size;
+ // handshake_timeout is the period of time before giving up QUIC
+ // connection establishment.
+ ngtcp2_duration handshake_timeout;
+ // preferred_versions includes QUIC versions in the order of
+ // preference. Client uses this field to select a version from the
+ // version set offered in Version Negotiation packet.
+ std::vector<uint32_t> preferred_versions;
+ // other_versions includes QUIC versions that are sent in
+ // other_versions field of version_information transport_parameter.
+ std::vector<uint32_t> other_versions;
+ // no_pmtud disables Path MTU Discovery.
+ bool no_pmtud;
+ // ack_thresh is the minimum number of the received ACK eliciting
+ // packets that triggers immediate acknowledgement.
+ size_t ack_thresh;
+};
+
+class ClientBase {
+public:
+ ClientBase();
+ ~ClientBase();
+
+ ngtcp2_conn *conn() const;
+
+ int write_transport_params(const char *path,
+ const ngtcp2_transport_params *params);
+ int read_transport_params(const char *path, ngtcp2_transport_params *params);
+
+ void write_qlog(const void *data, size_t datalen);
+
+ ngtcp2_crypto_conn_ref *conn_ref();
+
+protected:
+ ngtcp2_crypto_conn_ref conn_ref_;
+ TLSClientSession tls_session_;
+ FILE *qlog_;
+ ngtcp2_conn *conn_;
+ ngtcp2_connection_close_error last_error_;
+};
+
+void qlog_write_cb(void *user_data, uint32_t flags, const void *data,
+ size_t datalen);
+
+#endif // CLIENT_BASE_H
diff --git a/examples/debug.cc b/examples/debug.cc
new file mode 100644
index 0000000..98561fb
--- /dev/null
+++ b/examples/debug.cc
@@ -0,0 +1,298 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "debug.h"
+
+#include <cassert>
+#include <random>
+#include <iostream>
+
+#include "util.h"
+
+using namespace std::literals;
+
+namespace ngtcp2 {
+
+namespace debug {
+
+namespace {
+auto randgen = util::make_mt19937();
+} // namespace
+
+namespace {
+auto *outfile = stderr;
+} // namespace
+
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ fprintf(outfile, "QUIC handshake has completed\n");
+ return 0;
+}
+
+int handshake_confirmed(ngtcp2_conn *conn, void *user_data) {
+ fprintf(outfile, "QUIC handshake has been confirmed\n");
+ return 0;
+}
+
+bool packet_lost(double prob) {
+ auto p = std::uniform_real_distribution<>(0, 1)(randgen);
+ return p < prob;
+}
+
+void print_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data,
+ size_t datalen) {
+ const char *crypto_level_str;
+ switch (crypto_level) {
+ case NGTCP2_CRYPTO_LEVEL_INITIAL:
+ crypto_level_str = "Initial";
+ break;
+ case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
+ crypto_level_str = "Handshake";
+ break;
+ case NGTCP2_CRYPTO_LEVEL_APPLICATION:
+ crypto_level_str = "Application";
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+ fprintf(outfile, "Ordered CRYPTO data in %s crypto level\n",
+ crypto_level_str);
+ util::hexdump(outfile, data, datalen);
+}
+
+void print_stream_data(int64_t stream_id, const uint8_t *data, size_t datalen) {
+ fprintf(outfile, "Ordered STREAM data stream_id=0x%" PRIx64 "\n", stream_id);
+ util::hexdump(outfile, data, datalen);
+}
+
+void print_initial_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "initial_secret=%s\n", util::format_hex(data, len).c_str());
+}
+
+void print_client_in_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "client_in_secret=%s\n",
+ util::format_hex(data, len).c_str());
+}
+
+void print_server_in_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "server_in_secret=%s\n",
+ util::format_hex(data, len).c_str());
+}
+
+void print_handshake_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "handshake_secret=%s\n",
+ util::format_hex(data, len).c_str());
+}
+
+void print_client_hs_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "client_hs_secret=%s\n",
+ util::format_hex(data, len).c_str());
+}
+
+void print_server_hs_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "server_hs_secret=%s\n",
+ util::format_hex(data, len).c_str());
+}
+
+void print_client_0rtt_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "client_0rtt_secret=%s\n",
+ util::format_hex(data, len).c_str());
+}
+
+void print_client_1rtt_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "client_1rtt_secret=%s\n",
+ util::format_hex(data, len).c_str());
+}
+
+void print_server_1rtt_secret(const uint8_t *data, size_t len) {
+ fprintf(outfile, "server_1rtt_secret=%s\n",
+ util::format_hex(data, len).c_str());
+}
+
+void print_client_pp_key(const uint8_t *data, size_t len) {
+ fprintf(outfile, "+ client_pp_key=%s\n", util::format_hex(data, len).c_str());
+}
+
+void print_server_pp_key(const uint8_t *data, size_t len) {
+ fprintf(outfile, "+ server_pp_key=%s\n", util::format_hex(data, len).c_str());
+}
+
+void print_client_pp_iv(const uint8_t *data, size_t len) {
+ fprintf(outfile, "+ client_pp_iv=%s\n", util::format_hex(data, len).c_str());
+}
+
+void print_server_pp_iv(const uint8_t *data, size_t len) {
+ fprintf(outfile, "+ server_pp_iv=%s\n", util::format_hex(data, len).c_str());
+}
+
+void print_client_pp_hp(const uint8_t *data, size_t len) {
+ fprintf(outfile, "+ client_pp_hp=%s\n", util::format_hex(data, len).c_str());
+}
+
+void print_server_pp_hp(const uint8_t *data, size_t len) {
+ fprintf(outfile, "+ server_pp_hp=%s\n", util::format_hex(data, len).c_str());
+}
+
+void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key,
+ size_t keylen, const uint8_t *iv, size_t ivlen,
+ const uint8_t *hp, size_t hplen) {
+ std::cerr << "+ secret=" << util::format_hex(secret, secretlen) << "\n"
+ << "+ key=" << util::format_hex(key, keylen) << "\n"
+ << "+ iv=" << util::format_hex(iv, ivlen) << "\n"
+ << "+ hp=" << util::format_hex(hp, hplen) << std::endl;
+}
+
+void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key,
+ size_t keylen, const uint8_t *iv, size_t ivlen) {
+ std::cerr << "+ secret=" << util::format_hex(secret, secretlen) << "\n"
+ << "+ key=" << util::format_hex(key, keylen) << "\n"
+ << "+ iv=" << util::format_hex(iv, ivlen) << std::endl;
+}
+
+void print_hp_mask(const uint8_t *mask, size_t masklen, const uint8_t *sample,
+ size_t samplelen) {
+ fprintf(outfile, "mask=%s sample=%s\n",
+ util::format_hex(mask, masklen).c_str(),
+ util::format_hex(sample, samplelen).c_str());
+}
+
+void log_printf(void *user_data, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+
+void path_validation(const ngtcp2_path *path,
+ ngtcp2_path_validation_result res) {
+ auto local_addr = util::straddr(
+ reinterpret_cast<sockaddr *>(path->local.addr), path->local.addrlen);
+ auto remote_addr = util::straddr(
+ reinterpret_cast<sockaddr *>(path->remote.addr), path->remote.addrlen);
+
+ std::cerr << "Path validation against path {local:" << local_addr
+ << ", remote:" << remote_addr << "} "
+ << (res == NGTCP2_PATH_VALIDATION_RESULT_SUCCESS ? "succeeded"
+ : "failed")
+ << std::endl;
+}
+
+void print_http_begin_request_headers(int64_t stream_id) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " request headers started\n",
+ stream_id);
+}
+
+void print_http_begin_response_headers(int64_t stream_id) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " response headers started\n",
+ stream_id);
+}
+
+namespace {
+void print_header(const uint8_t *name, size_t namelen, const uint8_t *value,
+ size_t valuelen, uint8_t flags) {
+ fprintf(outfile, "[%.*s: %.*s]%s\n", static_cast<int>(namelen), name,
+ static_cast<int>(valuelen), value,
+ (flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? "(sensitive)" : "");
+}
+} // namespace
+
+namespace {
+void print_header(const nghttp3_rcbuf *name, const nghttp3_rcbuf *value,
+ uint8_t flags) {
+ auto namebuf = nghttp3_rcbuf_get_buf(name);
+ auto valuebuf = nghttp3_rcbuf_get_buf(value);
+ print_header(namebuf.base, namebuf.len, valuebuf.base, valuebuf.len, flags);
+}
+} // namespace
+
+namespace {
+void print_header(const nghttp3_nv &nv) {
+ print_header(nv.name, nv.namelen, nv.value, nv.valuelen, nv.flags);
+}
+} // namespace
+
+void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name,
+ const nghttp3_rcbuf *value, uint8_t flags) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " ", stream_id);
+ print_header(name, value, flags);
+}
+
+void print_http_end_headers(int64_t stream_id) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " headers ended\n", stream_id);
+}
+
+void print_http_data(int64_t stream_id, const uint8_t *data, size_t datalen) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " body %zu bytes\n", stream_id,
+ datalen);
+ util::hexdump(outfile, data, datalen);
+}
+
+void print_http_begin_trailers(int64_t stream_id) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " trailers started\n", stream_id);
+}
+
+void print_http_end_trailers(int64_t stream_id) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " trailers ended\n", stream_id);
+}
+
+void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva,
+ size_t nvlen) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " submit request headers\n",
+ stream_id);
+ for (size_t i = 0; i < nvlen; ++i) {
+ auto &nv = nva[i];
+ print_header(nv);
+ }
+}
+
+void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva,
+ size_t nvlen) {
+ fprintf(outfile, "http: stream 0x%" PRIx64 " submit response headers\n",
+ stream_id);
+ for (size_t i = 0; i < nvlen; ++i) {
+ auto &nv = nva[i];
+ print_header(nv);
+ }
+}
+
+std::string_view secret_title(ngtcp2_crypto_level level) {
+ switch (level) {
+ case NGTCP2_CRYPTO_LEVEL_EARLY:
+ return "early_traffic"sv;
+ case NGTCP2_CRYPTO_LEVEL_HANDSHAKE:
+ return "handshake_traffic"sv;
+ case NGTCP2_CRYPTO_LEVEL_APPLICATION:
+ return "application_traffic"sv;
+ default:
+ assert(0);
+ abort();
+ }
+}
+
+} // namespace debug
+
+} // namespace ngtcp2
diff --git a/examples/debug.h b/examples/debug.h
new file mode 100644
index 0000000..5b31388
--- /dev/null
+++ b/examples/debug.h
@@ -0,0 +1,124 @@
+/*
+ * 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 DEBUG_H
+#define DEBUG_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#ifndef __STDC_FORMAT_MACROS
+// For travis and PRIu64
+# define __STDC_FORMAT_MACROS
+#endif // __STDC_FORMAT_MACROS
+
+#include <cinttypes>
+#include <string_view>
+
+#include <ngtcp2/ngtcp2.h>
+#include <nghttp3/nghttp3.h>
+
+namespace ngtcp2 {
+
+namespace debug {
+
+int handshake_completed(ngtcp2_conn *conn, void *user_data);
+
+int handshake_confirmed(ngtcp2_conn *conn, void *user_data);
+
+bool packet_lost(double prob);
+
+void print_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data,
+ size_t datalen);
+
+void print_stream_data(int64_t stream_id, const uint8_t *data, size_t datalen);
+
+void print_initial_secret(const uint8_t *data, size_t len);
+
+void print_client_in_secret(const uint8_t *data, size_t len);
+void print_server_in_secret(const uint8_t *data, size_t len);
+
+void print_handshake_secret(const uint8_t *data, size_t len);
+
+void print_client_hs_secret(const uint8_t *data, size_t len);
+void print_server_hs_secret(const uint8_t *data, size_t len);
+
+void print_client_0rtt_secret(const uint8_t *data, size_t len);
+
+void print_client_1rtt_secret(const uint8_t *data, size_t len);
+void print_server_1rtt_secret(const uint8_t *data, size_t len);
+
+void print_client_pp_key(const uint8_t *data, size_t len);
+void print_server_pp_key(const uint8_t *data, size_t len);
+
+void print_client_pp_iv(const uint8_t *data, size_t len);
+void print_server_pp_iv(const uint8_t *data, size_t len);
+
+void print_client_pp_hp(const uint8_t *data, size_t len);
+void print_server_pp_hp(const uint8_t *data, size_t len);
+
+void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key,
+ size_t keylen, const uint8_t *iv, size_t ivlen,
+ const uint8_t *hp, size_t hplen);
+
+void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key,
+ size_t keylen, const uint8_t *iv, size_t ivlen);
+
+void print_hp_mask(const uint8_t *mask, size_t masklen, const uint8_t *sample,
+ size_t samplelen);
+
+void log_printf(void *user_data, const char *fmt, ...);
+
+void path_validation(const ngtcp2_path *path,
+ ngtcp2_path_validation_result res);
+
+void print_http_begin_request_headers(int64_t stream_id);
+
+void print_http_begin_response_headers(int64_t stream_id);
+
+void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name,
+ const nghttp3_rcbuf *value, uint8_t flags);
+
+void print_http_end_headers(int64_t stream_id);
+
+void print_http_data(int64_t stream_id, const uint8_t *data, size_t datalen);
+
+void print_http_begin_trailers(int64_t stream_id);
+
+void print_http_end_trailers(int64_t stream_id);
+
+void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva,
+ size_t nvlen);
+
+void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva,
+ size_t nvlen);
+
+std::string_view secret_title(ngtcp2_crypto_level level);
+
+} // namespace debug
+
+} // namespace ngtcp2
+
+#endif // DEBUG_H
diff --git a/examples/examplestest.cc b/examples/examplestest.cc
new file mode 100644
index 0000000..6487f81
--- /dev/null
+++ b/examples/examplestest.cc
@@ -0,0 +1,84 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2018 ngtcp2 contributors
+ * Copyright (c) 2013 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.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <stdio.h>
+#include <CUnit/Basic.h>
+// include test cases' include files here
+#include "util_test.h"
+
+static int init_suite1(void) { return 0; }
+
+static int clean_suite1(void) { return 0; }
+
+int main(int argc, char *argv[]) {
+ CU_pSuite pSuite = nullptr;
+ unsigned int num_tests_failed;
+
+ // initialize the CUnit test registry
+ if (CUE_SUCCESS != CU_initialize_registry())
+ return CU_get_error();
+
+ // add a suite to the registry
+ pSuite = CU_add_suite("TestSuite", init_suite1, clean_suite1);
+ if (nullptr == pSuite) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ // add the tests to the suite
+ if (!CU_add_test(pSuite, "util_format_durationf",
+ ngtcp2::test_util_format_durationf) ||
+ !CU_add_test(pSuite, "util_format_uint", ngtcp2::test_util_format_uint) ||
+ !CU_add_test(pSuite, "util_format_uint_iec",
+ ngtcp2::test_util_format_uint_iec) ||
+ !CU_add_test(pSuite, "util_format_duration",
+ ngtcp2::test_util_format_duration) ||
+ !CU_add_test(pSuite, "util_parse_uint", ngtcp2::test_util_parse_uint) ||
+ !CU_add_test(pSuite, "util_parse_uint_iec",
+ ngtcp2::test_util_parse_uint_iec) ||
+ !CU_add_test(pSuite, "util_parse_duration",
+ ngtcp2::test_util_parse_duration) ||
+ !CU_add_test(pSuite, "util_normalize_path",
+ ngtcp2::test_util_normalize_path)) {
+ CU_cleanup_registry();
+ return CU_get_error();
+ }
+
+ // Run all tests using the CUnit Basic interface
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+ num_tests_failed = CU_get_number_of_tests_failed();
+ CU_cleanup_registry();
+ if (CU_get_error() == CUE_SUCCESS) {
+ return num_tests_failed;
+ } else {
+ printf("CUnit Error: %s\n", CU_get_error_msg());
+ return CU_get_error();
+ }
+}
diff --git a/examples/gtlssimpleclient.c b/examples/gtlssimpleclient.c
new file mode 100644
index 0000000..60c0121
--- /dev/null
+++ b/examples/gtlssimpleclient.c
@@ -0,0 +1,720 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2021-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 /* HAVE_CONFIG_H */
+
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+
+#include <gnutls/crypto.h>
+#include <gnutls/gnutls.h>
+
+#include <ev.h>
+
+#define REMOTE_HOST "127.0.0.1"
+#define REMOTE_PORT "4433"
+#define ALPN "hq-interop"
+#define MESSAGE "GET /\r\n"
+
+/*
+ * Example 1: Handshake with www.google.com
+ *
+ * #define REMOTE_HOST "www.google.com"
+ * #define REMOTE_PORT "443"
+ * #define ALPN "h3"
+ *
+ * and undefine MESSAGE macro.
+ */
+
+static uint64_t timestamp(void) {
+ struct timespec tp;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
+ fprintf(stderr, "clock_gettime: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec;
+}
+
+static int create_sock(struct sockaddr *addr, socklen_t *paddrlen,
+ const char *host, const char *port) {
+ struct addrinfo hints = {0};
+ struct addrinfo *res, *rp;
+ int rv;
+ int fd = -1;
+
+ hints.ai_flags = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ rv = getaddrinfo(host, port, &hints, &res);
+ if (rv != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+ return -1;
+ }
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ continue;
+ }
+
+ break;
+ }
+
+ if (fd == -1) {
+ goto end;
+ }
+
+ *paddrlen = rp->ai_addrlen;
+ memcpy(addr, rp->ai_addr, rp->ai_addrlen);
+
+end:
+ freeaddrinfo(res);
+
+ return fd;
+}
+
+static int connect_sock(struct sockaddr *local_addr, socklen_t *plocal_addrlen,
+ int fd, const struct sockaddr *remote_addr,
+ size_t remote_addrlen) {
+ socklen_t len;
+
+ if (connect(fd, remote_addr, (socklen_t)remote_addrlen) != 0) {
+ fprintf(stderr, "connect: %s\n", strerror(errno));
+ return -1;
+ }
+
+ len = *plocal_addrlen;
+
+ if (getsockname(fd, local_addr, &len) == -1) {
+ fprintf(stderr, "getsockname: %s\n", strerror(errno));
+ return -1;
+ }
+
+ *plocal_addrlen = len;
+
+ return 0;
+}
+
+struct client {
+ ngtcp2_crypto_conn_ref conn_ref;
+ int fd;
+ struct sockaddr_storage local_addr;
+ socklen_t local_addrlen;
+ gnutls_certificate_credentials_t cred;
+ gnutls_session_t session;
+ ngtcp2_conn *conn;
+
+ struct {
+ int64_t stream_id;
+ const uint8_t *data;
+ size_t datalen;
+ size_t nwrite;
+ } stream;
+
+ ngtcp2_connection_close_error last_error;
+
+ ev_io rev;
+ ev_timer timer;
+};
+
+static int hook_func(gnutls_session_t session, unsigned int htype,
+ unsigned when, unsigned int incoming,
+ const gnutls_datum_t *msg) {
+ (void)session;
+ (void)htype;
+ (void)when;
+ (void)incoming;
+ (void)msg;
+ /* we could save session data here */
+
+ return 0;
+}
+
+static int numeric_host_family(const char *hostname, int family) {
+ uint8_t dst[sizeof(struct in6_addr)];
+ return inet_pton(family, hostname, dst) == 1;
+}
+
+static int numeric_host(const char *hostname) {
+ return numeric_host_family(hostname, AF_INET) ||
+ numeric_host_family(hostname, AF_INET6);
+}
+
+static const char priority[] =
+ "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:"
+ "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:"
+ "+GROUP-SECP384R1:"
+ "+GROUP-SECP521R1:%DISABLE_TLS13_COMPAT_MODE";
+
+static const gnutls_datum_t alpn = {(uint8_t *)ALPN, sizeof(ALPN) - 1};
+
+static int client_gnutls_init(struct client *c) {
+ int rv = gnutls_certificate_allocate_credentials(&c->cred);
+
+ if (rv == 0)
+ rv = gnutls_certificate_set_x509_system_trust(c->cred);
+ if (rv < 0) {
+ fprintf(stderr, "cred init failed: %d: %s\n", rv, gnutls_strerror(rv));
+ return -1;
+ }
+
+ rv = gnutls_init(&c->session, GNUTLS_CLIENT | GNUTLS_ENABLE_EARLY_DATA |
+ GNUTLS_NO_END_OF_EARLY_DATA);
+ if (rv != 0) {
+ fprintf(stderr, "gnutls_init: %s\n", gnutls_strerror(rv));
+ return -1;
+ }
+
+ if (ngtcp2_crypto_gnutls_configure_client_session(c->session) != 0) {
+ fprintf(stderr, "ngtcp2_crypto_gnutls_configure_client_session failed\n");
+ return -1;
+ }
+
+ rv = gnutls_priority_set_direct(c->session, priority, NULL);
+ if (rv != 0) {
+ fprintf(stderr, "gnutls_priority_set_direct: %s\n", gnutls_strerror(rv));
+ return -1;
+ }
+
+ gnutls_handshake_set_hook_function(c->session, GNUTLS_HANDSHAKE_ANY,
+ GNUTLS_HOOK_POST, hook_func);
+
+ gnutls_session_set_ptr(c->session, &c->conn_ref);
+
+ rv = gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred);
+
+ if (rv != 0) {
+ fprintf(stderr, "gnutls_credentials_set: %s\n", gnutls_strerror(rv));
+ return -1;
+ }
+
+ gnutls_alpn_set_protocols(c->session, &alpn, 1, GNUTLS_ALPN_MANDATORY);
+
+ if (!numeric_host(REMOTE_HOST)) {
+ gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, REMOTE_HOST,
+ strlen(REMOTE_HOST));
+ } else {
+ gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, "localhost",
+ strlen("localhost"));
+ }
+
+ return 0;
+}
+
+static void rand_cb(uint8_t *dest, size_t destlen,
+ const ngtcp2_rand_ctx *rand_ctx) {
+ (void)rand_ctx;
+
+ (void)gnutls_rnd(GNUTLS_RND_RANDOM, dest, destlen);
+}
+
+static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
+ uint8_t *token, size_t cidlen,
+ void *user_data) {
+ (void)conn;
+ (void)user_data;
+
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, cid->data, cidlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, token, NGTCP2_STATELESS_RESET_TOKENLEN) !=
+ 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int extend_max_local_streams_bidi(ngtcp2_conn *conn,
+ uint64_t max_streams,
+ void *user_data) {
+#ifdef MESSAGE
+ struct client *c = user_data;
+ int rv;
+ int64_t stream_id;
+ (void)max_streams;
+
+ if (c->stream.stream_id != -1) {
+ return 0;
+ }
+
+ rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
+ if (rv != 0) {
+ return 0;
+ }
+
+ c->stream.stream_id = stream_id;
+ c->stream.data = (const uint8_t *)MESSAGE;
+ c->stream.datalen = sizeof(MESSAGE) - 1;
+
+ return 0;
+#else /* !MESSAGE */
+ (void)conn;
+ (void)max_streams;
+ (void)user_data;
+
+ return 0;
+#endif /* !MESSAGE */
+}
+
+static void log_printf(void *user_data, const char *fmt, ...) {
+ va_list ap;
+ (void)user_data;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+
+static int client_quic_init(struct client *c,
+ const struct sockaddr *remote_addr,
+ socklen_t remote_addrlen,
+ const struct sockaddr *local_addr,
+ socklen_t local_addrlen) {
+ ngtcp2_path path = {
+ {
+ (struct sockaddr *)local_addr,
+ local_addrlen,
+ },
+ {
+ (struct sockaddr *)remote_addr,
+ remote_addrlen,
+ },
+ NULL,
+ };
+ ngtcp2_callbacks callbacks = {
+ ngtcp2_crypto_client_initial_cb,
+ NULL, /* recv_client_initial */
+ ngtcp2_crypto_recv_crypto_data_cb,
+ NULL, /* handshake_completed */
+ NULL, /* recv_version_negotiation */
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ NULL, /* recv_stream_data */
+ NULL, /* acked_stream_data_offset */
+ NULL, /* stream_open */
+ NULL, /* stream_close */
+ NULL, /* recv_stateless_reset */
+ ngtcp2_crypto_recv_retry_cb,
+ extend_max_local_streams_bidi,
+ NULL, /* extend_max_local_streams_uni */
+ rand_cb,
+ get_new_connection_id_cb,
+ NULL, /* remove_connection_id */
+ ngtcp2_crypto_update_key_cb,
+ NULL, /* path_validation */
+ NULL, /* select_preferred_address */
+ NULL, /* stream_reset */
+ NULL, /* extend_max_remote_streams_bidi */
+ NULL, /* extend_max_remote_streams_uni */
+ NULL, /* extend_max_stream_data */
+ NULL, /* dcid_status */
+ NULL, /* handshake_confirmed */
+ NULL, /* recv_new_token */
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ NULL, /* recv_datagram */
+ NULL, /* ack_datagram */
+ NULL, /* lost_datagram */
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ NULL, /* stream_stop_sending */
+ ngtcp2_crypto_version_negotiation_cb,
+ NULL, /* recv_rx_key */
+ NULL, /* recv_tx_key */
+ NULL, /* early_data_rejected */
+ };
+ ngtcp2_cid dcid, scid;
+ ngtcp2_settings settings;
+ ngtcp2_transport_params params;
+ int rv;
+
+ dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN;
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, dcid.data, dcid.datalen) != 0) {
+ fprintf(stderr, "gnutls_rnd failed\n");
+ return -1;
+ }
+
+ scid.datalen = 8;
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, scid.data, scid.datalen) != 0) {
+ fprintf(stderr, "gnutls_rnd failed\n");
+ return -1;
+ }
+
+ ngtcp2_settings_default(&settings);
+
+ settings.initial_ts = timestamp();
+ settings.log_printf = log_printf;
+
+ ngtcp2_transport_params_default(&params);
+
+ params.initial_max_streams_uni = 3;
+ params.initial_max_stream_data_bidi_local = 128 * 1024;
+ params.initial_max_data = 1024 * 1024;
+
+ rv =
+ ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1,
+ &callbacks, &settings, &params, NULL, c);
+ if (rv != 0) {
+ fprintf(stderr, "ngtcp2_conn_client_new: %s\n", ngtcp2_strerror(rv));
+ return -1;
+ }
+
+ ngtcp2_conn_set_tls_native_handle(c->conn, c->session);
+
+ return 0;
+}
+
+static int client_read(struct client *c) {
+ uint8_t buf[65536];
+ struct sockaddr_storage addr;
+ struct iovec iov = {buf, sizeof(buf)};
+ struct msghdr msg = {0};
+ ssize_t nread;
+ ngtcp2_path path;
+ ngtcp2_pkt_info pi = {0};
+ int rv;
+
+ msg.msg_name = &addr;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ for (;;) {
+ msg.msg_namelen = sizeof(addr);
+
+ nread = recvmsg(c->fd, &msg, MSG_DONTWAIT);
+
+ if (nread == -1) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ fprintf(stderr, "recvmsg: %s\n", strerror(errno));
+ }
+
+ break;
+ }
+
+ path.local.addrlen = c->local_addrlen;
+ path.local.addr = (struct sockaddr *)&c->local_addr;
+ path.remote.addrlen = msg.msg_namelen;
+ path.remote.addr = msg.msg_name;
+
+ rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)nread,
+ timestamp());
+ if (rv != 0) {
+ fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv));
+ if (!c->last_error.error_code) {
+ if (rv == NGTCP2_ERR_CRYPTO) {
+ ngtcp2_connection_close_error_set_transport_error_tls_alert(
+ &c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0);
+ } else {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &c->last_error, rv, NULL, 0);
+ }
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int client_send_packet(struct client *c, const uint8_t *data,
+ size_t datalen) {
+ struct iovec iov = {(uint8_t *)data, datalen};
+ struct msghdr msg = {0};
+ ssize_t nwrite;
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ do {
+ nwrite = sendmsg(c->fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ fprintf(stderr, "sendmsg: %s\n", strerror(errno));
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static size_t client_get_message(struct client *c, int64_t *pstream_id,
+ int *pfin, ngtcp2_vec *datav,
+ size_t datavcnt) {
+ if (datavcnt == 0) {
+ return 0;
+ }
+
+ if (c->stream.stream_id != -1 && c->stream.nwrite < c->stream.datalen) {
+ *pstream_id = c->stream.stream_id;
+ *pfin = 1;
+ datav->base = (uint8_t *)c->stream.data + c->stream.nwrite;
+ datav->len = c->stream.datalen - c->stream.nwrite;
+ return 1;
+ }
+
+ *pstream_id = -1;
+ *pfin = 0;
+ datav->base = NULL;
+ datav->len = 0;
+
+ return 0;
+}
+
+static int client_write_streams(struct client *c) {
+ ngtcp2_tstamp ts = timestamp();
+ ngtcp2_pkt_info pi;
+ ngtcp2_ssize nwrite;
+ uint8_t buf[1280];
+ ngtcp2_path_storage ps;
+ ngtcp2_vec datav;
+ size_t datavcnt;
+ int64_t stream_id;
+ ngtcp2_ssize wdatalen;
+ uint32_t flags;
+ int fin;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ for (;;) {
+ datavcnt = client_get_message(c, &stream_id, &fin, &datav, 1);
+
+ flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ if (fin) {
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, sizeof(buf),
+ &wdatalen, flags, stream_id, &datav,
+ datavcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_WRITE_MORE:
+ c->stream.nwrite += (size_t)wdatalen;
+ continue;
+ default:
+ fprintf(stderr, "ngtcp2_conn_writev_stream: %s\n",
+ ngtcp2_strerror((int)nwrite));
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &c->last_error, (int)nwrite, NULL, 0);
+ return -1;
+ }
+ }
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (wdatalen > 0) {
+ c->stream.nwrite += (size_t)wdatalen;
+ }
+
+ if (client_send_packet(c, buf, (size_t)nwrite) != 0) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int client_write(struct client *c) {
+ ngtcp2_tstamp expiry, now;
+ ev_tstamp t;
+
+ if (client_write_streams(c) != 0) {
+ return -1;
+ }
+
+ expiry = ngtcp2_conn_get_expiry(c->conn);
+ now = timestamp();
+
+ t = expiry < now ? 1e-9 : (ev_tstamp)(expiry - now) / NGTCP2_SECONDS;
+
+ c->timer.repeat = t;
+ ev_timer_again(EV_DEFAULT, &c->timer);
+
+ return 0;
+}
+
+static int client_handle_expiry(struct client *c) {
+ int rv = ngtcp2_conn_handle_expiry(c->conn, timestamp());
+ if (rv != 0) {
+ fprintf(stderr, "ngtcp2_conn_handle_expiry: %s\n", ngtcp2_strerror(rv));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void client_close(struct client *c) {
+ ngtcp2_ssize nwrite;
+ ngtcp2_pkt_info pi;
+ ngtcp2_path_storage ps;
+ uint8_t buf[1280];
+
+ if (ngtcp2_conn_is_in_closing_period(c->conn) ||
+ ngtcp2_conn_is_in_draining_period(c->conn)) {
+ goto fin;
+ }
+
+ ngtcp2_path_storage_zero(&ps);
+
+ nwrite = ngtcp2_conn_write_connection_close(
+ c->conn, &ps.path, &pi, buf, sizeof(buf), &c->last_error, timestamp());
+ if (nwrite < 0) {
+ fprintf(stderr, "ngtcp2_conn_write_connection_close: %s\n",
+ ngtcp2_strerror((int)nwrite));
+ goto fin;
+ }
+
+ client_send_packet(c, buf, (size_t)nwrite);
+
+fin:
+ ev_break(EV_DEFAULT, EVBREAK_ALL);
+}
+
+static void read_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ struct client *c = w->data;
+ (void)loop;
+ (void)revents;
+
+ if (client_read(c) != 0) {
+ client_close(c);
+ return;
+ }
+
+ if (client_write(c) != 0) {
+ client_close(c);
+ }
+}
+
+static void timer_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ struct client *c = w->data;
+ (void)loop;
+ (void)revents;
+
+ if (client_handle_expiry(c) != 0) {
+ client_close(c);
+ return;
+ }
+
+ if (client_write(c) != 0) {
+ client_close(c);
+ }
+}
+
+static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
+ struct client *c = conn_ref->user_data;
+ return c->conn;
+}
+
+static int client_init(struct client *c) {
+ struct sockaddr_storage remote_addr, local_addr;
+ socklen_t remote_addrlen, local_addrlen = sizeof(local_addr);
+
+ memset(c, 0, sizeof(*c));
+
+ ngtcp2_connection_close_error_default(&c->last_error);
+
+ c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen,
+ REMOTE_HOST, REMOTE_PORT);
+ if (c->fd == -1) {
+ return -1;
+ }
+
+ if (connect_sock((struct sockaddr *)&local_addr, &local_addrlen, c->fd,
+ (struct sockaddr *)&remote_addr, remote_addrlen) != 0) {
+ return -1;
+ }
+
+ memcpy(&c->local_addr, &local_addr, sizeof(c->local_addr));
+ c->local_addrlen = local_addrlen;
+
+ if (client_gnutls_init(c) != 0) {
+ return -1;
+ }
+
+ if (client_quic_init(c, (struct sockaddr *)&remote_addr, remote_addrlen,
+ (struct sockaddr *)&local_addr, local_addrlen) != 0) {
+ return -1;
+ }
+
+ c->stream.stream_id = -1;
+
+ c->conn_ref.get_conn = get_conn;
+ c->conn_ref.user_data = c;
+
+ ev_io_init(&c->rev, read_cb, c->fd, EV_READ);
+ c->rev.data = c;
+ ev_io_start(EV_DEFAULT, &c->rev);
+
+ ev_timer_init(&c->timer, timer_cb, 0., 0.);
+ c->timer.data = c;
+
+ return 0;
+}
+
+static void client_free(struct client *c) {
+ ngtcp2_conn_del(c->conn);
+ gnutls_deinit(c->session);
+ gnutls_certificate_free_credentials(c->cred);
+}
+
+int main(void) {
+ struct client c;
+
+ if (client_init(&c) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (client_write(&c) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ ev_run(EV_DEFAULT, 0);
+
+ client_free(&c);
+
+ return 0;
+}
diff --git a/examples/h09client.cc b/examples/h09client.cc
new file mode 100644
index 0000000..4df12ca
--- /dev/null
+++ b/examples/h09client.cc
@@ -0,0 +1,2604 @@
+/*
+ * 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 <cstdlib>
+#include <cassert>
+#include <cerrno>
+#include <iostream>
+#include <algorithm>
+#include <memory>
+#include <fstream>
+#include <iomanip>
+
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/mman.h>
+
+#include <http-parser/http_parser.h>
+
+#include "h09client.h"
+#include "network.h"
+#include "debug.h"
+#include "util.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+using namespace std::literals;
+
+namespace {
+auto randgen = util::make_mt19937();
+} // namespace
+
+namespace {
+constexpr size_t max_preferred_versionslen = 4;
+} // namespace
+
+Config config{};
+
+Stream::Stream(const Request &req, int64_t stream_id)
+ : req(req), stream_id(stream_id), fd(-1) {
+ nghttp3_buf_init(&reqbuf);
+}
+
+Stream::~Stream() {
+ if (fd != -1) {
+ close(fd);
+ }
+}
+
+int Stream::open_file(const std::string_view &path) {
+ assert(fd == -1);
+
+ std::string_view filename;
+
+ auto it = std::find(std::rbegin(path), std::rend(path), '/').base();
+ if (it == std::end(path)) {
+ filename = "index.html"sv;
+ } else {
+ filename = std::string_view{it, static_cast<size_t>(std::end(path) - it)};
+ if (filename == ".."sv || filename == "."sv) {
+ std::cerr << "Invalid file name: " << filename << std::endl;
+ return -1;
+ }
+ }
+
+ auto fname = std::string{config.download};
+ fname += '/';
+ fname += filename;
+
+ fd = open(fname.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd == -1) {
+ std::cerr << "open: Could not open file " << fname << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ c->on_write();
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto ep = static_cast<Endpoint *>(w->data);
+ auto c = ep->client;
+
+ if (c->on_read(*ep) != 0) {
+ return;
+ }
+
+ c->on_write();
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ int rv;
+ auto c = static_cast<Client *>(w->data);
+
+ rv = c->handle_expiry();
+ if (rv != 0) {
+ return;
+ }
+
+ c->on_write();
+}
+} // namespace
+
+namespace {
+void change_local_addrcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ c->change_local_addr();
+}
+} // namespace
+
+namespace {
+void key_updatecb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ if (c->initiate_key_update() != 0) {
+ c->disconnect();
+ }
+}
+} // namespace
+
+namespace {
+void delay_streamcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto c = static_cast<Client *>(w->data);
+
+ ev_timer_stop(loop, w);
+ c->on_extend_max_streams();
+ c->on_write();
+}
+} // namespace
+
+namespace {
+void siginthandler(struct ev_loop *loop, ev_signal *w, int revents) {
+ ev_break(loop, EVBREAK_ALL);
+}
+} // namespace
+
+Client::Client(struct ev_loop *loop, uint32_t client_chosen_version,
+ uint32_t original_version)
+ : remote_addr_{},
+ loop_(loop),
+ addr_(nullptr),
+ port_(nullptr),
+ nstreams_done_(0),
+ nstreams_closed_(0),
+ nkey_update_(0),
+ client_chosen_version_(client_chosen_version),
+ original_version_(original_version),
+ early_data_(false),
+ should_exit_(false),
+ should_exit_on_handshake_confirmed_(false),
+ handshake_confirmed_(false),
+ tx_{} {
+ ev_io_init(&wev_, writecb, 0, EV_WRITE);
+ wev_.data = this;
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+ ev_timer_init(&change_local_addr_timer_, change_local_addrcb,
+ static_cast<double>(config.change_local_addr) / NGTCP2_SECONDS,
+ 0.);
+ change_local_addr_timer_.data = this;
+ ev_timer_init(&key_update_timer_, key_updatecb,
+ static_cast<double>(config.key_update) / NGTCP2_SECONDS, 0.);
+ key_update_timer_.data = this;
+ ev_timer_init(&delay_stream_timer_, delay_streamcb,
+ static_cast<double>(config.delay_stream) / NGTCP2_SECONDS, 0.);
+ delay_stream_timer_.data = this;
+ ev_signal_init(&sigintev_, siginthandler, SIGINT);
+}
+
+Client::~Client() { disconnect(); }
+
+void Client::disconnect() {
+ tx_.send_blocked = false;
+
+ handle_error();
+
+ config.tx_loss_prob = 0;
+
+ ev_timer_stop(loop_, &delay_stream_timer_);
+ ev_timer_stop(loop_, &key_update_timer_);
+ ev_timer_stop(loop_, &change_local_addr_timer_);
+ ev_timer_stop(loop_, &timer_);
+
+ ev_io_stop(loop_, &wev_);
+
+ for (auto &ep : endpoints_) {
+ ev_io_stop(loop_, &ep.rev);
+ close(ep.fd);
+ }
+
+ endpoints_.clear();
+
+ ev_signal_stop(loop_, &sigintev_);
+}
+
+namespace {
+int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data) {
+ if (!config.quiet && !config.no_quic_dump) {
+ debug::print_crypto_data(crypto_level, data, datalen);
+ }
+
+ return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data,
+ datalen, user_data);
+}
+} // namespace
+
+namespace {
+int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet && !config.no_quic_dump) {
+ debug::print_stream_data(stream_id, data, datalen);
+ }
+
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->recv_stream_data(flags, stream_id, data, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->acked_stream_data_offset(stream_id, offset, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (!config.quiet) {
+ debug::handshake_completed(conn, user_data);
+ }
+
+ if (c->handshake_completed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::handshake_completed() {
+ if (early_data_ && !tls_session_.get_early_data_accepted()) {
+ if (!config.quiet) {
+ std::cerr << "Early data was rejected by server" << std::endl;
+ }
+
+ // Some TLS backends only report early data rejection after
+ // handshake completion (e.g., OpenSSL). For TLS backends which
+ // report it early (e.g., BoringSSL and PicoTLS), the following
+ // functions are noop.
+ if (auto rv = ngtcp2_conn_early_data_rejected(conn_); rv != 0) {
+ std::cerr << "ngtcp2_conn_early_data_rejected: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name()
+ << std::endl;
+ std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn()
+ << std::endl;
+ }
+
+ if (config.tp_file) {
+ auto params = ngtcp2_conn_get_remote_transport_params(conn_);
+
+ if (write_transport_params(config.tp_file, params) != 0) {
+ std::cerr << "Could not write transport parameters in " << config.tp_file
+ << std::endl;
+ }
+ }
+
+ return 0;
+}
+
+namespace {
+int handshake_confirmed(ngtcp2_conn *conn, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (!config.quiet) {
+ debug::handshake_confirmed(conn, user_data);
+ }
+
+ if (c->handshake_confirmed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Client::handshake_confirmed() {
+ handshake_confirmed_ = true;
+
+ if (config.change_local_addr) {
+ start_change_local_addr_timer();
+ }
+ if (config.key_update) {
+ start_key_update_timer();
+ }
+ if (config.delay_stream) {
+ start_delay_stream_timer();
+ }
+
+ if (should_exit_on_handshake_confirmed_) {
+ should_exit_ = true;
+ }
+
+ return 0;
+}
+
+namespace {
+int recv_version_negotiation(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd,
+ const uint32_t *sv, size_t nsv, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ c->recv_version_negotiation(sv, nsv);
+
+ return 0;
+}
+} // namespace
+
+void Client::recv_version_negotiation(const uint32_t *sv, size_t nsv) {
+ offered_versions_.resize(nsv);
+ std::copy_n(sv, nsv, std::begin(offered_versions_));
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->on_stream_close(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int extend_max_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
+ void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->on_extend_max_streams() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
+ std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); });
+}
+} // namespace
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ if (util::generate_secure_random(cid->data, cidlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+ if (ngtcp2_crypto_generate_stateless_reset_token(
+ token, config.static_secret.data(), config.static_secret.size(),
+ cid) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+ const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) {
+ if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (!config.quiet && config.show_secret) {
+ debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen,
+ void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ if (c->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx,
+ tx_iv, current_rx_secret, current_tx_secret,
+ secretlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path,
+ ngtcp2_path_validation_result res, void *user_data) {
+ if (!config.quiet) {
+ debug::path_validation(path, res);
+ }
+
+ if (flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR) {
+ auto c = static_cast<Client *>(user_data);
+
+ c->set_remote_addr(path->remote);
+ }
+
+ return 0;
+}
+} // namespace
+
+void Client::set_remote_addr(const ngtcp2_addr &remote_addr) {
+ memcpy(&remote_addr_.su, remote_addr.addr, remote_addr.addrlen);
+ remote_addr_.len = remote_addr.addrlen;
+}
+
+namespace {
+int select_preferred_address(ngtcp2_conn *conn, ngtcp2_path *dest,
+ const ngtcp2_preferred_addr *paddr,
+ void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+ Address remote_addr;
+
+ if (config.no_preferred_addr) {
+ return 0;
+ }
+
+ if (c->select_preferred_address(remote_addr, paddr) != 0) {
+ return 0;
+ }
+
+ auto ep = c->endpoint_for(remote_addr);
+ if (!ep) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ ngtcp2_addr_copy_byte(&dest->local, &(*ep)->addr.su.sa, (*ep)->addr.len);
+ ngtcp2_addr_copy_byte(&dest->remote, &remote_addr.su.sa, remote_addr.len);
+ dest->user_data = *ep;
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data) {
+ auto c = static_cast<Client *>(user_data);
+ if (c->extend_max_stream_data(stream_id, max_data) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Client::extend_max_stream_data(int64_t stream_id, uint64_t max_data) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ if (nghttp3_buf_len(&stream->reqbuf)) {
+ sendq_.emplace(stream.get());
+ }
+
+ return 0;
+}
+
+namespace {
+int recv_new_token(ngtcp2_conn *conn, const ngtcp2_vec *token,
+ void *user_data) {
+ if (config.token_file.empty()) {
+ return 0;
+ }
+
+ auto f = BIO_new_file(config.token_file.data(), "w");
+ if (f == nullptr) {
+ std::cerr << "Could not write token in " << config.token_file << std::endl;
+ return 0;
+ }
+
+ PEM_write_bio(f, "QUIC TOKEN", "", token->base, token->len);
+ BIO_free(f);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int early_data_rejected(ngtcp2_conn *conn, void *user_data) {
+ auto c = static_cast<Client *>(user_data);
+
+ c->early_data_rejected();
+
+ return 0;
+}
+} // namespace
+
+void Client::early_data_rejected() {
+ nstreams_done_ = 0;
+ streams_.clear();
+}
+
+int Client::init(int fd, const Address &local_addr, const Address &remote_addr,
+ const char *addr, const char *port,
+ TLSClientContext &tls_ctx) {
+ endpoints_.reserve(4);
+
+ endpoints_.emplace_back();
+ auto &ep = endpoints_.back();
+ ep.addr = local_addr;
+ ep.client = this;
+ ep.fd = fd;
+ ev_io_init(&ep.rev, readcb, fd, EV_READ);
+ ep.rev.data = &ep;
+
+ remote_addr_ = remote_addr;
+ addr_ = addr;
+ port_ = port;
+
+ auto callbacks = ngtcp2_callbacks{
+ ngtcp2_crypto_client_initial_cb,
+ nullptr, // recv_client_initial
+ ::recv_crypto_data,
+ ::handshake_completed,
+ ::recv_version_negotiation,
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ do_hp_mask,
+ ::recv_stream_data,
+ ::acked_stream_data_offset,
+ nullptr, // stream_open
+ stream_close,
+ nullptr, // recv_stateless_reset
+ ngtcp2_crypto_recv_retry_cb,
+ extend_max_streams_bidi,
+ nullptr, // extend_max_streams_uni
+ rand,
+ get_new_connection_id,
+ nullptr, // remove_connection_id
+ ::update_key,
+ path_validation,
+ ::select_preferred_address,
+ nullptr, // stream_reset
+ nullptr, // extend_max_remote_streams_bidi,
+ nullptr, // extend_max_remote_streams_uni,
+ ::extend_max_stream_data,
+ nullptr, // dcid_status
+ ::handshake_confirmed,
+ ::recv_new_token,
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ nullptr, // stream_stop_sending
+ ngtcp2_crypto_version_negotiation_cb,
+ nullptr, // recv_rx_key
+ nullptr, // recv_tx_key
+ ::early_data_rejected,
+ };
+
+ ngtcp2_cid scid, dcid;
+ scid.datalen = 17;
+ if (util::generate_secure_random(scid.data, scid.datalen) != 0) {
+ std::cerr << "Could not generate source connection ID" << std::endl;
+ return -1;
+ }
+ if (config.dcid.datalen == 0) {
+ dcid.datalen = 18;
+ if (util::generate_secure_random(dcid.data, dcid.datalen) != 0) {
+ std::cerr << "Could not generate destination connection ID" << std::endl;
+ return -1;
+ }
+ } else {
+ dcid = config.dcid;
+ }
+
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ settings.log_printf = config.quiet ? nullptr : debug::log_printf;
+ if (!config.qlog_file.empty() || !config.qlog_dir.empty()) {
+ std::string path;
+ if (!config.qlog_file.empty()) {
+ path = config.qlog_file;
+ } else {
+ path = std::string{config.qlog_dir};
+ path += '/';
+ path += util::format_hex(scid.data, scid.datalen);
+ path += ".sqlog";
+ }
+ qlog_ = fopen(path.c_str(), "w");
+ if (qlog_ == nullptr) {
+ std::cerr << "Could not open qlog file " << std::quoted(path) << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+ settings.qlog.write = qlog_write_cb;
+ }
+
+ settings.cc_algo = config.cc_algo;
+ settings.initial_ts = util::timestamp(loop_);
+ settings.initial_rtt = config.initial_rtt;
+ settings.max_window = config.max_window;
+ settings.max_stream_window = config.max_stream_window;
+ if (config.max_udp_payload_size) {
+ settings.max_tx_udp_payload_size = config.max_udp_payload_size;
+ settings.no_tx_udp_payload_size_shaping = 1;
+ }
+ settings.handshake_timeout = config.handshake_timeout;
+ settings.no_pmtud = config.no_pmtud;
+ settings.ack_thresh = config.ack_thresh;
+
+ std::string token;
+
+ if (!config.token_file.empty()) {
+ std::cerr << "Reading token file " << config.token_file << std::endl;
+
+ auto t = util::read_token(config.token_file);
+ if (t) {
+ token = std::move(*t);
+ settings.token.base = reinterpret_cast<uint8_t *>(token.data());
+ settings.token.len = token.size();
+ }
+ }
+
+ if (!config.other_versions.empty()) {
+ settings.other_versions = config.other_versions.data();
+ settings.other_versionslen = config.other_versions.size();
+ }
+
+ if (!config.preferred_versions.empty()) {
+ settings.preferred_versions = config.preferred_versions.data();
+ settings.preferred_versionslen = config.preferred_versions.size();
+ }
+
+ settings.original_version = original_version_;
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+ params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
+ params.initial_max_stream_data_bidi_remote =
+ config.max_stream_data_bidi_remote;
+ params.initial_max_stream_data_uni = config.max_stream_data_uni;
+ params.initial_max_data = config.max_data;
+ params.initial_max_streams_bidi = config.max_streams_bidi;
+ params.initial_max_streams_uni = 0;
+ params.max_idle_timeout = config.timeout;
+ params.active_connection_id_limit = 7;
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&ep.addr.su.sa),
+ ep.addr.len,
+ },
+ {
+ const_cast<sockaddr *>(&remote_addr.su.sa),
+ remote_addr.len,
+ },
+ &ep,
+ };
+ auto rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path,
+ client_chosen_version_, &callbacks,
+ &settings, &params, nullptr, this);
+
+ if (rv != 0) {
+ std::cerr << "ngtcp2_conn_client_new: " << ngtcp2_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (tls_session_.init(early_data_, tls_ctx, addr_, this,
+ client_chosen_version_, AppProtocol::HQ) != 0) {
+ return -1;
+ }
+
+ ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle());
+
+ if (early_data_ && config.tp_file) {
+ ngtcp2_transport_params params;
+ if (read_transport_params(config.tp_file, &params) != 0) {
+ std::cerr << "Could not read transport parameters from " << config.tp_file
+ << std::endl;
+ early_data_ = false;
+ } else {
+ ngtcp2_conn_set_early_remote_transport_params(conn_, &params);
+ if (make_stream_early() != 0) {
+ return -1;
+ }
+ }
+ }
+
+ ev_io_start(loop_, &ep.rev);
+
+ ev_signal_start(loop_, &sigintev_);
+
+ return 0;
+}
+
+int Client::feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data,
+ size_t datalen) {
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&ep.addr.su.sa),
+ ep.addr.len,
+ },
+ {
+ const_cast<sockaddr *>(sa),
+ salen,
+ },
+ const_cast<Endpoint *>(&ep),
+ };
+ if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen,
+ util::timestamp(loop_));
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl;
+ if (!last_error_.error_code) {
+ if (rv == NGTCP2_ERR_CRYPTO) {
+ ngtcp2_connection_close_error_set_transport_error_tls_alert(
+ &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0);
+ } else {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, rv, nullptr, 0);
+ }
+ }
+ disconnect();
+ return -1;
+ }
+ return 0;
+}
+
+int Client::on_read(const Endpoint &ep) {
+ std::array<uint8_t, 64_k> buf;
+ sockaddr_union su;
+ size_t pktcnt = 0;
+ ngtcp2_pkt_info pi;
+
+ iovec msg_iov;
+ msg_iov.iov_base = buf.data();
+ msg_iov.iov_len = buf.size();
+
+ msghdr msg{};
+ msg.msg_name = &su;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint8_t))];
+ msg.msg_control = msg_ctrl;
+
+ for (;;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(ep.fd, &msg, 0);
+
+ if (nread == -1) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ std::cerr << "recvmsg: " << strerror(errno) << std::endl;
+ }
+ break;
+ }
+
+ pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family);
+
+ if (!config.quiet) {
+ std::cerr << "Received packet: local="
+ << util::straddr(&ep.addr.su.sa, ep.addr.len)
+ << " remote=" << util::straddr(&su.sa, msg.msg_namelen)
+ << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread
+ << " bytes" << std::endl;
+ }
+
+ if (debug::packet_lost(config.rx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated incoming packet loss **" << std::endl;
+ }
+ break;
+ }
+
+ if (feed_data(ep, &su.sa, msg.msg_namelen, &pi, buf.data(), nread) != 0) {
+ return -1;
+ }
+
+ if (++pktcnt >= 10) {
+ break;
+ }
+ }
+
+ if (should_exit_) {
+ disconnect();
+ return -1;
+ }
+
+ update_timer();
+
+ return 0;
+}
+
+int Client::handle_expiry() {
+ auto now = util::timestamp(loop_);
+ if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) {
+ std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv,
+ nullptr, 0);
+ disconnect();
+ return -1;
+ }
+
+ return 0;
+}
+
+int Client::on_write() {
+ if (tx_.send_blocked) {
+ if (auto rv = send_blocked_packet(); rv != 0) {
+ return rv;
+ }
+
+ if (tx_.send_blocked) {
+ return 0;
+ }
+ }
+
+ if (auto rv = write_streams(); rv != 0) {
+ return rv;
+ }
+
+ if (should_exit_) {
+ disconnect();
+ return -1;
+ }
+
+ update_timer();
+ return 0;
+}
+
+int Client::write_streams() {
+ ngtcp2_vec vec;
+ ngtcp2_path_storage ps;
+ size_t pktcnt = 0;
+ auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_);
+ auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size;
+ auto ts = util::timestamp(loop_);
+
+ ngtcp2_path_storage_zero(&ps);
+
+ for (;;) {
+ int64_t stream_id = -1;
+ size_t vcnt = 0;
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ Stream *stream = nullptr;
+
+ if (!sendq_.empty() && ngtcp2_conn_get_max_data_left(conn_)) {
+ stream = *std::begin(sendq_);
+
+ stream_id = stream->stream_id;
+ vec.base = stream->reqbuf.pos;
+ vec.len = nghttp3_buf_len(&stream->reqbuf);
+ vcnt = 1;
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ ngtcp2_ssize ndatalen;
+ ngtcp2_pkt_info pi;
+
+ auto nwrite = ngtcp2_conn_writev_stream(
+ conn_, &ps.path, &pi, tx_.data.data(), max_udp_payload_size, &ndatalen,
+ flags, stream_id, &vec, vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ sendq_.erase(std::begin(sendq_));
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ stream->reqbuf.pos += ndatalen;
+ if (nghttp3_buf_len(&stream->reqbuf) == 0) {
+ sendq_.erase(std::begin(sendq_));
+ }
+ continue;
+ }
+
+ assert(ndatalen == -1);
+
+ std::cerr << "ngtcp2_conn_write_stream: " << ngtcp2_strerror(nwrite)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, nwrite, nullptr, 0);
+ disconnect();
+ return -1;
+ } else if (ndatalen >= 0) {
+ stream->reqbuf.pos += ndatalen;
+ if (nghttp3_buf_len(&stream->reqbuf) == 0) {
+ sendq_.erase(std::begin(sendq_));
+ }
+ }
+
+ if (nwrite == 0) {
+ // We are congestion limited.
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ ev_io_stop(loop_, &wev_);
+ return 0;
+ }
+
+ auto &ep = *static_cast<Endpoint *>(ps.path.user_data);
+
+ if (auto rv =
+ send_packet(ep, ps.path.remote, pi.ecn, tx_.data.data(), nwrite);
+ rv != NETWORK_ERR_OK) {
+ if (rv != NETWORK_ERR_SEND_BLOCKED) {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0);
+ disconnect();
+
+ return rv;
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ on_send_blocked(ep, ps.path.remote, pi.ecn, nwrite);
+
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt) {
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ start_wev_endpoint(ep);
+ return 0;
+ }
+ }
+}
+
+void Client::update_timer() {
+ auto expiry = ngtcp2_conn_get_expiry(conn_);
+ auto now = util::timestamp(loop_);
+
+ if (expiry <= now) {
+ if (!config.quiet) {
+ auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS;
+ std::cerr << "Timer has already expired: " << t << "s" << std::endl;
+ }
+
+ ev_feed_event(loop_, &timer_, EV_TIMER);
+
+ return;
+ }
+
+ auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS;
+ if (!config.quiet) {
+ std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat
+ << std::endl;
+ }
+ timer_.repeat = t;
+ ev_timer_again(loop_, &timer_);
+}
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+namespace {
+int bind_addr(Address &local_addr, int fd, const in_addr_union *iau,
+ int family) {
+ addrinfo hints{};
+ addrinfo *res, *rp;
+
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ char *node;
+ std::array<char, NI_MAXHOST> nodebuf;
+
+ if (iau) {
+ if (inet_ntop(family, iau, nodebuf.data(), nodebuf.size()) == nullptr) {
+ std::cerr << "inet_ntop: " << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ node = nodebuf.data();
+ } else {
+ node = nullptr;
+ }
+
+ if (auto rv = getaddrinfo(node, "0", &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
+ break;
+ }
+ }
+
+ if (!rp) {
+ std::cerr << "Could not bind" << std::endl;
+ return -1;
+ }
+
+ socklen_t len = sizeof(local_addr.su.storage);
+ if (getsockname(fd, &local_addr.su.sa, &len) == -1) {
+ std::cerr << "getsockname: " << strerror(errno) << std::endl;
+ return -1;
+ }
+ local_addr.len = len;
+ local_addr.ifindex = 0;
+
+ return 0;
+}
+} // namespace
+#endif // HAVE_LINUX_RTNETLINK_H
+
+#ifndef HAVE_LINUX_RTNETLINK_H
+namespace {
+int connect_sock(Address &local_addr, int fd, const Address &remote_addr) {
+ if (connect(fd, &remote_addr.su.sa, remote_addr.len) != 0) {
+ std::cerr << "connect: " << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ socklen_t len = sizeof(local_addr.su.storage);
+ if (getsockname(fd, &local_addr.su.sa, &len) == -1) {
+ std::cerr << "getsockname: " << strerror(errno) << std::endl;
+ return -1;
+ }
+ local_addr.len = len;
+ local_addr.ifindex = 0;
+
+ return 0;
+}
+} // namespace
+#endif // !HAVE_LINUX_RTNETLINK_H
+
+namespace {
+int udp_sock(int family) {
+ auto fd = util::create_nonblock_socket(family, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd == -1) {
+ return -1;
+ }
+
+ fd_set_recv_ecn(fd, family);
+ fd_set_ip_mtu_discover(fd, family);
+ fd_set_ip_dontfrag(fd, family);
+
+ return fd;
+}
+} // namespace
+
+namespace {
+int create_sock(Address &remote_addr, const char *addr, const char *port) {
+ addrinfo hints{};
+ addrinfo *res, *rp;
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ int fd = -1;
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ fd = udp_sock(rp->ai_family);
+ if (fd == -1) {
+ continue;
+ }
+
+ break;
+ }
+
+ if (!rp) {
+ std::cerr << "Could not create socket" << std::endl;
+ return -1;
+ }
+
+ remote_addr.len = rp->ai_addrlen;
+ memcpy(&remote_addr.su, rp->ai_addr, rp->ai_addrlen);
+
+ return fd;
+}
+} // namespace
+
+std::optional<Endpoint *> Client::endpoint_for(const Address &remote_addr) {
+#ifdef HAVE_LINUX_RTNETLINK_H
+ in_addr_union iau;
+
+ if (get_local_addr(iau, remote_addr) != 0) {
+ std::cerr << "Could not get local address for a selected preferred address"
+ << std::endl;
+ return nullptr;
+ }
+
+ auto current_path = ngtcp2_conn_get_path(conn_);
+ auto current_ep = static_cast<Endpoint *>(current_path->user_data);
+ if (addreq(&current_ep->addr.su.sa, iau)) {
+ return current_ep;
+ }
+#endif // HAVE_LINUX_RTNETLINK_H
+
+ auto fd = udp_sock(remote_addr.su.sa.sa_family);
+ if (fd == -1) {
+ return nullptr;
+ }
+
+ Address local_addr;
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+ if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) {
+ close(fd);
+ return nullptr;
+ }
+#else // !HAVE_LINUX_RTNETLINK_H
+ if (connect_sock(local_addr, fd, remote_addr) != 0) {
+ close(fd);
+ return nullptr;
+ }
+#endif // !HAVE_LINUX_RTNETLINK_H
+
+ endpoints_.emplace_back();
+ auto &ep = endpoints_.back();
+ ep.addr = local_addr;
+ ep.client = this;
+ ep.fd = fd;
+ ev_io_init(&ep.rev, readcb, fd, EV_READ);
+ ep.rev.data = &ep;
+
+ ev_io_start(loop_, &ep.rev);
+
+ return &ep;
+}
+
+void Client::start_change_local_addr_timer() {
+ ev_timer_start(loop_, &change_local_addr_timer_);
+}
+
+int Client::change_local_addr() {
+ Address local_addr;
+
+ if (!config.quiet) {
+ std::cerr << "Changing local address" << std::endl;
+ }
+
+ auto nfd = udp_sock(remote_addr_.su.sa.sa_family);
+ if (nfd == -1) {
+ return -1;
+ }
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+ in_addr_union iau;
+
+ if (get_local_addr(iau, remote_addr_) != 0) {
+ std::cerr << "Could not get local address" << std::endl;
+ close(nfd);
+ return -1;
+ }
+
+ if (bind_addr(local_addr, nfd, &iau, remote_addr_.su.sa.sa_family) != 0) {
+ close(nfd);
+ return -1;
+ }
+#else // !HAVE_LINUX_RTNETLINK_H
+ if (connect_sock(local_addr, nfd, remote_addr_) != 0) {
+ close(nfd);
+ return -1;
+ }
+#endif // !HAVE_LINUX_RTNETLINK_H
+
+ if (!config.quiet) {
+ std::cerr << "Local address is now "
+ << util::straddr(&local_addr.su.sa, local_addr.len) << std::endl;
+ }
+
+ endpoints_.emplace_back();
+ auto &ep = endpoints_.back();
+ ep.addr = local_addr;
+ ep.client = this;
+ ep.fd = nfd;
+ ev_io_init(&ep.rev, readcb, nfd, EV_READ);
+ ep.rev.data = &ep;
+
+ ngtcp2_addr addr;
+ ngtcp2_addr_init(&addr, &local_addr.su.sa, local_addr.len);
+
+ if (config.nat_rebinding) {
+ ngtcp2_conn_set_local_addr(conn_, &addr);
+ ngtcp2_conn_set_path_user_data(conn_, &ep);
+ } else {
+ auto path = ngtcp2_path{
+ addr,
+ {
+ const_cast<sockaddr *>(&remote_addr_.su.sa),
+ remote_addr_.len,
+ },
+ &ep,
+ };
+ if (auto rv = ngtcp2_conn_initiate_immediate_migration(
+ conn_, &path, util::timestamp(loop_));
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_initiate_immediate_migration: "
+ << ngtcp2_strerror(rv) << std::endl;
+ }
+ }
+
+ ev_io_start(loop_, &ep.rev);
+
+ return 0;
+}
+
+void Client::start_key_update_timer() {
+ ev_timer_start(loop_, &key_update_timer_);
+}
+
+int Client::update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen) {
+ if (!config.quiet) {
+ std::cerr << "Updating traffic key" << std::endl;
+ }
+
+ auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_);
+ auto aead = &crypto_ctx->aead;
+ auto keylen = ngtcp2_crypto_aead_keylen(aead);
+ auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
+
+ ++nkey_update_;
+
+ std::array<uint8_t, 64> rx_key, tx_key;
+
+ if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx,
+ rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(),
+ tx_iv, current_rx_secret, current_tx_secret,
+ secretlen) != 0) {
+ return -1;
+ }
+
+ if (!config.quiet && config.show_secret) {
+ std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl;
+ debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv,
+ ivlen);
+ std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl;
+ debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv,
+ ivlen);
+ }
+
+ return 0;
+}
+
+int Client::initiate_key_update() {
+ if (!config.quiet) {
+ std::cerr << "Initiate key update" << std::endl;
+ }
+
+ if (auto rv = ngtcp2_conn_initiate_key_update(conn_, util::timestamp(loop_));
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_initiate_key_update: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+void Client::start_delay_stream_timer() {
+ ev_timer_start(loop_, &delay_stream_timer_);
+}
+
+int Client::send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr,
+ unsigned int ecn, const uint8_t *data, size_t datalen) {
+ if (debug::packet_lost(config.tx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated outgoing packet loss **" << std::endl;
+ }
+ return NETWORK_ERR_OK;
+ }
+
+ iovec msg_iov;
+ msg_iov.iov_base = const_cast<uint8_t *>(data);
+ msg_iov.iov_len = datalen;
+
+ msghdr msg{};
+#ifdef HAVE_LINUX_RTNETLINK_H
+ msg.msg_name = const_cast<sockaddr *>(remote_addr.addr);
+ msg.msg_namelen = remote_addr.addrlen;
+#endif // HAVE_LINUX_RTNETLINK_H
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ fd_set_ecn(ep.fd, remote_addr.addr->sa_family, ecn);
+
+ ssize_t nwrite = 0;
+
+ do {
+ nwrite = sendmsg(ep.fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return NETWORK_ERR_SEND_BLOCKED;
+ }
+ std::cerr << "sendmsg: " << strerror(errno) << std::endl;
+ if (errno == EMSGSIZE) {
+ return 0;
+ }
+ return NETWORK_ERR_FATAL;
+ }
+
+ assert(static_cast<size_t>(nwrite) == datalen);
+
+ if (!config.quiet) {
+ std::cerr << "Sent packet: local="
+ << util::straddr(&ep.addr.su.sa, ep.addr.len) << " remote="
+ << util::straddr(remote_addr.addr, remote_addr.addrlen)
+ << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite
+ << " bytes" << std::endl;
+ }
+
+ return NETWORK_ERR_OK;
+}
+
+void Client::on_send_blocked(const Endpoint &ep, const ngtcp2_addr &remote_addr,
+ unsigned int ecn, size_t datalen) {
+ assert(!tx_.send_blocked);
+
+ tx_.send_blocked = true;
+
+ memcpy(&tx_.blocked.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+ tx_.blocked.remote_addr.len = remote_addr.addrlen;
+ tx_.blocked.ecn = ecn;
+ tx_.blocked.datalen = datalen;
+ tx_.blocked.endpoint = &ep;
+
+ start_wev_endpoint(ep);
+}
+
+void Client::start_wev_endpoint(const Endpoint &ep) {
+ // We do not close ep.fd, so we can expect that each Endpoint has
+ // unique fd.
+ if (ep.fd != wev_.fd) {
+ if (ev_is_active(&wev_)) {
+ ev_io_stop(loop_, &wev_);
+ }
+
+ ev_io_set(&wev_, ep.fd, EV_WRITE);
+ }
+
+ ev_io_start(loop_, &wev_);
+}
+
+int Client::send_blocked_packet() {
+ assert(tx_.send_blocked);
+
+ ngtcp2_addr remote_addr{
+ .addr = &tx_.blocked.remote_addr.su.sa,
+ .addrlen = tx_.blocked.remote_addr.len,
+ };
+
+ auto rv = send_packet(*tx_.blocked.endpoint, remote_addr, tx_.blocked.ecn,
+ tx_.data.data(), tx_.blocked.datalen);
+ if (rv != 0) {
+ if (rv == NETWORK_ERR_SEND_BLOCKED) {
+ assert(wev_.fd == tx_.blocked.endpoint->fd);
+
+ ev_io_start(loop_, &wev_);
+
+ return 0;
+ }
+
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0);
+ disconnect();
+
+ return rv;
+ }
+
+ tx_.send_blocked = false;
+
+ return 0;
+}
+
+int Client::handle_error() {
+ if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_) ||
+ ngtcp2_conn_is_in_draining_period(conn_)) {
+ return 0;
+ }
+
+ std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf;
+
+ ngtcp2_path_storage ps;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ ngtcp2_pkt_info pi;
+
+ auto nwrite = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, buf.data(), buf.size(), &last_error_,
+ util::timestamp(loop_));
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_conn_write_connection_close: "
+ << ngtcp2_strerror(nwrite) << std::endl;
+ return -1;
+ }
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ return send_packet(*static_cast<Endpoint *>(ps.path.user_data),
+ ps.path.remote, pi.ecn, buf.data(), nwrite);
+}
+
+int Client::on_stream_close(int64_t stream_id, uint64_t app_error_code) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ sendq_.erase(stream.get());
+
+ ++nstreams_closed_;
+
+ if (config.exit_on_first_stream_close ||
+ (config.exit_on_all_streams_close && config.nstreams == nstreams_done_ &&
+ nstreams_closed_ == nstreams_done_)) {
+ if (handshake_confirmed_) {
+ should_exit_ = true;
+ } else {
+ should_exit_on_handshake_confirmed_ = true;
+ }
+ }
+
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
+ ngtcp2_conn_extend_max_streams_uni(conn_, 1);
+ }
+
+ if (!config.quiet) {
+ std::cerr << "HTTP stream " << stream_id << " closed with error code "
+ << app_error_code << std::endl;
+ }
+ streams_.erase(it);
+
+ return 0;
+}
+
+int Client::make_stream_early() { return on_extend_max_streams(); }
+
+int Client::on_extend_max_streams() {
+ int64_t stream_id;
+
+ if ((config.delay_stream && !handshake_confirmed_) ||
+ ev_is_active(&delay_stream_timer_)) {
+ return 0;
+ }
+
+ for (; nstreams_done_ < config.nstreams; ++nstreams_done_) {
+ if (auto rv = ngtcp2_conn_open_bidi_stream(conn_, &stream_id, nullptr);
+ rv != 0) {
+ assert(NGTCP2_ERR_STREAM_ID_BLOCKED == rv);
+ break;
+ }
+
+ auto stream = std::make_unique<Stream>(
+ config.requests[nstreams_done_ % config.requests.size()], stream_id);
+
+ if (submit_http_request(stream.get()) != 0) {
+ break;
+ }
+
+ if (!config.download.empty()) {
+ stream->open_file(stream->req.path);
+ }
+ streams_.emplace(stream_id, std::move(stream));
+ }
+ return 0;
+}
+
+int Client::submit_http_request(Stream *stream) {
+ const auto &req = stream->req;
+
+ stream->rawreqbuf = config.http_method;
+ stream->rawreqbuf += ' ';
+ stream->rawreqbuf += req.path;
+ stream->rawreqbuf += "\r\n";
+
+ nghttp3_buf_init(&stream->reqbuf);
+ stream->reqbuf.begin = reinterpret_cast<uint8_t *>(stream->rawreqbuf.data());
+ stream->reqbuf.pos = stream->reqbuf.begin;
+ stream->reqbuf.end = stream->reqbuf.last =
+ stream->reqbuf.begin + stream->rawreqbuf.size();
+
+ if (!config.quiet) {
+ auto nva = std::array<nghttp3_nv, 2>{
+ util::make_nv_nn(":method", config.http_method),
+ util::make_nv_nn(":path", req.path),
+ };
+ debug::print_http_request_headers(stream->stream_id, nva.data(),
+ nva.size());
+ }
+
+ sendq_.emplace(stream);
+
+ return 0;
+}
+
+int Client::recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, datalen);
+ ngtcp2_conn_extend_max_offset(conn_, datalen);
+
+ if (stream->fd == -1) {
+ return 0;
+ }
+
+ ssize_t nwrite;
+ do {
+ nwrite = write(stream->fd, data, datalen);
+ } while (nwrite == -1 && errno == EINTR);
+
+ return 0;
+}
+
+int Client::acked_stream_data_offset(int64_t stream_id, uint64_t offset,
+ uint64_t datalen) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+ (void)stream;
+ assert(static_cast<uint64_t>(stream->reqbuf.end - stream->reqbuf.begin) >=
+ offset + datalen);
+ return 0;
+}
+
+int Client::select_preferred_address(Address &selected_addr,
+ const ngtcp2_preferred_addr *paddr) {
+ auto path = ngtcp2_conn_get_path(conn_);
+
+ switch (path->local.addr->sa_family) {
+ case AF_INET:
+ if (!paddr->ipv4_present) {
+ return -1;
+ }
+ selected_addr.su.in = paddr->ipv4;
+ selected_addr.len = sizeof(paddr->ipv4);
+ break;
+ case AF_INET6:
+ if (!paddr->ipv6_present) {
+ return -1;
+ }
+ selected_addr.su.in6 = paddr->ipv6;
+ selected_addr.len = sizeof(paddr->ipv6);
+ break;
+ default:
+ return -1;
+ }
+
+ char host[NI_MAXHOST], service[NI_MAXSERV];
+ if (auto rv = getnameinfo(&selected_addr.su.sa, selected_addr.len, host,
+ sizeof(host), service, sizeof(service),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "selected server preferred_address is [" << host
+ << "]:" << service << std::endl;
+ }
+
+ return 0;
+}
+
+const std::vector<uint32_t> &Client::get_offered_versions() const {
+ return offered_versions_;
+}
+
+namespace {
+int run(Client &c, const char *addr, const char *port,
+ TLSClientContext &tls_ctx) {
+ Address remote_addr, local_addr;
+
+ auto fd = create_sock(remote_addr, addr, port);
+ if (fd == -1) {
+ return -1;
+ }
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+ in_addr_union iau;
+
+ if (get_local_addr(iau, remote_addr) != 0) {
+ std::cerr << "Could not get local address" << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) {
+ close(fd);
+ return -1;
+ }
+#else // !HAVE_LINUX_RTNETLINK_H
+ if (connect_sock(local_addr, fd, remote_addr) != 0) {
+ close(fd);
+ return -1;
+ }
+#endif // !HAVE_LINUX_RTNETLINK_H
+
+ if (c.init(fd, local_addr, remote_addr, addr, port, tls_ctx) != 0) {
+ return -1;
+ }
+
+ // TODO Do we need this ?
+ if (auto rv = c.on_write(); rv != 0) {
+ return rv;
+ }
+
+ ev_run(EV_DEFAULT, 0);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+std::string_view get_string(const char *uri, const http_parser_url &u,
+ http_parser_url_fields f) {
+ auto p = &u.field_data[f];
+ return {uri + p->off, p->len};
+}
+} // namespace
+
+namespace {
+int parse_uri(Request &req, const char *uri) {
+ http_parser_url u;
+
+ http_parser_url_init(&u);
+ if (http_parser_parse_url(uri, strlen(uri), /* is_connect = */ 0, &u) != 0) {
+ return -1;
+ }
+
+ if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) {
+ return -1;
+ }
+
+ req.scheme = get_string(uri, u, UF_SCHEMA);
+
+ req.authority = get_string(uri, u, UF_HOST);
+ if (util::numeric_host(req.authority.c_str(), AF_INET6)) {
+ req.authority = '[' + req.authority + ']';
+ }
+ if (u.field_set & (1 << UF_PORT)) {
+ req.authority += ':';
+ req.authority += get_string(uri, u, UF_PORT);
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ req.path = get_string(uri, u, UF_PATH);
+ } else {
+ req.path = "/";
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ req.path += '?';
+ req.path += get_string(uri, u, UF_QUERY);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int parse_requests(char **argv, size_t argvlen) {
+ for (size_t i = 0; i < argvlen; ++i) {
+ auto uri = argv[i];
+ Request req;
+ if (parse_uri(req, uri) != 0) {
+ std::cerr << "Could not parse URI: " << uri << std::endl;
+ return -1;
+ }
+ config.requests.emplace_back(std::move(req));
+ }
+ return 0;
+}
+} // namespace
+
+std::ofstream keylog_file;
+
+namespace {
+void print_usage() {
+ std::cerr << "Usage: h09client [OPTIONS] <HOST> <PORT> [<URI>...]"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void config_set_default(Config &config) {
+ config = Config{};
+ config.tx_loss_prob = 0.;
+ config.rx_loss_prob = 0.;
+ config.fd = -1;
+ config.ciphers = util::crypto_default_ciphers();
+ config.groups = util::crypto_default_groups();
+ config.nstreams = 0;
+ config.data = nullptr;
+ config.datalen = 0;
+ config.version = NGTCP2_PROTO_VER_V1;
+ config.timeout = 30 * NGTCP2_SECONDS;
+ config.http_method = "GET"sv;
+ config.max_data = 15_m;
+ config.max_stream_data_bidi_local = 6_m;
+ config.max_stream_data_bidi_remote = 6_m;
+ config.max_stream_data_uni = 6_m;
+ config.max_window = 24_m;
+ config.max_stream_window = 16_m;
+ config.max_streams_uni = 100;
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT;
+ config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT;
+ config.ack_thresh = 2;
+}
+} // namespace
+
+namespace {
+void print_help() {
+ print_usage();
+
+ config_set_default(config);
+
+ std::cout << R"(
+ <HOST> Remote server host (DNS name or IP address). In case of
+ DNS name, it will be sent in TLS SNI extension.
+ <PORT> Remote server port
+ <URI> Remote URI
+Options:
+ -t, --tx-loss=<P>
+ The probability of losing outgoing packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ -r, --rx-loss=<P>
+ The probability of losing incoming packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ -d, --data=<PATH>
+ Read data from <PATH>, and send them as STREAM data.
+ -n, --nstreams=<N>
+ The number of requests. <URI>s are used in the order of
+ appearance in the command-line. If the number of <URI>
+ list is less than <N>, <URI> list is wrapped. It
+ defaults to 0 which means the number of <URI> specified.
+ -v, --version=<HEX>
+ Specify QUIC version to use in hex string. If the given
+ version is not supported by libngtcp2, client will use
+ QUIC v1 long packet types. Instead of specifying hex
+ string, there are special aliases available: "v1"
+ indicates QUIC v1, and "v2draft" indicates QUIC v2
+ draft.
+ Default: )"
+ << std::hex << "0x" << config.version << std::dec << R"(
+ --preferred-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string in the order of
+ preference. Client chooses one of those versions if
+ client received Version Negotiation packet from server.
+ These versions must be supported by libngtcp2. Instead
+ of specifying hex string, there are special aliases
+ available: "v1" indicates QUIC v1, and "v2draft"
+ indicates QUIC v2 draft.
+ --other-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string that are sent in
+ other_versions field of version_information transport
+ parameter. This list can include a version which is not
+ supported by libngtcp2. Instead of specifying hex
+ string, there are special aliases available: "v1"
+ indicates QUIC v1, and "v2draft" indicates QUIC v2
+ draft.
+ -q, --quiet Suppress debug output.
+ -s, --show-secret
+ Print out secrets unless --quiet is used.
+ --timeout=<DURATION>
+ Specify idle timeout.
+ Default: )"
+ << util::format_duration(config.timeout) << R"(
+ --ciphers=<CIPHERS>
+ Specify the cipher suite list to enable.
+ Default: )"
+ << config.ciphers << R"(
+ --groups=<GROUPS>
+ Specify the supported groups.
+ Default: )"
+ << config.groups << R"(
+ --session-file=<PATH>
+ Read/write TLS session from/to <PATH>. To resume a
+ session, the previous session must be supplied with this
+ option.
+ --tp-file=<PATH>
+ Read/write QUIC transport parameters from/to <PATH>. To
+ send 0-RTT data, the transport parameters received from
+ the previous session must be supplied with this option.
+ --dcid=<DCID>
+ Specify initial DCID. <DCID> is hex string. When
+ decoded as binary, it should be at least 8 bytes and at
+ most 18 bytes long.
+ --change-local-addr=<DURATION>
+ Client changes local address when <DURATION> elapse
+ after handshake completes.
+ --nat-rebinding
+ When used with --change-local-addr, simulate NAT
+ rebinding. In other words, client changes local
+ address, but it does not start path validation.
+ --key-update=<DURATION>
+ Client initiates key update when <DURATION> elapse after
+ handshake completes.
+ -m, --http-method=<METHOD>
+ Specify HTTP method. Default: )"
+ << config.http_method << R"(
+ --delay-stream=<DURATION>
+ Delay sending STREAM data in 1-RTT for <DURATION> after
+ handshake completes.
+ --no-preferred-addr
+ Do not try to use preferred address offered by server.
+ --key=<PATH>
+ The path to client private key PEM file.
+ --cert=<PATH>
+ The path to client certificate PEM file.
+ --download=<PATH>
+ The path to the directory to save a downloaded content.
+ It is undefined if 2 concurrent requests write to the
+ same file. If a request path does not contain a path
+ component usable as a file name, it defaults to
+ "index.html".
+ --no-quic-dump
+ Disables printing QUIC STREAM and CRYPTO frame data out.
+ --no-http-dump
+ Disables printing HTTP response body out.
+ --qlog-file=<PATH>
+ The path to write qlog. This option and --qlog-dir are
+ mutually exclusive.
+ --qlog-dir=<PATH>
+ Path to the directory where qlog file is stored. The
+ file name of each qlog is the Source Connection ID of
+ client. This option and --qlog-file are mutually
+ exclusive.
+ --max-data=<SIZE>
+ The initial connection-level flow control window.
+ Default: )"
+ << util::format_uint_iec(config.max_data) << R"(
+ --max-stream-data-bidi-local=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the local endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_local) << R"(
+ --max-stream-data-bidi-remote=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the remote endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"(
+ --max-stream-data-uni=<SIZE>
+ The initial stream-level flow control window for a
+ unidirectional stream.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_uni) << R"(
+ --max-streams-bidi=<N>
+ The number of the concurrent bidirectional streams.
+ Default: )"
+ << config.max_streams_bidi << R"(
+ --max-streams-uni=<N>
+ The number of the concurrent unidirectional streams.
+ Default: )"
+ << config.max_streams_uni << R"(
+ --exit-on-first-stream-close
+ Exit when a first HTTP stream is closed.
+ --exit-on-all-streams-close
+ Exit when all HTTP streams are closed.
+ --disable-early-data
+ Disable early data.
+ --cc=(cubic|reno|bbr|bbr2)
+ The name of congestion controller algorithm.
+ Default: )"
+ << util::strccalgo(config.cc_algo) << R"(
+ --token-file=<PATH>
+ Read/write token from/to <PATH>. Token is obtained from
+ NEW_TOKEN frame from server.
+ --sni=<DNSNAME>
+ Send <DNSNAME> in TLS SNI, overriding the DNS name
+ specified in <HOST>.
+ --initial-rtt=<DURATION>
+ Set an initial RTT.
+ Default: )"
+ << util::format_duration(config.initial_rtt) << R"(
+ --max-window=<SIZE>
+ Maximum connection-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_window) << R"(
+ --max-stream-window=<SIZE>
+ Maximum stream-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_window) << R"(
+ --max-udp-payload-size=<SIZE>
+ Override maximum UDP payload size that client transmits.
+ --handshake-timeout=<DURATION>
+ Set the QUIC handshake timeout.
+ Default: )"
+ << util::format_duration(config.handshake_timeout) << R"(
+ --no-pmtud Disables Path MTU Discovery.
+ --ack-thresh=<N>
+ The minimum number of the received ACK eliciting packets
+ that triggers immediate acknowledgement.
+ Default: )"
+ << config.ack_thresh << R"(
+ -h, --help Display this help and exit.
+
+---
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms,
+ us, or ns (hours, minutes, seconds, milliseconds, microseconds, and
+ nanoseconds respectively). If a unit is omitted, a second is used
+ as unit.
+
+ The <HEX> argument is an hex string which must start with "0x"
+ (e.g., 0x00000001).)"
+ << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+ config_set_default(config);
+ char *data_path = nullptr;
+ const char *private_key_file = nullptr;
+ const char *cert_file = nullptr;
+
+ for (;;) {
+ static int flag = 0;
+ constexpr static option long_opts[] = {
+ {"help", no_argument, nullptr, 'h'},
+ {"tx-loss", required_argument, nullptr, 't'},
+ {"rx-loss", required_argument, nullptr, 'r'},
+ {"data", required_argument, nullptr, 'd'},
+ {"http-method", required_argument, nullptr, 'm'},
+ {"nstreams", required_argument, nullptr, 'n'},
+ {"version", required_argument, nullptr, 'v'},
+ {"quiet", no_argument, nullptr, 'q'},
+ {"show-secret", no_argument, nullptr, 's'},
+ {"ciphers", required_argument, &flag, 1},
+ {"groups", required_argument, &flag, 2},
+ {"timeout", required_argument, &flag, 3},
+ {"session-file", required_argument, &flag, 4},
+ {"tp-file", required_argument, &flag, 5},
+ {"dcid", required_argument, &flag, 6},
+ {"change-local-addr", required_argument, &flag, 7},
+ {"key-update", required_argument, &flag, 8},
+ {"nat-rebinding", no_argument, &flag, 9},
+ {"delay-stream", required_argument, &flag, 10},
+ {"no-preferred-addr", no_argument, &flag, 11},
+ {"key", required_argument, &flag, 12},
+ {"cert", required_argument, &flag, 13},
+ {"download", required_argument, &flag, 14},
+ {"no-quic-dump", no_argument, &flag, 15},
+ {"no-http-dump", no_argument, &flag, 16},
+ {"qlog-file", required_argument, &flag, 17},
+ {"max-data", required_argument, &flag, 18},
+ {"max-stream-data-bidi-local", required_argument, &flag, 19},
+ {"max-stream-data-bidi-remote", required_argument, &flag, 20},
+ {"max-stream-data-uni", required_argument, &flag, 21},
+ {"max-streams-bidi", required_argument, &flag, 22},
+ {"max-streams-uni", required_argument, &flag, 23},
+ {"exit-on-first-stream-close", no_argument, &flag, 24},
+ {"disable-early-data", no_argument, &flag, 25},
+ {"qlog-dir", required_argument, &flag, 26},
+ {"cc", required_argument, &flag, 27},
+ {"exit-on-all-streams-close", no_argument, &flag, 28},
+ {"token-file", required_argument, &flag, 29},
+ {"sni", required_argument, &flag, 30},
+ {"initial-rtt", required_argument, &flag, 31},
+ {"max-window", required_argument, &flag, 32},
+ {"max-stream-window", required_argument, &flag, 33},
+ {"max-udp-payload-size", required_argument, &flag, 35},
+ {"handshake-timeout", required_argument, &flag, 36},
+ {"other-versions", required_argument, &flag, 37},
+ {"no-pmtud", no_argument, &flag, 38},
+ {"preferred-versions", required_argument, &flag, 39},
+ {"ack-thresh", required_argument, &flag, 40},
+ {nullptr, 0, nullptr, 0},
+ };
+
+ auto optidx = 0;
+ auto c = getopt_long(argc, argv, "d:him:n:qr:st:v:", long_opts, &optidx);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'd':
+ // --data
+ data_path = optarg;
+ break;
+ case 'h':
+ // --help
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'm':
+ // --http-method
+ config.http_method = optarg;
+ break;
+ case 'n':
+ // --streams
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "streams: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > NGTCP2_MAX_VARINT) {
+ std::cerr << "streams: must not exceed " << NGTCP2_MAX_VARINT
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.nstreams = *n;
+ }
+ break;
+ case 'q':
+ // --quiet
+ config.quiet = true;
+ break;
+ case 'r':
+ // --rx-loss
+ config.rx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 's':
+ // --show-secret
+ config.show_secret = true;
+ break;
+ case 't':
+ // --tx-loss
+ config.tx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 'v': {
+ // --version
+ if (optarg == "v1"sv) {
+ config.version = NGTCP2_PROTO_VER_V1;
+ break;
+ }
+ if (optarg == "v2draft"sv) {
+ config.version = NGTCP2_PROTO_VER_V2_DRAFT;
+ break;
+ }
+ auto rv = util::parse_version(optarg);
+ if (!rv) {
+ std::cerr << "version: invalid version " << std::quoted(optarg)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.version = *rv;
+ break;
+ }
+ case '?':
+ print_usage();
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // --ciphers
+ config.ciphers = optarg;
+ break;
+ case 2:
+ // --groups
+ config.groups = optarg;
+ break;
+ case 3:
+ // --timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.timeout = *t;
+ }
+ break;
+ case 4:
+ // --session-file
+ config.session_file = optarg;
+ break;
+ case 5:
+ // --tp-file
+ config.tp_file = optarg;
+ break;
+ case 6: {
+ // --dcid
+ auto dcidlen2 = strlen(optarg);
+ if (dcidlen2 % 2 || dcidlen2 / 2 < 8 || dcidlen2 / 2 > 18) {
+ std::cerr << "dcid: wrong length" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ auto dcid = util::decode_hex(optarg);
+ ngtcp2_cid_init(&config.dcid,
+ reinterpret_cast<const uint8_t *>(dcid.c_str()),
+ dcid.size());
+ break;
+ }
+ case 7:
+ // --change-local-addr
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "change-local-addr: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.change_local_addr = *t;
+ }
+ break;
+ case 8:
+ // --key-update
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "key-update: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.key_update = *t;
+ }
+ break;
+ case 9:
+ // --nat-rebinding
+ config.nat_rebinding = true;
+ break;
+ case 10:
+ // --delay-stream
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "delay-stream: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.delay_stream = *t;
+ }
+ break;
+ case 11:
+ // --no-preferred-addr
+ config.no_preferred_addr = true;
+ break;
+ case 12:
+ // --key
+ private_key_file = optarg;
+ break;
+ case 13:
+ // --cert
+ cert_file = optarg;
+ break;
+ case 14:
+ // --download
+ config.download = optarg;
+ break;
+ case 15:
+ // --no-quic-dump
+ config.no_quic_dump = true;
+ break;
+ case 16:
+ // --no-http-dump
+ config.no_http_dump = true;
+ break;
+ case 17:
+ // --qlog-file
+ config.qlog_file = optarg;
+ break;
+ case 18:
+ // --max-data
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-data: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_data = *n;
+ }
+ break;
+ case 19:
+ // --max-stream-data-bidi-local
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-local: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_local = *n;
+ }
+ break;
+ case 20:
+ // --max-stream-data-bidi-remote
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-remote: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_remote = *n;
+ }
+ break;
+ case 21:
+ // --max-stream-data-uni
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_uni = *n;
+ }
+ break;
+ case 22:
+ // --max-streams-bidi
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-bidi: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_bidi = *n;
+ }
+ break;
+ case 23:
+ // --max-streams-uni
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_uni = *n;
+ }
+ break;
+ case 24:
+ // --exit-on-first-stream-close
+ config.exit_on_first_stream_close = true;
+ break;
+ case 25:
+ // --disable-early-data
+ config.disable_early_data = true;
+ break;
+ case 26:
+ // --qlog-dir
+ config.qlog_dir = optarg;
+ break;
+ case 27:
+ // --cc
+ if (strcmp("cubic", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ break;
+ }
+ if (strcmp("reno", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_RENO;
+ break;
+ }
+ if (strcmp("bbr", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR;
+ break;
+ }
+ if (strcmp("bbr2", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR2;
+ break;
+ }
+ std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl;
+ exit(EXIT_FAILURE);
+ case 28:
+ // --exit-on-all-streams-close
+ config.exit_on_all_streams_close = true;
+ break;
+ case 29:
+ // --token-file
+ config.token_file = optarg;
+ break;
+ case 30:
+ // --sni
+ config.sni = optarg;
+ break;
+ case 31:
+ // --initial-rtt
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "initial-rtt: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.initial_rtt = *t;
+ }
+ break;
+ case 32:
+ // --max-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_window = *n;
+ }
+ break;
+ case 33:
+ // --max-stream-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_window = *n;
+ }
+ break;
+ case 35:
+ // --max-udp-payload-size
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-udp-payload-size: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 64_k) {
+ std::cerr << "max-udp-payload-size: must not exceed 65536"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n == 0) {
+ std::cerr << "max-udp-payload-size: must not be 0" << std::endl;
+ } else {
+ config.max_udp_payload_size = *n;
+ }
+ break;
+ case 36:
+ // --handshake-timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "handshake-timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.handshake_timeout = *t;
+ }
+ break;
+ case 37: {
+ // --other-versions
+ if (strlen(optarg) == 0) {
+ config.other_versions.resize(0);
+ break;
+ }
+ auto l = util::split_str(optarg);
+ config.other_versions.resize(l.size());
+ auto it = std::begin(config.other_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "other-versions: invalid version " << std::quoted(k)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 38:
+ // --no-pmtud
+ config.no_pmtud = true;
+ break;
+ case 39: {
+ // --preferred-versions
+ auto l = util::split_str(optarg);
+ if (l.size() > max_preferred_versionslen) {
+ std::cerr << "preferred-versions: too many versions > "
+ << max_preferred_versionslen << std::endl;
+ }
+ config.preferred_versions.resize(l.size());
+ auto it = std::begin(config.preferred_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "preferred-versions: invalid version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (!ngtcp2_is_supported_version(*rv)) {
+ std::cerr << "preferred-versions: unsupported version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 40:
+ // --ack-thresh
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "ack-thresh: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 100) {
+ std::cerr << "ack-thresh: must not exceed 100" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.ack_thresh = *n;
+ }
+ break;
+ }
+ break;
+ default:
+ break;
+ };
+ }
+
+ if (argc - optind < 2) {
+ std::cerr << "Too few arguments" << std::endl;
+ print_usage();
+ exit(EXIT_FAILURE);
+ }
+
+ if (!config.qlog_file.empty() && !config.qlog_dir.empty()) {
+ std::cerr << "qlog-file and qlog-dir are mutually exclusive" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.exit_on_first_stream_close && config.exit_on_all_streams_close) {
+ std::cerr << "exit-on-first-stream-close and exit-on-all-streams-close are "
+ "mutually exclusive"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (data_path) {
+ auto fd = open(data_path, O_RDONLY);
+ if (fd == -1) {
+ std::cerr << "data: Could not open file " << data_path << ": "
+ << strerror(errno) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ struct stat st;
+ if (fstat(fd, &st) != 0) {
+ std::cerr << "data: Could not stat file " << data_path << ": "
+ << strerror(errno) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.fd = fd;
+ config.datalen = st.st_size;
+ auto addr = mmap(nullptr, config.datalen, PROT_READ, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED) {
+ std::cerr << "data: Could not mmap file " << data_path << ": "
+ << strerror(errno) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.data = static_cast<uint8_t *>(addr);
+ }
+
+ auto addr = argv[optind++];
+ auto port = argv[optind++];
+
+ if (parse_requests(&argv[optind], argc - optind) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (!ngtcp2_is_reserved_version(config.version)) {
+ if (!config.preferred_versions.empty() &&
+ std::find(std::begin(config.preferred_versions),
+ std::end(config.preferred_versions),
+ config.version) == std::end(config.preferred_versions)) {
+ std::cerr << "preferred-version: must include version "
+ << "0x" << config.version << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (!config.other_versions.empty() &&
+ std::find(std::begin(config.other_versions),
+ std::end(config.other_versions),
+ config.version) == std::end(config.other_versions)) {
+ std::cerr << "other-versions: must include version "
+ << "0x" << config.version << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (config.nstreams == 0) {
+ config.nstreams = config.requests.size();
+ }
+
+ TLSClientContext tls_ctx;
+ if (tls_ctx.init(private_key_file, cert_file) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT);
+
+ auto keylog_filename = getenv("SSLKEYLOGFILE");
+ if (keylog_filename) {
+ keylog_file.open(keylog_filename, std::ios_base::app);
+ if (keylog_file) {
+ tls_ctx.enable_keylog();
+ }
+ }
+
+ if (util::generate_secret(config.static_secret.data(),
+ config.static_secret.size()) != 0) {
+ std::cerr << "Unable to generate static secret" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ auto client_chosen_version = config.version;
+
+ for (;;) {
+ Client c(EV_DEFAULT, client_chosen_version, config.version);
+
+ if (run(c, addr, port, tls_ctx) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.preferred_versions.empty()) {
+ break;
+ }
+
+ auto &offered_versions = c.get_offered_versions();
+ if (offered_versions.empty()) {
+ break;
+ }
+
+ client_chosen_version = ngtcp2_select_version(
+ config.preferred_versions.data(), config.preferred_versions.size(),
+ offered_versions.data(), offered_versions.size());
+
+ if (client_chosen_version == 0) {
+ std::cerr << "Unable to select a version" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Client selected version " << std::hex << "0x"
+ << client_chosen_version << std::dec << std::endl;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/h09client.h b/examples/h09client.h
new file mode 100644
index 0000000..702eb17
--- /dev/null
+++ b/examples/h09client.h
@@ -0,0 +1,196 @@
+/*
+ * 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 H09CLIENT_H
+#define H09CLIENT_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <vector>
+#include <deque>
+#include <map>
+#include <string_view>
+#include <memory>
+#include <set>
+
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include <nghttp3/nghttp3.h>
+
+#include <ev.h>
+
+#include "client_base.h"
+#include "tls_client_context.h"
+#include "tls_client_session.h"
+#include "network.h"
+#include "shared.h"
+#include "template.h"
+
+using namespace ngtcp2;
+
+struct Stream {
+ Stream(const Request &req, int64_t stream_id);
+ ~Stream();
+
+ int open_file(const std::string_view &path);
+
+ Request req;
+ int64_t stream_id;
+ int fd;
+ std::string rawreqbuf;
+ nghttp3_buf reqbuf;
+};
+
+struct StreamIDLess {
+ constexpr bool operator()(const Stream *lhs, const Stream *rhs) const {
+ return lhs->stream_id < rhs->stream_id;
+ }
+};
+
+class Client;
+
+struct Endpoint {
+ Address addr;
+ ev_io rev;
+ Client *client;
+ int fd;
+};
+
+class Client : public ClientBase {
+public:
+ Client(struct ev_loop *loop, uint32_t client_chosen_version,
+ uint32_t original_version);
+ ~Client();
+
+ int init(int fd, const Address &local_addr, const Address &remote_addr,
+ const char *addr, const char *port, TLSClientContext &tls_ctx);
+ void disconnect();
+
+ int on_read(const Endpoint &ep);
+ int on_write();
+ int write_streams();
+ int feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen);
+ int handle_expiry();
+ void update_timer();
+ int handshake_completed();
+ int handshake_confirmed();
+ void recv_version_negotiation(const uint32_t *sv, size_t nsv);
+
+ int send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr,
+ unsigned int ecn, const uint8_t *data, size_t datalen);
+ int on_stream_close(int64_t stream_id, uint64_t app_error_code);
+ int on_extend_max_streams();
+ int handle_error();
+ int make_stream_early();
+ int change_local_addr();
+ void start_change_local_addr_timer();
+ int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen);
+ int initiate_key_update();
+ void start_key_update_timer();
+ void start_delay_stream_timer();
+
+ int select_preferred_address(Address &selected_addr,
+ const ngtcp2_preferred_addr *paddr);
+
+ std::optional<Endpoint *> endpoint_for(const Address &remote_addr);
+
+ void set_remote_addr(const ngtcp2_addr &remote_addr);
+
+ int submit_http_request(Stream *stream);
+ int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data,
+ size_t datalen);
+ int acked_stream_data_offset(int64_t stream_id, uint64_t offset,
+ uint64_t datalen);
+ int extend_max_stream_data(int64_t stream_id, uint64_t max_data);
+
+ void write_qlog(const void *data, size_t datalen);
+
+ void on_send_blocked(const Endpoint &ep, const ngtcp2_addr &remote_addr,
+ unsigned int ecn, size_t datalen);
+ void start_wev_endpoint(const Endpoint &ep);
+ int send_blocked_packet();
+
+ const std::vector<uint32_t> &get_offered_versions() const;
+
+ void early_data_rejected();
+
+private:
+ std::vector<Endpoint> endpoints_;
+ Address remote_addr_;
+ ev_io wev_;
+ ev_timer timer_;
+ ev_timer change_local_addr_timer_;
+ ev_timer key_update_timer_;
+ ev_timer delay_stream_timer_;
+ ev_signal sigintev_;
+ struct ev_loop *loop_;
+ std::map<int64_t, std::unique_ptr<Stream>> streams_;
+ std::set<Stream *, StreamIDLess> sendq_;
+ std::vector<uint32_t> offered_versions_;
+ // addr_ is the server host address.
+ const char *addr_;
+ // port_ is the server port.
+ const char *port_;
+ // nstreams_done_ is the number of streams opened.
+ size_t nstreams_done_;
+ // nstreams_closed_ is the number of streams get closed.
+ size_t nstreams_closed_;
+ // nkey_update_ is the number of key update occurred.
+ size_t nkey_update_;
+ uint32_t client_chosen_version_;
+ uint32_t original_version_;
+ // early_data_ is true if client attempts to do 0RTT data transfer.
+ bool early_data_;
+ // should_exit_ is true if client should exit rather than waiting
+ // for timeout.
+ bool should_exit_;
+ // should_exit_on_handshake_confirmed_ is true if client should exit
+ // when handshake confirmed.
+ bool should_exit_on_handshake_confirmed_;
+ // handshake_confirmed_ gets true after handshake has been
+ // confirmed.
+ bool handshake_confirmed_;
+
+ struct {
+ bool send_blocked;
+ // blocked field is effective only when send_blocked is true.
+ struct {
+ const Endpoint *endpoint;
+ Address remote_addr;
+ unsigned int ecn;
+ size_t datalen;
+ } blocked;
+ std::array<uint8_t, 64_k> data;
+ } tx_;
+};
+
+#endif // CLIENT_H
diff --git a/examples/h09server.cc b/examples/h09server.cc
new file mode 100644
index 0000000..4af67da
--- /dev/null
+++ b/examples/h09server.cc
@@ -0,0 +1,3034 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <chrono>
+#include <cstdlib>
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <algorithm>
+#include <memory>
+#include <fstream>
+#include <iomanip>
+
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <netinet/udp.h>
+#include <net/if.h>
+
+#include <http-parser/http_parser.h>
+
+#include "h09server.h"
+#include "network.h"
+#include "debug.h"
+#include "util.h"
+#include "shared.h"
+#include "http.h"
+#include "template.h"
+
+using namespace ngtcp2;
+using namespace std::literals;
+
+namespace {
+constexpr size_t NGTCP2_SV_SCIDLEN = 18;
+} // namespace
+
+namespace {
+constexpr size_t max_preferred_versionslen = 4;
+} // namespace
+
+namespace {
+auto randgen = util::make_mt19937();
+} // namespace
+
+Config config{};
+
+Stream::Stream(int64_t stream_id, Handler *handler)
+ : stream_id(stream_id), handler(handler), eos(false) {
+ nghttp3_buf_init(&respbuf);
+ htp.data = this;
+ http_parser_init(&htp, HTTP_REQUEST);
+}
+
+namespace {
+constexpr auto NGTCP2_SERVER = "ngtcp2 server"sv;
+} // namespace
+
+namespace {
+std::string make_status_body(unsigned int status_code) {
+ auto status_string = util::format_uint(status_code);
+ auto reason_phrase = http::get_reason_phrase(status_code);
+
+ std::string body;
+ body = "<html><head><title>";
+ body += status_string;
+ body += ' ';
+ body += reason_phrase;
+ body += "</title></head><body><h1>";
+ body += status_string;
+ body += ' ';
+ body += reason_phrase;
+ body += "</h1><hr><address>";
+ body += NGTCP2_SERVER;
+ body += " at port ";
+ body += util::format_uint(config.port);
+ body += "</address>";
+ body += "</body></html>";
+ return body;
+}
+} // namespace
+
+struct Request {
+ std::string path;
+};
+
+namespace {
+Request request_path(const std::string_view &uri) {
+ http_parser_url u;
+ Request req;
+
+ http_parser_url_init(&u);
+
+ if (auto rv = http_parser_parse_url(uri.data(), uri.size(),
+ /* is_connect = */ 0, &u);
+ rv != 0) {
+ return req;
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ req.path = std::string(uri.data() + u.field_data[UF_PATH].off,
+ u.field_data[UF_PATH].len);
+ if (req.path.find('%') != std::string::npos) {
+ req.path = util::percent_decode(std::begin(req.path), std::end(req.path));
+ }
+ if (!req.path.empty() && req.path.back() == '/') {
+ req.path += "index.html";
+ }
+ } else {
+ req.path = "/index.html";
+ }
+
+ req.path = util::normalize_path(req.path);
+ if (req.path == "/") {
+ req.path = "/index.html";
+ }
+
+ return req;
+}
+} // namespace
+
+enum FileEntryFlag {
+ FILE_ENTRY_TYPE_DIR = 0x1,
+};
+
+struct FileEntry {
+ uint64_t len;
+ void *map;
+ int fd;
+ uint8_t flags;
+};
+
+namespace {
+std::unordered_map<std::string, FileEntry> file_cache;
+} // namespace
+
+std::pair<FileEntry, int> Stream::open_file(const std::string &path) {
+ auto it = file_cache.find(path);
+ if (it != std::end(file_cache)) {
+ return {(*it).second, 0};
+ }
+
+ auto fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ return {{}, -1};
+ }
+
+ struct stat st {};
+ if (fstat(fd, &st) != 0) {
+ close(fd);
+ return {{}, -1};
+ }
+
+ FileEntry fe{};
+ if (st.st_mode & S_IFDIR) {
+ fe.flags |= FILE_ENTRY_TYPE_DIR;
+ fe.fd = -1;
+ close(fd);
+ } else {
+ fe.fd = fd;
+ fe.len = st.st_size;
+ fe.map = mmap(nullptr, fe.len, PROT_READ, MAP_SHARED, fd, 0);
+ if (fe.map == MAP_FAILED) {
+ std::cerr << "mmap: " << strerror(errno) << std::endl;
+ close(fd);
+ return {{}, -1};
+ }
+ }
+
+ file_cache.emplace(path, fe);
+
+ return {std::move(fe), 0};
+}
+
+void Stream::map_file(const FileEntry &fe) {
+ respbuf.begin = respbuf.pos = static_cast<uint8_t *>(fe.map);
+ respbuf.end = respbuf.last = respbuf.begin + fe.len;
+}
+
+int Stream::send_status_response(unsigned int status_code) {
+ status_resp_body = make_status_body(status_code);
+
+ respbuf.begin = respbuf.pos =
+ reinterpret_cast<uint8_t *>(status_resp_body.data());
+ respbuf.end = respbuf.last = respbuf.begin + status_resp_body.size();
+
+ handler->add_sendq(this);
+ handler->shutdown_read(stream_id, 0);
+
+ return 0;
+}
+
+int Stream::start_response() {
+ if (uri.empty()) {
+ return send_status_response(400);
+ }
+
+ auto req = request_path(uri);
+ if (req.path.empty()) {
+ return send_status_response(400);
+ }
+
+ auto path = config.htdocs + req.path;
+ auto [fe, rv] = open_file(path);
+ if (rv != 0) {
+ send_status_response(404);
+ return 0;
+ }
+
+ if (fe.flags & FILE_ENTRY_TYPE_DIR) {
+ send_status_response(308);
+ return 0;
+ }
+
+ map_file(fe);
+
+ if (!config.quiet) {
+ std::array<nghttp3_nv, 1> nva{
+ util::make_nv_nn(":status", "200"),
+ };
+
+ debug::print_http_response_headers(stream_id, nva.data(), nva.size());
+ }
+
+ handler->add_sendq(this);
+
+ return 0;
+}
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto h = static_cast<Handler *>(w->data);
+ auto s = h->server();
+
+ switch (h->on_write()) {
+ case 0:
+ case NETWORK_ERR_CLOSE_WAIT:
+ return;
+ default:
+ s->remove(h);
+ }
+}
+} // namespace
+
+namespace {
+void close_waitcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto h = static_cast<Handler *>(w->data);
+ auto s = h->server();
+ auto conn = h->conn();
+
+ if (ngtcp2_conn_is_in_closing_period(conn)) {
+ if (!config.quiet) {
+ std::cerr << "Closing Period is over" << std::endl;
+ }
+
+ s->remove(h);
+ return;
+ }
+ if (ngtcp2_conn_is_in_draining_period(conn)) {
+ if (!config.quiet) {
+ std::cerr << "Draining Period is over" << std::endl;
+ }
+
+ s->remove(h);
+ return;
+ }
+
+ assert(0);
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ int rv;
+
+ auto h = static_cast<Handler *>(w->data);
+ auto s = h->server();
+
+ if (!config.quiet) {
+ std::cerr << "Timer expired" << std::endl;
+ }
+
+ rv = h->handle_expiry();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ rv = h->on_write();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return;
+
+fail:
+ switch (rv) {
+ case NETWORK_ERR_CLOSE_WAIT:
+ ev_timer_stop(loop, w);
+ return;
+ default:
+ s->remove(h);
+ return;
+ }
+}
+} // namespace
+
+Handler::Handler(struct ev_loop *loop, Server *server)
+ : loop_(loop),
+ server_(server),
+ qlog_(nullptr),
+ scid_{},
+ nkey_update_(0),
+ no_gso_{
+#ifdef UDP_SEGMENT
+ false
+#else // !UDP_SEGMENT
+ true
+#endif // !UDP_SEGMENT
+ },
+ tx_{
+ .data = std::unique_ptr<uint8_t[]>(new uint8_t[64_k]),
+ } {
+ ev_io_init(&wev_, writecb, 0, EV_WRITE);
+ wev_.data = this;
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+}
+
+Handler::~Handler() {
+ if (!config.quiet) {
+ std::cerr << scid_ << " Closing QUIC connection " << std::endl;
+ }
+
+ ev_timer_stop(loop_, &timer_);
+ ev_io_stop(loop_, &wev_);
+
+ if (qlog_) {
+ fclose(qlog_);
+ }
+}
+
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+
+ if (!config.quiet) {
+ debug::handshake_completed(conn, user_data);
+ }
+
+ if (h->handshake_completed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Handler::handshake_completed() {
+ if (!config.quiet) {
+ std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name()
+ << std::endl;
+ std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn()
+ << std::endl;
+ }
+
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN> token;
+
+ auto path = ngtcp2_conn_get_path(conn_);
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto tokenlen = ngtcp2_crypto_generate_regular_token(
+ token.data(), config.static_secret.data(), config.static_secret.size(),
+ path->remote.addr, path->remote.addrlen, t);
+ if (tokenlen < 0) {
+ if (!config.quiet) {
+ std::cerr << "Unable to generate token" << std::endl;
+ }
+ return 0;
+ }
+
+ if (auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen);
+ rv != 0) {
+ if (!config.quiet) {
+ std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv)
+ << std::endl;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+ const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) {
+ if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (!config.quiet && config.show_secret) {
+ debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data) {
+ if (!config.quiet && !config.no_quic_dump) {
+ debug::print_crypto_data(crypto_level, data, datalen);
+ }
+
+ return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data,
+ datalen, user_data);
+}
+} // namespace
+
+namespace {
+int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+
+ if (h->recv_stream_data(flags, stream_id, data, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->acked_stream_data_offset(stream_id, offset, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t offset,
+ uint64_t datalen) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+ (void)stream;
+
+ assert(static_cast<uint64_t>(stream->respbuf.end - stream->respbuf.begin) >=
+ offset + datalen);
+
+ return 0;
+}
+
+namespace {
+int stream_open(ngtcp2_conn *conn, int64_t stream_id, void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ h->on_stream_open(stream_id);
+ return 0;
+}
+} // namespace
+
+void Handler::on_stream_open(int64_t stream_id) {
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ return;
+ }
+ auto it = streams_.find(stream_id);
+ (void)it;
+ assert(it == std::end(streams_));
+ streams_.emplace(stream_id, std::make_unique<Stream>(stream_id, this));
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->on_stream_close(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
+ std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); });
+}
+} // namespace
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ if (util::generate_secure_random(cid->data, cidlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+ if (ngtcp2_crypto_generate_stateless_reset_token(
+ token, config.static_secret.data(), config.static_secret.size(),
+ cid) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ auto h = static_cast<Handler *>(user_data);
+ h->server()->associate_cid(cid, h);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
+ void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ h->server()->dissociate_cid(cid);
+ return 0;
+}
+} // namespace
+
+namespace {
+int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen,
+ void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx,
+ tx_iv, current_rx_secret, current_tx_secret,
+ secretlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path,
+ ngtcp2_path_validation_result res, void *user_data) {
+ if (!config.quiet) {
+ debug::path_validation(path, res);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->extend_max_stream_data(stream_id, max_data) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::extend_max_stream_data(int64_t stream_id, uint64_t max_data) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ if (nghttp3_buf_len(&stream->respbuf)) {
+ sendq_.emplace(stream.get());
+ }
+
+ return 0;
+}
+
+namespace {
+void write_qlog(void *user_data, uint32_t flags, const void *data,
+ size_t datalen) {
+ auto h = static_cast<Handler *>(user_data);
+ h->write_qlog(data, datalen);
+}
+} // namespace
+
+void Handler::write_qlog(const void *data, size_t datalen) {
+ assert(qlog_);
+ fwrite(data, 1, datalen, qlog_);
+}
+
+int Handler::init(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen, const ngtcp2_cid *dcid,
+ const ngtcp2_cid *scid, const ngtcp2_cid *ocid,
+ const uint8_t *token, size_t tokenlen, uint32_t version,
+ TLSServerContext &tls_ctx) {
+ auto callbacks = ngtcp2_callbacks{
+ nullptr, // client_initial
+ ngtcp2_crypto_recv_client_initial_cb,
+ ::recv_crypto_data,
+ ::handshake_completed,
+ nullptr, // recv_version_negotiation
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ do_hp_mask,
+ ::recv_stream_data,
+ ::acked_stream_data_offset,
+ stream_open,
+ stream_close,
+ nullptr, // recv_stateless_reset
+ nullptr, // recv_retry
+ nullptr, // extend_max_streams_bidi
+ nullptr, // extend_max_streams_uni
+ rand,
+ get_new_connection_id,
+ remove_connection_id,
+ ::update_key,
+ path_validation,
+ nullptr, // select_preferred_addr
+ nullptr, // stream_reset
+ nullptr, // extend_max_remote_streams_bidi
+ nullptr, // extend_max_remote_streams_uni
+ ::extend_max_stream_data,
+ nullptr, // dcid_status
+ nullptr, // handshake_confirmed
+ nullptr, // recv_new_token
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ nullptr, // stream_stop_sending
+ ngtcp2_crypto_version_negotiation_cb,
+ nullptr, // recv_rx_key
+ nullptr, // recv_tx_key
+ };
+
+ scid_.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(scid_.data, scid_.datalen) != 0) {
+ std::cerr << "Could not generate connection ID" << std::endl;
+ return -1;
+ }
+
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ settings.log_printf = config.quiet ? nullptr : debug::log_printf;
+ settings.initial_ts = util::timestamp(loop_);
+ settings.token = ngtcp2_vec{const_cast<uint8_t *>(token), tokenlen};
+ settings.cc_algo = config.cc_algo;
+ settings.initial_rtt = config.initial_rtt;
+ settings.max_window = config.max_window;
+ settings.max_stream_window = config.max_stream_window;
+ settings.handshake_timeout = config.handshake_timeout;
+ settings.no_pmtud = config.no_pmtud;
+ settings.ack_thresh = config.ack_thresh;
+ if (config.max_udp_payload_size) {
+ settings.max_tx_udp_payload_size = config.max_udp_payload_size;
+ settings.no_tx_udp_payload_size_shaping = 1;
+ }
+ if (!config.qlog_dir.empty()) {
+ auto path = std::string{config.qlog_dir};
+ path += '/';
+ path += util::format_hex(scid_.data, scid_.datalen);
+ path += ".sqlog";
+ qlog_ = fopen(path.c_str(), "w");
+ if (qlog_ == nullptr) {
+ std::cerr << "Could not open qlog file " << std::quoted(path) << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+ settings.qlog.write = ::write_qlog;
+ settings.qlog.odcid = *scid;
+ }
+ if (!config.preferred_versions.empty()) {
+ settings.preferred_versions = config.preferred_versions.data();
+ settings.preferred_versionslen = config.preferred_versions.size();
+ }
+ if (!config.other_versions.empty()) {
+ settings.other_versions = config.other_versions.data();
+ settings.other_versionslen = config.other_versions.size();
+ }
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+ params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
+ params.initial_max_stream_data_bidi_remote =
+ config.max_stream_data_bidi_remote;
+ params.initial_max_stream_data_uni = config.max_stream_data_uni;
+ params.initial_max_data = config.max_data;
+ params.initial_max_streams_bidi = config.max_streams_bidi;
+ params.initial_max_streams_uni = 0;
+ params.max_idle_timeout = config.timeout;
+ params.stateless_reset_token_present = 1;
+ params.active_connection_id_limit = 7;
+
+ if (ocid) {
+ params.original_dcid = *ocid;
+ params.retry_scid = *scid;
+ params.retry_scid_present = 1;
+ } else {
+ params.original_dcid = *scid;
+ }
+
+ if (util::generate_secure_random(params.stateless_reset_token,
+ sizeof(params.stateless_reset_token)) != 0) {
+ std::cerr << "Could not generate stateless reset token" << std::endl;
+ return -1;
+ }
+
+ if (config.preferred_ipv4_addr.len || config.preferred_ipv6_addr.len) {
+ params.preferred_address_present = 1;
+
+ if (config.preferred_ipv4_addr.len) {
+ params.preferred_address.ipv4 = config.preferred_ipv4_addr.su.in;
+ params.preferred_address.ipv4_present = 1;
+ }
+
+ if (config.preferred_ipv6_addr.len) {
+ params.preferred_address.ipv6 = config.preferred_ipv6_addr.su.in6;
+ params.preferred_address.ipv6_present = 1;
+ }
+
+ auto &token = params.preferred_address.stateless_reset_token;
+ if (util::generate_secure_random(token, sizeof(token)) != 0) {
+ std::cerr << "Could not generate preferred address stateless reset token"
+ << std::endl;
+ return -1;
+ }
+
+ params.preferred_address.cid.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(params.preferred_address.cid.data,
+ params.preferred_address.cid.datalen) !=
+ 0) {
+ std::cerr << "Could not generate preferred address connection ID"
+ << std::endl;
+ return -1;
+ }
+ }
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ },
+ {
+ const_cast<sockaddr *>(sa),
+ salen,
+ },
+ const_cast<Endpoint *>(&ep),
+ };
+ if (auto rv =
+ ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version,
+ &callbacks, &settings, &params, nullptr, this);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (tls_session_.init(tls_ctx, this) != 0) {
+ return -1;
+ }
+
+ tls_session_.enable_keylog();
+
+ ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle());
+
+ ev_io_set(&wev_, ep.fd, EV_WRITE);
+
+ return 0;
+}
+
+int Handler::feed_data(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data,
+ size_t datalen) {
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ },
+ {
+ const_cast<sockaddr *>(sa),
+ salen,
+ },
+ const_cast<Endpoint *>(&ep),
+ };
+
+ if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen,
+ util::timestamp(loop_));
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl;
+ switch (rv) {
+ case NGTCP2_ERR_DRAINING:
+ start_draining_period();
+ return NETWORK_ERR_CLOSE_WAIT;
+ case NGTCP2_ERR_RETRY:
+ return NETWORK_ERR_RETRY;
+ case NGTCP2_ERR_DROP_CONN:
+ return NETWORK_ERR_DROP_CONN;
+ case NGTCP2_ERR_CRYPTO:
+ if (!last_error_.error_code) {
+ ngtcp2_connection_close_error_set_transport_error_tls_alert(
+ &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0);
+ }
+ break;
+ default:
+ if (!last_error_.error_code) {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, rv, nullptr, 0);
+ }
+ }
+ return handle_error();
+ }
+
+ return 0;
+}
+
+int Handler::on_read(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen) {
+ if (auto rv = feed_data(ep, local_addr, sa, salen, pi, data, datalen);
+ rv != 0) {
+ return rv;
+ }
+
+ update_timer();
+
+ return 0;
+}
+
+int Handler::handle_expiry() {
+ auto now = util::timestamp(loop_);
+ if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) {
+ std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv,
+ nullptr, 0);
+ return handle_error();
+ }
+
+ return 0;
+}
+
+int Handler::on_write() {
+ if (ngtcp2_conn_is_in_closing_period(conn_) ||
+ ngtcp2_conn_is_in_draining_period(conn_)) {
+ return 0;
+ }
+
+ if (tx_.send_blocked) {
+ if (auto rv = send_blocked_packet(); rv != 0) {
+ return rv;
+ }
+
+ if (tx_.send_blocked) {
+ return 0;
+ }
+ }
+
+ if (auto rv = write_streams(); rv != 0) {
+ return rv;
+ }
+
+ update_timer();
+
+ return 0;
+}
+
+int Handler::write_streams() {
+ ngtcp2_vec vec;
+ ngtcp2_path_storage ps, prev_ps;
+ uint32_t prev_ecn = 0;
+ size_t pktcnt = 0;
+ auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_);
+ auto path_max_udp_payload_size =
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(conn_);
+ auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size;
+ uint8_t *bufpos = tx_.data.get();
+ ngtcp2_pkt_info pi;
+ size_t gso_size = 0;
+ auto ts = util::timestamp(loop_);
+
+ ngtcp2_path_storage_zero(&ps);
+ ngtcp2_path_storage_zero(&prev_ps);
+
+ max_pktcnt = std::min(max_pktcnt, static_cast<size_t>(config.max_gso_dgrams));
+
+ for (;;) {
+ int64_t stream_id = -1;
+ size_t vcnt = 0;
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ Stream *stream = nullptr;
+
+ if (!sendq_.empty() && ngtcp2_conn_get_max_data_left(conn_)) {
+ stream = *std::begin(sendq_);
+
+ stream_id = stream->stream_id;
+ vec.base = stream->respbuf.pos;
+ vec.len = nghttp3_buf_len(&stream->respbuf);
+ vcnt = 1;
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ ngtcp2_ssize ndatalen;
+
+ auto nwrite = ngtcp2_conn_writev_stream(conn_, &ps.path, &pi, bufpos,
+ max_udp_payload_size, &ndatalen,
+ flags, stream_id, &vec, vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ sendq_.erase(std::begin(sendq_));
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ stream->respbuf.pos += ndatalen;
+ if (nghttp3_buf_len(&stream->respbuf) == 0) {
+ sendq_.erase(std::begin(sendq_));
+ }
+ continue;
+ }
+
+ assert(ndatalen == -1);
+
+ std::cerr << "ngtcp2_conn_writev_stream: " << ngtcp2_strerror(nwrite)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, nwrite, nullptr, 0);
+ return handle_error();
+ } else if (ndatalen >= 0) {
+ stream->respbuf.pos += ndatalen;
+ if (nghttp3_buf_len(&stream->respbuf) == 0) {
+ sendq_.erase(std::begin(sendq_));
+ }
+ }
+
+ if (nwrite == 0) {
+ if (bufpos - tx_.data.get()) {
+ auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ if (auto [nsent, rv] = server_->send_packet(
+ ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data, datalen, gso_size);
+ rv != NETWORK_ERR_OK) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data + nsent, datalen - nsent, gso_size);
+
+ start_wev_endpoint(ep);
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+ }
+
+ ev_io_stop(loop_, &wev_);
+
+ // We are congestion limited.
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+
+ bufpos += nwrite;
+
+ if (pktcnt == 0) {
+ ngtcp2_path_copy(&prev_ps.path, &ps.path);
+ prev_ecn = pi.ecn;
+ gso_size = nwrite;
+ } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) || prev_ecn != pi.ecn ||
+ static_cast<size_t>(nwrite) > gso_size ||
+ (gso_size > path_max_udp_payload_size &&
+ static_cast<size_t>(nwrite) != gso_size)) {
+ auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data - nwrite;
+
+ if (auto [nsent, rv] = server_->send_packet(
+ ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data, datalen, gso_size);
+ rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data + nsent, datalen - nsent, gso_size);
+
+ on_send_blocked(*static_cast<Endpoint *>(ps.path.user_data),
+ ps.path.local, ps.path.remote, pi.ecn, bufpos - nwrite,
+ nwrite, 0);
+
+ start_wev_endpoint(ep);
+ } else {
+ auto &ep = *static_cast<Endpoint *>(ps.path.user_data);
+ auto data = bufpos - nwrite;
+
+ if (auto [nsent, rv] =
+ server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote,
+ pi.ecn, data, nwrite, nwrite);
+ rv != 0) {
+ assert(nsent == 0);
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data,
+ nwrite, 0);
+ }
+
+ start_wev_endpoint(ep);
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) {
+ auto &ep = *static_cast<Endpoint *>(ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ if (auto [nsent, rv] =
+ server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote,
+ pi.ecn, data, datalen, gso_size);
+ rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data + nsent,
+ datalen - nsent, gso_size);
+ }
+
+ start_wev_endpoint(ep);
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+ }
+}
+
+void Handler::on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen,
+ size_t gso_size) {
+ assert(tx_.num_blocked || !tx_.send_blocked);
+ assert(tx_.num_blocked < 2);
+
+ tx_.send_blocked = true;
+
+ auto &p = tx_.blocked[tx_.num_blocked++];
+
+ memcpy(&p.local_addr.su, local_addr.addr, local_addr.addrlen);
+ memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+
+ p.local_addr.len = local_addr.addrlen;
+ p.remote_addr.len = remote_addr.addrlen;
+ p.endpoint = &ep;
+ p.ecn = ecn;
+ p.data = data;
+ p.datalen = datalen;
+ p.gso_size = gso_size;
+}
+
+void Handler::start_wev_endpoint(const Endpoint &ep) {
+ // We do not close ep.fd, so we can expect that each Endpoint has
+ // unique fd.
+ if (ep.fd != wev_.fd) {
+ if (ev_is_active(&wev_)) {
+ ev_io_stop(loop_, &wev_);
+ }
+
+ ev_io_set(&wev_, ep.fd, EV_WRITE);
+ }
+
+ ev_io_start(loop_, &wev_);
+}
+
+int Handler::send_blocked_packet() {
+ assert(tx_.send_blocked);
+
+ for (; tx_.num_blocked_sent < tx_.num_blocked; ++tx_.num_blocked_sent) {
+ auto &p = tx_.blocked[tx_.num_blocked_sent];
+
+ ngtcp2_addr local_addr{
+ .addr = &p.local_addr.su.sa,
+ .addrlen = p.local_addr.len,
+ };
+ ngtcp2_addr remote_addr{
+ .addr = &p.remote_addr.su.sa,
+ .addrlen = p.remote_addr.len,
+ };
+
+ auto [nsent, rv] =
+ server_->send_packet(*p.endpoint, no_gso_, local_addr, remote_addr,
+ p.ecn, p.data, p.datalen, p.gso_size);
+ if (rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ p.data += nsent;
+ p.datalen -= nsent;
+
+ start_wev_endpoint(*p.endpoint);
+
+ return 0;
+ }
+ }
+
+ tx_.send_blocked = false;
+ tx_.num_blocked = 0;
+ tx_.num_blocked_sent = 0;
+
+ return 0;
+}
+
+void Handler::signal_write() { ev_io_start(loop_, &wev_); }
+
+void Handler::start_draining_period() {
+ ev_io_stop(loop_, &wev_);
+
+ ev_set_cb(&timer_, close_waitcb);
+ timer_.repeat =
+ static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3;
+ ev_timer_again(loop_, &timer_);
+
+ if (!config.quiet) {
+ std::cerr << "Draining period has started (" << timer_.repeat << " seconds)"
+ << std::endl;
+ }
+}
+
+int Handler::start_closing_period() {
+ if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_) ||
+ ngtcp2_conn_is_in_draining_period(conn_)) {
+ return 0;
+ }
+
+ ev_io_stop(loop_, &wev_);
+
+ ev_set_cb(&timer_, close_waitcb);
+ timer_.repeat =
+ static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3;
+ ev_timer_again(loop_, &timer_);
+
+ if (!config.quiet) {
+ std::cerr << "Closing period has started (" << timer_.repeat << " seconds)"
+ << std::endl;
+ }
+
+ conn_closebuf_ = std::make_unique<Buffer>(NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+
+ ngtcp2_path_storage ps;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ ngtcp2_pkt_info pi;
+ auto n = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(),
+ &last_error_, util::timestamp(loop_));
+ if (n < 0) {
+ std::cerr << "ngtcp2_conn_write_connection_close: " << ngtcp2_strerror(n)
+ << std::endl;
+ return -1;
+ }
+
+ if (n == 0) {
+ return 0;
+ }
+
+ conn_closebuf_->push(n);
+
+ return 0;
+}
+
+int Handler::handle_error() {
+ if (last_error_.type ==
+ NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE) {
+ return -1;
+ }
+
+ if (start_closing_period() != 0) {
+ return -1;
+ }
+
+ if (ngtcp2_conn_is_in_draining_period(conn_)) {
+ return NETWORK_ERR_CLOSE_WAIT;
+ }
+
+ if (auto rv = send_conn_close(); rv != NETWORK_ERR_OK) {
+ return rv;
+ }
+
+ return NETWORK_ERR_CLOSE_WAIT;
+}
+
+int Handler::send_conn_close() {
+ if (!config.quiet) {
+ std::cerr << "Closing Period: TX CONNECTION_CLOSE" << std::endl;
+ }
+
+ assert(conn_closebuf_ && conn_closebuf_->size());
+ assert(conn_);
+ assert(!ngtcp2_conn_is_in_draining_period(conn_));
+
+ auto path = ngtcp2_conn_get_path(conn_);
+
+ return server_->send_packet(
+ *static_cast<Endpoint *>(path->user_data), path->local, path->remote,
+ /* ecn = */ 0, conn_closebuf_->rpos(), conn_closebuf_->size());
+}
+
+void Handler::update_timer() {
+ auto expiry = ngtcp2_conn_get_expiry(conn_);
+ auto now = util::timestamp(loop_);
+
+ if (expiry <= now) {
+ if (!config.quiet) {
+ auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS;
+ std::cerr << "Timer has already expired: " << t << "s" << std::endl;
+ }
+
+ ev_feed_event(loop_, &timer_, EV_TIMER);
+
+ return;
+ }
+
+ auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS;
+ if (!config.quiet) {
+ std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat
+ << std::endl;
+ }
+ timer_.repeat = t;
+ ev_timer_again(loop_, &timer_);
+}
+
+namespace {
+int on_msg_begin(http_parser *htp) {
+ auto s = static_cast<Stream *>(htp->data);
+ if (s->eos) {
+ return -1;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_url_cb(http_parser *htp, const char *data, size_t datalen) {
+ auto s = static_cast<Stream *>(htp->data);
+ s->uri.append(data, datalen);
+ return 0;
+}
+} // namespace
+
+namespace {
+int on_msg_complete(http_parser *htp) {
+ auto s = static_cast<Stream *>(htp->data);
+ s->eos = true;
+ if (s->start_response() != 0) {
+ return -1;
+ }
+ return 0;
+}
+} // namespace
+
+auto htp_settings = http_parser_settings{
+ on_msg_begin, // on_message_begin
+ on_url_cb, // on_url
+ nullptr, // on_status
+ nullptr, // on_header_field
+ nullptr, // on_header_value
+ nullptr, // on_headers_complete
+ nullptr, // on_body
+ on_msg_complete, // on_message_complete
+ nullptr, // on_chunk_header,
+ nullptr, // on_chunk_complete
+};
+
+int Handler::recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ if (!config.quiet && !config.no_quic_dump) {
+ debug::print_stream_data(stream_id, data, datalen);
+ }
+
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ if (!stream->eos) {
+ auto nread =
+ http_parser_execute(&stream->htp, &htp_settings,
+ reinterpret_cast<const char *>(data), datalen);
+ if (nread != datalen) {
+ if (auto rv = ngtcp2_conn_shutdown_stream(conn_, stream_id,
+ /* app error code */ 1);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0);
+ return -1;
+ }
+ }
+ }
+
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, datalen);
+ ngtcp2_conn_extend_max_offset(conn_, datalen);
+
+ return 0;
+}
+
+int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen) {
+ auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_);
+ auto aead = &crypto_ctx->aead;
+ auto keylen = ngtcp2_crypto_aead_keylen(aead);
+ auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
+
+ ++nkey_update_;
+
+ std::array<uint8_t, 64> rx_key, tx_key;
+
+ if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx,
+ rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(),
+ tx_iv, current_rx_secret, current_tx_secret,
+ secretlen) != 0) {
+ return -1;
+ }
+
+ if (!config.quiet && config.show_secret) {
+ std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl;
+ debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv,
+ ivlen);
+ std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl;
+ debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv,
+ ivlen);
+ }
+
+ return 0;
+}
+
+Server *Handler::server() const { return server_; }
+
+int Handler::on_stream_close(int64_t stream_id, uint64_t app_error_code) {
+ if (!config.quiet) {
+ std::cerr << "QUIC stream " << stream_id << " closed" << std::endl;
+ }
+
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ sendq_.erase(stream.get());
+
+ if (!config.quiet) {
+ std::cerr << "HTTP stream " << stream_id << " closed with error code "
+ << app_error_code << std::endl;
+ }
+
+ streams_.erase(it);
+
+ if (ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
+ ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
+ }
+
+ return 0;
+}
+
+void Handler::shutdown_read(int64_t stream_id, int app_error_code) {
+ ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
+}
+
+void Handler::add_sendq(Stream *stream) { sendq_.emplace(stream); }
+
+namespace {
+void sreadcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto ep = static_cast<Endpoint *>(w->data);
+
+ ep->server->on_read(*ep);
+}
+} // namespace
+
+namespace {
+void siginthandler(struct ev_loop *loop, ev_signal *watcher, int revents) {
+ ev_break(loop, EVBREAK_ALL);
+}
+} // namespace
+
+Server::Server(struct ev_loop *loop, TLSServerContext &tls_ctx)
+ : loop_(loop), tls_ctx_(tls_ctx) {
+ ev_signal_init(&sigintev_, siginthandler, SIGINT);
+}
+
+Server::~Server() {
+ disconnect();
+ close();
+}
+
+void Server::disconnect() {
+ config.tx_loss_prob = 0;
+
+ for (auto &ep : endpoints_) {
+ ev_io_stop(loop_, &ep.rev);
+ }
+
+ ev_signal_stop(loop_, &sigintev_);
+
+ while (!handlers_.empty()) {
+ auto it = std::begin(handlers_);
+ auto &h = (*it).second;
+
+ h->handle_error();
+
+ remove(h);
+ }
+}
+
+void Server::close() {
+ for (auto &ep : endpoints_) {
+ ::close(ep.fd);
+ }
+
+ endpoints_.clear();
+}
+
+namespace {
+int create_sock(Address &local_addr, const char *addr, const char *port,
+ int family) {
+ addrinfo hints{};
+ addrinfo *res, *rp;
+ int val = 1;
+
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ if (strcmp(addr, "*") == 0) {
+ addr = nullptr;
+ }
+
+ if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ int fd = -1;
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ fd = util::create_nonblock_socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (fd == -1) {
+ continue;
+ }
+
+ if (rp->ai_family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+ } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
+ break;
+ }
+
+ close(fd);
+ }
+
+ if (!rp) {
+ std::cerr << "Could not bind" << std::endl;
+ return -1;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ return -1;
+ }
+
+ fd_set_recv_ecn(fd, rp->ai_family);
+ fd_set_ip_mtu_discover(fd, rp->ai_family);
+ fd_set_ip_dontfrag(fd, family);
+
+ socklen_t len = sizeof(local_addr.su.storage);
+ if (getsockname(fd, &local_addr.su.sa, &len) == -1) {
+ std::cerr << "getsockname: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+ local_addr.len = len;
+ local_addr.ifindex = 0;
+
+ return fd;
+}
+
+} // namespace
+
+namespace {
+int add_endpoint(std::vector<Endpoint> &endpoints, const char *addr,
+ const char *port, int af) {
+ Address dest;
+ auto fd = create_sock(dest, addr, port, af);
+ if (fd == -1) {
+ return -1;
+ }
+
+ endpoints.emplace_back();
+ auto &ep = endpoints.back();
+ ep.addr = dest;
+ ep.fd = fd;
+ ev_io_init(&ep.rev, sreadcb, 0, EV_READ);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int add_endpoint(std::vector<Endpoint> &endpoints, const Address &addr) {
+ auto fd = util::create_nonblock_socket(addr.su.sa.sa_family, SOCK_DGRAM, 0);
+ if (fd == -1) {
+ std::cerr << "socket: " << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ int val = 1;
+ if (addr.su.sa.sa_family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+ } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (bind(fd, &addr.su.sa, addr.len) == -1) {
+ std::cerr << "bind: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ return -1;
+ }
+
+ fd_set_recv_ecn(fd, addr.su.sa.sa_family);
+ fd_set_ip_mtu_discover(fd, addr.su.sa.sa_family);
+ fd_set_ip_dontfrag(fd, addr.su.sa.sa_family);
+
+ endpoints.emplace_back(Endpoint{});
+ auto &ep = endpoints.back();
+ ep.addr = addr;
+ ep.fd = fd;
+ ev_io_init(&ep.rev, sreadcb, 0, EV_READ);
+
+ return 0;
+}
+} // namespace
+
+int Server::init(const char *addr, const char *port) {
+ endpoints_.reserve(4);
+
+ auto ready = false;
+ if (!util::numeric_host(addr, AF_INET6) &&
+ add_endpoint(endpoints_, addr, port, AF_INET) == 0) {
+ ready = true;
+ }
+ if (!util::numeric_host(addr, AF_INET) &&
+ add_endpoint(endpoints_, addr, port, AF_INET6) == 0) {
+ ready = true;
+ }
+ if (!ready) {
+ return -1;
+ }
+
+ if (config.preferred_ipv4_addr.len &&
+ add_endpoint(endpoints_, config.preferred_ipv4_addr) != 0) {
+ return -1;
+ }
+ if (config.preferred_ipv6_addr.len &&
+ add_endpoint(endpoints_, config.preferred_ipv6_addr) != 0) {
+ return -1;
+ }
+
+ for (auto &ep : endpoints_) {
+ ep.server = this;
+ ep.rev.data = &ep;
+
+ ev_io_set(&ep.rev, ep.fd, EV_READ);
+
+ ev_io_start(loop_, &ep.rev);
+ }
+
+ ev_signal_start(loop_, &sigintev_);
+
+ return 0;
+}
+
+int Server::on_read(Endpoint &ep) {
+ sockaddr_union su;
+ std::array<uint8_t, 64_k> buf;
+ ngtcp2_pkt_hd hd;
+ size_t pktcnt = 0;
+ ngtcp2_pkt_info pi;
+
+ iovec msg_iov;
+ msg_iov.iov_base = buf.data();
+ msg_iov.iov_len = buf.size();
+
+ msghdr msg{};
+ msg.msg_name = &su;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t
+ msg_ctrl[CMSG_SPACE(sizeof(uint8_t)) + CMSG_SPACE(sizeof(in6_pktinfo))];
+ msg.msg_control = msg_ctrl;
+
+ for (; pktcnt < 10;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(ep.fd, &msg, 0);
+ if (nread == -1) {
+ if (!(errno == EAGAIN || errno == ENOTCONN)) {
+ std::cerr << "recvmsg: " << strerror(errno) << std::endl;
+ }
+ return 0;
+ }
+
+ ++pktcnt;
+
+ pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family);
+ auto local_addr = msghdr_get_local_addr(&msg, su.storage.ss_family);
+ if (!local_addr) {
+ std::cerr << "Unable to obtain local address" << std::endl;
+ continue;
+ }
+
+ set_port(*local_addr, ep.addr);
+
+ if (!config.quiet) {
+ std::array<char, IF_NAMESIZE> ifname;
+ std::cerr << "Received packet: local="
+ << util::straddr(&local_addr->su.sa, local_addr->len)
+ << " remote=" << util::straddr(&su.sa, msg.msg_namelen)
+ << " if=" << if_indextoname(local_addr->ifindex, ifname.data())
+ << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread
+ << " bytes" << std::endl;
+ }
+
+ if (debug::packet_lost(config.rx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated incoming packet loss **" << std::endl;
+ }
+ continue;
+ }
+
+ if (nread == 0) {
+ continue;
+ }
+
+ ngtcp2_version_cid vc;
+
+ switch (auto rv = ngtcp2_pkt_decode_version_cid(&vc, buf.data(), nread,
+ NGTCP2_SV_SCIDLEN);
+ rv) {
+ case 0:
+ break;
+ case NGTCP2_ERR_VERSION_NEGOTIATION:
+ send_version_negotiation(vc.version, vc.scid, vc.scidlen, vc.dcid,
+ vc.dcidlen, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ default:
+ std::cerr << "Could not decode version and CID from QUIC packet header: "
+ << ngtcp2_strerror(rv) << std::endl;
+ continue;
+ }
+
+ auto dcid_key = util::make_cid_key(vc.dcid, vc.dcidlen);
+
+ auto handler_it = handlers_.find(dcid_key);
+ if (handler_it == std::end(handlers_)) {
+ switch (auto rv = ngtcp2_accept(&hd, buf.data(), nread); rv) {
+ case 0:
+ break;
+ case NGTCP2_ERR_RETRY:
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected packet received: length=" << nread
+ << std::endl;
+ }
+ continue;
+ }
+
+ ngtcp2_cid ocid;
+ ngtcp2_cid *pocid = nullptr;
+
+ assert(hd.type == NGTCP2_PKT_INITIAL);
+
+ if (config.validate_addr || hd.token.len) {
+ std::cerr << "Perform stateless address validation" << std::endl;
+ if (hd.token.len == 0) {
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ }
+
+ if (hd.token.base[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY &&
+ hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) {
+ send_stateless_connection_close(&hd, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ }
+
+ switch (hd.token.base[0]) {
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY:
+ if (verify_retry_token(&ocid, &hd, &su.sa, msg.msg_namelen) != 0) {
+ send_stateless_connection_close(&hd, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ }
+ pocid = &ocid;
+ break;
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR:
+ if (verify_token(&hd, &su.sa, msg.msg_namelen) != 0) {
+ if (config.validate_addr) {
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen,
+ nread * 3);
+ continue;
+ }
+
+ hd.token.base = nullptr;
+ hd.token.len = 0;
+ }
+ break;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Ignore unrecognized token" << std::endl;
+ }
+ if (config.validate_addr) {
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen,
+ nread * 3);
+ continue;
+ }
+
+ hd.token.base = nullptr;
+ hd.token.len = 0;
+ break;
+ }
+ }
+
+ auto h = std::make_unique<Handler>(loop_, this);
+ if (h->init(ep, *local_addr, &su.sa, msg.msg_namelen, &hd.scid, &hd.dcid,
+ pocid, hd.token.base, hd.token.len, hd.version,
+ tls_ctx_) != 0) {
+ continue;
+ }
+
+ switch (h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi,
+ buf.data(), nread)) {
+ case 0:
+ break;
+ case NETWORK_ERR_RETRY:
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ default:
+ continue;
+ }
+
+ switch (h->on_write()) {
+ case 0:
+ break;
+ default:
+ continue;
+ }
+
+ std::array<ngtcp2_cid, 2> scids;
+ auto conn = h->conn();
+
+ auto num_scid = ngtcp2_conn_get_num_scid(conn);
+
+ assert(num_scid <= scids.size());
+
+ ngtcp2_conn_get_scid(conn, scids.data());
+
+ for (size_t i = 0; i < num_scid; ++i) {
+ handlers_.emplace(util::make_cid_key(&scids[i]), h.get());
+ }
+
+ handlers_.emplace(dcid_key, h.get());
+
+ h.release();
+
+ continue;
+ }
+
+ auto h = (*handler_it).second;
+ auto conn = h->conn();
+ if (ngtcp2_conn_is_in_closing_period(conn)) {
+ // TODO do exponential backoff.
+ switch (h->send_conn_close()) {
+ case 0:
+ break;
+ default:
+ remove(h);
+ }
+ continue;
+ }
+ if (ngtcp2_conn_is_in_draining_period(conn)) {
+ continue;
+ }
+
+ if (auto rv = h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi,
+ buf.data(), nread);
+ rv != 0) {
+ if (rv != NETWORK_ERR_CLOSE_WAIT) {
+ remove(h);
+ }
+ continue;
+ }
+
+ h->signal_write();
+ }
+
+ return 0;
+}
+
+namespace {
+uint32_t generate_reserved_version(const sockaddr *sa, socklen_t salen,
+ uint32_t version) {
+ uint32_t h = 0x811C9DC5u;
+ const uint8_t *p = (const uint8_t *)sa;
+ const uint8_t *ep = p + salen;
+ for (; p != ep; ++p) {
+ h ^= *p;
+ h *= 0x01000193u;
+ }
+ version = htonl(version);
+ p = (const uint8_t *)&version;
+ ep = p + sizeof(version);
+ for (; p != ep; ++p) {
+ h ^= *p;
+ h *= 0x01000193u;
+ }
+ h &= 0xf0f0f0f0u;
+ h |= 0x0a0a0a0au;
+ return h;
+}
+} // namespace
+
+int Server::send_version_negotiation(uint32_t version, const uint8_t *dcid,
+ size_t dcidlen, const uint8_t *scid,
+ size_t scidlen, Endpoint &ep,
+ const Address &local_addr,
+ const sockaddr *sa, socklen_t salen) {
+ Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};
+ std::array<uint32_t, 1 + max_preferred_versionslen> sv;
+
+ auto p = std::begin(sv);
+
+ *p++ = generate_reserved_version(sa, salen, version);
+
+ if (config.preferred_versions.empty()) {
+ *p++ = NGTCP2_PROTO_VER_V1;
+ } else {
+ for (auto v : config.preferred_versions) {
+ *p++ = v;
+ }
+ }
+
+ auto nwrite = ngtcp2_pkt_write_version_negotiation(
+ buf.wpos(), buf.left(),
+ std::uniform_int_distribution<uint8_t>(
+ 0, std::numeric_limits<uint8_t>::max())(randgen),
+ dcid, dcidlen, scid, scidlen, sv.data(), p - std::begin(sv));
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_pkt_write_version_negotiation: "
+ << ngtcp2_strerror(nwrite) << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep,
+ const Address &local_addr, const sockaddr *sa,
+ socklen_t salen, size_t max_pktlen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Sending Retry packet to [" << host.data()
+ << "]:" << port.data() << std::endl;
+ }
+
+ ngtcp2_cid scid;
+
+ scid.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(scid.data, scid.datalen) != 0) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN> token;
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto tokenlen = ngtcp2_crypto_generate_retry_token(
+ token.data(), config.static_secret.data(), config.static_secret.size(),
+ chd->version, sa, salen, &scid, &chd->dcid, t);
+ if (tokenlen < 0) {
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Generated address validation token:" << std::endl;
+ util::hexdump(stderr, token.data(), tokenlen);
+ }
+
+ Buffer buf{
+ std::min(static_cast<size_t>(NGTCP2_MAX_UDP_PAYLOAD_SIZE), max_pktlen)};
+
+ auto nwrite = ngtcp2_crypto_write_retry(buf.wpos(), buf.left(), chd->version,
+ &chd->scid, &scid, &chd->dcid,
+ token.data(), tokenlen);
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_crypto_write_retry failed" << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::send_stateless_connection_close(const ngtcp2_pkt_hd *chd,
+ Endpoint &ep,
+ const Address &local_addr,
+ const sockaddr *sa,
+ socklen_t salen) {
+ Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};
+
+ auto nwrite = ngtcp2_crypto_write_connection_close(
+ buf.wpos(), buf.left(), chd->version, &chd->scid, &chd->dcid,
+ NGTCP2_INVALID_TOKEN, nullptr, 0);
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_crypto_write_connection_close failed" << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
+ const sockaddr *sa, socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Verifying Retry token from [" << host.data()
+ << "]:" << port.data() << std::endl;
+ util::hexdump(stderr, hd->token.base, hd->token.len);
+ }
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_retry_token(
+ ocid, hd->token.base, hd->token.len, config.static_secret.data(),
+ config.static_secret.size(), hd->version, sa, salen, &hd->dcid,
+ 10 * NGTCP2_SECONDS, t) != 0) {
+ std::cerr << "Could not verify Retry token" << std::endl;
+
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Token was successfully validated" << std::endl;
+ }
+
+ return 0;
+}
+
+int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
+ socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Verifying token from [" << host.data() << "]:" << port.data()
+ << std::endl;
+ util::hexdump(stderr, hd->token.base, hd->token.len);
+ }
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_regular_token(hd->token.base, hd->token.len,
+ config.static_secret.data(),
+ config.static_secret.size(), sa, salen,
+ 3600 * NGTCP2_SECONDS, t) != 0) {
+ std::cerr << "Could not verify token" << std::endl;
+
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Token was successfully validated" << std::endl;
+ }
+
+ return 0;
+}
+
+int Server::send_packet(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen) {
+ auto no_gso = false;
+ auto [_, rv] = send_packet(ep, no_gso, local_addr, remote_addr, ecn, data,
+ datalen, datalen);
+
+ return rv;
+}
+
+std::pair<size_t, int>
+Server::send_packet(Endpoint &ep, bool &no_gso, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen, size_t gso_size) {
+ assert(gso_size);
+
+ if (debug::packet_lost(config.tx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated outgoing packet loss **" << std::endl;
+ }
+ return {0, NETWORK_ERR_OK};
+ }
+
+ if (no_gso && datalen > gso_size) {
+ size_t nsent = 0;
+
+ for (auto p = data; p < data + datalen; p += gso_size) {
+ auto len = std::min(gso_size, static_cast<size_t>(data + datalen - p));
+
+ auto [n, rv] =
+ send_packet(ep, no_gso, local_addr, remote_addr, ecn, p, len, len);
+ if (rv != 0) {
+ return {nsent, rv};
+ }
+
+ nsent += n;
+ }
+
+ return {nsent, 0};
+ }
+
+ iovec msg_iov;
+ msg_iov.iov_base = const_cast<uint8_t *>(data);
+ msg_iov.iov_len = datalen;
+
+ msghdr msg{};
+ msg.msg_name = const_cast<sockaddr *>(remote_addr.addr);
+ msg.msg_namelen = remote_addr.addrlen;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t
+ msg_ctrl[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(in6_pktinfo))];
+
+ memset(msg_ctrl, 0, sizeof(msg_ctrl));
+
+ msg.msg_control = msg_ctrl;
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ size_t controllen = 0;
+
+ auto cm = CMSG_FIRSTHDR(&msg);
+
+ switch (local_addr.addr->sa_family) {
+ case AF_INET: {
+ controllen += CMSG_SPACE(sizeof(in_pktinfo));
+ cm->cmsg_level = IPPROTO_IP;
+ cm->cmsg_type = IP_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
+ auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cm));
+ memset(pktinfo, 0, sizeof(in_pktinfo));
+ auto addrin = reinterpret_cast<sockaddr_in *>(local_addr.addr);
+ pktinfo->ipi_spec_dst = addrin->sin_addr;
+ break;
+ }
+ case AF_INET6: {
+ controllen += CMSG_SPACE(sizeof(in6_pktinfo));
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
+ auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cm));
+ memset(pktinfo, 0, sizeof(in6_pktinfo));
+ auto addrin = reinterpret_cast<sockaddr_in6 *>(local_addr.addr);
+ pktinfo->ipi6_addr = addrin->sin6_addr;
+ break;
+ }
+ default:
+ assert(0);
+ }
+
+#ifdef UDP_SEGMENT
+ if (datalen > gso_size) {
+ controllen += CMSG_SPACE(sizeof(uint16_t));
+ cm = CMSG_NXTHDR(&msg, cm);
+ cm->cmsg_level = SOL_UDP;
+ cm->cmsg_type = UDP_SEGMENT;
+ cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+ *(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size;
+ }
+#endif // UDP_SEGMENT
+
+ msg.msg_controllen = controllen;
+
+ if (ep.ecn != ecn) {
+ ep.ecn = ecn;
+ fd_set_ecn(ep.fd, ep.addr.su.storage.ss_family, ecn);
+ }
+
+ ssize_t nwrite = 0;
+
+ do {
+ nwrite = sendmsg(ep.fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif // EAGAIN != EWOULDBLOCK
+ return {0, NETWORK_ERR_SEND_BLOCKED};
+#ifdef UDP_SEGMENT
+ case EIO:
+ if (datalen > gso_size) {
+ // GSO failure; send each packet in a separate sendmsg call.
+ std::cerr << "sendmsg: disabling GSO due to " << strerror(errno)
+ << std::endl;
+
+ no_gso = true;
+
+ return send_packet(ep, no_gso, local_addr, remote_addr, ecn, data,
+ datalen, gso_size);
+ }
+ break;
+#endif // UDP_SEGMENT
+ }
+
+ std::cerr << "sendmsg: " << strerror(errno) << std::endl;
+ // TODO We have packet which is expected to fail to send (e.g.,
+ // path validation to old path).
+ return {0, NETWORK_ERR_OK};
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Sent packet: local="
+ << util::straddr(local_addr.addr, local_addr.addrlen)
+ << " remote="
+ << util::straddr(remote_addr.addr, remote_addr.addrlen)
+ << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite
+ << " bytes" << std::endl;
+ }
+
+ return {nwrite, NETWORK_ERR_OK};
+}
+
+void Server::associate_cid(const ngtcp2_cid *cid, Handler *h) {
+ handlers_.emplace(util::make_cid_key(cid), h);
+}
+
+void Server::dissociate_cid(const ngtcp2_cid *cid) {
+ handlers_.erase(util::make_cid_key(cid));
+}
+
+void Server::remove(const Handler *h) {
+ auto conn = h->conn();
+
+ handlers_.erase(
+ util::make_cid_key(ngtcp2_conn_get_client_initial_dcid(conn)));
+
+ std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(conn));
+ ngtcp2_conn_get_scid(conn, cids.data());
+
+ for (auto &cid : cids) {
+ handlers_.erase(util::make_cid_key(&cid));
+ }
+
+ delete h;
+}
+
+namespace {
+int parse_host_port(Address &dest, int af, const char *first,
+ const char *last) {
+ if (std::distance(first, last) == 0) {
+ return -1;
+ }
+
+ const char *host_begin, *host_end, *it;
+ if (*first == '[') {
+ host_begin = first + 1;
+ it = std::find(host_begin, last, ']');
+ if (it == last) {
+ return -1;
+ }
+ host_end = it;
+ ++it;
+ if (it == last || *it != ':') {
+ return -1;
+ }
+ } else {
+ host_begin = first;
+ it = std::find(host_begin, last, ':');
+ if (it == last) {
+ return -1;
+ }
+ host_end = it;
+ }
+
+ if (++it == last) {
+ return -1;
+ }
+ auto svc_begin = it;
+
+ std::array<char, NI_MAXHOST> host;
+ *std::copy(host_begin, host_end, std::begin(host)) = '\0';
+
+ addrinfo hints{}, *res;
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ if (auto rv = getaddrinfo(host.data(), svc_begin, &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: [" << host.data() << "]:" << svc_begin << ": "
+ << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ dest.len = res->ai_addrlen;
+ memcpy(&dest.su, res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void print_usage() {
+ std::cerr << "Usage: server [OPTIONS] <ADDR> <PORT> <PRIVATE_KEY_FILE> "
+ "<CERTIFICATE_FILE>"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void config_set_default(Config &config) {
+ config = Config{};
+ config.tx_loss_prob = 0.;
+ config.rx_loss_prob = 0.;
+ config.ciphers = util::crypto_default_ciphers();
+ config.groups = util::crypto_default_groups();
+ config.timeout = 30 * NGTCP2_SECONDS;
+ {
+ auto path = realpath(".", nullptr);
+ assert(path);
+ config.htdocs = path;
+ free(path);
+ }
+ config.mime_types_file = "/etc/mime.types"sv;
+ config.max_data = 1_m;
+ config.max_stream_data_bidi_local = 256_k;
+ config.max_stream_data_bidi_remote = 256_k;
+ config.max_stream_data_uni = 256_k;
+ config.max_window = 6_m;
+ config.max_stream_window = 6_m;
+ config.max_streams_bidi = 100;
+ config.max_streams_uni = 3;
+ config.max_dyn_length = 20_m;
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT;
+ config.max_gso_dgrams = 64;
+ config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT;
+ config.ack_thresh = 2;
+}
+} // namespace
+
+namespace {
+void print_help() {
+ print_usage();
+
+ config_set_default(config);
+
+ std::cout << R"(
+ <ADDR> Address to listen to. '*' binds to any address.
+ <PORT> Port
+ <PRIVATE_KEY_FILE>
+ Path to private key file
+ <CERTIFICATE_FILE>
+ Path to certificate file
+Options:
+ -t, --tx-loss=<P>
+ The probability of losing outgoing packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ -r, --rx-loss=<P>
+ The probability of losing incoming packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ --ciphers=<CIPHERS>
+ Specify the cipher suite list to enable.
+ Default: )"
+ << config.ciphers << R"(
+ --groups=<GROUPS>
+ Specify the supported groups.
+ Default: )"
+ << config.groups << R"(
+ -d, --htdocs=<PATH>
+ Specify document root. If this option is not specified,
+ the document root is the current working directory.
+ -q, --quiet Suppress debug output.
+ -s, --show-secret
+ Print out secrets unless --quiet is used.
+ --timeout=<DURATION>
+ Specify idle timeout.
+ Default: )"
+ << util::format_duration(config.timeout) << R"(
+ -V, --validate-addr
+ Perform address validation.
+ --preferred-ipv4-addr=<ADDR>:<PORT>
+ Specify preferred IPv4 address and port.
+ --preferred-ipv6-addr=<ADDR>:<PORT>
+ Specify preferred IPv6 address and port. A numeric IPv6
+ address must be enclosed by '[' and ']' (e.g.,
+ [::1]:8443)
+ --mime-types-file=<PATH>
+ Path to file that contains MIME media types and the
+ extensions.
+ Default: )"
+ << config.mime_types_file << R"(
+ --early-response
+ Start sending response when it receives HTTP header
+ fields without waiting for request body. If HTTP
+ response data is written before receiving request body,
+ STOP_SENDING is sent.
+ --verify-client
+ Request a client certificate. At the moment, we just
+ request a certificate and no verification is done.
+ --qlog-dir=<PATH>
+ Path to the directory where qlog file is stored. The
+ file name of each qlog is the Source Connection ID of
+ server.
+ --no-quic-dump
+ Disables printing QUIC STREAM and CRYPTO frame data out.
+ --no-http-dump
+ Disables printing HTTP response body out.
+ --max-data=<SIZE>
+ The initial connection-level flow control window.
+ Default: )"
+ << util::format_uint_iec(config.max_data) << R"(
+ --max-stream-data-bidi-local=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the local endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_local) << R"(
+ --max-stream-data-bidi-remote=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the remote endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"(
+ --max-stream-data-uni=<SIZE>
+ The initial stream-level flow control window for a
+ unidirectional stream.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_uni) << R"(
+ --max-streams-bidi=<N>
+ The number of the concurrent bidirectional streams.
+ Default: )"
+ << config.max_streams_bidi << R"(
+ --max-streams-uni=<N>
+ The number of the concurrent unidirectional streams.
+ Default: )"
+ << config.max_streams_uni << R"(
+ --max-dyn-length=<SIZE>
+ The maximum length of a dynamically generated content.
+ Default: )"
+ << util::format_uint_iec(config.max_dyn_length) << R"(
+ --cc=(cubic|reno|bbr|bbr2)
+ The name of congestion controller algorithm.
+ Default: )"
+ << util::strccalgo(config.cc_algo) << R"(
+ --initial-rtt=<DURATION>
+ Set an initial RTT.
+ Default: )"
+ << util::format_duration(config.initial_rtt) << R"(
+ --max-udp-payload-size=<SIZE>
+ Override maximum UDP payload size that server transmits.
+ --max-window=<SIZE>
+ Maximum connection-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_window) << R"(
+ --max-stream-window=<SIZE>
+ Maximum stream-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_window) << R"(
+ --send-trailers
+ Send trailer fields.
+ --max-gso-dgrams=<N>
+ Maximum number of UDP datagrams that are sent in a
+ single GSO sendmsg call.
+ Default: )"
+ << config.max_gso_dgrams << R"(
+ --handshake-timeout=<DURATION>
+ Set the QUIC handshake timeout.
+ Default: )"
+ << util::format_duration(config.handshake_timeout) << R"(
+ --preferred-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string in the order of
+ preference. Server negotiates one of those versions if
+ client initially selects a less preferred version.
+ These versions must be supported by libngtcp2. Instead
+ of specifying hex string, there are special aliases
+ available: "v1" indicates QUIC v1, and "v2draft"
+ indicates QUIC v2 draft.
+ --other-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string that are sent in
+ other_versions field of version_information transport
+ parameter. This list can include a version which is not
+ supported by libngtcp2. Instead of specifying hex
+ string, there are special aliases available: "v1"
+ indicates QUIC v1, and "v2draft" indicates QUIC v2
+ draft.
+ --no-pmtud Disables Path MTU Discovery.
+ --ack-thresh=<N>
+ The minimum number of the received ACK eliciting packets
+ that triggers immediate acknowledgement.
+ Default: )"
+ << config.ack_thresh << R"(
+ -h, --help Display this help and exit.
+
+---
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms,
+ us, or ns (hours, minutes, seconds, milliseconds, microseconds, and
+ nanoseconds respectively). If a unit is omitted, a second is used
+ as unit.
+
+ The <HEX> argument is an hex string which must start with "0x"
+ (e.g., 0x00000001).)"
+ << std::endl;
+}
+} // namespace
+
+std::ofstream keylog_file;
+
+int main(int argc, char **argv) {
+ config_set_default(config);
+
+ for (;;) {
+ static int flag = 0;
+ constexpr static option long_opts[] = {
+ {"help", no_argument, nullptr, 'h'},
+ {"tx-loss", required_argument, nullptr, 't'},
+ {"rx-loss", required_argument, nullptr, 'r'},
+ {"htdocs", required_argument, nullptr, 'd'},
+ {"quiet", no_argument, nullptr, 'q'},
+ {"show-secret", no_argument, nullptr, 's'},
+ {"validate-addr", no_argument, nullptr, 'V'},
+ {"ciphers", required_argument, &flag, 1},
+ {"groups", required_argument, &flag, 2},
+ {"timeout", required_argument, &flag, 3},
+ {"preferred-ipv4-addr", required_argument, &flag, 4},
+ {"preferred-ipv6-addr", required_argument, &flag, 5},
+ {"mime-types-file", required_argument, &flag, 6},
+ {"early-response", no_argument, &flag, 7},
+ {"verify-client", no_argument, &flag, 8},
+ {"qlog-dir", required_argument, &flag, 9},
+ {"no-quic-dump", no_argument, &flag, 10},
+ {"no-http-dump", no_argument, &flag, 11},
+ {"max-data", required_argument, &flag, 12},
+ {"max-stream-data-bidi-local", required_argument, &flag, 13},
+ {"max-stream-data-bidi-remote", required_argument, &flag, 14},
+ {"max-stream-data-uni", required_argument, &flag, 15},
+ {"max-streams-bidi", required_argument, &flag, 16},
+ {"max-streams-uni", required_argument, &flag, 17},
+ {"max-dyn-length", required_argument, &flag, 18},
+ {"cc", required_argument, &flag, 19},
+ {"initial-rtt", required_argument, &flag, 20},
+ {"max-udp-payload-size", required_argument, &flag, 21},
+ {"send-trailers", no_argument, &flag, 22},
+ {"max-window", required_argument, &flag, 23},
+ {"max-stream-window", required_argument, &flag, 24},
+ {"max-gso-dgrams", required_argument, &flag, 25},
+ {"handshake-timeout", required_argument, &flag, 26},
+ {"preferred-versions", required_argument, &flag, 27},
+ {"other-versions", required_argument, &flag, 28},
+ {"no-pmtud", no_argument, &flag, 29},
+ {"ack-thresh", required_argument, &flag, 30},
+ {nullptr, 0, nullptr, 0}};
+
+ auto optidx = 0;
+ auto c = getopt_long(argc, argv, "d:hqr:st:V", long_opts, &optidx);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'd': {
+ // --htdocs
+ auto path = realpath(optarg, nullptr);
+ if (path == nullptr) {
+ std::cerr << "path: invalid path " << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.htdocs = path;
+ free(path);
+ break;
+ }
+ case 'h':
+ // --help
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'q':
+ // --quiet
+ config.quiet = true;
+ break;
+ case 'r':
+ // --rx-loss
+ config.rx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 's':
+ // --show-secret
+ config.show_secret = true;
+ break;
+ case 't':
+ // --tx-loss
+ config.tx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 'V':
+ // --validate-addr
+ config.validate_addr = true;
+ break;
+ case '?':
+ print_usage();
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // --ciphers
+ config.ciphers = optarg;
+ break;
+ case 2:
+ // --groups
+ config.groups = optarg;
+ break;
+ case 3:
+ // --timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.timeout = *t;
+ }
+ break;
+ case 4:
+ // --preferred-ipv4-addr
+ if (parse_host_port(config.preferred_ipv4_addr, AF_INET, optarg,
+ optarg + strlen(optarg)) != 0) {
+ std::cerr << "preferred-ipv4-addr: could not use "
+ << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 5:
+ // --preferred-ipv6-addr
+ if (parse_host_port(config.preferred_ipv6_addr, AF_INET6, optarg,
+ optarg + strlen(optarg)) != 0) {
+ std::cerr << "preferred-ipv6-addr: could not use "
+ << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 6:
+ // --mime-types-file
+ config.mime_types_file = optarg;
+ break;
+ case 7:
+ // --early-response
+ config.early_response = true;
+ break;
+ case 8:
+ // --verify-client
+ config.verify_client = true;
+ break;
+ case 9:
+ // --qlog-dir
+ config.qlog_dir = optarg;
+ break;
+ case 10:
+ // --no-quic-dump
+ config.no_quic_dump = true;
+ break;
+ case 11:
+ // --no-http-dump
+ config.no_http_dump = true;
+ break;
+ case 12:
+ // --max-data
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-data: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_data = *n;
+ }
+ break;
+ case 13:
+ // --max-stream-data-bidi-local
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-local: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_local = *n;
+ }
+ break;
+ case 14:
+ // --max-stream-data-bidi-remote
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-remote: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_remote = *n;
+ }
+ break;
+ case 15:
+ // --max-stream-data-uni
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_uni = *n;
+ }
+ break;
+ case 16:
+ // --max-streams-bidi
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-bidi: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_bidi = *n;
+ }
+ break;
+ case 17:
+ // --max-streams-uni
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_uni = *n;
+ }
+ break;
+ case 18:
+ // --max-dyn-length
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-dyn-length: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_dyn_length = *n;
+ }
+ break;
+ case 19:
+ // --cc
+ if (strcmp("cubic", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ break;
+ }
+ if (strcmp("reno", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_RENO;
+ break;
+ }
+ if (strcmp("bbr", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR;
+ break;
+ }
+ if (strcmp("bbr2", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR2;
+ break;
+ }
+ std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl;
+ exit(EXIT_FAILURE);
+ case 20:
+ // --initial-rtt
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "initial-rtt: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.initial_rtt = *t;
+ }
+ break;
+ case 21:
+ // --max-udp-payload-size
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-udp-payload-size: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 64_k) {
+ std::cerr << "max-udp-payload-size: must not exceed 65536"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_udp_payload_size = *n;
+ }
+ break;
+ case 22:
+ // --send-trailers
+ config.send_trailers = true;
+ break;
+ case 23:
+ // --max-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_window = *n;
+ }
+ break;
+ case 24:
+ // --max-stream-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_window = *n;
+ }
+ break;
+ case 25:
+ // --max-gso-dgrams
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-gso-dgrams: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_gso_dgrams = *n;
+ }
+ break;
+ case 26:
+ // --handshake-timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "handshake-timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.handshake_timeout = *t;
+ }
+ break;
+ case 27: {
+ // --preferred-versions
+ auto l = util::split_str(optarg);
+ if (l.size() > max_preferred_versionslen) {
+ std::cerr << "preferred-versions: too many versions > "
+ << max_preferred_versionslen << std::endl;
+ }
+ config.preferred_versions.resize(l.size());
+ auto it = std::begin(config.preferred_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "preferred-versions: invalid version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (!ngtcp2_is_supported_version(*rv)) {
+ std::cerr << "preferred-versions: unsupported version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 28: {
+ // --other-versions
+ auto l = util::split_str(optarg);
+ config.other_versions.resize(l.size());
+ auto it = std::begin(config.other_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "other-versions: invalid version " << std::quoted(k)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 29:
+ // --no-pmtud
+ config.no_pmtud = true;
+ break;
+ case 30:
+ // --ack-thresh
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "ack-thresh: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 100) {
+ std::cerr << "ack-thresh: must not exceed 100" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.ack_thresh = *n;
+ }
+ break;
+ }
+ break;
+ default:
+ break;
+ };
+ }
+
+ if (argc - optind < 4) {
+ std::cerr << "Too few arguments" << std::endl;
+ print_usage();
+ exit(EXIT_FAILURE);
+ }
+
+ auto addr = argv[optind++];
+ auto port = argv[optind++];
+ auto private_key_file = argv[optind++];
+ auto cert_file = argv[optind++];
+
+ if (auto n = util::parse_uint(port); !n) {
+ std::cerr << "port: invalid port number" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 65535) {
+ std::cerr << "port: must not exceed 65535" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.port = *n;
+ }
+
+ if (auto mt = util::read_mime_types(config.mime_types_file); !mt) {
+ std::cerr << "mime-types-file: Could not read MIME media types file "
+ << std::quoted(config.mime_types_file) << std::endl;
+ } else {
+ config.mime_types = std::move(*mt);
+ }
+
+ TLSServerContext tls_ctx;
+
+ if (tls_ctx.init(private_key_file, cert_file, AppProtocol::HQ) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.htdocs.back() != '/') {
+ config.htdocs += '/';
+ }
+
+ std::cerr << "Using document root " << config.htdocs << std::endl;
+
+ auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT);
+
+ auto keylog_filename = getenv("SSLKEYLOGFILE");
+ if (keylog_filename) {
+ keylog_file.open(keylog_filename, std::ios_base::app);
+ if (keylog_file) {
+ tls_ctx.enable_keylog();
+ }
+ }
+
+ if (util::generate_secret(config.static_secret.data(),
+ config.static_secret.size()) != 0) {
+ std::cerr << "Unable to generate static secret" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ Server s(EV_DEFAULT, tls_ctx);
+ if (s.init(addr, port) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ ev_run(EV_DEFAULT, 0);
+
+ s.disconnect();
+ s.close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/h09server.h b/examples/h09server.h
new file mode 100644
index 0000000..8ab11f5
--- /dev/null
+++ b/examples/h09server.h
@@ -0,0 +1,237 @@
+/*
+ * 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 SERVER_H
+#define SERVER_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <vector>
+#include <unordered_map>
+#include <string>
+#include <deque>
+#include <string_view>
+#include <memory>
+#include <set>
+
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <nghttp3/nghttp3.h>
+
+#include <ev.h>
+
+#include "server_base.h"
+#include "tls_server_context.h"
+#include "network.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+struct HTTPHeader {
+ HTTPHeader(const std::string_view &name, const std::string_view &value)
+ : name(name), value(value) {}
+
+ std::string_view name;
+ std::string_view value;
+};
+
+class Handler;
+struct FileEntry;
+
+struct Stream {
+ Stream(int64_t stream_id, Handler *handler);
+
+ int start_response();
+ std::pair<FileEntry, int> open_file(const std::string &path);
+ void map_file(const FileEntry &fe);
+ int send_status_response(unsigned int status_code);
+
+ int64_t stream_id;
+ Handler *handler;
+ // uri is request uri/path.
+ std::string uri;
+ std::string status_resp_body;
+ nghttp3_buf respbuf;
+ http_parser htp;
+ // eos gets true when one HTTP request message is seen.
+ bool eos;
+};
+
+struct StreamIDLess {
+ constexpr bool operator()(const Stream *lhs, const Stream *rhs) const {
+ return lhs->stream_id < rhs->stream_id;
+ }
+};
+
+class Server;
+
+// Endpoint is a local endpoint.
+struct Endpoint {
+ Address addr;
+ ev_io rev;
+ Server *server;
+ int fd;
+ // ecn is the last ECN bits set to fd.
+ unsigned int ecn;
+};
+
+class Handler : public HandlerBase {
+public:
+ Handler(struct ev_loop *loop, Server *server);
+ ~Handler();
+
+ int init(const Endpoint &ep, const Address &local_addr, const sockaddr *sa,
+ socklen_t salen, const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
+ const ngtcp2_cid *ocid, const uint8_t *token, size_t tokenlen,
+ uint32_t version, TLSServerContext &tls_ctx);
+
+ int on_read(const Endpoint &ep, const Address &local_addr, const sockaddr *sa,
+ socklen_t salen, const ngtcp2_pkt_info *pi, uint8_t *data,
+ size_t datalen);
+ int on_write();
+ int write_streams();
+ int feed_data(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi,
+ uint8_t *data, size_t datalen);
+ void update_timer();
+ int handle_expiry();
+ void signal_write();
+ int handshake_completed();
+
+ Server *server() const;
+ int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data,
+ size_t datalen);
+ int acked_stream_data_offset(int64_t stream_id, uint64_t offset,
+ uint64_t datalen);
+ uint32_t version() const;
+ void on_stream_open(int64_t stream_id);
+ int on_stream_close(int64_t stream_id, uint64_t app_error_code);
+ void start_draining_period();
+ int start_closing_period();
+ int handle_error();
+ int send_conn_close();
+
+ int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen);
+
+ Stream *find_stream(int64_t stream_id);
+ int extend_max_stream_data(int64_t stream_id, uint64_t max_data);
+ void shutdown_read(int64_t stream_id, int app_error_code);
+
+ void write_qlog(const void *data, size_t datalen);
+ void add_sendq(Stream *stream);
+
+ void on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen, size_t gso_size);
+ void start_wev_endpoint(const Endpoint &ep);
+ int send_blocked_packet();
+
+private:
+ struct ev_loop *loop_;
+ Server *server_;
+ ev_io wev_;
+ ev_timer timer_;
+ FILE *qlog_;
+ ngtcp2_cid scid_;
+ std::unordered_map<int64_t, std::unique_ptr<Stream>> streams_;
+ std::set<Stream *, StreamIDLess> sendq_;
+ // conn_closebuf_ contains a packet which contains CONNECTION_CLOSE.
+ // This packet is repeatedly sent as a response to the incoming
+ // packet in draining period.
+ std::unique_ptr<Buffer> conn_closebuf_;
+ // nkey_update_ is the number of key update occurred.
+ size_t nkey_update_;
+ bool no_gso_;
+
+ struct {
+ bool send_blocked;
+ size_t num_blocked;
+ size_t num_blocked_sent;
+ // blocked field is effective only when send_blocked is true.
+ struct {
+ Endpoint *endpoint;
+ Address local_addr;
+ Address remote_addr;
+ unsigned int ecn;
+ const uint8_t *data;
+ size_t datalen;
+ size_t gso_size;
+ } blocked[2];
+ std::unique_ptr<uint8_t[]> data;
+ } tx_;
+};
+
+class Server {
+public:
+ Server(struct ev_loop *loop, TLSServerContext &tls_ctx);
+ ~Server();
+
+ int init(const char *addr, const char *port);
+ void disconnect();
+ void close();
+
+ int on_read(Endpoint &ep);
+ int send_version_negotiation(uint32_t version, const uint8_t *dcid,
+ size_t dcidlen, const uint8_t *scid,
+ size_t scidlen, Endpoint &ep,
+ const Address &local_addr, const sockaddr *sa,
+ socklen_t salen);
+ int send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep,
+ const Address &local_addr, const sockaddr *sa, socklen_t salen,
+ size_t max_pktlen);
+ int send_stateless_connection_close(const ngtcp2_pkt_hd *chd, Endpoint &ep,
+ const Address &local_addr,
+ const sockaddr *sa, socklen_t salen);
+ int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
+ const sockaddr *sa, socklen_t salen);
+ int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
+ socklen_t salen);
+ int send_packet(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen);
+ std::pair<size_t, int> send_packet(Endpoint &ep, bool &no_gso,
+ const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr,
+ unsigned int ecn, const uint8_t *data,
+ size_t datalen, size_t gso_size);
+ void remove(const Handler *h);
+
+ void associate_cid(const ngtcp2_cid *cid, Handler *h);
+ void dissociate_cid(const ngtcp2_cid *cid);
+
+private:
+ std::unordered_map<std::string, Handler *> handlers_;
+ struct ev_loop *loop_;
+ std::vector<Endpoint> endpoints_;
+ TLSServerContext &tls_ctx_;
+ ev_signal sigintev_;
+};
+
+#endif // SERVER_H
diff --git a/examples/http.cc b/examples/http.cc
new file mode 100644
index 0000000..bfcd188
--- /dev/null
+++ b/examples/http.cc
@@ -0,0 +1,138 @@
+/*
+ * 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 "http.h"
+
+namespace ngtcp2 {
+
+namespace http {
+
+std::string get_reason_phrase(unsigned int status_code) {
+ switch (status_code) {
+ case 100:
+ return "Continue";
+ case 101:
+ return "Switching Protocols";
+ case 200:
+ return "OK";
+ case 201:
+ return "Created";
+ case 202:
+ return "Accepted";
+ case 203:
+ return "Non-Authoritative Information";
+ case 204:
+ return "No Content";
+ case 205:
+ return "Reset Content";
+ case 206:
+ return "Partial Content";
+ case 300:
+ return "Multiple Choices";
+ case 301:
+ return "Moved Permanently";
+ case 302:
+ return "Found";
+ case 303:
+ return "See Other";
+ case 304:
+ return "Not Modified";
+ case 305:
+ return "Use Proxy";
+ // case 306: return "(Unused)";
+ case 307:
+ return "Temporary Redirect";
+ case 308:
+ return "Permanent Redirect";
+ case 400:
+ return "Bad Request";
+ case 401:
+ return "Unauthorized";
+ case 402:
+ return "Payment Required";
+ case 403:
+ return "Forbidden";
+ case 404:
+ return "Not Found";
+ case 405:
+ return "Method Not Allowed";
+ case 406:
+ return "Not Acceptable";
+ case 407:
+ return "Proxy Authentication Required";
+ case 408:
+ return "Request Timeout";
+ case 409:
+ return "Conflict";
+ case 410:
+ return "Gone";
+ case 411:
+ return "Length Required";
+ case 412:
+ return "Precondition Failed";
+ case 413:
+ return "Payload Too Large";
+ case 414:
+ return "URI Too Long";
+ case 415:
+ return "Unsupported Media Type";
+ case 416:
+ return "Requested Range Not Satisfiable";
+ case 417:
+ return "Expectation Failed";
+ case 421:
+ return "Misdirected Request";
+ case 426:
+ return "Upgrade Required";
+ case 428:
+ return "Precondition Required";
+ case 429:
+ return "Too Many Requests";
+ case 431:
+ return "Request Header Fields Too Large";
+ case 451:
+ return "Unavailable For Legal Reasons";
+ case 500:
+ return "Internal Server Error";
+ case 501:
+ return "Not Implemented";
+ case 502:
+ return "Bad Gateway";
+ case 503:
+ return "Service Unavailable";
+ case 504:
+ return "Gateway Timeout";
+ case 505:
+ return "HTTP Version Not Supported";
+ case 511:
+ return "Network Authentication Required";
+ default:
+ return "";
+ }
+}
+
+} // namespace http
+
+} // namespace ngtcp2
diff --git a/examples/http.h b/examples/http.h
new file mode 100644
index 0000000..6622b4c
--- /dev/null
+++ b/examples/http.h
@@ -0,0 +1,44 @@
+/*
+ * 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 HTTP_H
+#define HTTP_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <string>
+
+namespace ngtcp2 {
+
+namespace http {
+
+std::string get_reason_phrase(unsigned int status_code);
+
+} // namespace http
+
+} // namespace ngtcp2
+
+#endif // HTTP_H
diff --git a/examples/network.h b/examples/network.h
new file mode 100644
index 0000000..451e531
--- /dev/null
+++ b/examples/network.h
@@ -0,0 +1,80 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2016 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 NETWORK_H
+#define NETWORK_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif // HAVE_SYS_SOCKET_H
+#include <sys/un.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+
+#include <array>
+
+#include <ngtcp2/ngtcp2.h>
+
+namespace ngtcp2 {
+
+enum network_error {
+ NETWORK_ERR_OK = 0,
+ NETWORK_ERR_FATAL = -10,
+ NETWORK_ERR_SEND_BLOCKED = -11,
+ NETWORK_ERR_CLOSE_WAIT = -12,
+ NETWORK_ERR_RETRY = -13,
+ NETWORK_ERR_DROP_CONN = -14,
+};
+
+union in_addr_union {
+ in_addr in;
+ in6_addr in6;
+};
+
+union sockaddr_union {
+ sockaddr_storage storage;
+ sockaddr sa;
+ sockaddr_in6 in6;
+ sockaddr_in in;
+};
+
+struct Address {
+ socklen_t len;
+ union sockaddr_union su;
+ uint32_t ifindex;
+};
+
+} // namespace ngtcp2
+
+#endif // NETWORK_H
diff --git a/examples/server.cc b/examples/server.cc
new file mode 100644
index 0000000..30db269
--- /dev/null
+++ b/examples/server.cc
@@ -0,0 +1,3741 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <chrono>
+#include <cstdlib>
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <algorithm>
+#include <memory>
+#include <fstream>
+#include <iomanip>
+
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <netinet/udp.h>
+#include <net/if.h>
+
+#include <http-parser/http_parser.h>
+
+#include "server.h"
+#include "network.h"
+#include "debug.h"
+#include "util.h"
+#include "shared.h"
+#include "http.h"
+#include "template.h"
+
+using namespace ngtcp2;
+using namespace std::literals;
+
+namespace {
+constexpr size_t NGTCP2_SV_SCIDLEN = 18;
+} // namespace
+
+namespace {
+constexpr size_t MAX_DYNBUFLEN = 10_m;
+} // namespace
+
+namespace {
+constexpr size_t max_preferred_versionslen = 4;
+} // namespace
+
+namespace {
+auto randgen = util::make_mt19937();
+} // namespace
+
+Config config{};
+
+Stream::Stream(int64_t stream_id, Handler *handler)
+ : stream_id(stream_id),
+ handler(handler),
+ data(nullptr),
+ datalen(0),
+ dynresp(false),
+ dyndataleft(0),
+ dynbuflen(0) {}
+
+namespace {
+constexpr auto NGTCP2_SERVER = "nghttp3/ngtcp2 server"sv;
+} // namespace
+
+namespace {
+std::string make_status_body(unsigned int status_code) {
+ auto status_string = util::format_uint(status_code);
+ auto reason_phrase = http::get_reason_phrase(status_code);
+
+ std::string body;
+ body = "<html><head><title>";
+ body += status_string;
+ body += ' ';
+ body += reason_phrase;
+ body += "</title></head><body><h1>";
+ body += status_string;
+ body += ' ';
+ body += reason_phrase;
+ body += "</h1><hr><address>";
+ body += NGTCP2_SERVER;
+ body += " at port ";
+ body += util::format_uint(config.port);
+ body += "</address>";
+ body += "</body></html>";
+ return body;
+}
+} // namespace
+
+struct Request {
+ std::string path;
+ struct {
+ int32_t urgency;
+ int inc;
+ } pri;
+};
+
+namespace {
+Request request_path(const std::string_view &uri, bool is_connect) {
+ http_parser_url u;
+ Request req;
+
+ req.pri.urgency = -1;
+ req.pri.inc = -1;
+
+ http_parser_url_init(&u);
+
+ if (auto rv = http_parser_parse_url(uri.data(), uri.size(), is_connect, &u);
+ rv != 0) {
+ return req;
+ }
+
+ if (u.field_set & (1 << UF_PATH)) {
+ req.path = std::string(uri.data() + u.field_data[UF_PATH].off,
+ u.field_data[UF_PATH].len);
+ if (req.path.find('%') != std::string::npos) {
+ req.path = util::percent_decode(std::begin(req.path), std::end(req.path));
+ }
+ if (!req.path.empty() && req.path.back() == '/') {
+ req.path += "index.html";
+ }
+ } else {
+ req.path = "/index.html";
+ }
+
+ req.path = util::normalize_path(req.path);
+ if (req.path == "/") {
+ req.path = "/index.html";
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ static constexpr auto urgency_prefix = "u="sv;
+ static constexpr auto inc_prefix = "i="sv;
+ auto q = std::string(uri.data() + u.field_data[UF_QUERY].off,
+ u.field_data[UF_QUERY].len);
+ for (auto p = std::begin(q); p != std::end(q);) {
+ if (util::istarts_with(p, std::end(q), std::begin(urgency_prefix),
+ std::end(urgency_prefix))) {
+ auto urgency_start = p + urgency_prefix.size();
+ auto urgency_end = std::find(urgency_start, std::end(q), '&');
+ if (urgency_start + 1 == urgency_end && '0' <= *urgency_start &&
+ *urgency_start <= '7') {
+ req.pri.urgency = *urgency_start - '0';
+ }
+ if (urgency_end == std::end(q)) {
+ break;
+ }
+ p = urgency_end + 1;
+ continue;
+ }
+ if (util::istarts_with(p, std::end(q), std::begin(inc_prefix),
+ std::end(inc_prefix))) {
+ auto inc_start = p + inc_prefix.size();
+ auto inc_end = std::find(inc_start, std::end(q), '&');
+ if (inc_start + 1 == inc_end &&
+ (*inc_start == '0' || *inc_start == '1')) {
+ req.pri.inc = *inc_start - '0';
+ }
+ if (inc_end == std::end(q)) {
+ break;
+ }
+ p = inc_end + 1;
+ continue;
+ }
+
+ p = std::find(p, std::end(q), '&');
+ if (p == std::end(q)) {
+ break;
+ }
+ ++p;
+ }
+ }
+ return req;
+}
+} // namespace
+
+enum FileEntryFlag {
+ FILE_ENTRY_TYPE_DIR = 0x1,
+};
+
+struct FileEntry {
+ uint64_t len;
+ void *map;
+ int fd;
+ uint8_t flags;
+};
+
+namespace {
+std::unordered_map<std::string, FileEntry> file_cache;
+} // namespace
+
+std::pair<FileEntry, int> Stream::open_file(const std::string &path) {
+ auto it = file_cache.find(path);
+ if (it != std::end(file_cache)) {
+ return {(*it).second, 0};
+ }
+
+ auto fd = open(path.c_str(), O_RDONLY);
+ if (fd == -1) {
+ return {{}, -1};
+ }
+
+ struct stat st {};
+ if (fstat(fd, &st) != 0) {
+ close(fd);
+ return {{}, -1};
+ }
+
+ FileEntry fe{};
+ if (st.st_mode & S_IFDIR) {
+ fe.flags |= FILE_ENTRY_TYPE_DIR;
+ fe.fd = -1;
+ close(fd);
+ } else {
+ fe.fd = fd;
+ fe.len = st.st_size;
+ fe.map = mmap(nullptr, fe.len, PROT_READ, MAP_SHARED, fd, 0);
+ if (fe.map == MAP_FAILED) {
+ std::cerr << "mmap: " << strerror(errno) << std::endl;
+ close(fd);
+ return {{}, -1};
+ }
+ }
+
+ file_cache.emplace(path, fe);
+
+ return {std::move(fe), 0};
+}
+
+void Stream::map_file(const FileEntry &fe) {
+ data = static_cast<uint8_t *>(fe.map);
+ datalen = fe.len;
+}
+
+int64_t Stream::find_dyn_length(const std::string_view &path) {
+ assert(path[0] == '/');
+
+ if (path.size() == 1) {
+ return -1;
+ }
+
+ uint64_t n = 0;
+
+ for (auto it = std::begin(path) + 1; it != std::end(path); ++it) {
+ if (*it < '0' || '9' < *it) {
+ return -1;
+ }
+ auto d = *it - '0';
+ if (n > (((1ull << 62) - 1) - d) / 10) {
+ return -1;
+ }
+ n = n * 10 + d;
+ if (n > config.max_dyn_length) {
+ return -1;
+ }
+ }
+
+ return static_cast<int64_t>(n);
+}
+
+namespace {
+nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
+ size_t veccnt, uint32_t *pflags, void *user_data,
+ void *stream_user_data) {
+ auto stream = static_cast<Stream *>(stream_user_data);
+
+ vec[0].base = stream->data;
+ vec[0].len = stream->datalen;
+ *pflags |= NGHTTP3_DATA_FLAG_EOF;
+ if (config.send_trailers) {
+ *pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM;
+ }
+
+ return 1;
+}
+} // namespace
+
+auto dyn_buf = std::make_unique<std::array<uint8_t, 16_k>>();
+
+namespace {
+nghttp3_ssize dyn_read_data(nghttp3_conn *conn, int64_t stream_id,
+ nghttp3_vec *vec, size_t veccnt, uint32_t *pflags,
+ void *user_data, void *stream_user_data) {
+ auto stream = static_cast<Stream *>(stream_user_data);
+
+ if (stream->dynbuflen > MAX_DYNBUFLEN) {
+ return NGHTTP3_ERR_WOULDBLOCK;
+ }
+
+ auto len =
+ std::min(dyn_buf->size(), static_cast<size_t>(stream->dyndataleft));
+
+ vec[0].base = dyn_buf->data();
+ vec[0].len = len;
+
+ stream->dynbuflen += len;
+ stream->dyndataleft -= len;
+
+ if (stream->dyndataleft == 0) {
+ *pflags |= NGHTTP3_DATA_FLAG_EOF;
+ if (config.send_trailers) {
+ *pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM;
+ auto stream_id_str = util::format_uint(stream_id);
+ std::array<nghttp3_nv, 1> trailers{
+ util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str),
+ };
+
+ if (auto rv = nghttp3_conn_submit_trailers(
+ conn, stream_id, trailers.data(), trailers.size());
+ rv != 0) {
+ std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
+ << std::endl;
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ }
+ }
+
+ return 1;
+}
+} // namespace
+
+void Stream::http_acked_stream_data(uint64_t datalen) {
+ if (!dynresp) {
+ return;
+ }
+
+ assert(dynbuflen >= datalen);
+
+ dynbuflen -= datalen;
+}
+
+int Stream::send_status_response(nghttp3_conn *httpconn,
+ unsigned int status_code,
+ const std::vector<HTTPHeader> &extra_headers) {
+ status_resp_body = make_status_body(status_code);
+
+ auto status_code_str = util::format_uint(status_code);
+ auto content_length_str = util::format_uint(status_resp_body.size());
+
+ std::vector<nghttp3_nv> nva(4 + extra_headers.size());
+ nva[0] = util::make_nv_nc(":status"sv, status_code_str);
+ nva[1] = util::make_nv_nn("server"sv, NGTCP2_SERVER);
+ nva[2] = util::make_nv_nn("content-type"sv, "text/html; charset=utf-8");
+ nva[3] = util::make_nv_nc("content-length"sv, content_length_str);
+ for (size_t i = 0; i < extra_headers.size(); ++i) {
+ auto &hdr = extra_headers[i];
+ auto &nv = nva[4 + i];
+ nv = util::make_nv_cc(hdr.name, hdr.value);
+ }
+
+ data = (uint8_t *)status_resp_body.data();
+ datalen = status_resp_body.size();
+
+ nghttp3_data_reader dr{};
+ dr.read_data = read_data;
+
+ if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(),
+ nva.size(), &dr);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (config.send_trailers) {
+ auto stream_id_str = util::format_uint(stream_id);
+ std::array<nghttp3_nv, 1> trailers{
+ util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str),
+ };
+
+ if (auto rv = nghttp3_conn_submit_trailers(
+ httpconn, stream_id, trailers.data(), trailers.size());
+ rv != 0) {
+ std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ }
+
+ handler->shutdown_read(stream_id, NGHTTP3_H3_NO_ERROR);
+
+ return 0;
+}
+
+int Stream::send_redirect_response(nghttp3_conn *httpconn,
+ unsigned int status_code,
+ const std::string_view &path) {
+ return send_status_response(httpconn, status_code, {{"location", path}});
+}
+
+int Stream::start_response(nghttp3_conn *httpconn) {
+ // TODO This should be handled by nghttp3
+ if (uri.empty() || method.empty()) {
+ return send_status_response(httpconn, 400);
+ }
+
+ auto req = request_path(uri, method == "CONNECT");
+ if (req.path.empty()) {
+ return send_status_response(httpconn, 400);
+ }
+
+ auto dyn_len = find_dyn_length(req.path);
+
+ int64_t content_length = -1;
+ nghttp3_data_reader dr{};
+ auto content_type = "text/plain"sv;
+
+ if (dyn_len == -1) {
+ auto path = config.htdocs + req.path;
+ auto [fe, rv] = open_file(path);
+ if (rv != 0) {
+ send_status_response(httpconn, 404);
+ return 0;
+ }
+
+ if (fe.flags & FILE_ENTRY_TYPE_DIR) {
+ send_redirect_response(httpconn, 308,
+ path.substr(config.htdocs.size() - 1) + '/');
+ return 0;
+ }
+
+ content_length = fe.len;
+
+ if (method != "HEAD") {
+ map_file(fe);
+ }
+
+ dr.read_data = read_data;
+
+ auto ext = std::end(req.path) - 1;
+ for (; ext != std::begin(req.path) && *ext != '.' && *ext != '/'; --ext)
+ ;
+ if (*ext == '.') {
+ ++ext;
+ auto it = config.mime_types.find(std::string{ext, std::end(req.path)});
+ if (it != std::end(config.mime_types)) {
+ content_type = (*it).second;
+ }
+ }
+ } else {
+ content_length = dyn_len;
+ dynresp = true;
+ dr.read_data = dyn_read_data;
+
+ if (method != "HEAD") {
+ datalen = dyn_len;
+ dyndataleft = dyn_len;
+ }
+
+ content_type = "application/octet-stream"sv;
+ }
+
+ auto content_length_str = util::format_uint(content_length);
+
+ std::array<nghttp3_nv, 5> nva{
+ util::make_nv_nn(":status"sv, "200"sv),
+ util::make_nv_nn("server"sv, NGTCP2_SERVER),
+ util::make_nv_nn("content-type"sv, content_type),
+ util::make_nv_nc("content-length"sv, content_length_str),
+ };
+
+ size_t nvlen = 4;
+
+ std::string prival;
+
+ if (req.pri.urgency != -1 || req.pri.inc != -1) {
+ nghttp3_pri pri;
+
+ if (auto rv = nghttp3_conn_get_stream_priority(httpconn, &pri, stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_get_stream_priority: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (req.pri.urgency != -1) {
+ pri.urgency = req.pri.urgency;
+ }
+ if (req.pri.inc != -1) {
+ pri.inc = req.pri.inc;
+ }
+
+ if (auto rv = nghttp3_conn_set_stream_priority(httpconn, stream_id, &pri);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_set_stream_priority: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ prival = "u=";
+ prival += pri.urgency + '0';
+ prival += ",i";
+ if (!pri.inc) {
+ prival += "=?0";
+ }
+
+ nva[nvlen++] = util::make_nv_nc("priority"sv, prival);
+ }
+
+ if (!config.quiet) {
+ debug::print_http_response_headers(stream_id, nva.data(), nvlen);
+ }
+
+ if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(),
+ nvlen, &dr);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (config.send_trailers && dyn_len == -1) {
+ auto stream_id_str = util::format_uint(stream_id);
+ std::array<nghttp3_nv, 1> trailers{
+ util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str),
+ };
+
+ if (auto rv = nghttp3_conn_submit_trailers(
+ httpconn, stream_id, trailers.data(), trailers.size());
+ rv != 0) {
+ std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto h = static_cast<Handler *>(w->data);
+ auto s = h->server();
+
+ switch (h->on_write()) {
+ case 0:
+ case NETWORK_ERR_CLOSE_WAIT:
+ return;
+ default:
+ s->remove(h);
+ }
+}
+} // namespace
+
+namespace {
+void close_waitcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ auto h = static_cast<Handler *>(w->data);
+ auto s = h->server();
+ auto conn = h->conn();
+
+ if (ngtcp2_conn_is_in_closing_period(conn)) {
+ if (!config.quiet) {
+ std::cerr << "Closing Period is over" << std::endl;
+ }
+
+ s->remove(h);
+ return;
+ }
+ if (ngtcp2_conn_is_in_draining_period(conn)) {
+ if (!config.quiet) {
+ std::cerr << "Draining Period is over" << std::endl;
+ }
+
+ s->remove(h);
+ return;
+ }
+
+ assert(0);
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+ int rv;
+
+ auto h = static_cast<Handler *>(w->data);
+ auto s = h->server();
+
+ if (!config.quiet) {
+ std::cerr << "Timer expired" << std::endl;
+ }
+
+ rv = h->handle_expiry();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ rv = h->on_write();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return;
+
+fail:
+ switch (rv) {
+ case NETWORK_ERR_CLOSE_WAIT:
+ ev_timer_stop(loop, w);
+ return;
+ default:
+ s->remove(h);
+ return;
+ }
+}
+} // namespace
+
+Handler::Handler(struct ev_loop *loop, Server *server)
+ : loop_(loop),
+ server_(server),
+ qlog_(nullptr),
+ scid_{},
+ httpconn_{nullptr},
+ nkey_update_(0),
+ no_gso_{
+#ifdef UDP_SEGMENT
+ false
+#else // !UDP_SEGMENT
+ true
+#endif // !UDP_SEGMENT
+ },
+ tx_{
+ .data = std::unique_ptr<uint8_t[]>(new uint8_t[64_k]),
+ } {
+ ev_io_init(&wev_, writecb, 0, EV_WRITE);
+ wev_.data = this;
+ ev_timer_init(&timer_, timeoutcb, 0., 0.);
+ timer_.data = this;
+}
+
+Handler::~Handler() {
+ if (!config.quiet) {
+ std::cerr << scid_ << " Closing QUIC connection " << std::endl;
+ }
+
+ ev_timer_stop(loop_, &timer_);
+ ev_io_stop(loop_, &wev_);
+
+ if (httpconn_) {
+ nghttp3_conn_del(httpconn_);
+ }
+
+ if (qlog_) {
+ fclose(qlog_);
+ }
+}
+
+namespace {
+int handshake_completed(ngtcp2_conn *conn, void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+
+ if (!config.quiet) {
+ debug::handshake_completed(conn, user_data);
+ }
+
+ if (h->handshake_completed() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+int Handler::handshake_completed() {
+ if (!config.quiet) {
+ std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name()
+ << std::endl;
+ std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn()
+ << std::endl;
+ }
+
+ if (tls_session_.send_session_ticket() != 0) {
+ std::cerr << "Unable to send session ticket" << std::endl;
+ }
+
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN> token;
+
+ auto path = ngtcp2_conn_get_path(conn_);
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto tokenlen = ngtcp2_crypto_generate_regular_token(
+ token.data(), config.static_secret.data(), config.static_secret.size(),
+ path->remote.addr, path->remote.addrlen, t);
+ if (tokenlen < 0) {
+ if (!config.quiet) {
+ std::cerr << "Unable to generate token" << std::endl;
+ }
+ return 0;
+ }
+
+ if (auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen);
+ rv != 0) {
+ if (!config.quiet) {
+ std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv)
+ << std::endl;
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp,
+ const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) {
+ if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ if (!config.quiet && config.show_secret) {
+ debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN);
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data) {
+ if (!config.quiet && !config.no_quic_dump) {
+ debug::print_crypto_data(crypto_level, data, datalen);
+ }
+
+ return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data,
+ datalen, user_data);
+}
+} // namespace
+
+namespace {
+int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t offset, const uint8_t *data, size_t datalen,
+ void *user_data, void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+
+ if (h->recv_stream_data(flags, stream_id, data, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t offset, uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->acked_stream_data_offset(stream_id, datalen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t datalen) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ if (auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+int stream_open(ngtcp2_conn *conn, int64_t stream_id, void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ h->on_stream_open(stream_id);
+ return 0;
+}
+} // namespace
+
+void Handler::on_stream_open(int64_t stream_id) {
+ if (!ngtcp2_is_bidi_stream(stream_id)) {
+ return;
+ }
+ auto it = streams_.find(stream_id);
+ (void)it;
+ assert(it == std::end(streams_));
+ streams_.emplace(stream_id, std::make_unique<Stream>(stream_id, this));
+}
+
+namespace {
+int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+
+ if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+
+ if (h->on_stream_close(stream_id, app_error_code) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->on_stream_reset(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::on_stream_reset(int64_t stream_id) {
+ if (httpconn_) {
+ if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+namespace {
+int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->on_stream_stop_sending(stream_id) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::on_stream_stop_sending(int64_t stream_id) {
+ if (!httpconn_) {
+ return 0;
+ }
+
+ if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace {
+void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
+ auto dis = std::uniform_int_distribution<uint8_t>(0, 255);
+ std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); });
+}
+} // namespace
+
+namespace {
+int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
+ size_t cidlen, void *user_data) {
+ if (util::generate_secure_random(cid->data, cidlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+ if (ngtcp2_crypto_generate_stateless_reset_token(
+ token, config.static_secret.data(), config.static_secret.size(),
+ cid) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ auto h = static_cast<Handler *>(user_data);
+ h->server()->associate_cid(cid, h);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid,
+ void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ h->server()->dissociate_cid(cid);
+ return 0;
+}
+} // namespace
+
+namespace {
+int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen,
+ void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx,
+ tx_iv, current_rx_secret, current_tx_secret,
+ secretlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path,
+ ngtcp2_path_validation_result res, void *user_data) {
+ if (!config.quiet) {
+ debug::path_validation(path, res);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int extend_max_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams,
+ void *user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ h->extend_max_remote_streams_bidi(max_streams);
+ return 0;
+}
+} // namespace
+
+void Handler::extend_max_remote_streams_bidi(uint64_t max_streams) {
+ if (!httpconn_) {
+ return;
+ }
+
+ nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams);
+}
+
+namespace {
+int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
+ size_t datalen, void *user_data, void *stream_user_data) {
+ if (!config.quiet && !config.no_http_dump) {
+ debug::print_http_data(stream_id, data, datalen);
+ }
+ auto h = static_cast<Handler *>(user_data);
+ h->http_consume(stream_id, datalen);
+ return 0;
+}
+} // namespace
+
+namespace {
+int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
+ size_t nconsumed, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ h->http_consume(stream_id, nconsumed);
+ return 0;
+}
+} // namespace
+
+void Handler::http_consume(int64_t stream_id, size_t nconsumed) {
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(conn_, nconsumed);
+}
+
+namespace {
+int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_begin_request_headers(stream_id);
+ }
+
+ auto h = static_cast<Handler *>(user_data);
+ h->http_begin_request_headers(stream_id);
+ return 0;
+}
+} // namespace
+
+void Handler::http_begin_request_headers(int64_t stream_id) {
+ auto it = streams_.find(stream_id);
+ assert(it != std::end(streams_));
+ auto &stream = (*it).second;
+
+ nghttp3_conn_set_stream_user_data(httpconn_, stream_id, stream.get());
+}
+
+namespace {
+int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id,
+ int32_t token, nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value, uint8_t flags,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_header(stream_id, name, value, flags);
+ }
+
+ auto h = static_cast<Handler *>(user_data);
+ auto stream = static_cast<Stream *>(stream_user_data);
+ h->http_recv_request_header(stream, token, name, value);
+ return 0;
+}
+} // namespace
+
+void Handler::http_recv_request_header(Stream *stream, int32_t token,
+ nghttp3_rcbuf *name,
+ nghttp3_rcbuf *value) {
+ auto v = nghttp3_rcbuf_get_buf(value);
+
+ switch (token) {
+ case NGHTTP3_QPACK_TOKEN__PATH:
+ stream->uri = std::string{v.base, v.base + v.len};
+ break;
+ case NGHTTP3_QPACK_TOKEN__METHOD:
+ stream->method = std::string{v.base, v.base + v.len};
+ break;
+ case NGHTTP3_QPACK_TOKEN__AUTHORITY:
+ stream->authority = std::string{v.base, v.base + v.len};
+ break;
+ }
+}
+
+namespace {
+int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin,
+ void *user_data, void *stream_user_data) {
+ if (!config.quiet) {
+ debug::print_http_end_headers(stream_id);
+ }
+
+ auto h = static_cast<Handler *>(user_data);
+ auto stream = static_cast<Stream *>(stream_user_data);
+ if (h->http_end_request_headers(stream) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::http_end_request_headers(Stream *stream) {
+ if (config.early_response) {
+ if (start_response(stream) != 0) {
+ return -1;
+ }
+
+ shutdown_read(stream->stream_id, NGHTTP3_H3_NO_ERROR);
+ }
+ return 0;
+}
+
+namespace {
+int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ auto stream = static_cast<Stream *>(stream_user_data);
+ if (h->http_end_stream(stream) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::http_end_stream(Stream *stream) {
+ if (!config.early_response) {
+ return start_response(stream);
+ }
+ return 0;
+}
+
+int Handler::start_response(Stream *stream) {
+ return stream->start_response(httpconn_);
+}
+
+namespace {
+int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t datalen, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ auto stream = static_cast<Stream *>(stream_user_data);
+ h->http_acked_stream_data(stream, datalen);
+ return 0;
+}
+} // namespace
+
+void Handler::http_acked_stream_data(Stream *stream, uint64_t datalen) {
+ stream->http_acked_stream_data(datalen);
+
+ if (stream->dynresp && stream->dynbuflen < MAX_DYNBUFLEN - 16_k) {
+ if (auto rv = nghttp3_conn_resume_stream(httpconn_, stream->stream_id);
+ rv != 0) {
+ // TODO Handle error
+ std::cerr << "nghttp3_conn_resume_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ }
+ }
+}
+
+namespace {
+int http_stream_close(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *conn_user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(conn_user_data);
+ h->http_stream_close(stream_id, app_error_code);
+ return 0;
+}
+} // namespace
+
+void Handler::http_stream_close(int64_t stream_id, uint64_t app_error_code) {
+ auto it = streams_.find(stream_id);
+ if (it == std::end(streams_)) {
+ return;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "HTTP stream " << stream_id << " closed with error code "
+ << app_error_code << std::endl;
+ }
+
+ streams_.erase(it);
+
+ if (ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
+ ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
+ }
+}
+
+namespace {
+int http_stop_sending(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->http_stop_sending(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::http_stop_sending(int64_t stream_id, uint64_t app_error_code) {
+ if (auto rv =
+ ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int http_reset_stream(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->http_reset_stream(stream_id, app_error_code) != 0) {
+ return NGHTTP3_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::http_reset_stream(int64_t stream_id, uint64_t app_error_code) {
+ if (auto rv =
+ ngtcp2_conn_shutdown_stream_write(conn_, stream_id, app_error_code);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+int Handler::setup_httpconn() {
+ if (httpconn_) {
+ return 0;
+ }
+
+ if (ngtcp2_conn_get_max_local_streams_uni(conn_) < 3) {
+ std::cerr << "peer does not allow at least 3 unidirectional streams."
+ << std::endl;
+ return -1;
+ }
+
+ nghttp3_callbacks callbacks{
+ ::http_acked_stream_data, // acked_stream_data
+ ::http_stream_close,
+ ::http_recv_data,
+ ::http_deferred_consume,
+ ::http_begin_request_headers,
+ ::http_recv_request_header,
+ ::http_end_request_headers,
+ nullptr, // begin_trailers
+ nullptr, // recv_trailer
+ nullptr, // end_trailers
+ ::http_stop_sending,
+ ::http_end_stream,
+ ::http_reset_stream,
+ };
+ nghttp3_settings settings;
+ nghttp3_settings_default(&settings);
+ settings.qpack_max_dtable_capacity = 4096;
+ settings.qpack_blocked_streams = 100;
+
+ auto mem = nghttp3_mem_default();
+
+ if (auto rv =
+ nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_server_new: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ auto params = ngtcp2_conn_get_local_transport_params(conn_);
+
+ nghttp3_conn_set_max_client_streams_bidi(httpconn_,
+ params->initial_max_streams_bidi);
+
+ int64_t ctrl_stream_id;
+
+ if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (auto rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ fprintf(stderr, "http: control stream=%" PRIx64 "\n", ctrl_stream_id);
+ }
+
+ int64_t qpack_enc_stream_id, qpack_dec_stream_id;
+
+ if (auto rv =
+ ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (auto rv =
+ ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (auto rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id,
+ qpack_dec_stream_id);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ fprintf(stderr,
+ "http: QPACK streams encoder=%" PRIx64 " decoder=%" PRIx64 "\n",
+ qpack_enc_stream_id, qpack_dec_stream_id);
+ }
+
+ return 0;
+}
+
+namespace {
+int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id,
+ uint64_t max_data, void *user_data,
+ void *stream_user_data) {
+ auto h = static_cast<Handler *>(user_data);
+ if (h->extend_max_stream_data(stream_id, max_data) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+ return 0;
+}
+} // namespace
+
+int Handler::extend_max_stream_data(int64_t stream_id, uint64_t max_data) {
+ if (auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); rv != 0) {
+ std::cerr << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+ return 0;
+}
+
+namespace {
+int recv_tx_key(ngtcp2_conn *conn, ngtcp2_crypto_level level, void *user_data) {
+ if (level != NGTCP2_CRYPTO_LEVEL_APPLICATION) {
+ return 0;
+ }
+
+ auto h = static_cast<Handler *>(user_data);
+ if (h->setup_httpconn() != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void write_qlog(void *user_data, uint32_t flags, const void *data,
+ size_t datalen) {
+ auto h = static_cast<Handler *>(user_data);
+ h->write_qlog(data, datalen);
+}
+} // namespace
+
+void Handler::write_qlog(const void *data, size_t datalen) {
+ assert(qlog_);
+ fwrite(data, 1, datalen, qlog_);
+}
+
+int Handler::init(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen, const ngtcp2_cid *dcid,
+ const ngtcp2_cid *scid, const ngtcp2_cid *ocid,
+ const uint8_t *token, size_t tokenlen, uint32_t version,
+ TLSServerContext &tls_ctx) {
+ auto callbacks = ngtcp2_callbacks{
+ nullptr, // client_initial
+ ngtcp2_crypto_recv_client_initial_cb,
+ ::recv_crypto_data,
+ ::handshake_completed,
+ nullptr, // recv_version_negotiation
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ do_hp_mask,
+ ::recv_stream_data,
+ ::acked_stream_data_offset,
+ stream_open,
+ stream_close,
+ nullptr, // recv_stateless_reset
+ nullptr, // recv_retry
+ nullptr, // extend_max_streams_bidi
+ nullptr, // extend_max_streams_uni
+ rand,
+ get_new_connection_id,
+ remove_connection_id,
+ ::update_key,
+ path_validation,
+ nullptr, // select_preferred_addr
+ ::stream_reset,
+ ::extend_max_remote_streams_bidi,
+ nullptr, // extend_max_remote_streams_uni
+ ::extend_max_stream_data,
+ nullptr, // dcid_status
+ nullptr, // handshake_confirmed
+ nullptr, // recv_new_token
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ nullptr, // recv_datagram
+ nullptr, // ack_datagram
+ nullptr, // lost_datagram
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ stream_stop_sending,
+ ngtcp2_crypto_version_negotiation_cb,
+ nullptr, // recv_rx_key
+ ::recv_tx_key,
+ };
+
+ scid_.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(scid_.data, scid_.datalen) != 0) {
+ std::cerr << "Could not generate connection ID" << std::endl;
+ return -1;
+ }
+
+ ngtcp2_settings settings;
+ ngtcp2_settings_default(&settings);
+ settings.log_printf = config.quiet ? nullptr : debug::log_printf;
+ settings.initial_ts = util::timestamp(loop_);
+ settings.token = ngtcp2_vec{const_cast<uint8_t *>(token), tokenlen};
+ settings.cc_algo = config.cc_algo;
+ settings.initial_rtt = config.initial_rtt;
+ settings.max_window = config.max_window;
+ settings.max_stream_window = config.max_stream_window;
+ settings.handshake_timeout = config.handshake_timeout;
+ settings.no_pmtud = config.no_pmtud;
+ settings.ack_thresh = config.ack_thresh;
+ if (config.max_udp_payload_size) {
+ settings.max_tx_udp_payload_size = config.max_udp_payload_size;
+ settings.no_tx_udp_payload_size_shaping = 1;
+ }
+ if (!config.qlog_dir.empty()) {
+ auto path = std::string{config.qlog_dir};
+ path += '/';
+ path += util::format_hex(scid_.data, scid_.datalen);
+ path += ".sqlog";
+ qlog_ = fopen(path.c_str(), "w");
+ if (qlog_ == nullptr) {
+ std::cerr << "Could not open qlog file " << std::quoted(path) << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+ settings.qlog.write = ::write_qlog;
+ settings.qlog.odcid = *scid;
+ }
+ if (!config.preferred_versions.empty()) {
+ settings.preferred_versions = config.preferred_versions.data();
+ settings.preferred_versionslen = config.preferred_versions.size();
+ }
+ if (!config.other_versions.empty()) {
+ settings.other_versions = config.other_versions.data();
+ settings.other_versionslen = config.other_versions.size();
+ }
+
+ ngtcp2_transport_params params;
+ ngtcp2_transport_params_default(&params);
+ params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
+ params.initial_max_stream_data_bidi_remote =
+ config.max_stream_data_bidi_remote;
+ params.initial_max_stream_data_uni = config.max_stream_data_uni;
+ params.initial_max_data = config.max_data;
+ params.initial_max_streams_bidi = config.max_streams_bidi;
+ params.initial_max_streams_uni = config.max_streams_uni;
+ params.max_idle_timeout = config.timeout;
+ params.stateless_reset_token_present = 1;
+ params.active_connection_id_limit = 7;
+
+ if (ocid) {
+ params.original_dcid = *ocid;
+ params.retry_scid = *scid;
+ params.retry_scid_present = 1;
+ } else {
+ params.original_dcid = *scid;
+ }
+
+ if (util::generate_secure_random(params.stateless_reset_token,
+ sizeof(params.stateless_reset_token)) != 0) {
+ std::cerr << "Could not generate stateless reset token" << std::endl;
+ return -1;
+ }
+
+ if (config.preferred_ipv4_addr.len || config.preferred_ipv6_addr.len) {
+ params.preferred_address_present = 1;
+
+ if (config.preferred_ipv4_addr.len) {
+ params.preferred_address.ipv4 = config.preferred_ipv4_addr.su.in;
+ params.preferred_address.ipv4_present = 1;
+ }
+
+ if (config.preferred_ipv6_addr.len) {
+ params.preferred_address.ipv6 = config.preferred_ipv6_addr.su.in6;
+ params.preferred_address.ipv6_present = 1;
+ }
+
+ auto &token = params.preferred_address.stateless_reset_token;
+ if (util::generate_secure_random(token, sizeof(token)) != 0) {
+ std::cerr << "Could not generate preferred address stateless reset token"
+ << std::endl;
+ return -1;
+ }
+
+ params.preferred_address.cid.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(params.preferred_address.cid.data,
+ params.preferred_address.cid.datalen) !=
+ 0) {
+ std::cerr << "Could not generate preferred address connection ID"
+ << std::endl;
+ return -1;
+ }
+ }
+
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ },
+ {
+ const_cast<sockaddr *>(sa),
+ salen,
+ },
+ const_cast<Endpoint *>(&ep),
+ };
+ if (auto rv =
+ ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version,
+ &callbacks, &settings, &params, nullptr, this);
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (tls_session_.init(tls_ctx, this) != 0) {
+ return -1;
+ }
+
+ tls_session_.enable_keylog();
+
+ ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle());
+
+ ev_io_set(&wev_, ep.fd, EV_WRITE);
+
+ return 0;
+}
+
+int Handler::feed_data(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data,
+ size_t datalen) {
+ auto path = ngtcp2_path{
+ {
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ },
+ {
+ const_cast<sockaddr *>(sa),
+ salen,
+ },
+ const_cast<Endpoint *>(&ep),
+ };
+
+ if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen,
+ util::timestamp(loop_));
+ rv != 0) {
+ std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl;
+ switch (rv) {
+ case NGTCP2_ERR_DRAINING:
+ start_draining_period();
+ return NETWORK_ERR_CLOSE_WAIT;
+ case NGTCP2_ERR_RETRY:
+ return NETWORK_ERR_RETRY;
+ case NGTCP2_ERR_DROP_CONN:
+ return NETWORK_ERR_DROP_CONN;
+ case NGTCP2_ERR_CRYPTO:
+ if (!last_error_.error_code) {
+ ngtcp2_connection_close_error_set_transport_error_tls_alert(
+ &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0);
+ }
+ break;
+ default:
+ if (!last_error_.error_code) {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, rv, nullptr, 0);
+ }
+ }
+ return handle_error();
+ }
+
+ return 0;
+}
+
+int Handler::on_read(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen,
+ const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen) {
+ if (auto rv = feed_data(ep, local_addr, sa, salen, pi, data, datalen);
+ rv != 0) {
+ return rv;
+ }
+
+ update_timer();
+
+ return 0;
+}
+
+int Handler::handle_expiry() {
+ auto now = util::timestamp(loop_);
+ if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) {
+ std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv,
+ nullptr, 0);
+ return handle_error();
+ }
+
+ return 0;
+}
+
+int Handler::on_write() {
+ if (ngtcp2_conn_is_in_closing_period(conn_) ||
+ ngtcp2_conn_is_in_draining_period(conn_)) {
+ return 0;
+ }
+
+ if (tx_.send_blocked) {
+ if (auto rv = send_blocked_packet(); rv != 0) {
+ return rv;
+ }
+
+ if (tx_.send_blocked) {
+ return 0;
+ }
+ }
+
+ if (auto rv = write_streams(); rv != 0) {
+ return rv;
+ }
+
+ update_timer();
+
+ return 0;
+}
+
+int Handler::write_streams() {
+ std::array<nghttp3_vec, 16> vec;
+ ngtcp2_path_storage ps, prev_ps;
+ uint32_t prev_ecn = 0;
+ size_t pktcnt = 0;
+ auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_);
+ auto path_max_udp_payload_size =
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(conn_);
+ auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size;
+ uint8_t *bufpos = tx_.data.get();
+ ngtcp2_pkt_info pi;
+ size_t gso_size = 0;
+ auto ts = util::timestamp(loop_);
+
+ ngtcp2_path_storage_zero(&ps);
+ ngtcp2_path_storage_zero(&prev_ps);
+
+ max_pktcnt = std::min(max_pktcnt, static_cast<size_t>(config.max_gso_dgrams));
+
+ for (;;) {
+ int64_t stream_id = -1;
+ int fin = 0;
+ nghttp3_ssize sveccnt = 0;
+
+ if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) {
+ sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin,
+ vec.data(), vec.size());
+ if (sveccnt < 0) {
+ std::cerr << "nghttp3_conn_writev_stream: " << nghttp3_strerror(sveccnt)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(sveccnt),
+ nullptr, 0);
+ return handle_error();
+ }
+ }
+
+ ngtcp2_ssize ndatalen;
+ auto v = vec.data();
+ auto vcnt = static_cast<size_t>(sveccnt);
+
+ uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ if (fin) {
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ auto nwrite = ngtcp2_conn_writev_stream(
+ conn_, &ps.path, &pi, bufpos, max_udp_payload_size, &ndatalen, flags,
+ stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+ assert(ndatalen == -1);
+ nghttp3_conn_block_stream(httpconn_, stream_id);
+ continue;
+ case NGTCP2_ERR_STREAM_SHUT_WR:
+ assert(ndatalen == -1);
+ nghttp3_conn_shutdown_stream_write(httpconn_, stream_id);
+ continue;
+ case NGTCP2_ERR_WRITE_MORE:
+ assert(ndatalen >= 0);
+ if (auto rv =
+ nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr,
+ 0);
+ return handle_error();
+ }
+ continue;
+ }
+
+ assert(ndatalen == -1);
+
+ std::cerr << "ngtcp2_conn_writev_stream: " << ngtcp2_strerror(nwrite)
+ << std::endl;
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &last_error_, nwrite, nullptr, 0);
+ return handle_error();
+ } else if (ndatalen >= 0) {
+ if (auto rv =
+ nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen);
+ rv != 0) {
+ std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr,
+ 0);
+ return handle_error();
+ }
+ }
+
+ if (nwrite == 0) {
+ if (bufpos - tx_.data.get()) {
+ auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ if (auto [nsent, rv] = server_->send_packet(
+ ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data, datalen, gso_size);
+ rv != NETWORK_ERR_OK) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data + nsent, datalen - nsent, gso_size);
+
+ start_wev_endpoint(ep);
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+ }
+
+ ev_io_stop(loop_, &wev_);
+
+ // We are congestion limited.
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+
+ bufpos += nwrite;
+
+ if (pktcnt == 0) {
+ ngtcp2_path_copy(&prev_ps.path, &ps.path);
+ prev_ecn = pi.ecn;
+ gso_size = nwrite;
+ } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) || prev_ecn != pi.ecn ||
+ static_cast<size_t>(nwrite) > gso_size ||
+ (gso_size > path_max_udp_payload_size &&
+ static_cast<size_t>(nwrite) != gso_size)) {
+ auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data - nwrite;
+
+ if (auto [nsent, rv] = server_->send_packet(
+ ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data, datalen, gso_size);
+ rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn,
+ data + nsent, datalen - nsent, gso_size);
+
+ on_send_blocked(*static_cast<Endpoint *>(ps.path.user_data),
+ ps.path.local, ps.path.remote, pi.ecn, bufpos - nwrite,
+ nwrite, 0);
+
+ start_wev_endpoint(ep);
+ } else {
+ auto &ep = *static_cast<Endpoint *>(ps.path.user_data);
+ auto data = bufpos - nwrite;
+
+ if (auto [nsent, rv] =
+ server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote,
+ pi.ecn, data, nwrite, nwrite);
+ rv != 0) {
+ assert(nsent == 0);
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data,
+ nwrite, 0);
+ }
+
+ start_wev_endpoint(ep);
+ }
+
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+
+ if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) {
+ auto &ep = *static_cast<Endpoint *>(ps.path.user_data);
+ auto data = tx_.data.get();
+ auto datalen = bufpos - data;
+
+ if (auto [nsent, rv] =
+ server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote,
+ pi.ecn, data, datalen, gso_size);
+ rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data + nsent,
+ datalen - nsent, gso_size);
+ }
+
+ start_wev_endpoint(ep);
+ ngtcp2_conn_update_pkt_tx_time(conn_, ts);
+ return 0;
+ }
+ }
+}
+
+void Handler::on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen,
+ size_t gso_size) {
+ assert(tx_.num_blocked || !tx_.send_blocked);
+ assert(tx_.num_blocked < 2);
+
+ tx_.send_blocked = true;
+
+ auto &p = tx_.blocked[tx_.num_blocked++];
+
+ memcpy(&p.local_addr.su, local_addr.addr, local_addr.addrlen);
+ memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen);
+
+ p.local_addr.len = local_addr.addrlen;
+ p.remote_addr.len = remote_addr.addrlen;
+ p.endpoint = &ep;
+ p.ecn = ecn;
+ p.data = data;
+ p.datalen = datalen;
+ p.gso_size = gso_size;
+}
+
+void Handler::start_wev_endpoint(const Endpoint &ep) {
+ // We do not close ep.fd, so we can expect that each Endpoint has
+ // unique fd.
+ if (ep.fd != wev_.fd) {
+ if (ev_is_active(&wev_)) {
+ ev_io_stop(loop_, &wev_);
+ }
+
+ ev_io_set(&wev_, ep.fd, EV_WRITE);
+ }
+
+ ev_io_start(loop_, &wev_);
+}
+
+int Handler::send_blocked_packet() {
+ assert(tx_.send_blocked);
+
+ for (; tx_.num_blocked_sent < tx_.num_blocked; ++tx_.num_blocked_sent) {
+ auto &p = tx_.blocked[tx_.num_blocked_sent];
+
+ ngtcp2_addr local_addr{
+ .addr = &p.local_addr.su.sa,
+ .addrlen = p.local_addr.len,
+ };
+ ngtcp2_addr remote_addr{
+ .addr = &p.remote_addr.su.sa,
+ .addrlen = p.remote_addr.len,
+ };
+
+ auto [nsent, rv] =
+ server_->send_packet(*p.endpoint, no_gso_, local_addr, remote_addr,
+ p.ecn, p.data, p.datalen, p.gso_size);
+ if (rv != 0) {
+ assert(NETWORK_ERR_SEND_BLOCKED == rv);
+
+ p.data += nsent;
+ p.datalen -= nsent;
+
+ start_wev_endpoint(*p.endpoint);
+
+ return 0;
+ }
+ }
+
+ tx_.send_blocked = false;
+ tx_.num_blocked = 0;
+ tx_.num_blocked_sent = 0;
+
+ return 0;
+}
+
+void Handler::signal_write() { ev_io_start(loop_, &wev_); }
+
+void Handler::start_draining_period() {
+ ev_io_stop(loop_, &wev_);
+
+ ev_set_cb(&timer_, close_waitcb);
+ timer_.repeat =
+ static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3;
+ ev_timer_again(loop_, &timer_);
+
+ if (!config.quiet) {
+ std::cerr << "Draining period has started (" << timer_.repeat << " seconds)"
+ << std::endl;
+ }
+}
+
+int Handler::start_closing_period() {
+ if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_) ||
+ ngtcp2_conn_is_in_draining_period(conn_)) {
+ return 0;
+ }
+
+ ev_io_stop(loop_, &wev_);
+
+ ev_set_cb(&timer_, close_waitcb);
+ timer_.repeat =
+ static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3;
+ ev_timer_again(loop_, &timer_);
+
+ if (!config.quiet) {
+ std::cerr << "Closing period has started (" << timer_.repeat << " seconds)"
+ << std::endl;
+ }
+
+ conn_closebuf_ = std::make_unique<Buffer>(NGTCP2_MAX_UDP_PAYLOAD_SIZE);
+
+ ngtcp2_path_storage ps;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ ngtcp2_pkt_info pi;
+ auto n = ngtcp2_conn_write_connection_close(
+ conn_, &ps.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(),
+ &last_error_, util::timestamp(loop_));
+ if (n < 0) {
+ std::cerr << "ngtcp2_conn_write_connection_close: " << ngtcp2_strerror(n)
+ << std::endl;
+ return -1;
+ }
+
+ if (n == 0) {
+ return 0;
+ }
+
+ conn_closebuf_->push(n);
+
+ return 0;
+}
+
+int Handler::handle_error() {
+ if (last_error_.type ==
+ NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE) {
+ return -1;
+ }
+
+ if (start_closing_period() != 0) {
+ return -1;
+ }
+
+ if (ngtcp2_conn_is_in_draining_period(conn_)) {
+ return NETWORK_ERR_CLOSE_WAIT;
+ }
+
+ if (auto rv = send_conn_close(); rv != NETWORK_ERR_OK) {
+ return rv;
+ }
+
+ return NETWORK_ERR_CLOSE_WAIT;
+}
+
+int Handler::send_conn_close() {
+ if (!config.quiet) {
+ std::cerr << "Closing Period: TX CONNECTION_CLOSE" << std::endl;
+ }
+
+ assert(conn_closebuf_ && conn_closebuf_->size());
+ assert(conn_);
+ assert(!ngtcp2_conn_is_in_draining_period(conn_));
+
+ auto path = ngtcp2_conn_get_path(conn_);
+
+ return server_->send_packet(
+ *static_cast<Endpoint *>(path->user_data), path->local, path->remote,
+ /* ecn = */ 0, conn_closebuf_->rpos(), conn_closebuf_->size());
+}
+
+void Handler::update_timer() {
+ auto expiry = ngtcp2_conn_get_expiry(conn_);
+ auto now = util::timestamp(loop_);
+
+ if (expiry <= now) {
+ if (!config.quiet) {
+ auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS;
+ std::cerr << "Timer has already expired: " << t << "s" << std::endl;
+ }
+
+ ev_feed_event(loop_, &timer_, EV_TIMER);
+
+ return;
+ }
+
+ auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS;
+ if (!config.quiet) {
+ std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat
+ << std::endl;
+ }
+ timer_.repeat = t;
+ ev_timer_again(loop_, &timer_);
+}
+
+int Handler::recv_stream_data(uint32_t flags, int64_t stream_id,
+ const uint8_t *data, size_t datalen) {
+ if (!config.quiet && !config.no_quic_dump) {
+ debug::print_stream_data(stream_id, data, datalen);
+ }
+
+ if (!httpconn_) {
+ return 0;
+ }
+
+ auto nconsumed = nghttp3_conn_read_stream(
+ httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
+ if (nconsumed < 0) {
+ std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr,
+ 0);
+ return -1;
+ }
+
+ ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed);
+ ngtcp2_conn_extend_max_offset(conn_, nconsumed);
+
+ return 0;
+}
+
+int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen) {
+ auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_);
+ auto aead = &crypto_ctx->aead;
+ auto keylen = ngtcp2_crypto_aead_keylen(aead);
+ auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead);
+
+ ++nkey_update_;
+
+ std::array<uint8_t, 64> rx_key, tx_key;
+
+ if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx,
+ rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(),
+ tx_iv, current_rx_secret, current_tx_secret,
+ secretlen) != 0) {
+ return -1;
+ }
+
+ if (!config.quiet && config.show_secret) {
+ std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl;
+ debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv,
+ ivlen);
+ std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl;
+ debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv,
+ ivlen);
+ }
+
+ return 0;
+}
+
+Server *Handler::server() const { return server_; }
+
+int Handler::on_stream_close(int64_t stream_id, uint64_t app_error_code) {
+ if (!config.quiet) {
+ std::cerr << "QUIC stream " << stream_id << " closed" << std::endl;
+ }
+
+ if (httpconn_) {
+ if (app_error_code == 0) {
+ app_error_code = NGHTTP3_H3_NO_ERROR;
+ }
+ auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code);
+ switch (rv) {
+ case 0:
+ break;
+ case NGHTTP3_ERR_STREAM_NOT_FOUND:
+ if (ngtcp2_is_bidi_stream(stream_id)) {
+ assert(!ngtcp2_conn_is_local_stream(conn_, stream_id));
+ ngtcp2_conn_extend_max_streams_bidi(conn_, 1);
+ }
+ break;
+ default:
+ std::cerr << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv)
+ << std::endl;
+ ngtcp2_connection_close_error_set_application_error(
+ &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void Handler::shutdown_read(int64_t stream_id, int app_error_code) {
+ ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code);
+}
+
+namespace {
+void sreadcb(struct ev_loop *loop, ev_io *w, int revents) {
+ auto ep = static_cast<Endpoint *>(w->data);
+
+ ep->server->on_read(*ep);
+}
+} // namespace
+
+namespace {
+void siginthandler(struct ev_loop *loop, ev_signal *watcher, int revents) {
+ ev_break(loop, EVBREAK_ALL);
+}
+} // namespace
+
+Server::Server(struct ev_loop *loop, TLSServerContext &tls_ctx)
+ : loop_(loop), tls_ctx_(tls_ctx) {
+ ev_signal_init(&sigintev_, siginthandler, SIGINT);
+}
+
+Server::~Server() {
+ disconnect();
+ close();
+}
+
+void Server::disconnect() {
+ config.tx_loss_prob = 0;
+
+ for (auto &ep : endpoints_) {
+ ev_io_stop(loop_, &ep.rev);
+ }
+
+ ev_signal_stop(loop_, &sigintev_);
+
+ while (!handlers_.empty()) {
+ auto it = std::begin(handlers_);
+ auto &h = (*it).second;
+
+ h->handle_error();
+
+ remove(h);
+ }
+}
+
+void Server::close() {
+ for (auto &ep : endpoints_) {
+ ::close(ep.fd);
+ }
+
+ endpoints_.clear();
+}
+
+namespace {
+int create_sock(Address &local_addr, const char *addr, const char *port,
+ int family) {
+ addrinfo hints{};
+ addrinfo *res, *rp;
+ int val = 1;
+
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ if (strcmp(addr, "*") == 0) {
+ addr = nullptr;
+ }
+
+ if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ auto res_d = defer(freeaddrinfo, res);
+
+ int fd = -1;
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ fd = util::create_nonblock_socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (fd == -1) {
+ continue;
+ }
+
+ if (rp->ai_family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+ } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ continue;
+ }
+
+ fd_set_recv_ecn(fd, rp->ai_family);
+ fd_set_ip_mtu_discover(fd, rp->ai_family);
+ fd_set_ip_dontfrag(fd, family);
+
+ if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
+ break;
+ }
+
+ close(fd);
+ }
+
+ if (!rp) {
+ std::cerr << "Could not bind" << std::endl;
+ return -1;
+ }
+
+ socklen_t len = sizeof(local_addr.su.storage);
+ if (getsockname(fd, &local_addr.su.sa, &len) == -1) {
+ std::cerr << "getsockname: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+ local_addr.len = len;
+ local_addr.ifindex = 0;
+
+ return fd;
+}
+
+} // namespace
+
+namespace {
+int add_endpoint(std::vector<Endpoint> &endpoints, const char *addr,
+ const char *port, int af) {
+ Address dest;
+ auto fd = create_sock(dest, addr, port, af);
+ if (fd == -1) {
+ return -1;
+ }
+
+ endpoints.emplace_back();
+ auto &ep = endpoints.back();
+ ep.addr = dest;
+ ep.fd = fd;
+ ev_io_init(&ep.rev, sreadcb, 0, EV_READ);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int add_endpoint(std::vector<Endpoint> &endpoints, const Address &addr) {
+ auto fd = util::create_nonblock_socket(addr.su.sa.sa_family, SOCK_DGRAM, 0);
+ if (fd == -1) {
+ std::cerr << "socket: " << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ int val = 1;
+ if (addr.su.sa.sa_family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+ } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ close(fd);
+ return -1;
+ }
+
+ fd_set_recv_ecn(fd, addr.su.sa.sa_family);
+ fd_set_ip_mtu_discover(fd, addr.su.sa.sa_family);
+ fd_set_ip_dontfrag(fd, addr.su.sa.sa_family);
+
+ if (bind(fd, &addr.su.sa, addr.len) == -1) {
+ std::cerr << "bind: " << strerror(errno) << std::endl;
+ close(fd);
+ return -1;
+ }
+
+ endpoints.emplace_back(Endpoint{});
+ auto &ep = endpoints.back();
+ ep.addr = addr;
+ ep.fd = fd;
+ ev_io_init(&ep.rev, sreadcb, 0, EV_READ);
+
+ return 0;
+}
+} // namespace
+
+int Server::init(const char *addr, const char *port) {
+ endpoints_.reserve(4);
+
+ auto ready = false;
+ if (!util::numeric_host(addr, AF_INET6) &&
+ add_endpoint(endpoints_, addr, port, AF_INET) == 0) {
+ ready = true;
+ }
+ if (!util::numeric_host(addr, AF_INET) &&
+ add_endpoint(endpoints_, addr, port, AF_INET6) == 0) {
+ ready = true;
+ }
+ if (!ready) {
+ return -1;
+ }
+
+ if (config.preferred_ipv4_addr.len &&
+ add_endpoint(endpoints_, config.preferred_ipv4_addr) != 0) {
+ return -1;
+ }
+ if (config.preferred_ipv6_addr.len &&
+ add_endpoint(endpoints_, config.preferred_ipv6_addr) != 0) {
+ return -1;
+ }
+
+ for (auto &ep : endpoints_) {
+ ep.server = this;
+ ep.rev.data = &ep;
+
+ ev_io_set(&ep.rev, ep.fd, EV_READ);
+
+ ev_io_start(loop_, &ep.rev);
+ }
+
+ ev_signal_start(loop_, &sigintev_);
+
+ return 0;
+}
+
+int Server::on_read(Endpoint &ep) {
+ sockaddr_union su;
+ std::array<uint8_t, 64_k> buf;
+ ngtcp2_pkt_hd hd;
+ size_t pktcnt = 0;
+ ngtcp2_pkt_info pi;
+
+ iovec msg_iov;
+ msg_iov.iov_base = buf.data();
+ msg_iov.iov_len = buf.size();
+
+ msghdr msg{};
+ msg.msg_name = &su;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t
+ msg_ctrl[CMSG_SPACE(sizeof(uint8_t)) + CMSG_SPACE(sizeof(in6_pktinfo))];
+ msg.msg_control = msg_ctrl;
+
+ for (; pktcnt < 10;) {
+ msg.msg_namelen = sizeof(su);
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ auto nread = recvmsg(ep.fd, &msg, 0);
+ if (nread == -1) {
+ if (!(errno == EAGAIN || errno == ENOTCONN)) {
+ std::cerr << "recvmsg: " << strerror(errno) << std::endl;
+ }
+ return 0;
+ }
+
+ ++pktcnt;
+
+ pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family);
+ auto local_addr = msghdr_get_local_addr(&msg, su.storage.ss_family);
+ if (!local_addr) {
+ std::cerr << "Unable to obtain local address" << std::endl;
+ continue;
+ }
+
+ set_port(*local_addr, ep.addr);
+
+ if (!config.quiet) {
+ std::array<char, IF_NAMESIZE> ifname;
+ std::cerr << "Received packet: local="
+ << util::straddr(&local_addr->su.sa, local_addr->len)
+ << " remote=" << util::straddr(&su.sa, msg.msg_namelen)
+ << " if=" << if_indextoname(local_addr->ifindex, ifname.data())
+ << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread
+ << " bytes" << std::endl;
+ }
+
+ if (debug::packet_lost(config.rx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated incoming packet loss **" << std::endl;
+ }
+ continue;
+ }
+
+ if (nread == 0) {
+ continue;
+ }
+
+ ngtcp2_version_cid vc;
+
+ switch (auto rv = ngtcp2_pkt_decode_version_cid(&vc, buf.data(), nread,
+ NGTCP2_SV_SCIDLEN);
+ rv) {
+ case 0:
+ break;
+ case NGTCP2_ERR_VERSION_NEGOTIATION:
+ send_version_negotiation(vc.version, vc.scid, vc.scidlen, vc.dcid,
+ vc.dcidlen, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ default:
+ std::cerr << "Could not decode version and CID from QUIC packet header: "
+ << ngtcp2_strerror(rv) << std::endl;
+ continue;
+ }
+
+ auto dcid_key = util::make_cid_key(vc.dcid, vc.dcidlen);
+
+ auto handler_it = handlers_.find(dcid_key);
+ if (handler_it == std::end(handlers_)) {
+ switch (auto rv = ngtcp2_accept(&hd, buf.data(), nread); rv) {
+ case 0:
+ break;
+ case NGTCP2_ERR_RETRY:
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected packet received: length=" << nread
+ << std::endl;
+ }
+ continue;
+ }
+
+ ngtcp2_cid ocid;
+ ngtcp2_cid *pocid = nullptr;
+
+ assert(hd.type == NGTCP2_PKT_INITIAL);
+
+ if (config.validate_addr || hd.token.len) {
+ std::cerr << "Perform stateless address validation" << std::endl;
+ if (hd.token.len == 0) {
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ }
+
+ if (hd.token.base[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY &&
+ hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) {
+ send_stateless_connection_close(&hd, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ }
+
+ switch (hd.token.base[0]) {
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY:
+ if (verify_retry_token(&ocid, &hd, &su.sa, msg.msg_namelen) != 0) {
+ send_stateless_connection_close(&hd, ep, *local_addr, &su.sa,
+ msg.msg_namelen);
+ continue;
+ }
+ pocid = &ocid;
+ break;
+ case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR:
+ if (verify_token(&hd, &su.sa, msg.msg_namelen) != 0) {
+ if (config.validate_addr) {
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen,
+ nread * 3);
+ continue;
+ }
+
+ hd.token.base = nullptr;
+ hd.token.len = 0;
+ }
+ break;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Ignore unrecognized token" << std::endl;
+ }
+ if (config.validate_addr) {
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen,
+ nread * 3);
+ continue;
+ }
+
+ hd.token.base = nullptr;
+ hd.token.len = 0;
+ break;
+ }
+ }
+
+ auto h = std::make_unique<Handler>(loop_, this);
+ if (h->init(ep, *local_addr, &su.sa, msg.msg_namelen, &hd.scid, &hd.dcid,
+ pocid, hd.token.base, hd.token.len, hd.version,
+ tls_ctx_) != 0) {
+ continue;
+ }
+
+ switch (h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi,
+ buf.data(), nread)) {
+ case 0:
+ break;
+ case NETWORK_ERR_RETRY:
+ send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3);
+ continue;
+ default:
+ continue;
+ }
+
+ switch (h->on_write()) {
+ case 0:
+ break;
+ default:
+ continue;
+ }
+
+ std::array<ngtcp2_cid, 2> scids;
+ auto conn = h->conn();
+
+ auto num_scid = ngtcp2_conn_get_num_scid(conn);
+
+ assert(num_scid <= scids.size());
+
+ ngtcp2_conn_get_scid(conn, scids.data());
+
+ for (size_t i = 0; i < num_scid; ++i) {
+ handlers_.emplace(util::make_cid_key(&scids[i]), h.get());
+ }
+
+ handlers_.emplace(dcid_key, h.get());
+
+ h.release();
+
+ continue;
+ }
+
+ auto h = (*handler_it).second;
+ auto conn = h->conn();
+ if (ngtcp2_conn_is_in_closing_period(conn)) {
+ // TODO do exponential backoff.
+ switch (h->send_conn_close()) {
+ case 0:
+ break;
+ default:
+ remove(h);
+ }
+ continue;
+ }
+ if (ngtcp2_conn_is_in_draining_period(conn)) {
+ continue;
+ }
+
+ if (auto rv = h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi,
+ buf.data(), nread);
+ rv != 0) {
+ if (rv != NETWORK_ERR_CLOSE_WAIT) {
+ remove(h);
+ }
+ continue;
+ }
+
+ h->signal_write();
+ }
+
+ return 0;
+}
+
+namespace {
+uint32_t generate_reserved_version(const sockaddr *sa, socklen_t salen,
+ uint32_t version) {
+ uint32_t h = 0x811C9DC5u;
+ const uint8_t *p = (const uint8_t *)sa;
+ const uint8_t *ep = p + salen;
+ for (; p != ep; ++p) {
+ h ^= *p;
+ h *= 0x01000193u;
+ }
+ version = htonl(version);
+ p = (const uint8_t *)&version;
+ ep = p + sizeof(version);
+ for (; p != ep; ++p) {
+ h ^= *p;
+ h *= 0x01000193u;
+ }
+ h &= 0xf0f0f0f0u;
+ h |= 0x0a0a0a0au;
+ return h;
+}
+} // namespace
+
+int Server::send_version_negotiation(uint32_t version, const uint8_t *dcid,
+ size_t dcidlen, const uint8_t *scid,
+ size_t scidlen, Endpoint &ep,
+ const Address &local_addr,
+ const sockaddr *sa, socklen_t salen) {
+ Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};
+ std::array<uint32_t, 1 + max_preferred_versionslen> sv;
+
+ auto p = std::begin(sv);
+
+ *p++ = generate_reserved_version(sa, salen, version);
+
+ if (config.preferred_versions.empty()) {
+ *p++ = NGTCP2_PROTO_VER_V1;
+ } else {
+ for (auto v : config.preferred_versions) {
+ *p++ = v;
+ }
+ }
+
+ auto nwrite = ngtcp2_pkt_write_version_negotiation(
+ buf.wpos(), buf.left(),
+ std::uniform_int_distribution<uint8_t>(
+ 0, std::numeric_limits<uint8_t>::max())(randgen),
+ dcid, dcidlen, scid, scidlen, sv.data(), p - std::begin(sv));
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_pkt_write_version_negotiation: "
+ << ngtcp2_strerror(nwrite) << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep,
+ const Address &local_addr, const sockaddr *sa,
+ socklen_t salen, size_t max_pktlen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Sending Retry packet to [" << host.data()
+ << "]:" << port.data() << std::endl;
+ }
+
+ ngtcp2_cid scid;
+
+ scid.datalen = NGTCP2_SV_SCIDLEN;
+ if (util::generate_secure_random(scid.data, scid.datalen) != 0) {
+ return -1;
+ }
+
+ std::array<uint8_t, NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN> token;
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ auto tokenlen = ngtcp2_crypto_generate_retry_token(
+ token.data(), config.static_secret.data(), config.static_secret.size(),
+ chd->version, sa, salen, &scid, &chd->dcid, t);
+ if (tokenlen < 0) {
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Generated address validation token:" << std::endl;
+ util::hexdump(stderr, token.data(), tokenlen);
+ }
+
+ Buffer buf{
+ std::min(static_cast<size_t>(NGTCP2_MAX_UDP_PAYLOAD_SIZE), max_pktlen)};
+
+ auto nwrite = ngtcp2_crypto_write_retry(buf.wpos(), buf.left(), chd->version,
+ &chd->scid, &scid, &chd->dcid,
+ token.data(), tokenlen);
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_crypto_write_retry failed" << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::send_stateless_connection_close(const ngtcp2_pkt_hd *chd,
+ Endpoint &ep,
+ const Address &local_addr,
+ const sockaddr *sa,
+ socklen_t salen) {
+ Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE};
+
+ auto nwrite = ngtcp2_crypto_write_connection_close(
+ buf.wpos(), buf.left(), chd->version, &chd->scid, &chd->dcid,
+ NGTCP2_INVALID_TOKEN, nullptr, 0);
+ if (nwrite < 0) {
+ std::cerr << "ngtcp2_crypto_write_connection_close failed" << std::endl;
+ return -1;
+ }
+
+ buf.push(nwrite);
+
+ ngtcp2_addr laddr{
+ const_cast<sockaddr *>(&local_addr.su.sa),
+ local_addr.len,
+ };
+ ngtcp2_addr raddr{
+ const_cast<sockaddr *>(sa),
+ salen,
+ };
+
+ if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) !=
+ NETWORK_ERR_OK) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
+ const sockaddr *sa, socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Verifying Retry token from [" << host.data()
+ << "]:" << port.data() << std::endl;
+ util::hexdump(stderr, hd->token.base, hd->token.len);
+ }
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_retry_token(
+ ocid, hd->token.base, hd->token.len, config.static_secret.data(),
+ config.static_secret.size(), hd->version, sa, salen, &hd->dcid,
+ 10 * NGTCP2_SECONDS, t) != 0) {
+ std::cerr << "Could not verify Retry token" << std::endl;
+
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Token was successfully validated" << std::endl;
+ }
+
+ return 0;
+}
+
+int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
+ socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Verifying token from [" << host.data() << "]:" << port.data()
+ << std::endl;
+ util::hexdump(stderr, hd->token.base, hd->token.len);
+ }
+
+ auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (ngtcp2_crypto_verify_regular_token(hd->token.base, hd->token.len,
+ config.static_secret.data(),
+ config.static_secret.size(), sa, salen,
+ 3600 * NGTCP2_SECONDS, t) != 0) {
+ if (!config.quiet) {
+ std::cerr << "Could not verify token" << std::endl;
+ }
+ return -1;
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Token was successfully validated" << std::endl;
+ }
+
+ return 0;
+}
+
+int Server::send_packet(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen) {
+ auto no_gso = false;
+ auto [_, rv] = send_packet(ep, no_gso, local_addr, remote_addr, ecn, data,
+ datalen, datalen);
+
+ return rv;
+}
+
+std::pair<size_t, int>
+Server::send_packet(Endpoint &ep, bool &no_gso, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen, size_t gso_size) {
+ assert(gso_size);
+
+ if (debug::packet_lost(config.tx_loss_prob)) {
+ if (!config.quiet) {
+ std::cerr << "** Simulated outgoing packet loss **" << std::endl;
+ }
+ return {0, NETWORK_ERR_OK};
+ }
+
+ if (no_gso && datalen > gso_size) {
+ size_t nsent = 0;
+
+ for (auto p = data; p < data + datalen; p += gso_size) {
+ auto len = std::min(gso_size, static_cast<size_t>(data + datalen - p));
+
+ auto [n, rv] =
+ send_packet(ep, no_gso, local_addr, remote_addr, ecn, p, len, len);
+ if (rv != 0) {
+ return {nsent, rv};
+ }
+
+ nsent += n;
+ }
+
+ return {nsent, 0};
+ }
+
+ iovec msg_iov;
+ msg_iov.iov_base = const_cast<uint8_t *>(data);
+ msg_iov.iov_len = datalen;
+
+ msghdr msg{};
+ msg.msg_name = const_cast<sockaddr *>(remote_addr.addr);
+ msg.msg_namelen = remote_addr.addrlen;
+ msg.msg_iov = &msg_iov;
+ msg.msg_iovlen = 1;
+
+ uint8_t
+ msg_ctrl[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(in6_pktinfo))];
+
+ memset(msg_ctrl, 0, sizeof(msg_ctrl));
+
+ msg.msg_control = msg_ctrl;
+ msg.msg_controllen = sizeof(msg_ctrl);
+
+ size_t controllen = 0;
+
+ auto cm = CMSG_FIRSTHDR(&msg);
+
+ switch (local_addr.addr->sa_family) {
+ case AF_INET: {
+ controllen += CMSG_SPACE(sizeof(in_pktinfo));
+ cm->cmsg_level = IPPROTO_IP;
+ cm->cmsg_type = IP_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
+ auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cm));
+ memset(pktinfo, 0, sizeof(in_pktinfo));
+ auto addrin = reinterpret_cast<sockaddr_in *>(local_addr.addr);
+ pktinfo->ipi_spec_dst = addrin->sin_addr;
+ break;
+ }
+ case AF_INET6: {
+ controllen += CMSG_SPACE(sizeof(in6_pktinfo));
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
+ auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cm));
+ memset(pktinfo, 0, sizeof(in6_pktinfo));
+ auto addrin = reinterpret_cast<sockaddr_in6 *>(local_addr.addr);
+ pktinfo->ipi6_addr = addrin->sin6_addr;
+ break;
+ }
+ default:
+ assert(0);
+ }
+
+#ifdef UDP_SEGMENT
+ if (datalen > gso_size) {
+ controllen += CMSG_SPACE(sizeof(uint16_t));
+ cm = CMSG_NXTHDR(&msg, cm);
+ cm->cmsg_level = SOL_UDP;
+ cm->cmsg_type = UDP_SEGMENT;
+ cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+ *(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size;
+ }
+#endif // UDP_SEGMENT
+
+ msg.msg_controllen = controllen;
+
+ if (ep.ecn != ecn) {
+ ep.ecn = ecn;
+ fd_set_ecn(ep.fd, ep.addr.su.storage.ss_family, ecn);
+ }
+
+ ssize_t nwrite = 0;
+
+ do {
+ nwrite = sendmsg(ep.fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ switch (errno) {
+ case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+ case EWOULDBLOCK:
+#endif // EAGAIN != EWOULDBLOCK
+ return {0, NETWORK_ERR_SEND_BLOCKED};
+#ifdef UDP_SEGMENT
+ case EIO:
+ if (datalen > gso_size) {
+ // GSO failure; send each packet in a separate sendmsg call.
+ std::cerr << "sendmsg: disabling GSO due to " << strerror(errno)
+ << std::endl;
+
+ no_gso = true;
+
+ return send_packet(ep, no_gso, local_addr, remote_addr, ecn, data,
+ datalen, gso_size);
+ }
+ break;
+#endif // UDP_SEGMENT
+ }
+
+ std::cerr << "sendmsg: " << strerror(errno) << std::endl;
+ // TODO We have packet which is expected to fail to send (e.g.,
+ // path validation to old path).
+ return {0, NETWORK_ERR_OK};
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Sent packet: local="
+ << util::straddr(local_addr.addr, local_addr.addrlen)
+ << " remote="
+ << util::straddr(remote_addr.addr, remote_addr.addrlen)
+ << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite
+ << " bytes" << std::endl;
+ }
+
+ return {nwrite, NETWORK_ERR_OK};
+}
+
+void Server::associate_cid(const ngtcp2_cid *cid, Handler *h) {
+ handlers_.emplace(util::make_cid_key(cid), h);
+}
+
+void Server::dissociate_cid(const ngtcp2_cid *cid) {
+ handlers_.erase(util::make_cid_key(cid));
+}
+
+void Server::remove(const Handler *h) {
+ auto conn = h->conn();
+
+ handlers_.erase(
+ util::make_cid_key(ngtcp2_conn_get_client_initial_dcid(conn)));
+
+ std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(conn));
+ ngtcp2_conn_get_scid(conn, cids.data());
+
+ for (auto &cid : cids) {
+ handlers_.erase(util::make_cid_key(&cid));
+ }
+
+ delete h;
+}
+
+namespace {
+int parse_host_port(Address &dest, int af, const char *first,
+ const char *last) {
+ if (std::distance(first, last) == 0) {
+ return -1;
+ }
+
+ const char *host_begin, *host_end, *it;
+ if (*first == '[') {
+ host_begin = first + 1;
+ it = std::find(host_begin, last, ']');
+ if (it == last) {
+ return -1;
+ }
+ host_end = it;
+ ++it;
+ if (it == last || *it != ':') {
+ return -1;
+ }
+ } else {
+ host_begin = first;
+ it = std::find(host_begin, last, ':');
+ if (it == last) {
+ return -1;
+ }
+ host_end = it;
+ }
+
+ if (++it == last) {
+ return -1;
+ }
+ auto svc_begin = it;
+
+ std::array<char, NI_MAXHOST> host;
+ *std::copy(host_begin, host_end, std::begin(host)) = '\0';
+
+ addrinfo hints{}, *res;
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ if (auto rv = getaddrinfo(host.data(), svc_begin, &hints, &res); rv != 0) {
+ std::cerr << "getaddrinfo: [" << host.data() << "]:" << svc_begin << ": "
+ << gai_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ dest.len = res->ai_addrlen;
+ memcpy(&dest.su, res->ai_addr, res->ai_addrlen);
+
+ freeaddrinfo(res);
+
+ return 0;
+}
+} // namespace
+
+namespace {
+void print_usage() {
+ std::cerr << "Usage: server [OPTIONS] <ADDR> <PORT> <PRIVATE_KEY_FILE> "
+ "<CERTIFICATE_FILE>"
+ << std::endl;
+}
+} // namespace
+
+namespace {
+void config_set_default(Config &config) {
+ config = Config{};
+ config.tx_loss_prob = 0.;
+ config.rx_loss_prob = 0.;
+ config.ciphers = util::crypto_default_ciphers();
+ config.groups = util::crypto_default_groups();
+ config.timeout = 30 * NGTCP2_SECONDS;
+ {
+ auto path = realpath(".", nullptr);
+ assert(path);
+ config.htdocs = path;
+ free(path);
+ }
+ config.mime_types_file = "/etc/mime.types"sv;
+ config.max_data = 1_m;
+ config.max_stream_data_bidi_local = 256_k;
+ config.max_stream_data_bidi_remote = 256_k;
+ config.max_stream_data_uni = 256_k;
+ config.max_window = 6_m;
+ config.max_stream_window = 6_m;
+ config.max_streams_bidi = 100;
+ config.max_streams_uni = 3;
+ config.max_dyn_length = 20_m;
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT;
+ config.max_gso_dgrams = 64;
+ config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT;
+ config.ack_thresh = 2;
+}
+} // namespace
+
+namespace {
+void print_help() {
+ print_usage();
+
+ config_set_default(config);
+
+ std::cout << R"(
+ <ADDR> Address to listen to. '*' binds to any address.
+ <PORT> Port
+ <PRIVATE_KEY_FILE>
+ Path to private key file
+ <CERTIFICATE_FILE>
+ Path to certificate file
+Options:
+ -t, --tx-loss=<P>
+ The probability of losing outgoing packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ -r, --rx-loss=<P>
+ The probability of losing incoming packets. <P> must be
+ [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0
+ means 100% packet loss.
+ --ciphers=<CIPHERS>
+ Specify the cipher suite list to enable.
+ Default: )"
+ << config.ciphers << R"(
+ --groups=<GROUPS>
+ Specify the supported groups.
+ Default: )"
+ << config.groups << R"(
+ -d, --htdocs=<PATH>
+ Specify document root. If this option is not specified,
+ the document root is the current working directory.
+ -q, --quiet Suppress debug output.
+ -s, --show-secret
+ Print out secrets unless --quiet is used.
+ --timeout=<DURATION>
+ Specify idle timeout.
+ Default: )"
+ << util::format_duration(config.timeout) << R"(
+ -V, --validate-addr
+ Perform address validation.
+ --preferred-ipv4-addr=<ADDR>:<PORT>
+ Specify preferred IPv4 address and port.
+ --preferred-ipv6-addr=<ADDR>:<PORT>
+ Specify preferred IPv6 address and port. A numeric IPv6
+ address must be enclosed by '[' and ']' (e.g.,
+ [::1]:8443)
+ --mime-types-file=<PATH>
+ Path to file that contains MIME media types and the
+ extensions.
+ Default: )"
+ << config.mime_types_file << R"(
+ --early-response
+ Start sending response when it receives HTTP header
+ fields without waiting for request body. If HTTP
+ response data is written before receiving request body,
+ STOP_SENDING is sent.
+ --verify-client
+ Request a client certificate. At the moment, we just
+ request a certificate and no verification is done.
+ --qlog-dir=<PATH>
+ Path to the directory where qlog file is stored. The
+ file name of each qlog is the Source Connection ID of
+ server.
+ --no-quic-dump
+ Disables printing QUIC STREAM and CRYPTO frame data out.
+ --no-http-dump
+ Disables printing HTTP response body out.
+ --max-data=<SIZE>
+ The initial connection-level flow control window.
+ Default: )"
+ << util::format_uint_iec(config.max_data) << R"(
+ --max-stream-data-bidi-local=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the local endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_local) << R"(
+ --max-stream-data-bidi-remote=<SIZE>
+ The initial stream-level flow control window for a
+ bidirectional stream that the remote endpoint initiates.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"(
+ --max-stream-data-uni=<SIZE>
+ The initial stream-level flow control window for a
+ unidirectional stream.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_data_uni) << R"(
+ --max-streams-bidi=<N>
+ The number of the concurrent bidirectional streams.
+ Default: )"
+ << config.max_streams_bidi << R"(
+ --max-streams-uni=<N>
+ The number of the concurrent unidirectional streams.
+ Default: )"
+ << config.max_streams_uni << R"(
+ --max-dyn-length=<SIZE>
+ The maximum length of a dynamically generated content.
+ Default: )"
+ << util::format_uint_iec(config.max_dyn_length) << R"(
+ --cc=(cubic|reno|bbr|bbr2)
+ The name of congestion controller algorithm.
+ Default: )"
+ << util::strccalgo(config.cc_algo) << R"(
+ --initial-rtt=<DURATION>
+ Set an initial RTT.
+ Default: )"
+ << util::format_duration(config.initial_rtt) << R"(
+ --max-udp-payload-size=<SIZE>
+ Override maximum UDP payload size that server transmits.
+ --send-trailers
+ Send trailer fields.
+ --max-window=<SIZE>
+ Maximum connection-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_window) << R"(
+ --max-stream-window=<SIZE>
+ Maximum stream-level flow control window size. The
+ window auto-tuning is enabled if nonzero value is given,
+ and window size is scaled up to this value.
+ Default: )"
+ << util::format_uint_iec(config.max_stream_window) << R"(
+ --max-gso-dgrams=<N>
+ Maximum number of UDP datagrams that are sent in a
+ single GSO sendmsg call.
+ Default: )"
+ << config.max_gso_dgrams << R"(
+ --handshake-timeout=<DURATION>
+ Set the QUIC handshake timeout.
+ Default: )"
+ << util::format_duration(config.handshake_timeout) << R"(
+ --preferred-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string in the order of
+ preference. Server negotiates one of those versions if
+ client initially selects a less preferred version.
+ These versions must be supported by libngtcp2. Instead
+ of specifying hex string, there are special aliases
+ available: "v1" indicates QUIC v1, and "v2draft"
+ indicates QUIC v2 draft.
+ --other-versions=<HEX>[[,<HEX>]...]
+ Specify QUIC versions in hex string that are sent in
+ other_versions field of version_information transport
+ parameter. This list can include a version which is not
+ supported by libngtcp2. Instead of specifying hex
+ string, there are special aliases available: "v1"
+ indicates QUIC v1, and "v2draft" indicates QUIC v2
+ draft.
+ --no-pmtud Disables Path MTU Discovery.
+ --ack-thresh=<N>
+ The minimum number of the received ACK eliciting packets
+ that triggers immediate acknowledgement.
+ Default: )"
+ << config.ack_thresh << R"(
+ -h, --help Display this help and exit.
+
+---
+
+ The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+ 10 * 1024). Units are K, M and G (powers of 1024).
+
+ The <DURATION> argument is an integer and an optional unit (e.g., 1s
+ is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms,
+ us, or ns (hours, minutes, seconds, milliseconds, microseconds, and
+ nanoseconds respectively). If a unit is omitted, a second is used
+ as unit.
+
+ The <HEX> argument is an hex string which must start with "0x"
+ (e.g., 0x00000001).)"
+ << std::endl;
+}
+} // namespace
+
+std::ofstream keylog_file;
+
+int main(int argc, char **argv) {
+ config_set_default(config);
+
+ for (;;) {
+ static int flag = 0;
+ constexpr static option long_opts[] = {
+ {"help", no_argument, nullptr, 'h'},
+ {"tx-loss", required_argument, nullptr, 't'},
+ {"rx-loss", required_argument, nullptr, 'r'},
+ {"htdocs", required_argument, nullptr, 'd'},
+ {"quiet", no_argument, nullptr, 'q'},
+ {"show-secret", no_argument, nullptr, 's'},
+ {"validate-addr", no_argument, nullptr, 'V'},
+ {"ciphers", required_argument, &flag, 1},
+ {"groups", required_argument, &flag, 2},
+ {"timeout", required_argument, &flag, 3},
+ {"preferred-ipv4-addr", required_argument, &flag, 4},
+ {"preferred-ipv6-addr", required_argument, &flag, 5},
+ {"mime-types-file", required_argument, &flag, 6},
+ {"early-response", no_argument, &flag, 7},
+ {"verify-client", no_argument, &flag, 8},
+ {"qlog-dir", required_argument, &flag, 9},
+ {"no-quic-dump", no_argument, &flag, 10},
+ {"no-http-dump", no_argument, &flag, 11},
+ {"max-data", required_argument, &flag, 12},
+ {"max-stream-data-bidi-local", required_argument, &flag, 13},
+ {"max-stream-data-bidi-remote", required_argument, &flag, 14},
+ {"max-stream-data-uni", required_argument, &flag, 15},
+ {"max-streams-bidi", required_argument, &flag, 16},
+ {"max-streams-uni", required_argument, &flag, 17},
+ {"max-dyn-length", required_argument, &flag, 18},
+ {"cc", required_argument, &flag, 19},
+ {"initial-rtt", required_argument, &flag, 20},
+ {"max-udp-payload-size", required_argument, &flag, 21},
+ {"send-trailers", no_argument, &flag, 22},
+ {"max-window", required_argument, &flag, 23},
+ {"max-stream-window", required_argument, &flag, 24},
+ {"max-gso-dgrams", required_argument, &flag, 25},
+ {"handshake-timeout", required_argument, &flag, 26},
+ {"preferred-versions", required_argument, &flag, 27},
+ {"other-versions", required_argument, &flag, 28},
+ {"no-pmtud", no_argument, &flag, 29},
+ {"ack-thresh", required_argument, &flag, 30},
+ {nullptr, 0, nullptr, 0}};
+
+ auto optidx = 0;
+ auto c = getopt_long(argc, argv, "d:hqr:st:V", long_opts, &optidx);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'd': {
+ // --htdocs
+ auto path = realpath(optarg, nullptr);
+ if (path == nullptr) {
+ std::cerr << "path: invalid path " << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ config.htdocs = path;
+ free(path);
+ break;
+ }
+ case 'h':
+ // --help
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 'q':
+ // --quiet
+ config.quiet = true;
+ break;
+ case 'r':
+ // --rx-loss
+ config.rx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 's':
+ // --show-secret
+ config.show_secret = true;
+ break;
+ case 't':
+ // --tx-loss
+ config.tx_loss_prob = strtod(optarg, nullptr);
+ break;
+ case 'V':
+ // --validate-addr
+ config.validate_addr = true;
+ break;
+ case '?':
+ print_usage();
+ exit(EXIT_FAILURE);
+ case 0:
+ switch (flag) {
+ case 1:
+ // --ciphers
+ config.ciphers = optarg;
+ break;
+ case 2:
+ // --groups
+ config.groups = optarg;
+ break;
+ case 3:
+ // --timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.timeout = *t;
+ }
+ break;
+ case 4:
+ // --preferred-ipv4-addr
+ if (parse_host_port(config.preferred_ipv4_addr, AF_INET, optarg,
+ optarg + strlen(optarg)) != 0) {
+ std::cerr << "preferred-ipv4-addr: could not use "
+ << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 5:
+ // --preferred-ipv6-addr
+ if (parse_host_port(config.preferred_ipv6_addr, AF_INET6, optarg,
+ optarg + strlen(optarg)) != 0) {
+ std::cerr << "preferred-ipv6-addr: could not use "
+ << std::quoted(optarg) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 6:
+ // --mime-types-file
+ config.mime_types_file = optarg;
+ break;
+ case 7:
+ // --early-response
+ config.early_response = true;
+ break;
+ case 8:
+ // --verify-client
+ config.verify_client = true;
+ break;
+ case 9:
+ // --qlog-dir
+ config.qlog_dir = optarg;
+ break;
+ case 10:
+ // --no-quic-dump
+ config.no_quic_dump = true;
+ break;
+ case 11:
+ // --no-http-dump
+ config.no_http_dump = true;
+ break;
+ case 12:
+ // --max-data
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-data: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_data = *n;
+ }
+ break;
+ case 13:
+ // --max-stream-data-bidi-local
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-local: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_local = *n;
+ }
+ break;
+ case 14:
+ // --max-stream-data-bidi-remote
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-bidi-remote: invalid argument"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_bidi_remote = *n;
+ }
+ break;
+ case 15:
+ // --max-stream-data-uni
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-data-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_data_uni = *n;
+ }
+ break;
+ case 16:
+ // --max-streams-bidi
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-bidi: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_bidi = *n;
+ }
+ break;
+ case 17:
+ // --max-streams-uni
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-streams-uni: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_streams_uni = *n;
+ }
+ break;
+ case 18:
+ // --max-dyn-length
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-dyn-length: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_dyn_length = *n;
+ }
+ break;
+ case 19:
+ // --cc
+ if (strcmp("cubic", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_CUBIC;
+ break;
+ }
+ if (strcmp("reno", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_RENO;
+ break;
+ }
+ if (strcmp("bbr", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR;
+ break;
+ }
+ if (strcmp("bbr2", optarg) == 0) {
+ config.cc_algo = NGTCP2_CC_ALGO_BBR2;
+ break;
+ }
+ std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl;
+ exit(EXIT_FAILURE);
+ case 20:
+ // --initial-rtt
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "initial-rtt: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.initial_rtt = *t;
+ }
+ break;
+ case 21:
+ // --max-udp-payload-size
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-udp-payload-size: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 64_k) {
+ std::cerr << "max-udp-payload-size: must not exceed 65536"
+ << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_udp_payload_size = *n;
+ }
+ break;
+ case 22:
+ // --send-trailers
+ config.send_trailers = true;
+ break;
+ case 23:
+ // --max-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_window = *n;
+ }
+ break;
+ case 24:
+ // --max-stream-window
+ if (auto n = util::parse_uint_iec(optarg); !n) {
+ std::cerr << "max-stream-window: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_stream_window = *n;
+ }
+ break;
+ case 25:
+ // --max-gso-dgrams
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "max-gso-dgrams: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.max_gso_dgrams = *n;
+ }
+ break;
+ case 26:
+ // --handshake-timeout
+ if (auto t = util::parse_duration(optarg); !t) {
+ std::cerr << "handshake-timeout: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.handshake_timeout = *t;
+ }
+ break;
+ case 27: {
+ // --preferred-versions
+ auto l = util::split_str(optarg);
+ if (l.size() > max_preferred_versionslen) {
+ std::cerr << "preferred-versions: too many versions > "
+ << max_preferred_versionslen << std::endl;
+ }
+ config.preferred_versions.resize(l.size());
+ auto it = std::begin(config.preferred_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "preferred-versions: invalid version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ if (!ngtcp2_is_supported_version(*rv)) {
+ std::cerr << "preferred-versions: unsupported version "
+ << std::quoted(k) << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 28: {
+ // --other-versions
+ auto l = util::split_str(optarg);
+ config.other_versions.resize(l.size());
+ auto it = std::begin(config.other_versions);
+ for (const auto &k : l) {
+ if (k == "v1"sv) {
+ *it++ = NGTCP2_PROTO_VER_V1;
+ continue;
+ }
+ if (k == "v2draft"sv) {
+ *it++ = NGTCP2_PROTO_VER_V2_DRAFT;
+ continue;
+ }
+ auto rv = util::parse_version(k);
+ if (!rv) {
+ std::cerr << "other-versions: invalid version " << std::quoted(k)
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ *it++ = *rv;
+ }
+ break;
+ }
+ case 29:
+ // --no-pmtud
+ config.no_pmtud = true;
+ break;
+ case 30:
+ // --ack-thresh
+ if (auto n = util::parse_uint(optarg); !n) {
+ std::cerr << "ack-thresh: invalid argument" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 100) {
+ std::cerr << "ack-thresh: must not exceed 100" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.ack_thresh = *n;
+ }
+ break;
+ }
+ break;
+ default:
+ break;
+ };
+ }
+
+ if (argc - optind < 4) {
+ std::cerr << "Too few arguments" << std::endl;
+ print_usage();
+ exit(EXIT_FAILURE);
+ }
+
+ auto addr = argv[optind++];
+ auto port = argv[optind++];
+ auto private_key_file = argv[optind++];
+ auto cert_file = argv[optind++];
+
+ if (auto n = util::parse_uint(port); !n) {
+ std::cerr << "port: invalid port number" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (*n > 65535) {
+ std::cerr << "port: must not exceed 65535" << std::endl;
+ exit(EXIT_FAILURE);
+ } else {
+ config.port = *n;
+ }
+
+ if (auto mt = util::read_mime_types(config.mime_types_file); !mt) {
+ std::cerr << "mime-types-file: Could not read MIME media types file "
+ << std::quoted(config.mime_types_file) << std::endl;
+ } else {
+ config.mime_types = std::move(*mt);
+ }
+
+ TLSServerContext tls_ctx;
+
+ if (tls_ctx.init(private_key_file, cert_file, AppProtocol::H3) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (config.htdocs.back() != '/') {
+ config.htdocs += '/';
+ }
+
+ std::cerr << "Using document root " << config.htdocs << std::endl;
+
+ auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT);
+
+ auto keylog_filename = getenv("SSLKEYLOGFILE");
+ if (keylog_filename) {
+ keylog_file.open(keylog_filename, std::ios_base::app);
+ if (keylog_file) {
+ tls_ctx.enable_keylog();
+ }
+ }
+
+ if (util::generate_secret(config.static_secret.data(),
+ config.static_secret.size()) != 0) {
+ std::cerr << "Unable to generate static secret" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ Server s(EV_DEFAULT, tls_ctx);
+ if (s.init(addr, port) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ ev_run(EV_DEFAULT, 0);
+
+ s.disconnect();
+ s.close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/examples/server.h b/examples/server.h
new file mode 100644
index 0000000..51fdfd0
--- /dev/null
+++ b/examples/server.h
@@ -0,0 +1,256 @@
+/*
+ * 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 SERVER_H
+#define SERVER_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <vector>
+#include <unordered_map>
+#include <string>
+#include <deque>
+#include <string_view>
+#include <memory>
+
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <nghttp3/nghttp3.h>
+
+#include <ev.h>
+
+#include "server_base.h"
+#include "tls_server_context.h"
+#include "network.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+struct HTTPHeader {
+ HTTPHeader(const std::string_view &name, const std::string_view &value)
+ : name(name), value(value) {}
+
+ std::string_view name;
+ std::string_view value;
+};
+
+class Handler;
+struct FileEntry;
+
+struct Stream {
+ Stream(int64_t stream_id, Handler *handler);
+
+ int start_response(nghttp3_conn *conn);
+ std::pair<FileEntry, int> open_file(const std::string &path);
+ void map_file(const FileEntry &fe);
+ int send_status_response(nghttp3_conn *conn, unsigned int status_code,
+ const std::vector<HTTPHeader> &extra_headers = {});
+ int send_redirect_response(nghttp3_conn *conn, unsigned int status_code,
+ const std::string_view &path);
+ int64_t find_dyn_length(const std::string_view &path);
+ void http_acked_stream_data(uint64_t datalen);
+
+ int64_t stream_id;
+ Handler *handler;
+ // uri is request uri/path.
+ std::string uri;
+ std::string method;
+ std::string authority;
+ std::string status_resp_body;
+ // data is a pointer to the memory which maps file denoted by fd.
+ uint8_t *data;
+ // datalen is the length of mapped file by data.
+ uint64_t datalen;
+ // dynresp is true if dynamic data response is enabled.
+ bool dynresp;
+ // dyndataleft is the number of dynamic data left to send.
+ uint64_t dyndataleft;
+ // dynbuflen is the number of bytes in-flight.
+ uint64_t dynbuflen;
+};
+
+class Server;
+
+// Endpoint is a local endpoint.
+struct Endpoint {
+ Address addr;
+ ev_io rev;
+ Server *server;
+ int fd;
+ // ecn is the last ECN bits set to fd.
+ unsigned int ecn;
+};
+
+class Handler : public HandlerBase {
+public:
+ Handler(struct ev_loop *loop, Server *server);
+ ~Handler();
+
+ int init(const Endpoint &ep, const Address &local_addr, const sockaddr *sa,
+ socklen_t salen, const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
+ const ngtcp2_cid *ocid, const uint8_t *token, size_t tokenlen,
+ uint32_t version, TLSServerContext &tls_ctx);
+
+ int on_read(const Endpoint &ep, const Address &local_addr, const sockaddr *sa,
+ socklen_t salen, const ngtcp2_pkt_info *pi, uint8_t *data,
+ size_t datalen);
+ int on_write();
+ int write_streams();
+ int feed_data(const Endpoint &ep, const Address &local_addr,
+ const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi,
+ uint8_t *data, size_t datalen);
+ void update_timer();
+ int handle_expiry();
+ void signal_write();
+ int handshake_completed();
+
+ Server *server() const;
+ int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data,
+ size_t datalen);
+ int acked_stream_data_offset(int64_t stream_id, uint64_t datalen);
+ uint32_t version() const;
+ void on_stream_open(int64_t stream_id);
+ int on_stream_close(int64_t stream_id, uint64_t app_error_code);
+ void start_draining_period();
+ int start_closing_period();
+ int handle_error();
+ int send_conn_close();
+
+ int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
+ ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
+ ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
+ const uint8_t *current_rx_secret,
+ const uint8_t *current_tx_secret, size_t secretlen);
+
+ int setup_httpconn();
+ void http_consume(int64_t stream_id, size_t nconsumed);
+ void extend_max_remote_streams_bidi(uint64_t max_streams);
+ Stream *find_stream(int64_t stream_id);
+ void http_begin_request_headers(int64_t stream_id);
+ void http_recv_request_header(Stream *stream, int32_t token,
+ nghttp3_rcbuf *name, nghttp3_rcbuf *value);
+ int http_end_request_headers(Stream *stream);
+ int http_end_stream(Stream *stream);
+ int start_response(Stream *stream);
+ int on_stream_reset(int64_t stream_id);
+ int on_stream_stop_sending(int64_t stream_id);
+ int extend_max_stream_data(int64_t stream_id, uint64_t max_data);
+ void shutdown_read(int64_t stream_id, int app_error_code);
+ void http_acked_stream_data(Stream *stream, uint64_t datalen);
+ void http_stream_close(int64_t stream_id, uint64_t app_error_code);
+ int http_stop_sending(int64_t stream_id, uint64_t app_error_code);
+ int http_reset_stream(int64_t stream_id, uint64_t app_error_code);
+
+ void write_qlog(const void *data, size_t datalen);
+
+ void on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen, size_t gso_size);
+ void start_wev_endpoint(const Endpoint &ep);
+ int send_blocked_packet();
+
+private:
+ struct ev_loop *loop_;
+ Server *server_;
+ ev_io wev_;
+ ev_timer timer_;
+ FILE *qlog_;
+ ngtcp2_cid scid_;
+ nghttp3_conn *httpconn_;
+ std::unordered_map<int64_t, std::unique_ptr<Stream>> streams_;
+ // conn_closebuf_ contains a packet which contains CONNECTION_CLOSE.
+ // This packet is repeatedly sent as a response to the incoming
+ // packet in draining period.
+ std::unique_ptr<Buffer> conn_closebuf_;
+ // nkey_update_ is the number of key update occurred.
+ size_t nkey_update_;
+ bool no_gso_;
+
+ struct {
+ bool send_blocked;
+ size_t num_blocked;
+ size_t num_blocked_sent;
+ // blocked field is effective only when send_blocked is true.
+ struct {
+ Endpoint *endpoint;
+ Address local_addr;
+ Address remote_addr;
+ unsigned int ecn;
+ const uint8_t *data;
+ size_t datalen;
+ size_t gso_size;
+ } blocked[2];
+ std::unique_ptr<uint8_t[]> data;
+ } tx_;
+};
+
+class Server {
+public:
+ Server(struct ev_loop *loop, TLSServerContext &tls_ctx);
+ ~Server();
+
+ int init(const char *addr, const char *port);
+ void disconnect();
+ void close();
+
+ int on_read(Endpoint &ep);
+ int send_version_negotiation(uint32_t version, const uint8_t *dcid,
+ size_t dcidlen, const uint8_t *scid,
+ size_t scidlen, Endpoint &ep,
+ const Address &local_addr, const sockaddr *sa,
+ socklen_t salen);
+ int send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep,
+ const Address &local_addr, const sockaddr *sa, socklen_t salen,
+ size_t max_pktlen);
+ int send_stateless_connection_close(const ngtcp2_pkt_hd *chd, Endpoint &ep,
+ const Address &local_addr,
+ const sockaddr *sa, socklen_t salen);
+ int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
+ const sockaddr *sa, socklen_t salen);
+ int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
+ socklen_t salen);
+ int send_packet(Endpoint &ep, const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr, unsigned int ecn,
+ const uint8_t *data, size_t datalen);
+ std::pair<size_t, int> send_packet(Endpoint &ep, bool &no_gso,
+ const ngtcp2_addr &local_addr,
+ const ngtcp2_addr &remote_addr,
+ unsigned int ecn, const uint8_t *data,
+ size_t datalen, size_t gso_size);
+ void remove(const Handler *h);
+
+ void associate_cid(const ngtcp2_cid *cid, Handler *h);
+ void dissociate_cid(const ngtcp2_cid *cid);
+
+private:
+ std::unordered_map<std::string, Handler *> handlers_;
+ struct ev_loop *loop_;
+ std::vector<Endpoint> endpoints_;
+ TLSServerContext &tls_ctx_;
+ ev_signal sigintev_;
+};
+
+#endif // SERVER_H
diff --git a/examples/server_base.cc b/examples/server_base.cc
new file mode 100644
index 0000000..aea86bd
--- /dev/null
+++ b/examples/server_base.cc
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+#include "server_base.h"
+
+#include <cassert>
+#include <array>
+#include <iostream>
+
+#include "debug.h"
+
+using namespace ngtcp2;
+
+extern Config config;
+
+Buffer::Buffer(const uint8_t *data, size_t datalen)
+ : buf{data, data + datalen}, begin(buf.data()), tail(begin + datalen) {}
+Buffer::Buffer(size_t datalen) : buf(datalen), begin(buf.data()), tail(begin) {}
+
+static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+ return h->conn();
+}
+
+HandlerBase::HandlerBase() : conn_ref_{get_conn, this}, conn_(nullptr) {
+ ngtcp2_connection_close_error_default(&last_error_);
+}
+
+HandlerBase::~HandlerBase() {
+ if (conn_) {
+ ngtcp2_conn_del(conn_);
+ }
+}
+
+ngtcp2_conn *HandlerBase::conn() const { return conn_; }
+
+ngtcp2_crypto_conn_ref *HandlerBase::conn_ref() { return &conn_ref_; }
diff --git a/examples/server_base.h b/examples/server_base.h
new file mode 100644
index 0000000..adbbd20
--- /dev/null
+++ b/examples/server_base.h
@@ -0,0 +1,194 @@
+/*
+ * 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 SERVER_BASE_H
+#define SERVER_BASE_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <vector>
+#include <deque>
+#include <unordered_map>
+#include <string>
+#include <string_view>
+#include <functional>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include "tls_server_session.h"
+#include "network.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+struct Config {
+ Address preferred_ipv4_addr;
+ Address preferred_ipv6_addr;
+ // tx_loss_prob is probability of losing outgoing packet.
+ double tx_loss_prob;
+ // rx_loss_prob is probability of losing incoming packet.
+ double rx_loss_prob;
+ // ciphers is the list of enabled ciphers.
+ const char *ciphers;
+ // groups is the list of supported groups.
+ const char *groups;
+ // htdocs is a root directory to serve documents.
+ std::string htdocs;
+ // mime_types_file is a path to "MIME media types and the
+ // extensions" file. Ubuntu mime-support package includes it in
+ // /etc/mime/types.
+ std::string_view mime_types_file;
+ // mime_types maps file extension to MIME media type.
+ std::unordered_map<std::string, std::string> mime_types;
+ // port is the port number which server listens on for incoming
+ // connections.
+ uint16_t port;
+ // quiet suppresses the output normally shown except for the error
+ // messages.
+ bool quiet;
+ // timeout is an idle timeout for QUIC connection.
+ ngtcp2_duration timeout;
+ // show_secret is true if transport secrets should be printed out.
+ bool show_secret;
+ // validate_addr is true if server requires address validation.
+ bool validate_addr;
+ // early_response is true if server starts sending response when it
+ // receives HTTP header fields without waiting for request body. If
+ // HTTP response data is written before receiving request body,
+ // STOP_SENDING is sent.
+ bool early_response;
+ // verify_client is true if server verifies client with X.509
+ // certificate based authentication.
+ bool verify_client;
+ // qlog_dir is the path to directory where qlog is stored.
+ std::string_view qlog_dir;
+ // no_quic_dump is true if hexdump of QUIC STREAM and CRYPTO data
+ // should be disabled.
+ bool no_quic_dump;
+ // no_http_dump is true if hexdump of HTTP response body should be
+ // disabled.
+ bool no_http_dump;
+ // max_data is the initial connection-level flow control window.
+ uint64_t max_data;
+ // max_stream_data_bidi_local is the initial stream-level flow
+ // control window for a bidirectional stream that the local endpoint
+ // initiates.
+ uint64_t max_stream_data_bidi_local;
+ // max_stream_data_bidi_remote is the initial stream-level flow
+ // control window for a bidirectional stream that the remote
+ // endpoint initiates.
+ uint64_t max_stream_data_bidi_remote;
+ // max_stream_data_uni is the initial stream-level flow control
+ // window for a unidirectional stream.
+ uint64_t max_stream_data_uni;
+ // max_streams_bidi is the number of the concurrent bidirectional
+ // streams.
+ uint64_t max_streams_bidi;
+ // max_streams_uni is the number of the concurrent unidirectional
+ // streams.
+ uint64_t max_streams_uni;
+ // max_window is the maximum connection-level flow control window
+ // size if auto-tuning is enabled.
+ uint64_t max_window;
+ // max_stream_window is the maximum stream-level flow control window
+ // size if auto-tuning is enabled.
+ uint64_t max_stream_window;
+ // max_dyn_length is the maximum length of dynamically generated
+ // response.
+ uint64_t max_dyn_length;
+ // static_secret is used to derive keying materials for Retry and
+ // Stateless Retry token.
+ std::array<uint8_t, 32> static_secret;
+ // cc_algo is the congestion controller algorithm.
+ ngtcp2_cc_algo cc_algo;
+ // initial_rtt is an initial RTT.
+ ngtcp2_duration initial_rtt;
+ // max_udp_payload_size is the maximum UDP payload size that server
+ // transmits.
+ size_t max_udp_payload_size;
+ // send_trailers controls whether server sends trailer fields or
+ // not.
+ bool send_trailers;
+ // max_gso_dgrams is the maximum number of UDP datagrams in one GSO
+ // sendmsg call.
+ size_t max_gso_dgrams;
+ // handshake_timeout is the period of time before giving up QUIC
+ // connection establishment.
+ ngtcp2_duration handshake_timeout;
+ // preferred_versions includes QUIC versions in the order of
+ // preference. Server negotiates one of those versions if a client
+ // initially selects a less preferred version.
+ std::vector<uint32_t> preferred_versions;
+ // other_versions includes QUIC versions that are sent in
+ // other_versions field of version_information transport_parameter.
+ std::vector<uint32_t> other_versions;
+ // no_pmtud disables Path MTU Discovery.
+ bool no_pmtud;
+ // ack_thresh is the minimum number of the received ACK eliciting
+ // packets that triggers immediate acknowledgement.
+ size_t ack_thresh;
+};
+
+struct Buffer {
+ Buffer(const uint8_t *data, size_t datalen);
+ explicit Buffer(size_t datalen);
+
+ size_t size() const { return tail - begin; }
+ size_t left() const { return buf.data() + buf.size() - tail; }
+ uint8_t *const wpos() { return tail; }
+ const uint8_t *rpos() const { return begin; }
+ void push(size_t len) { tail += len; }
+ void reset() { tail = begin; }
+
+ std::vector<uint8_t> buf;
+ // begin points to the beginning of the buffer. This might point to
+ // buf.data() if a buffer space is allocated by this object. It is
+ // also allowed to point to the external shared buffer.
+ uint8_t *begin;
+ // tail points to the position of the buffer where write should
+ // occur.
+ uint8_t *tail;
+};
+
+class HandlerBase {
+public:
+ HandlerBase();
+ ~HandlerBase();
+
+ ngtcp2_conn *conn() const;
+
+ TLSServerSession *get_session() { return &tls_session_; }
+
+ ngtcp2_crypto_conn_ref *conn_ref();
+
+protected:
+ ngtcp2_crypto_conn_ref conn_ref_;
+ TLSServerSession tls_session_;
+ ngtcp2_conn *conn_;
+ ngtcp2_connection_close_error last_error_;
+};
+
+#endif // SERVER_BASE_H
diff --git a/examples/shared.cc b/examples/shared.cc
new file mode 100644
index 0000000..d65819a
--- /dev/null
+++ b/examples/shared.cc
@@ -0,0 +1,385 @@
+/*
+ * 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"
+
+#include <nghttp3/nghttp3.h>
+
+#include <cstring>
+#include <cassert>
+#include <iostream>
+
+#include <unistd.h>
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif // HAVE_NETINET_IN_H
+#ifdef HAVE_ASM_TYPES_H
+# include <asm/types.h>
+#endif // HAVE_ASM_TYPES_H
+#ifdef HAVE_LINUX_NETLINK_H
+# include <linux/netlink.h>
+#endif // HAVE_LINUX_NETLINK_H
+#ifdef HAVE_LINUX_RTNETLINK_H
+# include <linux/rtnetlink.h>
+#endif // HAVE_LINUX_RTNETLINK_H
+
+#include "template.h"
+
+namespace ngtcp2 {
+
+unsigned int msghdr_get_ecn(msghdr *msg, int family) {
+ switch (family) {
+ case AF_INET:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS &&
+ cmsg->cmsg_len) {
+ return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg));
+ }
+ }
+ break;
+ case AF_INET6:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS &&
+ cmsg->cmsg_len) {
+ return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg));
+ }
+ }
+ break;
+ }
+
+ return 0;
+}
+
+void fd_set_ecn(int fd, int family, unsigned int ecn) {
+ switch (family) {
+ case AF_INET:
+ if (setsockopt(fd, IPPROTO_IP, IP_TOS, &ecn,
+ static_cast<socklen_t>(sizeof(ecn))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ }
+ break;
+ case AF_INET6:
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &ecn,
+ static_cast<socklen_t>(sizeof(ecn))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ }
+ break;
+ }
+}
+
+void fd_set_recv_ecn(int fd, int family) {
+ unsigned int tos = 1;
+ switch (family) {
+ case AF_INET:
+ if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &tos,
+ static_cast<socklen_t>(sizeof(tos))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ }
+ break;
+ case AF_INET6:
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &tos,
+ static_cast<socklen_t>(sizeof(tos))) == -1) {
+ std::cerr << "setsockopt: " << strerror(errno) << std::endl;
+ }
+ break;
+ }
+}
+
+void fd_set_ip_mtu_discover(int fd, int family) {
+#if defined(IP_MTU_DISCOVER) && defined(IPV6_MTU_DISCOVER)
+ int val;
+
+ switch (family) {
+ case AF_INET:
+ val = IP_PMTUDISC_DO;
+ if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: IP_MTU_DISCOVER: " << strerror(errno)
+ << std::endl;
+ }
+ break;
+ case AF_INET6:
+ val = IPV6_PMTUDISC_DO;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: IPV6_MTU_DISCOVER: " << strerror(errno)
+ << std::endl;
+ }
+ break;
+ }
+#endif // defined(IP_MTU_DISCOVER) && defined(IPV6_MTU_DISCOVER)
+}
+
+void fd_set_ip_dontfrag(int fd, int family) {
+#if defined(IP_DONTFRAG) && defined(IPV6_DONTFRAG)
+ int val = 1;
+
+ switch (family) {
+ case AF_INET:
+ if (setsockopt(fd, IPPROTO_IP, IP_DONTFRAG, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: IP_DONTFRAG: " << strerror(errno) << std::endl;
+ }
+ break;
+ case AF_INET6:
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_DONTFRAG, &val,
+ static_cast<socklen_t>(sizeof(val))) == -1) {
+ std::cerr << "setsockopt: IPV6_DONTFRAG: " << strerror(errno)
+ << std::endl;
+ }
+ break;
+ }
+#endif // defined(IP_DONTFRAG) && defined(IPV6_DONTFRAG)
+}
+
+std::optional<Address> msghdr_get_local_addr(msghdr *msg, int family) {
+ switch (family) {
+ case AF_INET:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
+ auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cmsg));
+ Address res{};
+ res.ifindex = pktinfo->ipi_ifindex;
+ res.len = sizeof(res.su.in);
+ auto &sa = res.su.in;
+ sa.sin_family = AF_INET;
+ sa.sin_addr = pktinfo->ipi_addr;
+ return res;
+ }
+ }
+ return {};
+ case AF_INET6:
+ for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
+ auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsg));
+ Address res{};
+ res.ifindex = pktinfo->ipi6_ifindex;
+ res.len = sizeof(res.su.in6);
+ auto &sa = res.su.in6;
+ sa.sin6_family = AF_INET6;
+ sa.sin6_addr = pktinfo->ipi6_addr;
+ return res;
+ }
+ }
+ return {};
+ }
+ return {};
+}
+
+void set_port(Address &dst, Address &src) {
+ switch (dst.su.storage.ss_family) {
+ case AF_INET:
+ assert(AF_INET == src.su.storage.ss_family);
+ dst.su.in.sin_port = src.su.in.sin_port;
+ return;
+ case AF_INET6:
+ assert(AF_INET6 == src.su.storage.ss_family);
+ dst.su.in6.sin6_port = src.su.in6.sin6_port;
+ return;
+ default:
+ assert(0);
+ }
+}
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+
+struct nlmsg {
+ nlmsghdr hdr;
+ rtmsg msg;
+ rtattr dst;
+ in_addr_union dst_addr;
+};
+
+namespace {
+int send_netlink_msg(int fd, const Address &remote_addr) {
+ nlmsg nlmsg{};
+ nlmsg.hdr.nlmsg_type = RTM_GETROUTE;
+ nlmsg.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+ nlmsg.msg.rtm_family = remote_addr.su.sa.sa_family;
+
+ nlmsg.dst.rta_type = RTA_DST;
+
+ switch (remote_addr.su.sa.sa_family) {
+ case AF_INET:
+ nlmsg.dst.rta_len = RTA_LENGTH(sizeof(remote_addr.su.in.sin_addr));
+ memcpy(RTA_DATA(&nlmsg.dst), &remote_addr.su.in.sin_addr,
+ sizeof(remote_addr.su.in.sin_addr));
+ break;
+ case AF_INET6:
+ nlmsg.dst.rta_len = RTA_LENGTH(sizeof(remote_addr.su.in6.sin6_addr));
+ memcpy(RTA_DATA(&nlmsg.dst), &remote_addr.su.in6.sin6_addr,
+ sizeof(remote_addr.su.in6.sin6_addr));
+ break;
+ default:
+ assert(0);
+ }
+
+ nlmsg.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(nlmsg.msg) + nlmsg.dst.rta_len);
+
+ sockaddr_nl sa{};
+ sa.nl_family = AF_NETLINK;
+
+ iovec iov{&nlmsg, nlmsg.hdr.nlmsg_len};
+ msghdr msg{};
+ msg.msg_name = &sa;
+ msg.msg_namelen = sizeof(sa);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ ssize_t nwrite;
+
+ do {
+ nwrite = sendmsg(fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ std::cerr << "sendmsg: Could not write netlink message: " << strerror(errno)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int recv_netlink_msg(in_addr_union &iau, int fd) {
+ std::array<uint8_t, 8192> buf;
+ iovec iov = {buf.data(), buf.size()};
+ sockaddr_nl sa{};
+ msghdr msg{};
+
+ msg.msg_name = &sa;
+ msg.msg_namelen = sizeof(sa);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ ssize_t nread;
+
+ do {
+ nread = recvmsg(fd, &msg, 0);
+ } while (nread == -1 && errno == EINTR);
+
+ if (nread == -1) {
+ std::cerr << "recvmsg: Could not receive netlink message: "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ for (auto hdr = reinterpret_cast<nlmsghdr *>(buf.data());
+ NLMSG_OK(hdr, nread); hdr = NLMSG_NEXT(hdr, nread)) {
+ switch (hdr->nlmsg_type) {
+ case NLMSG_DONE:
+ std::cerr << "netlink: no info returned from kernel" << std::endl;
+ return -1;
+ case NLMSG_NOOP:
+ continue;
+ case NLMSG_ERROR:
+ std::cerr << "netlink: "
+ << strerror(-static_cast<nlmsgerr *>(NLMSG_DATA(hdr))->error)
+ << std::endl;
+ return -1;
+ }
+
+ auto attrlen = hdr->nlmsg_len - NLMSG_SPACE(sizeof(rtmsg));
+
+ for (auto rta = reinterpret_cast<rtattr *>(
+ static_cast<uint8_t *>(NLMSG_DATA(hdr)) + sizeof(rtmsg));
+ RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
+ if (rta->rta_type != RTA_PREFSRC) {
+ continue;
+ }
+
+ size_t in_addrlen;
+
+ switch (static_cast<rtmsg *>(NLMSG_DATA(hdr))->rtm_family) {
+ case AF_INET:
+ in_addrlen = sizeof(in_addr);
+ break;
+ case AF_INET6:
+ in_addrlen = sizeof(in6_addr);
+ break;
+ default:
+ assert(0);
+ abort();
+ }
+
+ if (RTA_LENGTH(in_addrlen) != rta->rta_len) {
+ return -1;
+ }
+
+ memcpy(&iau, RTA_DATA(rta), in_addrlen);
+
+ return 0;
+ }
+ }
+
+ return -1;
+}
+} // namespace
+
+int get_local_addr(in_addr_union &iau, const Address &remote_addr) {
+ sockaddr_nl sa{};
+ sa.nl_family = AF_NETLINK;
+
+ auto fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (fd == -1) {
+ std::cerr << "socket: Could not create netlink socket: " << strerror(errno)
+ << std::endl;
+ return -1;
+ }
+
+ auto fd_d = defer(close, fd);
+
+ if (bind(fd, reinterpret_cast<sockaddr *>(&sa), sizeof(sa)) == -1) {
+ std::cerr << "bind: Could not bind netlink socket: " << strerror(errno)
+ << std::endl;
+ return -1;
+ }
+
+ if (send_netlink_msg(fd, remote_addr) != 0) {
+ return -1;
+ }
+
+ return recv_netlink_msg(iau, fd);
+}
+
+#endif // HAVE_LINUX_NETLINK_H
+
+bool addreq(const sockaddr *sa, const in_addr_union &iau) {
+ switch (sa->sa_family) {
+ case AF_INET:
+ return memcmp(&reinterpret_cast<const sockaddr_in *>(sa)->sin_addr, &iau.in,
+ sizeof(iau.in)) == 0;
+ case AF_INET6:
+ return memcmp(&reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr,
+ &iau.in6, sizeof(iau.in6)) == 0;
+ default:
+ assert(0);
+ abort();
+ }
+}
+
+} // namespace ngtcp2
diff --git a/examples/shared.h b/examples/shared.h
new file mode 100644
index 0000000..e0e4cec
--- /dev/null
+++ b/examples/shared.h
@@ -0,0 +1,96 @@
+/*
+ * 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 SHARED_H
+#define SHARED_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <optional>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "network.h"
+
+namespace ngtcp2 {
+
+enum class AppProtocol {
+ H3,
+ HQ,
+};
+
+constexpr uint8_t HQ_ALPN[] = "\xahq-interop\x5hq-29\x5hq-30\x5hq-31\x5hq-32";
+constexpr uint8_t HQ_ALPN_DRAFT29[] = "\x5hq-29";
+constexpr uint8_t HQ_ALPN_DRAFT30[] = "\x5hq-30";
+constexpr uint8_t HQ_ALPN_DRAFT31[] = "\x5hq-31";
+constexpr uint8_t HQ_ALPN_DRAFT32[] = "\x5hq-32";
+constexpr uint8_t HQ_ALPN_V1[] = "\xahq-interop";
+
+constexpr uint8_t H3_ALPN[] = "\x2h3\x5h3-29\x5h3-30\x5h3-31\x5h3-32";
+constexpr uint8_t H3_ALPN_DRAFT29[] = "\x5h3-29";
+constexpr uint8_t H3_ALPN_DRAFT30[] = "\x5h3-30";
+constexpr uint8_t H3_ALPN_DRAFT31[] = "\x5h3-31";
+constexpr uint8_t H3_ALPN_DRAFT32[] = "\x5h3-32";
+constexpr uint8_t H3_ALPN_V1[] = "\x2h3";
+
+constexpr uint32_t QUIC_VER_DRAFT29 = 0xff00001du;
+constexpr uint32_t QUIC_VER_DRAFT30 = 0xff00001eu;
+constexpr uint32_t QUIC_VER_DRAFT31 = 0xff00001fu;
+constexpr uint32_t QUIC_VER_DRAFT32 = 0xff000020u;
+
+// msghdr_get_ecn gets ECN bits from |msg|. |family| is the address
+// family from which packet is received.
+unsigned int msghdr_get_ecn(msghdr *msg, int family);
+
+// fd_set_ecn sets ECN bits |ecn| to |fd|. |family| is the address
+// family of |fd|.
+void fd_set_ecn(int fd, int family, unsigned int ecn);
+
+// fd_set_recv_ecn sets socket option to |fd| so that it can receive
+// ECN bits.
+void fd_set_recv_ecn(int fd, int family);
+
+// fd_set_ip_mtu_discover sets IP(V6)_MTU_DISCOVER socket option to
+// |fd|.
+void fd_set_ip_mtu_discover(int fd, int family);
+
+// fd_set_ip_dontfrag sets IP(V6)_DONTFRAG socket option to |fd|.
+void fd_set_ip_dontfrag(int fd, int family);
+
+std::optional<Address> msghdr_get_local_addr(msghdr *msg, int family);
+
+void set_port(Address &dst, Address &src);
+
+// get_local_addr stores preferred local address (interface address)
+// in |iau| for a given destination address |remote_addr|.
+int get_local_addr(in_addr_union &iau, const Address &remote_addr);
+
+// addreq returns true if |sa| and |iau| contain the same address.
+bool addreq(const sockaddr *sa, const in_addr_union &iau);
+
+} // namespace ngtcp2
+
+#endif // SHARED_H
diff --git a/examples/simpleclient.c b/examples/simpleclient.c
new file mode 100644
index 0000000..59321b5
--- /dev/null
+++ b/examples/simpleclient.c
@@ -0,0 +1,683 @@
+/*
+ * 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.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <ngtcp2/ngtcp2.h>
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <ngtcp2/ngtcp2_crypto_openssl.h>
+
+#include <openssl/ssl.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+
+#include <ev.h>
+
+#define REMOTE_HOST "127.0.0.1"
+#define REMOTE_PORT "4433"
+#define ALPN "\xahq-interop"
+#define MESSAGE "GET /\r\n"
+
+/*
+ * Example 1: Handshake with www.google.com
+ *
+ * #define REMOTE_HOST "www.google.com"
+ * #define REMOTE_PORT "443"
+ * #define ALPN "\x2h3"
+ *
+ * and undefine MESSAGE macro.
+ */
+
+static uint64_t timestamp(void) {
+ struct timespec tp;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
+ fprintf(stderr, "clock_gettime: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec;
+}
+
+static int create_sock(struct sockaddr *addr, socklen_t *paddrlen,
+ const char *host, const char *port) {
+ struct addrinfo hints = {0};
+ struct addrinfo *res, *rp;
+ int rv;
+ int fd = -1;
+
+ hints.ai_flags = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ rv = getaddrinfo(host, port, &hints, &res);
+ if (rv != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+ return -1;
+ }
+
+ for (rp = res; rp; rp = rp->ai_next) {
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1) {
+ continue;
+ }
+
+ break;
+ }
+
+ if (fd == -1) {
+ goto end;
+ }
+
+ *paddrlen = rp->ai_addrlen;
+ memcpy(addr, rp->ai_addr, rp->ai_addrlen);
+
+end:
+ freeaddrinfo(res);
+
+ return fd;
+}
+
+static int connect_sock(struct sockaddr *local_addr, socklen_t *plocal_addrlen,
+ int fd, const struct sockaddr *remote_addr,
+ size_t remote_addrlen) {
+ socklen_t len;
+
+ if (connect(fd, remote_addr, (socklen_t)remote_addrlen) != 0) {
+ fprintf(stderr, "connect: %s\n", strerror(errno));
+ return -1;
+ }
+
+ len = *plocal_addrlen;
+
+ if (getsockname(fd, local_addr, &len) == -1) {
+ fprintf(stderr, "getsockname: %s\n", strerror(errno));
+ return -1;
+ }
+
+ *plocal_addrlen = len;
+
+ return 0;
+}
+
+struct client {
+ ngtcp2_crypto_conn_ref conn_ref;
+ int fd;
+ struct sockaddr_storage local_addr;
+ socklen_t local_addrlen;
+ SSL_CTX *ssl_ctx;
+ SSL *ssl;
+ ngtcp2_conn *conn;
+
+ struct {
+ int64_t stream_id;
+ const uint8_t *data;
+ size_t datalen;
+ size_t nwrite;
+ } stream;
+
+ ngtcp2_connection_close_error last_error;
+
+ ev_io rev;
+ ev_timer timer;
+};
+
+static int numeric_host_family(const char *hostname, int family) {
+ uint8_t dst[sizeof(struct in6_addr)];
+ return inet_pton(family, hostname, dst) == 1;
+}
+
+static int numeric_host(const char *hostname) {
+ return numeric_host_family(hostname, AF_INET) ||
+ numeric_host_family(hostname, AF_INET6);
+}
+
+static int client_ssl_init(struct client *c) {
+ c->ssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (!c->ssl_ctx) {
+ fprintf(stderr, "SSL_CTX_new: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+
+ if (ngtcp2_crypto_openssl_configure_client_context(c->ssl_ctx) != 0) {
+ fprintf(stderr, "ngtcp2_crypto_openssl_configure_client_context failed\n");
+ return -1;
+ }
+
+ c->ssl = SSL_new(c->ssl_ctx);
+ if (!c->ssl) {
+ fprintf(stderr, "SSL_new: %s\n", ERR_error_string(ERR_get_error(), NULL));
+ return -1;
+ }
+
+ SSL_set_app_data(c->ssl, &c->conn_ref);
+ SSL_set_connect_state(c->ssl);
+ SSL_set_alpn_protos(c->ssl, (const unsigned char *)ALPN, sizeof(ALPN) - 1);
+ if (!numeric_host(REMOTE_HOST)) {
+ SSL_set_tlsext_host_name(c->ssl, REMOTE_HOST);
+ }
+
+ /* For NGTCP2_PROTO_VER_V1 */
+ SSL_set_quic_transport_version(c->ssl, TLSEXT_TYPE_quic_transport_parameters);
+
+ return 0;
+}
+
+static void rand_cb(uint8_t *dest, size_t destlen,
+ const ngtcp2_rand_ctx *rand_ctx) {
+ size_t i;
+ (void)rand_ctx;
+
+ for (i = 0; i < destlen; ++i) {
+ *dest = (uint8_t)random();
+ }
+}
+
+static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
+ uint8_t *token, size_t cidlen,
+ void *user_data) {
+ (void)conn;
+ (void)user_data;
+
+ if (RAND_bytes(cid->data, (int)cidlen) != 1) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+
+ if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int extend_max_local_streams_bidi(ngtcp2_conn *conn,
+ uint64_t max_streams,
+ void *user_data) {
+#ifdef MESSAGE
+ struct client *c = user_data;
+ int rv;
+ int64_t stream_id;
+ (void)max_streams;
+
+ if (c->stream.stream_id != -1) {
+ return 0;
+ }
+
+ rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
+ if (rv != 0) {
+ return 0;
+ }
+
+ c->stream.stream_id = stream_id;
+ c->stream.data = (const uint8_t *)MESSAGE;
+ c->stream.datalen = sizeof(MESSAGE) - 1;
+
+ return 0;
+#else /* !MESSAGE */
+ (void)conn;
+ (void)max_streams;
+ (void)user_data;
+
+ return 0;
+#endif /* !MESSAGE */
+}
+
+static void log_printf(void *user_data, const char *fmt, ...) {
+ va_list ap;
+ (void)user_data;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+
+static int client_quic_init(struct client *c,
+ const struct sockaddr *remote_addr,
+ socklen_t remote_addrlen,
+ const struct sockaddr *local_addr,
+ socklen_t local_addrlen) {
+ ngtcp2_path path = {
+ {
+ (struct sockaddr *)local_addr,
+ local_addrlen,
+ },
+ {
+ (struct sockaddr *)remote_addr,
+ remote_addrlen,
+ },
+ NULL,
+ };
+ ngtcp2_callbacks callbacks = {
+ ngtcp2_crypto_client_initial_cb,
+ NULL, /* recv_client_initial */
+ ngtcp2_crypto_recv_crypto_data_cb,
+ NULL, /* handshake_completed */
+ NULL, /* recv_version_negotiation */
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ NULL, /* recv_stream_data */
+ NULL, /* acked_stream_data_offset */
+ NULL, /* stream_open */
+ NULL, /* stream_close */
+ NULL, /* recv_stateless_reset */
+ ngtcp2_crypto_recv_retry_cb,
+ extend_max_local_streams_bidi,
+ NULL, /* extend_max_local_streams_uni */
+ rand_cb,
+ get_new_connection_id_cb,
+ NULL, /* remove_connection_id */
+ ngtcp2_crypto_update_key_cb,
+ NULL, /* path_validation */
+ NULL, /* select_preferred_address */
+ NULL, /* stream_reset */
+ NULL, /* extend_max_remote_streams_bidi */
+ NULL, /* extend_max_remote_streams_uni */
+ NULL, /* extend_max_stream_data */
+ NULL, /* dcid_status */
+ NULL, /* handshake_confirmed */
+ NULL, /* recv_new_token */
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ NULL, /* recv_datagram */
+ NULL, /* ack_datagram */
+ NULL, /* lost_datagram */
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ NULL, /* stream_stop_sending */
+ ngtcp2_crypto_version_negotiation_cb,
+ NULL, /* recv_rx_key */
+ NULL, /* recv_tx_key */
+ NULL, /* early_data_rejected */
+ };
+ ngtcp2_cid dcid, scid;
+ ngtcp2_settings settings;
+ ngtcp2_transport_params params;
+ int rv;
+
+ dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN;
+ if (RAND_bytes(dcid.data, (int)dcid.datalen) != 1) {
+ fprintf(stderr, "RAND_bytes failed\n");
+ return -1;
+ }
+
+ scid.datalen = 8;
+ if (RAND_bytes(scid.data, (int)scid.datalen) != 1) {
+ fprintf(stderr, "RAND_bytes failed\n");
+ return -1;
+ }
+
+ ngtcp2_settings_default(&settings);
+
+ settings.initial_ts = timestamp();
+ settings.log_printf = log_printf;
+
+ ngtcp2_transport_params_default(&params);
+
+ params.initial_max_streams_uni = 3;
+ params.initial_max_stream_data_bidi_local = 128 * 1024;
+ params.initial_max_data = 1024 * 1024;
+
+ rv =
+ ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1,
+ &callbacks, &settings, &params, NULL, c);
+ if (rv != 0) {
+ fprintf(stderr, "ngtcp2_conn_client_new: %s\n", ngtcp2_strerror(rv));
+ return -1;
+ }
+
+ ngtcp2_conn_set_tls_native_handle(c->conn, c->ssl);
+
+ return 0;
+}
+
+static int client_read(struct client *c) {
+ uint8_t buf[65536];
+ struct sockaddr_storage addr;
+ struct iovec iov = {buf, sizeof(buf)};
+ struct msghdr msg = {0};
+ ssize_t nread;
+ ngtcp2_path path;
+ ngtcp2_pkt_info pi = {0};
+ int rv;
+
+ msg.msg_name = &addr;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ for (;;) {
+ msg.msg_namelen = sizeof(addr);
+
+ nread = recvmsg(c->fd, &msg, MSG_DONTWAIT);
+
+ if (nread == -1) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ fprintf(stderr, "recvmsg: %s\n", strerror(errno));
+ }
+
+ break;
+ }
+
+ path.local.addrlen = c->local_addrlen;
+ path.local.addr = (struct sockaddr *)&c->local_addr;
+ path.remote.addrlen = msg.msg_namelen;
+ path.remote.addr = msg.msg_name;
+
+ rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)nread,
+ timestamp());
+ if (rv != 0) {
+ fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv));
+ if (!c->last_error.error_code) {
+ if (rv == NGTCP2_ERR_CRYPTO) {
+ ngtcp2_connection_close_error_set_transport_error_tls_alert(
+ &c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0);
+ } else {
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &c->last_error, rv, NULL, 0);
+ }
+ }
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int client_send_packet(struct client *c, const uint8_t *data,
+ size_t datalen) {
+ struct iovec iov = {(uint8_t *)data, datalen};
+ struct msghdr msg = {0};
+ ssize_t nwrite;
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ do {
+ nwrite = sendmsg(c->fd, &msg, 0);
+ } while (nwrite == -1 && errno == EINTR);
+
+ if (nwrite == -1) {
+ fprintf(stderr, "sendmsg: %s\n", strerror(errno));
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static size_t client_get_message(struct client *c, int64_t *pstream_id,
+ int *pfin, ngtcp2_vec *datav,
+ size_t datavcnt) {
+ if (datavcnt == 0) {
+ return 0;
+ }
+
+ if (c->stream.stream_id != -1 && c->stream.nwrite < c->stream.datalen) {
+ *pstream_id = c->stream.stream_id;
+ *pfin = 1;
+ datav->base = (uint8_t *)c->stream.data + c->stream.nwrite;
+ datav->len = c->stream.datalen - c->stream.nwrite;
+ return 1;
+ }
+
+ *pstream_id = -1;
+ *pfin = 0;
+ datav->base = NULL;
+ datav->len = 0;
+
+ return 0;
+}
+
+static int client_write_streams(struct client *c) {
+ ngtcp2_tstamp ts = timestamp();
+ ngtcp2_pkt_info pi;
+ ngtcp2_ssize nwrite;
+ uint8_t buf[1280];
+ ngtcp2_path_storage ps;
+ ngtcp2_vec datav;
+ size_t datavcnt;
+ int64_t stream_id;
+ ngtcp2_ssize wdatalen;
+ uint32_t flags;
+ int fin;
+
+ ngtcp2_path_storage_zero(&ps);
+
+ for (;;) {
+ datavcnt = client_get_message(c, &stream_id, &fin, &datav, 1);
+
+ flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
+ if (fin) {
+ flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
+ }
+
+ nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, sizeof(buf),
+ &wdatalen, flags, stream_id, &datav,
+ datavcnt, ts);
+ if (nwrite < 0) {
+ switch (nwrite) {
+ case NGTCP2_ERR_WRITE_MORE:
+ c->stream.nwrite += (size_t)wdatalen;
+ continue;
+ default:
+ fprintf(stderr, "ngtcp2_conn_writev_stream: %s\n",
+ ngtcp2_strerror((int)nwrite));
+ ngtcp2_connection_close_error_set_transport_error_liberr(
+ &c->last_error, (int)nwrite, NULL, 0);
+ return -1;
+ }
+ }
+
+ if (nwrite == 0) {
+ return 0;
+ }
+
+ if (wdatalen > 0) {
+ c->stream.nwrite += (size_t)wdatalen;
+ }
+
+ if (client_send_packet(c, buf, (size_t)nwrite) != 0) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int client_write(struct client *c) {
+ ngtcp2_tstamp expiry, now;
+ ev_tstamp t;
+
+ if (client_write_streams(c) != 0) {
+ return -1;
+ }
+
+ expiry = ngtcp2_conn_get_expiry(c->conn);
+ now = timestamp();
+
+ t = expiry < now ? 1e-9 : (ev_tstamp)(expiry - now) / NGTCP2_SECONDS;
+
+ c->timer.repeat = t;
+ ev_timer_again(EV_DEFAULT, &c->timer);
+
+ return 0;
+}
+
+static int client_handle_expiry(struct client *c) {
+ int rv = ngtcp2_conn_handle_expiry(c->conn, timestamp());
+ if (rv != 0) {
+ fprintf(stderr, "ngtcp2_conn_handle_expiry: %s\n", ngtcp2_strerror(rv));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void client_close(struct client *c) {
+ ngtcp2_ssize nwrite;
+ ngtcp2_pkt_info pi;
+ ngtcp2_path_storage ps;
+ uint8_t buf[1280];
+
+ if (ngtcp2_conn_is_in_closing_period(c->conn) ||
+ ngtcp2_conn_is_in_draining_period(c->conn)) {
+ goto fin;
+ }
+
+ ngtcp2_path_storage_zero(&ps);
+
+ nwrite = ngtcp2_conn_write_connection_close(
+ c->conn, &ps.path, &pi, buf, sizeof(buf), &c->last_error, timestamp());
+ if (nwrite < 0) {
+ fprintf(stderr, "ngtcp2_conn_write_connection_close: %s\n",
+ ngtcp2_strerror((int)nwrite));
+ goto fin;
+ }
+
+ client_send_packet(c, buf, (size_t)nwrite);
+
+fin:
+ ev_break(EV_DEFAULT, EVBREAK_ALL);
+}
+
+static void read_cb(struct ev_loop *loop, ev_io *w, int revents) {
+ struct client *c = w->data;
+ (void)loop;
+ (void)revents;
+
+ if (client_read(c) != 0) {
+ client_close(c);
+ return;
+ }
+
+ if (client_write(c) != 0) {
+ client_close(c);
+ }
+}
+
+static void timer_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+ struct client *c = w->data;
+ (void)loop;
+ (void)revents;
+
+ if (client_handle_expiry(c) != 0) {
+ client_close(c);
+ return;
+ }
+
+ if (client_write(c) != 0) {
+ client_close(c);
+ }
+}
+
+static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
+ struct client *c = conn_ref->user_data;
+ return c->conn;
+}
+
+static int client_init(struct client *c) {
+ struct sockaddr_storage remote_addr, local_addr;
+ socklen_t remote_addrlen, local_addrlen = sizeof(local_addr);
+
+ memset(c, 0, sizeof(*c));
+
+ ngtcp2_connection_close_error_default(&c->last_error);
+
+ c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen,
+ REMOTE_HOST, REMOTE_PORT);
+ if (c->fd == -1) {
+ return -1;
+ }
+
+ if (connect_sock((struct sockaddr *)&local_addr, &local_addrlen, c->fd,
+ (struct sockaddr *)&remote_addr, remote_addrlen) != 0) {
+ return -1;
+ }
+
+ memcpy(&c->local_addr, &local_addr, sizeof(c->local_addr));
+ c->local_addrlen = local_addrlen;
+
+ if (client_ssl_init(c) != 0) {
+ return -1;
+ }
+
+ if (client_quic_init(c, (struct sockaddr *)&remote_addr, remote_addrlen,
+ (struct sockaddr *)&local_addr, local_addrlen) != 0) {
+ return -1;
+ }
+
+ c->stream.stream_id = -1;
+
+ c->conn_ref.get_conn = get_conn;
+ c->conn_ref.user_data = c;
+
+ ev_io_init(&c->rev, read_cb, c->fd, EV_READ);
+ c->rev.data = c;
+ ev_io_start(EV_DEFAULT, &c->rev);
+
+ ev_timer_init(&c->timer, timer_cb, 0., 0.);
+ c->timer.data = c;
+
+ return 0;
+}
+
+static void client_free(struct client *c) {
+ ngtcp2_conn_del(c->conn);
+ SSL_free(c->ssl);
+ SSL_CTX_free(c->ssl_ctx);
+}
+
+int main(void) {
+ struct client c;
+
+ srandom((unsigned int)timestamp());
+
+ if (client_init(&c) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (client_write(&c) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ ev_run(EV_DEFAULT, 0);
+
+ client_free(&c);
+
+ return 0;
+}
diff --git a/examples/template.h b/examples/template.h
new file mode 100644
index 0000000..e923fae
--- /dev/null
+++ b/examples/template.h
@@ -0,0 +1,71 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2017 ngtcp2 contributors
+ * Copyright (c) 2015 ngttp2 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 TEMPLATE_H
+#define TEMPLATE_H
+
+#include <functional>
+#include <utility>
+#include <type_traits>
+
+// inspired by <http://blog.korfuri.fr/post/go-defer-in-cpp/>, but our
+// template can take functions returning other than void.
+template <typename F, typename... T> struct Defer {
+ Defer(F &&f, T &&...t)
+ : f(std::bind(std::forward<F>(f), std::forward<T>(t)...)) {}
+ Defer(Defer &&o) noexcept : f(std::move(o.f)) {}
+ ~Defer() { f(); }
+
+ using ResultType = std::invoke_result_t<F, T...>;
+ std::function<ResultType()> f;
+};
+
+template <typename F, typename... T> Defer<F, T...> defer(F &&f, T &&...t) {
+ return Defer<F, T...>(std::forward<F>(f), std::forward<T>(t)...);
+}
+
+template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) {
+ return N;
+}
+
+template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) {
+ return N - 1;
+}
+
+// User-defined literals for K, M, and G (powers of 1024)
+
+constexpr unsigned long long operator"" _k(unsigned long long k) {
+ return k * 1024;
+}
+
+constexpr unsigned long long operator"" _m(unsigned long long m) {
+ return m * 1024 * 1024;
+}
+
+constexpr unsigned long long operator"" _g(unsigned long long g) {
+ return g * 1024 * 1024 * 1024;
+}
+
+#endif // TEMPLATE_H
diff --git a/examples/tests/.gitignore b/examples/tests/.gitignore
new file mode 100644
index 0000000..ba92e76
--- /dev/null
+++ b/examples/tests/.gitignore
@@ -0,0 +1,3 @@
+config.ini
+gen
+*.bak
diff --git a/examples/tests/README.rst b/examples/tests/README.rst
new file mode 100644
index 0000000..f583ecf
--- /dev/null
+++ b/examples/tests/README.rst
@@ -0,0 +1,60 @@
+Examples Tests
+==============
+
+This is a ``pytest`` suite intended to verify interoperability between
+the different example clients and servers built.
+
+You run it by executing ``pytest`` on top level project dir or in
+the examples/tests directory.
+
+.. code-block:: text
+
+ examples/test> pytest
+ ngtcp2-examples: [0.9.0-DEV, crypto_libs=['openssl', 'wolfssl']]
+ ...
+
+Requirements
+------------
+
+You need a Python3 (3.8 is probably sufficient), ``pytest`` and the
+Python ``cryptography`` module installed.
+
+Usage
+-----
+
+If you run ``pytest`` without arguments, it will print the test suite
+and a ``.`` for every test case passed. Add ``-v`` and all test cases
+will be listed in the full name. Adding several ``v`` will increase the
+logging level on failure output.
+
+The name of test cases include the crypto libs of the server and client
+used. For example:
+
+.. code-block:: text
+
+ test_01_handshake.py::TestHandshake::test_01_01_get[openssl-openssl] PASSED [ 16%]
+ test_01_handshake.py::TestHandshake::test_01_01_get[openssl-wolfssl] PASSED
+
+Here, ``test_01_01`` is run first with the OpenSSL server and client and then
+with the OpenSSL server and wolfSSL client. By default, the test suite runs
+all combinations of servers and clients that have been configured in the project.
+
+To track down problems, you can restrict the test cases that are run by
+matching patterns:
+
+.. code-block:: text
+
+ # only tests with wolfSSL example server
+ > pytest -v -k 'wolfssl-'
+ # only tests with wolfSSL example client
+ > pytest -v -k 'test and -wolfssl'
+ # tests with a specific combination
+ > pytest -v -k 'openssl-wolfssl'
+
+
+Analysing
+---------
+
+To make analysis of a broken test case easier, you best run only that
+test case. Use ``pytest -vv`` (or more) to get more verbose logging.
+Inspect server and client log files in ``examples/tests/gen``.
diff --git a/examples/tests/__init__.py b/examples/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/tests/__init__.py
diff --git a/examples/tests/config.ini.in b/examples/tests/config.ini.in
new file mode 100644
index 0000000..2d15dbb
--- /dev/null
+++ b/examples/tests/config.ini.in
@@ -0,0 +1,32 @@
+[ngtcp2]
+version = @PACKAGE_VERSION@
+examples = @EXAMPLES_ENABLED@
+
+[crypto]
+openssl = @have_openssl@
+gnutls = @have_gnutls@
+boringssl = @have_boringssl@
+picotls = @have_picotls@
+wolfssl = @have_wolfssl@
+
+[examples]
+port = 4433
+openssl = @EXAMPLES_OPENSSL@
+gnutls = @EXAMPLES_GNUTLS@
+boringssl = @EXAMPLES_BORINGSSL@
+picotls = @EXAMPLES_PICOTLS@
+wolfssl = @EXAMPLES_WOLFSSL@
+
+[clients]
+openssl = client
+gnutls = gtlsclient
+boringssl = bsslclient
+picotls = ptlsclient
+wolfssl = wsslclient
+
+[servers]
+openssl = server
+gnutls = gtlsserver
+boringssl = bsslserver
+picotls = ptlsserver
+wolfssl = wsslserver
diff --git a/examples/tests/conftest.py b/examples/tests/conftest.py
new file mode 100644
index 0000000..a566ff0
--- /dev/null
+++ b/examples/tests/conftest.py
@@ -0,0 +1,28 @@
+import logging
+import pytest
+
+from .ngtcp2test import Env
+
+
+@pytest.mark.usefixtures("env")
+def pytest_report_header(config):
+ env = Env()
+ return [
+ f"ngtcp2-examples: [{env.version}, crypto_libs={env.crypto_libs}]",
+ f"example clients: {env.clients}",
+ f"example servers: {env.servers}",
+ ]
+
+
+@pytest.fixture(scope="package")
+def env(pytestconfig) -> Env:
+ console = logging.StreamHandler()
+ console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
+ logging.getLogger('').addHandler(console)
+ env = Env(pytestconfig=pytestconfig)
+ level = logging.DEBUG if env.verbose > 0 else logging.INFO
+ console.setLevel(level)
+ logging.getLogger('').setLevel(level=level)
+ env.setup()
+
+ return env
diff --git a/examples/tests/ngtcp2test/__init__.py b/examples/tests/ngtcp2test/__init__.py
new file mode 100644
index 0000000..65c61d8
--- /dev/null
+++ b/examples/tests/ngtcp2test/__init__.py
@@ -0,0 +1,6 @@
+from .env import Env, CryptoLib
+from .log import LogFile
+from .client import ExampleClient, ClientRun
+from .server import ExampleServer, ServerRun
+from .certs import Ngtcp2TestCA, Credentials
+from .tls import HandShake, HSRecord
diff --git a/examples/tests/ngtcp2test/certs.py b/examples/tests/ngtcp2test/certs.py
new file mode 100644
index 0000000..3ab6260
--- /dev/null
+++ b/examples/tests/ngtcp2test/certs.py
@@ -0,0 +1,476 @@
+import os
+import re
+from datetime import timedelta, datetime
+from typing import List, Any, Optional
+
+from cryptography import x509
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.asymmetric import ec, rsa
+from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
+from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
+from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, load_pem_private_key
+from cryptography.x509 import ExtendedKeyUsageOID, NameOID
+
+
+EC_SUPPORTED = {}
+EC_SUPPORTED.update([(curve.name.upper(), curve) for curve in [
+ ec.SECP192R1,
+ ec.SECP224R1,
+ ec.SECP256R1,
+ ec.SECP384R1,
+]])
+
+
+def _private_key(key_type):
+ if isinstance(key_type, str):
+ key_type = key_type.upper()
+ m = re.match(r'^(RSA)?(\d+)$', key_type)
+ if m:
+ key_type = int(m.group(2))
+
+ if isinstance(key_type, int):
+ return rsa.generate_private_key(
+ public_exponent=65537,
+ key_size=key_type,
+ backend=default_backend()
+ )
+ if not isinstance(key_type, ec.EllipticCurve) and key_type in EC_SUPPORTED:
+ key_type = EC_SUPPORTED[key_type]
+ return ec.generate_private_key(
+ curve=key_type,
+ backend=default_backend()
+ )
+
+
+class CertificateSpec:
+
+ def __init__(self, name: str = None, domains: List[str] = None,
+ email: str = None,
+ key_type: str = None, single_file: bool = False,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ client: bool = False,
+ sub_specs: List['CertificateSpec'] = None):
+ self._name = name
+ self.domains = domains
+ self.client = client
+ self.email = email
+ self.key_type = key_type
+ self.single_file = single_file
+ self.valid_from = valid_from
+ self.valid_to = valid_to
+ self.sub_specs = sub_specs
+
+ @property
+ def name(self) -> Optional[str]:
+ if self._name:
+ return self._name
+ elif self.domains:
+ return self.domains[0]
+ return None
+
+ @property
+ def type(self) -> Optional[str]:
+ if self.domains and len(self.domains):
+ return "server"
+ elif self.client:
+ return "client"
+ elif self.name:
+ return "ca"
+ return None
+
+
+class Credentials:
+
+ def __init__(self, name: str, cert: Any, pkey: Any, issuer: 'Credentials' = None):
+ self._name = name
+ self._cert = cert
+ self._pkey = pkey
+ self._issuer = issuer
+ self._cert_file = None
+ self._pkey_file = None
+ self._store = None
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def subject(self) -> x509.Name:
+ return self._cert.subject
+
+ @property
+ def key_type(self):
+ if isinstance(self._pkey, RSAPrivateKey):
+ return f"rsa{self._pkey.key_size}"
+ elif isinstance(self._pkey, EllipticCurvePrivateKey):
+ return f"{self._pkey.curve.name}"
+ else:
+ raise Exception(f"unknown key type: {self._pkey}")
+
+ @property
+ def private_key(self) -> Any:
+ return self._pkey
+
+ @property
+ def certificate(self) -> Any:
+ return self._cert
+
+ @property
+ def cert_pem(self) -> bytes:
+ return self._cert.public_bytes(Encoding.PEM)
+
+ @property
+ def pkey_pem(self) -> bytes:
+ return self._pkey.private_bytes(
+ Encoding.PEM,
+ PrivateFormat.TraditionalOpenSSL if self.key_type.startswith('rsa') else PrivateFormat.PKCS8,
+ NoEncryption())
+
+ @property
+ def issuer(self) -> Optional['Credentials']:
+ return self._issuer
+
+ def set_store(self, store: 'CertStore'):
+ self._store = store
+
+ def set_files(self, cert_file: str, pkey_file: str = None):
+ self._cert_file = cert_file
+ self._pkey_file = pkey_file
+
+ @property
+ def cert_file(self) -> str:
+ return self._cert_file
+
+ @property
+ def pkey_file(self) -> Optional[str]:
+ return self._pkey_file
+
+ def get_first(self, name) -> Optional['Credentials']:
+ creds = self._store.get_credentials_for_name(name) if self._store else []
+ return creds[0] if len(creds) else None
+
+ def get_credentials_for_name(self, name) -> List['Credentials']:
+ return self._store.get_credentials_for_name(name) if self._store else []
+
+ def issue_certs(self, specs: List[CertificateSpec],
+ chain: List['Credentials'] = None) -> List['Credentials']:
+ return [self.issue_cert(spec=spec, chain=chain) for spec in specs]
+
+ def issue_cert(self, spec: CertificateSpec, chain: List['Credentials'] = None) -> 'Credentials':
+ key_type = spec.key_type if spec.key_type else self.key_type
+ creds = None
+ if self._store:
+ creds = self._store.load_credentials(
+ name=spec.name, key_type=key_type, single_file=spec.single_file, issuer=self)
+ if creds is None:
+ creds = Ngtcp2TestCA.create_credentials(spec=spec, issuer=self, key_type=key_type,
+ valid_from=spec.valid_from, valid_to=spec.valid_to)
+ if self._store:
+ self._store.save(creds, single_file=spec.single_file)
+ if spec.type == "ca":
+ self._store.save_chain(creds, "ca", with_root=True)
+
+ if spec.sub_specs:
+ if self._store:
+ sub_store = CertStore(fpath=os.path.join(self._store.path, creds.name))
+ creds.set_store(sub_store)
+ subchain = chain.copy() if chain else []
+ subchain.append(self)
+ creds.issue_certs(spec.sub_specs, chain=subchain)
+ return creds
+
+
+class CertStore:
+
+ def __init__(self, fpath: str):
+ self._store_dir = fpath
+ if not os.path.exists(self._store_dir):
+ os.makedirs(self._store_dir)
+ self._creds_by_name = {}
+
+ @property
+ def path(self) -> str:
+ return self._store_dir
+
+ def save(self, creds: Credentials, name: str = None,
+ chain: List[Credentials] = None,
+ single_file: bool = False) -> None:
+ name = name if name is not None else creds.name
+ cert_file = self.get_cert_file(name=name, key_type=creds.key_type)
+ pkey_file = self.get_pkey_file(name=name, key_type=creds.key_type)
+ if single_file:
+ pkey_file = None
+ with open(cert_file, "wb") as fd:
+ fd.write(creds.cert_pem)
+ if chain:
+ for c in chain:
+ fd.write(c.cert_pem)
+ if pkey_file is None:
+ fd.write(creds.pkey_pem)
+ if pkey_file is not None:
+ with open(pkey_file, "wb") as fd:
+ fd.write(creds.pkey_pem)
+ creds.set_files(cert_file, pkey_file)
+ self._add_credentials(name, creds)
+
+ def save_chain(self, creds: Credentials, infix: str, with_root=False):
+ name = creds.name
+ chain = [creds]
+ while creds.issuer is not None:
+ creds = creds.issuer
+ chain.append(creds)
+ if not with_root and len(chain) > 1:
+ chain = chain[:-1]
+ chain_file = os.path.join(self._store_dir, f'{name}-{infix}.pem')
+ with open(chain_file, "wb") as fd:
+ for c in chain:
+ fd.write(c.cert_pem)
+
+ def _add_credentials(self, name: str, creds: Credentials):
+ if name not in self._creds_by_name:
+ self._creds_by_name[name] = []
+ self._creds_by_name[name].append(creds)
+
+ def get_credentials_for_name(self, name) -> List[Credentials]:
+ return self._creds_by_name[name] if name in self._creds_by_name else []
+
+ def get_cert_file(self, name: str, key_type=None) -> str:
+ key_infix = ".{0}".format(key_type) if key_type is not None else ""
+ return os.path.join(self._store_dir, f'{name}{key_infix}.cert.pem')
+
+ def get_pkey_file(self, name: str, key_type=None) -> str:
+ key_infix = ".{0}".format(key_type) if key_type is not None else ""
+ return os.path.join(self._store_dir, f'{name}{key_infix}.pkey.pem')
+
+ def load_pem_cert(self, fpath: str) -> x509.Certificate:
+ with open(fpath) as fd:
+ return x509.load_pem_x509_certificate("".join(fd.readlines()).encode())
+
+ def load_pem_pkey(self, fpath: str):
+ with open(fpath) as fd:
+ return load_pem_private_key("".join(fd.readlines()).encode(), password=None)
+
+ def load_credentials(self, name: str, key_type=None, single_file: bool = False, issuer: Credentials = None):
+ cert_file = self.get_cert_file(name=name, key_type=key_type)
+ pkey_file = cert_file if single_file else self.get_pkey_file(name=name, key_type=key_type)
+ if os.path.isfile(cert_file) and os.path.isfile(pkey_file):
+ cert = self.load_pem_cert(cert_file)
+ pkey = self.load_pem_pkey(pkey_file)
+ creds = Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)
+ creds.set_store(self)
+ creds.set_files(cert_file, pkey_file)
+ self._add_credentials(name, creds)
+ return creds
+ return None
+
+
+class Ngtcp2TestCA:
+
+ @classmethod
+ def create_root(cls, name: str, store_dir: str, key_type: str = "rsa2048") -> Credentials:
+ store = CertStore(fpath=store_dir)
+ creds = store.load_credentials(name="ca", key_type=key_type, issuer=None)
+ if creds is None:
+ creds = Ngtcp2TestCA._make_ca_credentials(name=name, key_type=key_type)
+ store.save(creds, name="ca")
+ creds.set_store(store)
+ return creds
+
+ @staticmethod
+ def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ ) -> Credentials:
+ """Create a certificate signed by this CA for the given domains.
+ :returns: the certificate and private key PEM file paths
+ """
+ if spec.domains and len(spec.domains):
+ creds = Ngtcp2TestCA._make_server_credentials(name=spec.name, domains=spec.domains,
+ issuer=issuer, valid_from=valid_from,
+ valid_to=valid_to, key_type=key_type)
+ elif spec.client:
+ creds = Ngtcp2TestCA._make_client_credentials(name=spec.name, issuer=issuer,
+ email=spec.email, valid_from=valid_from,
+ valid_to=valid_to, key_type=key_type)
+ elif spec.name:
+ creds = Ngtcp2TestCA._make_ca_credentials(name=spec.name, issuer=issuer,
+ valid_from=valid_from, valid_to=valid_to,
+ key_type=key_type)
+ else:
+ raise Exception(f"unrecognized certificate specification: {spec}")
+ return creds
+
+ @staticmethod
+ def _make_x509_name(org_name: str = None, common_name: str = None, parent: x509.Name = None) -> x509.Name:
+ name_pieces = []
+ if org_name:
+ oid = NameOID.ORGANIZATIONAL_UNIT_NAME if parent else NameOID.ORGANIZATION_NAME
+ name_pieces.append(x509.NameAttribute(oid, org_name))
+ elif common_name:
+ name_pieces.append(x509.NameAttribute(NameOID.COMMON_NAME, common_name))
+ if parent:
+ name_pieces.extend([rdn for rdn in parent])
+ return x509.Name(name_pieces)
+
+ @staticmethod
+ def _make_csr(
+ subject: x509.Name,
+ pkey: Any,
+ issuer_subject: Optional[Credentials],
+ valid_from_delta: timedelta = None,
+ valid_until_delta: timedelta = None
+ ):
+ pubkey = pkey.public_key()
+ issuer_subject = issuer_subject if issuer_subject is not None else subject
+
+ valid_from = datetime.now()
+ if valid_until_delta is not None:
+ valid_from += valid_from_delta
+ valid_until = datetime.now()
+ if valid_until_delta is not None:
+ valid_until += valid_until_delta
+
+ return (
+ x509.CertificateBuilder()
+ .subject_name(subject)
+ .issuer_name(issuer_subject)
+ .public_key(pubkey)
+ .not_valid_before(valid_from)
+ .not_valid_after(valid_until)
+ .serial_number(x509.random_serial_number())
+ .add_extension(
+ x509.SubjectKeyIdentifier.from_public_key(pubkey),
+ critical=False,
+ )
+ )
+
+ @staticmethod
+ def _add_ca_usages(csr: Any) -> Any:
+ return csr.add_extension(
+ x509.BasicConstraints(ca=True, path_length=9),
+ critical=True,
+ ).add_extension(
+ x509.KeyUsage(
+ digital_signature=True,
+ content_commitment=False,
+ key_encipherment=False,
+ data_encipherment=False,
+ key_agreement=False,
+ key_cert_sign=True,
+ crl_sign=True,
+ encipher_only=False,
+ decipher_only=False),
+ critical=True
+ ).add_extension(
+ x509.ExtendedKeyUsage([
+ ExtendedKeyUsageOID.CLIENT_AUTH,
+ ExtendedKeyUsageOID.SERVER_AUTH,
+ ExtendedKeyUsageOID.CODE_SIGNING,
+ ]),
+ critical=True
+ )
+
+ @staticmethod
+ def _add_leaf_usages(csr: Any, domains: List[str], issuer: Credentials) -> Any:
+ return csr.add_extension(
+ x509.BasicConstraints(ca=False, path_length=None),
+ critical=True,
+ ).add_extension(
+ x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
+ issuer.certificate.extensions.get_extension_for_class(
+ x509.SubjectKeyIdentifier).value),
+ critical=False
+ ).add_extension(
+ x509.SubjectAlternativeName([x509.DNSName(domain) for domain in domains]),
+ critical=True,
+ ).add_extension(
+ x509.ExtendedKeyUsage([
+ ExtendedKeyUsageOID.SERVER_AUTH,
+ ]),
+ critical=True
+ )
+
+ @staticmethod
+ def _add_client_usages(csr: Any, issuer: Credentials, rfc82name: str = None) -> Any:
+ cert = csr.add_extension(
+ x509.BasicConstraints(ca=False, path_length=None),
+ critical=True,
+ ).add_extension(
+ x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
+ issuer.certificate.extensions.get_extension_for_class(
+ x509.SubjectKeyIdentifier).value),
+ critical=False
+ )
+ if rfc82name:
+ cert.add_extension(
+ x509.SubjectAlternativeName([x509.RFC822Name(rfc82name)]),
+ critical=True,
+ )
+ cert.add_extension(
+ x509.ExtendedKeyUsage([
+ ExtendedKeyUsageOID.CLIENT_AUTH,
+ ]),
+ critical=True
+ )
+ return cert
+
+ @staticmethod
+ def _make_ca_credentials(name, key_type: Any,
+ issuer: Credentials = None,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ ) -> Credentials:
+ pkey = _private_key(key_type=key_type)
+ if issuer is not None:
+ issuer_subject = issuer.certificate.subject
+ issuer_key = issuer.private_key
+ else:
+ issuer_subject = None
+ issuer_key = pkey
+ subject = Ngtcp2TestCA._make_x509_name(org_name=name, parent=issuer.subject if issuer else None)
+ csr = Ngtcp2TestCA._make_csr(subject=subject,
+ issuer_subject=issuer_subject, pkey=pkey,
+ valid_from_delta=valid_from, valid_until_delta=valid_to)
+ csr = Ngtcp2TestCA._add_ca_usages(csr)
+ cert = csr.sign(private_key=issuer_key,
+ algorithm=hashes.SHA256(),
+ backend=default_backend())
+ return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)
+
+ @staticmethod
+ def _make_server_credentials(name: str, domains: List[str], issuer: Credentials,
+ key_type: Any,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ ) -> Credentials:
+ name = name
+ pkey = _private_key(key_type=key_type)
+ subject = Ngtcp2TestCA._make_x509_name(common_name=name, parent=issuer.subject)
+ csr = Ngtcp2TestCA._make_csr(subject=subject,
+ issuer_subject=issuer.certificate.subject, pkey=pkey,
+ valid_from_delta=valid_from, valid_until_delta=valid_to)
+ csr = Ngtcp2TestCA._add_leaf_usages(csr, domains=domains, issuer=issuer)
+ cert = csr.sign(private_key=issuer.private_key,
+ algorithm=hashes.SHA256(),
+ backend=default_backend())
+ return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)
+
+ @staticmethod
+ def _make_client_credentials(name: str,
+ issuer: Credentials, email: Optional[str],
+ key_type: Any,
+ valid_from: timedelta = timedelta(days=-1),
+ valid_to: timedelta = timedelta(days=89),
+ ) -> Credentials:
+ pkey = _private_key(key_type=key_type)
+ subject = Ngtcp2TestCA._make_x509_name(common_name=name, parent=issuer.subject)
+ csr = Ngtcp2TestCA._make_csr(subject=subject,
+ issuer_subject=issuer.certificate.subject, pkey=pkey,
+ valid_from_delta=valid_from, valid_until_delta=valid_to)
+ csr = Ngtcp2TestCA._add_client_usages(csr, issuer=issuer, rfc82name=email)
+ cert = csr.sign(private_key=issuer.private_key,
+ algorithm=hashes.SHA256(),
+ backend=default_backend())
+ return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)
diff --git a/examples/tests/ngtcp2test/client.py b/examples/tests/ngtcp2test/client.py
new file mode 100644
index 0000000..2676343
--- /dev/null
+++ b/examples/tests/ngtcp2test/client.py
@@ -0,0 +1,187 @@
+import logging
+import os
+import re
+import subprocess
+from typing import List
+
+import pytest
+
+from .server import ExampleServer, ServerRun
+from .certs import Credentials
+from .tls import HandShake, HSRecord
+from .env import Env, CryptoLib
+from .log import LogFile, HexDumpScanner
+
+
+log = logging.getLogger(__name__)
+
+
+class ClientRun:
+
+ def __init__(self, env: Env, returncode, logfile: LogFile, srun: ServerRun):
+ self.env = env
+ self.returncode = returncode
+ self.logfile = logfile
+ self.log_lines = logfile.get_recent()
+ self._data_recs = None
+ self._hs_recs = None
+ self._srun = srun
+ if self.env.verbose > 1:
+ log.debug(f'read {len(self.log_lines)} lines from {logfile.path}')
+
+ @property
+ def handshake(self) -> List[HSRecord]:
+ if self._data_recs is None:
+ crypto_line = re.compile(r'Ordered CRYPTO data in \S+ crypto level')
+ scanner = HexDumpScanner(source=self.log_lines,
+ leading_regex=crypto_line)
+ self._data_recs = [data for data in scanner]
+ if self.env.verbose > 1:
+ log.debug(f'detected {len(self._data_recs)} crypto hexdumps '
+ f'in {self.logfile.path}')
+ if self._hs_recs is None:
+ self._hs_recs = [hrec for hrec in HandShake(source=self._data_recs,
+ verbose=self.env.verbose)]
+ if self.env.verbose > 1:
+ log.debug(f'detected {len(self._hs_recs)} crypto '
+ f'records in {self.logfile.path}')
+ return self._hs_recs
+
+ @property
+ def hs_stripe(self) -> str:
+ return ":".join([hrec.name for hrec in self.handshake])
+
+ @property
+ def early_data_rejected(self) -> bool:
+ for l in self.log_lines:
+ if re.match(r'^Early data was rejected by server.*', l):
+ return True
+ return False
+
+ @property
+ def server(self) -> ServerRun:
+ return self._srun
+
+ def norm_exp(self, c_hs, s_hs, allow_hello_retry=True):
+ if allow_hello_retry and self.hs_stripe.startswith('HelloRetryRequest:'):
+ c_hs = "HelloRetryRequest:" + c_hs
+ s_hs = "ClientHello:" + s_hs
+ return c_hs, s_hs
+
+ def _assert_hs(self, c_hs, s_hs):
+ if not self.hs_stripe.startswith(c_hs):
+ # what happened?
+ if self.hs_stripe == '':
+ # server send nothing
+ if self.server.hs_stripe == '':
+ # client send nothing
+ pytest.fail(f'client did not send a ClientHello"')
+ else:
+ # client send sth, but server did not respond
+ pytest.fail(f'server did not respond to ClientHello: '
+ f'{self.server.handshake[0].to_text()}"')
+ else:
+ pytest.fail(f'Expected "{c_hs}", got "{self.hs_stripe}"')
+ assert self.server.hs_stripe == s_hs, \
+ f'Expected "{s_hs}", got "{self.server.hs_stripe}"\n'
+
+ def assert_non_resume_handshake(self, allow_hello_retry=True):
+ # for client/server where KEY_SHARE do not match, the hello is retried
+ c_hs, s_hs = self.norm_exp(
+ "ServerHello:EncryptedExtensions:Certificate:CertificateVerify:Finished",
+ "ClientHello:Finished", allow_hello_retry=allow_hello_retry)
+ self._assert_hs(c_hs, s_hs)
+
+ def assert_resume_handshake(self):
+ # for client/server where KEY_SHARE do not match, the hello is retried
+ c_hs, s_hs = self.norm_exp("ServerHello:EncryptedExtensions:Finished",
+ "ClientHello:Finished")
+ self._assert_hs(c_hs, s_hs)
+
+ def assert_verify_null_handshake(self):
+ c_hs, s_hs = self.norm_exp(
+ "ServerHello:EncryptedExtensions:CertificateRequest:Certificate:CertificateVerify:Finished",
+ "ClientHello:Certificate:Finished")
+ self._assert_hs(c_hs, s_hs)
+
+ def assert_verify_cert_handshake(self):
+ c_hs, s_hs = self.norm_exp(
+ "ServerHello:EncryptedExtensions:CertificateRequest:Certificate:CertificateVerify:Finished",
+ "ClientHello:Certificate:CertificateVerify:Finished")
+ self._assert_hs(c_hs, s_hs)
+
+
+class ExampleClient:
+
+ def __init__(self, env: Env, crypto_lib: str):
+ self.env = env
+ self._crypto_lib = crypto_lib
+ self._path = env.client_path(self._crypto_lib)
+ self._log_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.log'
+ self._qlog_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.qlog'
+ self._session_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.session'
+ self._tp_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.tp'
+ self._data_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.data'
+
+ @property
+ def path(self):
+ return self._path
+
+ @property
+ def crypto_lib(self):
+ return self._crypto_lib
+
+ @property
+ def uses_cipher_config(self):
+ return CryptoLib.uses_cipher_config(self.crypto_lib)
+
+ def supports_cipher(self, cipher):
+ return CryptoLib.supports_cipher(self.crypto_lib, cipher)
+
+ def exists(self):
+ return os.path.isfile(self.path)
+
+ def clear_session(self):
+ if os.path.isfile(self._session_path):
+ os.remove(self._session_path)
+ if os.path.isfile(self._tp_path):
+ os.remove(self._tp_path)
+
+ def http_get(self, server: ExampleServer, url: str, extra_args: List[str] = None,
+ use_session=False, data=None,
+ credentials: Credentials = None,
+ ciphers: str = None):
+ args = [
+ self.path, '--exit-on-all-streams-close',
+ f'--qlog-file={self._qlog_path}'
+ ]
+ if use_session:
+ args.append(f'--session-file={self._session_path}')
+ args.append(f'--tp-file={self._tp_path}')
+ if data is not None:
+ with open(self._data_path, 'w') as fd:
+ fd.write(data)
+ args.append(f'--data={self._data_path}')
+ if ciphers is not None:
+ ciphers = CryptoLib.adjust_ciphers(self.crypto_lib, ciphers)
+ args.append(f'--ciphers={ciphers}')
+ if credentials is not None:
+ args.append(f'--key={credentials.pkey_file}')
+ args.append(f'--cert={credentials.cert_file}')
+ if extra_args is not None:
+ args.extend(extra_args)
+ args.extend([
+ 'localhost', str(self.env.examples_port),
+ url
+ ])
+ if os.path.isfile(self._qlog_path):
+ os.remove(self._qlog_path)
+ with open(self._log_path, 'w') as log_file:
+ logfile = LogFile(path=self._log_path)
+ server.log.advance()
+ process = subprocess.Popen(args=args, text=True,
+ stdout=log_file, stderr=log_file)
+ process.wait()
+ return ClientRun(env=self.env, returncode=process.returncode,
+ logfile=logfile, srun=server.get_run())
+
diff --git a/examples/tests/ngtcp2test/env.py b/examples/tests/ngtcp2test/env.py
new file mode 100644
index 0000000..9699d55
--- /dev/null
+++ b/examples/tests/ngtcp2test/env.py
@@ -0,0 +1,191 @@
+import logging
+import os
+from configparser import ConfigParser, ExtendedInterpolation
+from typing import Dict, Optional
+
+from .certs import CertificateSpec, Ngtcp2TestCA, Credentials
+
+log = logging.getLogger(__name__)
+
+
+class CryptoLib:
+
+ IGNORES_CIPHER_CONFIG = [
+ 'picotls', 'boringssl'
+ ]
+ UNSUPPORTED_CIPHERS = {
+ 'wolfssl': [
+ 'TLS_AES_128_CCM_SHA256', # no plans to
+ ],
+ 'picotls': [
+ 'TLS_AES_128_CCM_SHA256', # no plans to
+ ],
+ 'boringssl': [
+ 'TLS_AES_128_CCM_SHA256', # no plans to
+ ]
+ }
+ GNUTLS_CIPHERS = {
+ 'TLS_AES_128_GCM_SHA256': 'AES-128-GCM',
+ 'TLS_AES_256_GCM_SHA384': 'AES-256-GCM',
+ 'TLS_CHACHA20_POLY1305_SHA256': 'CHACHA20-POLY1305',
+ 'TLS_AES_128_CCM_SHA256': 'AES-128-CCM',
+ }
+
+ @classmethod
+ def uses_cipher_config(cls, crypto_lib):
+ return crypto_lib not in cls.IGNORES_CIPHER_CONFIG
+
+ @classmethod
+ def supports_cipher(cls, crypto_lib, cipher):
+ return crypto_lib not in cls.UNSUPPORTED_CIPHERS or \
+ cipher not in cls.UNSUPPORTED_CIPHERS[crypto_lib]
+
+ @classmethod
+ def adjust_ciphers(cls, crypto_lib, ciphers: str) -> str:
+ if crypto_lib == 'gnutls':
+ gciphers = "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL"
+ for cipher in ciphers.split(':'):
+ gciphers += f':+{cls.GNUTLS_CIPHERS[cipher]}'
+ return gciphers
+ return ciphers
+
+
+def init_config_from(conf_path):
+ if os.path.isfile(conf_path):
+ config = ConfigParser(interpolation=ExtendedInterpolation())
+ config.read(conf_path)
+ return config
+ return None
+
+
+TESTS_PATH = os.path.dirname(os.path.dirname(__file__))
+EXAMPLES_PATH = os.path.dirname(TESTS_PATH)
+DEF_CONFIG = init_config_from(os.path.join(TESTS_PATH, 'config.ini'))
+
+
+class Env:
+
+ @classmethod
+ def get_crypto_libs(cls, configurable_ciphers=None):
+ names = [name for name in DEF_CONFIG['examples']
+ if DEF_CONFIG['examples'][name] == 'yes']
+ if configurable_ciphers is not None:
+ names = [n for n in names if CryptoLib.uses_cipher_config(n)]
+ return names
+
+ def __init__(self, examples_dir=None, tests_dir=None, config=None,
+ pytestconfig=None):
+ self._verbose = pytestconfig.option.verbose if pytestconfig is not None else 0
+ self._examples_dir = examples_dir if examples_dir is not None else EXAMPLES_PATH
+ self._tests_dir = examples_dir if tests_dir is not None else TESTS_PATH
+ self._gen_dir = os.path.join(self._tests_dir, 'gen')
+ self.config = config if config is not None else DEF_CONFIG
+ self._version = self.config['ngtcp2']['version']
+ self._crypto_libs = [name for name in self.config['examples']
+ if self.config['examples'][name] == 'yes']
+ self._clients = [self.config['clients'][lib] for lib in self._crypto_libs
+ if lib in self.config['clients']]
+ self._servers = [self.config['servers'][lib] for lib in self._crypto_libs
+ if lib in self.config['servers']]
+ self._examples_pem = {
+ 'key': 'xxx',
+ 'cert': 'xxx',
+ }
+ self._htdocs_dir = os.path.join(self._gen_dir, 'htdocs')
+ self._tld = 'tests.ngtcp2.nghttp2.org'
+ self._example_domain = f"one.{self._tld}"
+ self._ca = None
+ self._cert_specs = [
+ CertificateSpec(domains=[self._example_domain], key_type='rsa2048'),
+ CertificateSpec(name="clientsX", sub_specs=[
+ CertificateSpec(name="user1", client=True),
+ ]),
+ ]
+
+ def issue_certs(self):
+ if self._ca is None:
+ self._ca = Ngtcp2TestCA.create_root(name=self._tld,
+ store_dir=os.path.join(self.gen_dir, 'ca'),
+ key_type="rsa2048")
+ self._ca.issue_certs(self._cert_specs)
+
+ def setup(self):
+ os.makedirs(self._gen_dir, exist_ok=True)
+ os.makedirs(self._htdocs_dir, exist_ok=True)
+ self.issue_certs()
+
+ def get_server_credentials(self) -> Optional[Credentials]:
+ creds = self.ca.get_credentials_for_name(self._example_domain)
+ if len(creds) > 0:
+ return creds[0]
+ return None
+
+ @property
+ def verbose(self) -> int:
+ return self._verbose
+
+ @property
+ def version(self) -> str:
+ return self._version
+
+ @property
+ def gen_dir(self) -> str:
+ return self._gen_dir
+
+ @property
+ def ca(self):
+ return self._ca
+
+ @property
+ def htdocs_dir(self) -> str:
+ return self._htdocs_dir
+
+ @property
+ def example_domain(self) -> str:
+ return self._example_domain
+
+ @property
+ def examples_dir(self) -> str:
+ return self._examples_dir
+
+ @property
+ def examples_port(self) -> int:
+ return int(self.config['examples']['port'])
+
+ @property
+ def examples_pem(self) -> Dict[str, str]:
+ return self._examples_pem
+
+ @property
+ def crypto_libs(self):
+ return self._crypto_libs
+
+ @property
+ def clients(self):
+ return self._clients
+
+ @property
+ def servers(self):
+ return self._servers
+
+ def client_name(self, crypto_lib):
+ if crypto_lib in self.config['clients']:
+ return self.config['clients'][crypto_lib]
+ return None
+
+ def client_path(self, crypto_lib):
+ cname = self.client_name(crypto_lib)
+ if cname is not None:
+ return os.path.join(self.examples_dir, cname)
+ return None
+
+ def server_name(self, crypto_lib):
+ if crypto_lib in self.config['servers']:
+ return self.config['servers'][crypto_lib]
+ return None
+
+ def server_path(self, crypto_lib):
+ sname = self.server_name(crypto_lib)
+ if sname is not None:
+ return os.path.join(self.examples_dir, sname)
+ return None
diff --git a/examples/tests/ngtcp2test/log.py b/examples/tests/ngtcp2test/log.py
new file mode 100644
index 0000000..9e8f399
--- /dev/null
+++ b/examples/tests/ngtcp2test/log.py
@@ -0,0 +1,101 @@
+import binascii
+import os
+import re
+import sys
+import time
+from datetime import timedelta, datetime
+from io import SEEK_END
+from typing import List
+
+
+class LogFile:
+
+ def __init__(self, path: str):
+ self._path = path
+ self._start_pos = 0
+ self._last_pos = self._start_pos
+
+ @property
+ def path(self) -> str:
+ return self._path
+
+ def reset(self):
+ self._start_pos = 0
+ self._last_pos = self._start_pos
+
+ def advance(self) -> None:
+ if os.path.isfile(self._path):
+ with open(self._path) as fd:
+ self._start_pos = fd.seek(0, SEEK_END)
+
+ def get_recent(self, advance=True) -> List[str]:
+ lines = []
+ if os.path.isfile(self._path):
+ with open(self._path) as fd:
+ fd.seek(self._last_pos, os.SEEK_SET)
+ for line in fd:
+ lines.append(line)
+ if advance:
+ self._last_pos = fd.tell()
+ return lines
+
+ def scan_recent(self, pattern: re, timeout=10) -> bool:
+ if not os.path.isfile(self.path):
+ return False
+ with open(self.path) as fd:
+ end = datetime.now() + timedelta(seconds=timeout)
+ while True:
+ fd.seek(self._last_pos, os.SEEK_SET)
+ for line in fd:
+ if pattern.match(line):
+ return True
+ if datetime.now() > end:
+ raise TimeoutError(f"pattern not found in error log after {timeout} seconds")
+ time.sleep(.1)
+ return False
+
+
+class HexDumpScanner:
+
+ def __init__(self, source, leading_regex=None):
+ self._source = source
+ self._leading_regex = leading_regex
+
+ def __iter__(self):
+ data = b''
+ offset = 0 if self._leading_regex is None else -1
+ idx = 0
+ for l in self._source:
+ if offset == -1:
+ pass
+ elif offset == 0:
+ # possible start of a hex dump
+ m = re.match(r'^\s*0+(\s+-)?((\s+[0-9a-f]{2}){1,16})(\s+.*)$',
+ l, re.IGNORECASE)
+ if m:
+ data = binascii.unhexlify(re.sub(r'\s+', '', m.group(2)))
+ offset = 16
+ idx = 1
+ continue
+ else:
+ # possible continuation of a hexdump
+ m = re.match(r'^\s*([0-9a-f]+)(\s+-)?((\s+[0-9a-f]{2}){1,16})'
+ r'(\s+.*)$', l, re.IGNORECASE)
+ if m:
+ loffset = int(m.group(1), 16)
+ if loffset == offset or loffset == idx:
+ data += binascii.unhexlify(re.sub(r'\s+', '',
+ m.group(3)))
+ offset += 16
+ idx += 1
+ continue
+ else:
+ sys.stderr.write(f'wrong offset {loffset}, expected {offset} or {idx}\n')
+ # not a hexdump line, produce any collected data
+ if len(data) > 0:
+ yield data
+ data = b''
+ offset = 0 if self._leading_regex is None \
+ or self._leading_regex.match(l) else -1
+ if len(data) > 0:
+ yield data
diff --git a/examples/tests/ngtcp2test/server.py b/examples/tests/ngtcp2test/server.py
new file mode 100644
index 0000000..9f4e9a0
--- /dev/null
+++ b/examples/tests/ngtcp2test/server.py
@@ -0,0 +1,137 @@
+import logging
+import os
+import re
+import subprocess
+import time
+from datetime import datetime, timedelta
+from threading import Thread
+
+from .tls import HandShake
+from .env import Env, CryptoLib
+from .log import LogFile, HexDumpScanner
+
+
+log = logging.getLogger(__name__)
+
+
+class ServerRun:
+
+ def __init__(self, env: Env, logfile: LogFile):
+ self.env = env
+ self._logfile = logfile
+ self.log_lines = self._logfile.get_recent()
+ self._data_recs = None
+ self._hs_recs = None
+ if self.env.verbose > 1:
+ log.debug(f'read {len(self.log_lines)} lines from {logfile.path}')
+
+ @property
+ def handshake(self):
+ if self._data_recs is None:
+ self._data_recs = [data for data in HexDumpScanner(source=self.log_lines)]
+ if self.env.verbose > 1:
+ log.debug(f'detected {len(self._data_recs)} hexdumps '
+ f'in {self._logfile.path}')
+ if self._hs_recs is None:
+ self._hs_recs = [hrec for hrec in HandShake(source=self._data_recs,
+ verbose=self.env.verbose)]
+ if self.env.verbose > 1:
+ log.debug(f'detected {len(self._hs_recs)} crypto records '
+ f'in {self._logfile.path}')
+ return self._hs_recs
+
+ @property
+ def hs_stripe(self):
+ return ":".join([hrec.name for hrec in self.handshake])
+
+
+def monitor_proc(env: Env, proc):
+ _env = env
+ proc.wait()
+
+
+class ExampleServer:
+
+ def __init__(self, env: Env, crypto_lib: str, verify_client=False):
+ self.env = env
+ self._crypto_lib = crypto_lib
+ self._path = env.server_path(self._crypto_lib)
+ self._logpath = f'{self.env.gen_dir}/{self._crypto_lib}-server.log'
+ self._log = LogFile(path=self._logpath)
+ self._logfile = None
+ self._process = None
+ self._verify_client = verify_client
+
+ @property
+ def path(self):
+ return self._path
+
+ @property
+ def crypto_lib(self):
+ return self._crypto_lib
+
+ @property
+ def uses_cipher_config(self):
+ return CryptoLib.uses_cipher_config(self.crypto_lib)
+
+ def supports_cipher(self, cipher):
+ return CryptoLib.supports_cipher(self.crypto_lib, cipher)
+
+ @property
+ def log(self):
+ return self._log
+
+ def exists(self):
+ return os.path.isfile(self.path)
+
+ def start(self):
+ if self._process is not None:
+ return False
+ creds = self.env.get_server_credentials()
+ assert creds
+ args = [
+ self.path,
+ f'--htdocs={self.env.htdocs_dir}',
+ ]
+ if self._verify_client:
+ args.append('--verify-client')
+ args.extend([
+ '*', str(self.env.examples_port),
+ creds.pkey_file, creds.cert_file
+ ])
+ self._logfile = open(self._logpath, 'w')
+ self._process = subprocess.Popen(args=args, text=True,
+ stdout=self._logfile, stderr=self._logfile)
+ t = Thread(target=monitor_proc, daemon=True, args=(self.env, self._process))
+ t.start()
+ timeout = 5
+ end = datetime.now() + timedelta(seconds=timeout)
+ while True:
+ if self._process.poll():
+ return False
+ try:
+ if self.log.scan_recent(pattern=re.compile(r'^Using document root'), timeout=0.5):
+ break
+ except TimeoutError:
+ pass
+ if datetime.now() > end:
+ raise TimeoutError(f"pattern not found in error log after {timeout} seconds")
+ self.log.advance()
+ return True
+
+ def stop(self):
+ if self._process:
+ self._process.terminate()
+ self._process = None
+ if self._logfile:
+ self._logfile.close()
+ self._logfile = None
+ return True
+
+ def restart(self):
+ self.stop()
+ self._log.reset()
+ return self.start()
+
+ def get_run(self) -> ServerRun:
+ return ServerRun(env=self.env, logfile=self.log)
diff --git a/examples/tests/ngtcp2test/tls.py b/examples/tests/ngtcp2test/tls.py
new file mode 100644
index 0000000..f9bce62
--- /dev/null
+++ b/examples/tests/ngtcp2test/tls.py
@@ -0,0 +1,983 @@
+import binascii
+import logging
+import sys
+from collections.abc import Iterator
+from typing import Dict, Any, Iterable
+
+
+log = logging.getLogger(__name__)
+
+
+class ParseError(Exception):
+ pass
+
+def _get_int(d, n):
+ if len(d) < n:
+ raise ParseError(f'get_int: {n} bytes needed, but data is {d}')
+ if n == 1:
+ dlen = d[0]
+ else:
+ dlen = int.from_bytes(d[0:n], byteorder='big')
+ return d[n:], dlen
+
+
+def _get_field(d, dlen):
+ if dlen > 0:
+ if len(d) < dlen:
+ raise ParseError(f'field len={dlen}, but data len={len(d)}')
+ field = d[0:dlen]
+ return d[dlen:], field
+ return d, b''
+
+
+def _get_len_field(d, n):
+ d, dlen = _get_int(d, n)
+ return _get_field(d, dlen)
+
+
+# d are bytes that start with a quic variable length integer
+def _get_qint(d):
+ i = d[0] & 0xc0
+ if i == 0:
+ return d[1:], int(d[0])
+ elif i == 0x40:
+ ndata = bytearray(d[0:2])
+ d = d[2:]
+ ndata[0] = ndata[0] & ~0xc0
+ return d, int.from_bytes(ndata, byteorder='big')
+ elif i == 0x80:
+ ndata = bytearray(d[0:4])
+ d = d[4:]
+ ndata[0] = ndata[0] & ~0xc0
+ return d, int.from_bytes(ndata, byteorder='big')
+ else:
+ ndata = bytearray(d[0:8])
+ d = d[8:]
+ ndata[0] = ndata[0] & ~0xc0
+ return d, int.from_bytes(ndata, byteorder='big')
+
+
+class TlsSupportedGroups:
+ NAME_BY_ID = {
+ 0: 'Reserved',
+ 1: 'sect163k1',
+ 2: 'sect163r1',
+ 3: 'sect163r2',
+ 4: 'sect193r1',
+ 5: 'sect193r2',
+ 6: 'sect233k1',
+ 7: 'sect233r1',
+ 8: 'sect239k1',
+ 9: 'sect283k1',
+ 10: 'sect283r1',
+ 11: 'sect409k1',
+ 12: 'sect409r1',
+ 13: 'sect571k1',
+ 14: 'sect571r1',
+ 15: 'secp160k1',
+ 16: 'secp160r1',
+ 17: 'secp160r2',
+ 18: 'secp192k1',
+ 19: 'secp192r1',
+ 20: 'secp224k1',
+ 21: 'secp224r1',
+ 22: 'secp256k1',
+ 23: 'secp256r1',
+ 24: 'secp384r1',
+ 25: 'secp521r1',
+ 26: 'brainpoolP256r1',
+ 27: 'brainpoolP384r1',
+ 28: 'brainpoolP512r1',
+ 29: 'x25519',
+ 30: 'x448',
+ 31: 'brainpoolP256r1tls13',
+ 32: 'brainpoolP384r1tls13',
+ 33: 'brainpoolP512r1tls13',
+ 34: 'GC256A',
+ 35: 'GC256B',
+ 36: 'GC256C',
+ 37: 'GC256D',
+ 38: 'GC512A',
+ 39: 'GC512B',
+ 40: 'GC512C',
+ 41: 'curveSM2',
+ }
+
+ @classmethod
+ def name(cls, gid):
+ if gid in cls.NAME_BY_ID:
+ return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})'
+ return f'0x{gid:0x}'
+
+
+class TlsSignatureScheme:
+ NAME_BY_ID = {
+ 0x0201: 'rsa_pkcs1_sha1',
+ 0x0202: 'Reserved',
+ 0x0203: 'ecdsa_sha1',
+ 0x0401: 'rsa_pkcs1_sha256',
+ 0x0403: 'ecdsa_secp256r1_sha256',
+ 0x0420: 'rsa_pkcs1_sha256_legacy',
+ 0x0501: 'rsa_pkcs1_sha384',
+ 0x0503: 'ecdsa_secp384r1_sha384',
+ 0x0520: 'rsa_pkcs1_sha384_legacy',
+ 0x0601: 'rsa_pkcs1_sha512',
+ 0x0603: 'ecdsa_secp521r1_sha512',
+ 0x0620: 'rsa_pkcs1_sha512_legacy',
+ 0x0704: 'eccsi_sha256',
+ 0x0705: 'iso_ibs1',
+ 0x0706: 'iso_ibs2',
+ 0x0707: 'iso_chinese_ibs',
+ 0x0708: 'sm2sig_sm3',
+ 0x0709: 'gostr34102012_256a',
+ 0x070A: 'gostr34102012_256b',
+ 0x070B: 'gostr34102012_256c',
+ 0x070C: 'gostr34102012_256d',
+ 0x070D: 'gostr34102012_512a',
+ 0x070E: 'gostr34102012_512b',
+ 0x070F: 'gostr34102012_512c',
+ 0x0804: 'rsa_pss_rsae_sha256',
+ 0x0805: 'rsa_pss_rsae_sha384',
+ 0x0806: 'rsa_pss_rsae_sha512',
+ 0x0807: 'ed25519',
+ 0x0808: 'ed448',
+ 0x0809: 'rsa_pss_pss_sha256',
+ 0x080A: 'rsa_pss_pss_sha384',
+ 0x080B: 'rsa_pss_pss_sha512',
+ 0x081A: 'ecdsa_brainpoolP256r1tls13_sha256',
+ 0x081B: 'ecdsa_brainpoolP384r1tls13_sha384',
+ 0x081C: 'ecdsa_brainpoolP512r1tls13_sha512',
+ }
+
+ @classmethod
+ def name(cls, gid):
+ if gid in cls.NAME_BY_ID:
+ return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})'
+ return f'0x{gid:0x}'
+
+
+class TlsCipherSuites:
+ NAME_BY_ID = {
+ 0x1301: 'TLS_AES_128_GCM_SHA256',
+ 0x1302: 'TLS_AES_256_GCM_SHA384',
+ 0x1303: 'TLS_CHACHA20_POLY1305_SHA256',
+ 0x1304: 'TLS_AES_128_CCM_SHA256',
+ 0x1305: 'TLS_AES_128_CCM_8_SHA256',
+ 0x00ff: 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV',
+ }
+
+ @classmethod
+ def name(cls, cid):
+ if cid in cls.NAME_BY_ID:
+ return f'{cls.NAME_BY_ID[cid]}(0x{cid:0x})'
+ return f'Cipher(0x{cid:0x})'
+
+
+class PskKeyExchangeMode:
+ NAME_BY_ID = {
+ 0x00: 'psk_ke',
+ 0x01: 'psk_dhe_ke',
+ }
+
+ @classmethod
+ def name(cls, gid):
+ if gid in cls.NAME_BY_ID:
+ return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})'
+ return f'0x{gid:0x}'
+
+
+class QuicTransportParam:
+ NAME_BY_ID = {
+ 0x00: 'original_destination_connection_id',
+ 0x01: 'max_idle_timeout',
+ 0x02: 'stateless_reset_token',
+ 0x03: 'max_udp_payload_size',
+ 0x04: 'initial_max_data',
+ 0x05: 'initial_max_stream_data_bidi_local',
+ 0x06: 'initial_max_stream_data_bidi_remote',
+ 0x07: 'initial_max_stream_data_uni',
+ 0x08: 'initial_max_streams_bidi',
+ 0x09: 'initial_max_streams_uni',
+ 0x0a: 'ack_delay_exponent',
+ 0x0b: 'max_ack_delay',
+ 0x0c: 'disable_active_migration',
+ 0x0d: 'preferred_address',
+ 0x0e: 'active_connection_id_limit',
+ 0x0f: 'initial_source_connection_id',
+ 0x10: 'retry_source_connection_id',
+ }
+ TYPE_BY_ID = {
+ 0x00: bytes,
+ 0x01: int,
+ 0x02: bytes,
+ 0x03: int,
+ 0x04: int,
+ 0x05: int,
+ 0x06: int,
+ 0x07: int,
+ 0x08: int,
+ 0x09: int,
+ 0x0a: int,
+ 0x0b: int,
+ 0x0c: int,
+ 0x0d: bytes,
+ 0x0e: int,
+ 0x0f: bytes,
+ 0x10: bytes,
+ }
+
+ @classmethod
+ def name(cls, cid):
+ if cid in cls.NAME_BY_ID:
+ return f'{cls.NAME_BY_ID[cid]}(0x{cid:0x})'
+ return f'QuicTP(0x{cid:0x})'
+
+ @classmethod
+ def is_qint(cls, cid):
+ if cid in cls.TYPE_BY_ID:
+ return cls.TYPE_BY_ID[cid] == int
+ return False
+
+
+class Extension:
+
+ def __init__(self, eid, name, edata, hsid):
+ self._eid = eid
+ self._name = name
+ self._edata = edata
+ self._hsid = hsid
+
+ @property
+ def data(self):
+ return self._edata
+
+ @property
+ def hsid(self):
+ return self._hsid
+
+ def to_json(self):
+ jdata = {
+ 'id': self._eid,
+ 'name': self._name,
+ }
+ if len(self.data) > 0:
+ jdata['data'] = binascii.hexlify(self.data).decode()
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ s = f'{ind}{self._name}(0x{self._eid:0x})'
+ if len(self._edata):
+ s += f'\n{ind} data({len(self._edata)}): ' \
+ f'{binascii.hexlify(self._edata).decode()}'
+ return s
+
+
+class ExtSupportedGroups(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ d = edata
+ self._groups = []
+ while len(d) > 0:
+ d, gid = _get_int(d, 2)
+ self._groups.append(gid)
+
+ def to_json(self):
+ jdata = {
+ 'id': self._eid,
+ 'name': self._name,
+ }
+ if len(self._groups):
+ jdata['groups'] = [TlsSupportedGroups.name(gid)
+ for gid in self._groups]
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ gnames = [TlsSupportedGroups.name(gid) for gid in self._groups]
+ s = f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(gnames)}'
+ return s
+
+
+class ExtKeyShare(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ d = self.data
+ self._keys = []
+ self._group = None
+ self._pubkey = None
+ if self.hsid == 2: # ServerHello
+ # single key share (group, pubkey)
+ d, self._group = _get_int(d, 2)
+ d, self._pubkey = _get_len_field(d, 2)
+ elif self.hsid == 6: # HelloRetryRequest
+ assert len(d) == 2
+ d, self._group = _get_int(d, 2)
+ else:
+ # list if key shares (group, pubkey)
+ d, shares = _get_len_field(d, 2)
+ while len(shares) > 0:
+ shares, group = _get_int(shares, 2)
+ shares, pubkey = _get_len_field(shares, 2)
+ self._keys.append({
+ 'group': TlsSupportedGroups.name(group),
+ 'pubkey': binascii.hexlify(pubkey).decode()
+ })
+
+ def to_json(self):
+ jdata = super().to_json()
+ if self._group is not None:
+ jdata['group'] = TlsSupportedGroups.name(self._group)
+ if self._pubkey is not None:
+ jdata['pubkey'] = binascii.hexlify(self._pubkey).decode()
+ if len(self._keys) > 0:
+ jdata['keys'] = self._keys
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ s = f'{ind}{self._name}(0x{self._eid:0x})'
+ if self._group is not None:
+ s += f'\n{ind} group: {TlsSupportedGroups.name(self._group)}'
+ if self._pubkey is not None:
+ s += f'\n{ind} pubkey: {binascii.hexlify(self._pubkey).decode()}'
+ if len(self._keys) > 0:
+ for idx, key in enumerate(self._keys):
+ s += f'\n{ind} {idx}: {key["group"]}, {key["pubkey"]}'
+ return s
+
+
+class ExtSNI(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ d = self.data
+ self._indicators = []
+ while len(d) > 0:
+ d, entry = _get_len_field(d, 2)
+ entry, stype = _get_int(entry, 1)
+ entry, sname = _get_len_field(entry, 2)
+ self._indicators.append({
+ 'type': stype,
+ 'name': sname.decode()
+ })
+
+ def to_json(self):
+ jdata = super().to_json()
+ for i in self._indicators:
+ if i['type'] == 0:
+ jdata['host_name'] = i['name']
+ else:
+ jdata[f'0x{i["type"]}'] = i['name']
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ s = f'{ind}{self._name}(0x{self._eid:0x})'
+ if len(self._indicators) == 1 and self._indicators[0]['type'] == 0:
+ s += f': {self._indicators[0]["name"]}'
+ else:
+ for i in self._indicators:
+ ikey = 'host_name' if i["type"] == 0 else f'type(0x{i["type"]:0x}'
+ s += f'\n{ind} {ikey}: {i["name"]}'
+ return s
+
+
+class ExtALPN(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ d = self.data
+ d, list_len = _get_int(d, 2)
+ self._protocols = []
+ while len(d) > 0:
+ d, proto = _get_len_field(d, 1)
+ self._protocols.append(proto.decode())
+
+ def to_json(self):
+ jdata = super().to_json()
+ if len(self._protocols) == 1:
+ jdata['alpn'] = self._protocols[0]
+ else:
+ jdata['alpn'] = self._protocols
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._protocols)}'
+
+
+class ExtEarlyData(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ self._max_size = None
+ d = self.data
+ if hsid == 4: # SessionTicket
+ assert len(d) == 4, f'expected 4, len is {len(d)} data={d}'
+ d, self._max_size = _get_int(d, 4)
+ else:
+ assert len(d) == 0
+
+ def to_json(self):
+ jdata = super().to_json()
+ if self._max_size is not None:
+ jdata['max_size'] = self._max_size
+ return jdata
+
+
+class ExtSignatureAlgorithms(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ d = self.data
+ d, list_len = _get_int(d, 2)
+ self._algos = []
+ while len(d) > 0:
+ d, algo = _get_int(d, 2)
+ self._algos.append(TlsSignatureScheme.name(algo))
+
+ def to_json(self):
+ jdata = super().to_json()
+ if len(self._algos) > 0:
+ jdata['algorithms'] = self._algos
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._algos)}'
+
+
+class ExtPSKExchangeModes(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ d = self.data
+ d, list_len = _get_int(d, 1)
+ self._modes = []
+ while len(d) > 0:
+ d, mode = _get_int(d, 1)
+ self._modes.append(PskKeyExchangeMode.name(mode))
+
+ def to_json(self):
+ jdata = super().to_json()
+ jdata['modes'] = self._modes
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._modes)}'
+
+
+class ExtPreSharedKey(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ self._kid = None
+ self._identities = None
+ self._binders = None
+ d = self.data
+ if hsid == 1: # client hello
+ d, idata = _get_len_field(d, 2)
+ self._identities = []
+ while len(idata):
+ idata, identity = _get_len_field(idata, 2)
+ idata, obfs_age = _get_int(idata, 4)
+ self._identities.append({
+ 'id': binascii.hexlify(identity).decode(),
+ 'age': obfs_age,
+ })
+ d, binders = _get_len_field(d, 2)
+ self._binders = []
+ while len(binders) > 0:
+ binders, hmac = _get_len_field(binders, 1)
+ self._binders.append(binascii.hexlify(hmac).decode())
+ assert len(d) == 0
+ else:
+ d, self._kid = _get_int(d, 2)
+
+ def to_json(self):
+ jdata = super().to_json()
+ if self.hsid == 1:
+ jdata['identities'] = self._identities
+ jdata['binders'] = self._binders
+ else:
+ jdata['identity'] = self._kid
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ s = f'{ind}{self._name}(0x{self._hsid:0x})'
+ if self.hsid == 1:
+ for idx, i in enumerate(self._identities):
+ s += f'\n{ind} {idx}: {i["id"]} ({i["age"]})'
+ s += f'\n{ind} binders: {self._binders}'
+ else:
+ s += f'\n{ind} identity: {self._kid}'
+ return s
+
+
+class ExtSupportedVersions(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ d = self.data
+ self._versions = []
+ if hsid == 1: # client hello
+ d, list_len = _get_int(d, 1)
+ while len(d) > 0:
+ d, version = _get_int(d, 2)
+ self._versions.append(f'0x{version:0x}')
+ else:
+ d, version = _get_int(d, 2)
+ self._versions.append(f'0x{version:0x}')
+
+ def to_json(self):
+ jdata = super().to_json()
+ if len(self._versions) == 1:
+ jdata['version'] = self._versions[0]
+ else:
+ jdata['versions'] = self._versions
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._versions)}'
+
+
+class ExtQuicTP(Extension):
+
+ def __init__(self, eid, name, edata, hsid):
+ super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
+ d = self.data
+ self._params = []
+ while len(d) > 0:
+ d, ptype = _get_qint(d)
+ d, plen = _get_qint(d)
+ d, pvalue = _get_field(d, plen)
+ if QuicTransportParam.is_qint(ptype):
+ _, pvalue = _get_qint(pvalue)
+ else:
+ pvalue = binascii.hexlify(pvalue).decode()
+ self._params.append({
+ 'key': QuicTransportParam.name(ptype),
+ 'value': pvalue,
+ })
+
+ def to_json(self):
+ jdata = super().to_json()
+ jdata['params'] = self._params
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ s = f'{ind}{self._name}(0x{self._eid:0x})'
+ for p in self._params:
+ s += f'\n{ind} {p["key"]}: {p["value"]}'
+ return s
+
+
+class TlsExtensions:
+
+ EXT_TYPES = [
+ (0x00, 'SNI', ExtSNI),
+ (0x01, 'MAX_FRAGMENT_LENGTH', Extension),
+ (0x03, 'TRUSTED_CA_KEYS', Extension),
+ (0x04, 'TRUNCATED_HMAC', Extension),
+ (0x05, 'OSCP_STATUS_REQUEST', Extension),
+ (0x0a, 'SUPPORTED_GROUPS', ExtSupportedGroups),
+ (0x0b, 'EC_POINT_FORMATS', Extension),
+ (0x0d, 'SIGNATURE_ALGORITHMS', ExtSignatureAlgorithms),
+ (0x0e, 'USE_SRTP', Extension),
+ (0x10, 'ALPN', ExtALPN),
+ (0x11, 'STATUS_REQUEST_V2', Extension),
+ (0x16, 'ENCRYPT_THEN_MAC', Extension),
+ (0x17, 'EXTENDED_MASTER_SECRET', Extension),
+ (0x23, 'SESSION_TICKET', Extension),
+ (0x29, 'PRE_SHARED_KEY', ExtPreSharedKey),
+ (0x2a, 'EARLY_DATA', ExtEarlyData),
+ (0x2b, 'SUPPORTED_VERSIONS', ExtSupportedVersions),
+ (0x2c, 'COOKIE', Extension),
+ (0x2d, 'PSK_KEY_EXCHANGE_MODES', ExtPSKExchangeModes),
+ (0x31, 'POST_HANDSHAKE_AUTH', Extension),
+ (0x32, 'SIGNATURE_ALGORITHMS_CERT', Extension),
+ (0x33, 'KEY_SHARE', ExtKeyShare),
+ (0x39, 'QUIC_TP_PARAMS', ExtQuicTP),
+ (0xff01, 'RENEGOTIATION_INFO', Extension),
+ (0xffa5, 'QUIC_TP_PARAMS_DRAFT', ExtQuicTP),
+ ]
+ NAME_BY_ID = {}
+ CLASS_BY_ID = {}
+
+ @classmethod
+ def init(cls):
+ for (eid, name, ecls) in cls.EXT_TYPES:
+ cls.NAME_BY_ID[eid] = name
+ cls.CLASS_BY_ID[eid] = ecls
+
+ @classmethod
+ def from_data(cls, hsid, data):
+ exts = []
+ d = data
+ while len(d):
+ d, eid = _get_int(d, 2)
+ d, elen = _get_int(d, 2)
+ d, edata = _get_field(d, elen)
+ if eid in cls.NAME_BY_ID:
+ ename = cls.NAME_BY_ID[eid]
+ ecls = cls.CLASS_BY_ID[eid]
+ exts.append(ecls(eid=eid, name=ename, edata=edata, hsid=hsid))
+ else:
+ exts.append(Extension(eid=eid, name=f'(0x{eid:0x})',
+ edata=edata, hsid=hsid))
+ return exts
+
+
+TlsExtensions.init()
+
+
+class HSRecord:
+
+ def __init__(self, hsid: int, name: str, data):
+ self._hsid = hsid
+ self._name = name
+ self._data = data
+
+ @property
+ def hsid(self):
+ return self._hsid
+
+ @property
+ def name(self):
+ return self._name
+
+ @name.setter
+ def name(self, value):
+ self._name = value
+
+ @property
+ def data(self):
+ return self._data
+
+ def __repr__(self):
+ return f'{self.name}[{binascii.hexlify(self._data).decode()}]'
+
+ def to_json(self) -> Dict[str, Any]:
+ return {
+ 'name': self.name,
+ 'data': binascii.hexlify(self._data).decode(),
+ }
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return f'{ind}{self._name}\n'\
+ f'{ind} id: 0x{self._hsid:0x}\n'\
+ f'{ind} data({len(self._data)}): '\
+ f'{binascii.hexlify(self._data).decode()}'
+
+
+class ClientHello(HSRecord):
+
+ def __init__(self, hsid: int, name: str, data):
+ super().__init__(hsid=hsid, name=name, data=data)
+ d = data
+ d, self._version = _get_int(d, 2)
+ d, self._random = _get_field(d, 32)
+ d, self._session_id = _get_len_field(d, 1)
+ self._ciphers = []
+ d, ciphers = _get_len_field(d, 2)
+ while len(ciphers):
+ ciphers, cipher = _get_int(ciphers, 2)
+ self._ciphers.append(TlsCipherSuites.name(cipher))
+ d, comps = _get_len_field(d, 1)
+ self._compressions = [int(c) for c in comps]
+ d, edata = _get_len_field(d, 2)
+ self._extensions = TlsExtensions.from_data(hsid, edata)
+
+ def to_json(self):
+ jdata = super().to_json()
+ jdata['version'] = f'0x{self._version:0x}'
+ jdata['random'] = f'{binascii.hexlify(self._random).decode()}'
+ jdata['session_id'] = binascii.hexlify(self._session_id).decode()
+ jdata['ciphers'] = self._ciphers
+ jdata['compressions'] = self._compressions
+ jdata['extensions'] = [ext.to_json() for ext in self._extensions]
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return super().to_text(indent=indent) + '\n'\
+ f'{ind} version: 0x{self._version:0x}\n'\
+ f'{ind} random: {binascii.hexlify(self._random).decode()}\n' \
+ f'{ind} session_id: {binascii.hexlify(self._session_id).decode()}\n' \
+ f'{ind} ciphers: {", ".join(self._ciphers)}\n'\
+ f'{ind} compressions: {self._compressions}\n'\
+ f'{ind} extensions: \n' + '\n'.join(
+ [ext.to_text(indent=indent+4) for ext in self._extensions])
+
+
+class ServerHello(HSRecord):
+
+ HELLO_RETRY_RANDOM = binascii.unhexlify(
+ 'CF21AD74E59A6111BE1D8C021E65B891C2A211167ABB8C5E079E09E2C8A8339C'
+ )
+
+ def __init__(self, hsid: int, name: str, data):
+ super().__init__(hsid=hsid, name=name, data=data)
+ d = data
+ d, self._version = _get_int(d, 2)
+ d, self._random = _get_field(d, 32)
+ if self._random == self.HELLO_RETRY_RANDOM:
+ self.name = 'HelloRetryRequest'
+ hsid = 6
+ d, self._session_id = _get_len_field(d, 1)
+ d, cipher = _get_int(d, 2)
+ self._cipher = TlsCipherSuites.name(cipher)
+ d, self._compression = _get_int(d, 1)
+ d, edata = _get_len_field(d, 2)
+ self._extensions = TlsExtensions.from_data(hsid, edata)
+
+ def to_json(self):
+ jdata = super().to_json()
+ jdata['version'] = f'0x{self._version:0x}'
+ jdata['random'] = f'{binascii.hexlify(self._random).decode()}'
+ jdata['session_id'] = binascii.hexlify(self._session_id).decode()
+ jdata['cipher'] = self._cipher
+ jdata['compression'] = int(self._compression)
+ jdata['extensions'] = [ext.to_json() for ext in self._extensions]
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return super().to_text(indent=indent) + '\n'\
+ f'{ind} version: 0x{self._version:0x}\n'\
+ f'{ind} random: {binascii.hexlify(self._random).decode()}\n' \
+ f'{ind} session_id: {binascii.hexlify(self._session_id).decode()}\n' \
+ f'{ind} cipher: {self._cipher}\n'\
+ f'{ind} compression: {int(self._compression)}\n'\
+ f'{ind} extensions: \n' + '\n'.join(
+ [ext.to_text(indent=indent+4) for ext in self._extensions])
+
+
+class EncryptedExtensions(HSRecord):
+
+ def __init__(self, hsid: int, name: str, data):
+ super().__init__(hsid=hsid, name=name, data=data)
+ d = data
+ d, edata = _get_len_field(d, 2)
+ self._extensions = TlsExtensions.from_data(hsid, edata)
+
+ def to_json(self):
+ jdata = super().to_json()
+ jdata['extensions'] = [ext.to_json() for ext in self._extensions]
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return super().to_text(indent=indent) + '\n'\
+ f'{ind} extensions: \n' + '\n'.join(
+ [ext.to_text(indent=indent+4) for ext in self._extensions])
+
+
+class CertificateRequest(HSRecord):
+
+ def __init__(self, hsid: int, name: str, data):
+ super().__init__(hsid=hsid, name=name, data=data)
+ d = data
+ d, self._context = _get_int(d, 1)
+ d, edata = _get_len_field(d, 2)
+ self._extensions = TlsExtensions.from_data(hsid, edata)
+
+ def to_json(self):
+ jdata = super().to_json()
+ jdata['context'] = self._context
+ jdata['extensions'] = [ext.to_json() for ext in self._extensions]
+ return jdata
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return super().to_text(indent=indent) + '\n'\
+ f'{ind} context: {self._context}\n'\
+ f'{ind} extensions: \n' + '\n'.join(
+ [ext.to_text(indent=indent+4) for ext in self._extensions])
+
+
+class Certificate(HSRecord):
+
+ def __init__(self, hsid: int, name: str, data):
+ super().__init__(hsid=hsid, name=name, data=data)
+ d = data
+ d, self._context = _get_int(d, 1)
+ d, clist = _get_len_field(d, 3)
+ self._cert_entries = []
+ while len(clist) > 0:
+ clist, cert_data = _get_len_field(clist, 3)
+ clist, cert_exts = _get_len_field(clist, 2)
+ exts = TlsExtensions.from_data(hsid, cert_exts)
+ self._cert_entries.append({
+ 'cert': binascii.hexlify(cert_data).decode(),
+ 'extensions': exts,
+ })
+
+ def to_json(self):
+ jdata = super().to_json()
+ jdata['context'] = self._context
+ jdata['certificate_list'] = [{
+ 'cert': e['cert'],
+ 'extensions': [x.to_json() for x in e['extensions']],
+ } for e in self._cert_entries]
+ return jdata
+
+ def _enxtry_text(self, e, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return f'{ind} cert: {e["cert"]}\n'\
+ f'{ind} extensions: \n' + '\n'.join(
+ [x.to_text(indent=indent + 4) for x in e['extensions']])
+
+ def to_text(self, indent: int = 0):
+ ind = ' ' * (indent + 2)
+ return super().to_text(indent=indent) + '\n'\
+ f'{ind} context: {self._context}\n'\
+ f'{ind} certificate_list: \n' + '\n'.join(
+ [self._enxtry_text(e, indent+4) for e in self._cert_entries])
+
+
+class SessionTicket(HSRecord):
+
+ def __init__(self, hsid: int, name: str, data):
+ super().__init__(hsid=hsid, name=name, data=data)
+ d = data
+ d, self._lifetime = _get_int(d, 4)
+ d, self._age = _get_int(d, 4)
+ d, self._nonce = _get_len_field(d, 1)
+ d, self._ticket = _get_len_field(d, 2)
+ d, edata = _get_len_field(d, 2)
+ self._extensions = TlsExtensions.from_data(hsid, edata)
+
+ def to_json(self):
+ jdata = super().to_json()
+ jdata['lifetime'] = self._lifetime
+ jdata['age'] = self._age
+ jdata['nonce'] = binascii.hexlify(self._nonce).decode()
+ jdata['ticket'] = binascii.hexlify(self._ticket).decode()
+ jdata['extensions'] = [ext.to_json() for ext in self._extensions]
+ return jdata
+
+
+class HSIterator(Iterator):
+
+ def __init__(self, recs):
+ self._recs = recs
+ self._index = 0
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ try:
+ result = self._recs[self._index]
+ except IndexError:
+ raise StopIteration
+ self._index += 1
+ return result
+
+
+class HandShake:
+ REC_TYPES = [
+ (1, 'ClientHello', ClientHello),
+ (2, 'ServerHello', ServerHello),
+ (3, 'HelloVerifyRequest', HSRecord),
+ (4, 'SessionTicket', SessionTicket),
+ (5, 'EndOfEarlyData', HSRecord),
+ (6, 'HelloRetryRequest', ServerHello),
+ (8, 'EncryptedExtensions', EncryptedExtensions),
+ (11, 'Certificate', Certificate),
+ (12, 'ServerKeyExchange ', HSRecord),
+ (13, 'CertificateRequest', CertificateRequest),
+ (14, 'ServerHelloDone', HSRecord),
+ (15, 'CertificateVerify', HSRecord),
+ (16, 'ClientKeyExchange', HSRecord),
+ (20, 'Finished', HSRecord),
+ (22, 'CertificateStatus', HSRecord),
+ (24, 'KeyUpdate', HSRecord),
+ ]
+ RT_NAME_BY_ID = {}
+ RT_CLS_BY_ID = {}
+
+ @classmethod
+ def _parse_rec(cls, data):
+ d, hsid = _get_int(data, 1)
+ if hsid not in cls.RT_CLS_BY_ID:
+ raise ParseError(f'unknown type {hsid}')
+ d, rec_len = _get_int(d, 3)
+ if rec_len > len(d):
+ # incomplete, need more data
+ return data, None
+ d, rec_data = _get_field(d, rec_len)
+ if hsid in cls.RT_CLS_BY_ID:
+ name = cls.RT_NAME_BY_ID[hsid]
+ rcls = cls.RT_CLS_BY_ID[hsid]
+ else:
+ name = f'CryptoRecord(0x{hsid:0x})'
+ rcls = HSRecord
+ return d, rcls(hsid=hsid, name=name, data=rec_data)
+
+ @classmethod
+ def _parse(cls, source, strict=False, verbose: int = 0):
+ d = b''
+ hsid = 0
+ hsrecs = []
+ if verbose > 0:
+ log.debug(f'scanning for handshake records')
+ blocks = [d for d in source]
+ while len(blocks) > 0:
+ try:
+ total_data = b''.join(blocks)
+ remain, r = cls._parse_rec(total_data)
+ if r is None:
+ # if we could not recognize a record, skip the first
+ # data block and try again
+ blocks = blocks[1:]
+ continue
+ hsrecs.append(r)
+ cons_len = len(total_data) - len(remain)
+ while cons_len > 0 and len(blocks) > 0:
+ if cons_len >= len(blocks[0]):
+ cons_len -= len(blocks[0])
+ blocks = blocks[1:]
+ else:
+ blocks[0] = blocks[0][cons_len:]
+ cons_len = 0
+ if verbose > 2:
+ log.debug(f'added record: {r.to_text()}')
+ except ParseError as err:
+ # if we could not recognize a record, skip the first
+ # data block and try again
+ blocks = blocks[1:]
+ if len(blocks) > 0 and strict:
+ raise Exception(f'possibly incomplete handshake record '
+ f'id={hsid}, from raw={blocks}\n')
+ return hsrecs
+
+
+
+ @classmethod
+ def init(cls):
+ for (hsid, name, rcls) in cls.REC_TYPES:
+ cls.RT_NAME_BY_ID[hsid] = name
+ cls.RT_CLS_BY_ID[hsid] = rcls
+
+ def __init__(self, source: Iterable[bytes], strict: bool = False,
+ verbose: int = 0):
+ self._source = source
+ self._strict = strict
+ self._verbose = verbose
+
+ def __iter__(self):
+ return HSIterator(recs=self._parse(self._source, strict=self._strict,
+ verbose=self._verbose))
+
+
+HandShake.init()
diff --git a/examples/tests/test_01_handshake.py b/examples/tests/test_01_handshake.py
new file mode 100644
index 0000000..f1a01d1
--- /dev/null
+++ b/examples/tests/test_01_handshake.py
@@ -0,0 +1,30 @@
+import pytest
+
+from .ngtcp2test import ExampleClient
+from .ngtcp2test import ExampleServer
+from .ngtcp2test import Env
+
+
+@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
+ reason="no crypto lib examples configured")
+class TestHandshake:
+
+ @pytest.fixture(scope='class', params=Env.get_crypto_libs())
+ def server(self, env, request) -> ExampleServer:
+ s = ExampleServer(env=env, crypto_lib=request.param)
+ assert s.exists(), f'server not found: {s.path}'
+ assert s.start()
+ yield s
+ s.stop()
+
+ @pytest.fixture(scope='function', params=Env.get_crypto_libs())
+ def client(self, env, request) -> ExampleClient:
+ client = ExampleClient(env=env, crypto_lib=request.param)
+ assert client.exists()
+ yield client
+
+ def test_01_01_get(self, env: Env, server, client):
+ # run simple GET, no sessions, needs to give full handshake
+ cr = client.http_get(server, url=f'https://{env.example_domain}/')
+ assert cr.returncode == 0
+ cr.assert_non_resume_handshake()
diff --git a/examples/tests/test_02_resume.py b/examples/tests/test_02_resume.py
new file mode 100644
index 0000000..3de1344
--- /dev/null
+++ b/examples/tests/test_02_resume.py
@@ -0,0 +1,46 @@
+import pytest
+
+from .ngtcp2test import ExampleClient
+from .ngtcp2test import ExampleServer
+from .ngtcp2test import Env
+
+
+@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
+ reason="no crypto lib examples configured")
+class TestResume:
+
+ @pytest.fixture(scope='class', params=Env.get_crypto_libs())
+ def server(self, env, request) -> ExampleServer:
+ s = ExampleServer(env=env, crypto_lib=request.param)
+ assert s.exists(), f'server not found: {s.path}'
+ assert s.start()
+ yield s
+ s.stop()
+
+ @pytest.fixture(scope='function', params=Env.get_crypto_libs())
+ def client(self, env, request) -> ExampleClient:
+ client = ExampleClient(env=env, crypto_lib=request.param)
+ assert client.exists()
+ yield client
+
+ def test_02_01(self, env: Env, server, client):
+ # run GET with sessions but no early data, cleared first, then reused
+ client.clear_session()
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ use_session=True,
+ extra_args=['--disable-early-data'])
+ assert cr.returncode == 0
+ cr.assert_non_resume_handshake()
+ # Now do this again and we expect a resumption, meaning no certificate
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ use_session=True,
+ extra_args=['--disable-early-data'])
+ assert cr.returncode == 0
+ cr.assert_resume_handshake()
+ # restart the server, do it again
+ server.restart()
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ use_session=True,
+ extra_args=['--disable-early-data'])
+ assert cr.returncode == 0
+ cr.assert_non_resume_handshake()
diff --git a/examples/tests/test_03_earlydata.py b/examples/tests/test_03_earlydata.py
new file mode 100644
index 0000000..a0170c3
--- /dev/null
+++ b/examples/tests/test_03_earlydata.py
@@ -0,0 +1,56 @@
+import pytest
+
+from .ngtcp2test import ExampleClient
+from .ngtcp2test import ExampleServer
+from .ngtcp2test import Env
+
+
+@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
+ reason="no crypto lib examples configured")
+class TestEarlyData:
+
+ @pytest.fixture(scope='class', params=Env.get_crypto_libs())
+ def server(self, env, request) -> ExampleServer:
+ s = ExampleServer(env=env, crypto_lib=request.param)
+ assert s.exists(), f'server not found: {s.path}'
+ assert s.start()
+ yield s
+ s.stop()
+
+ @pytest.fixture(scope='function', params=Env.get_crypto_libs())
+ def client(self, env, request) -> ExampleClient:
+ client = ExampleClient(env=env, crypto_lib=request.param)
+ assert client.exists()
+ yield client
+
+ def test_03_01(self, env: Env, server, client):
+ # run GET with sessions, cleared first, without a session, early
+ # data will not even be attempted
+ client.clear_session()
+ edata = 'This is the early data. It is not much.'
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ use_session=True, data=edata)
+ assert cr.returncode == 0
+ cr.assert_non_resume_handshake()
+ # resume session, early data is sent and accepted
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ use_session=True, data=edata)
+ assert cr.returncode == 0
+ cr.assert_resume_handshake()
+ assert not cr.early_data_rejected
+ # restart the server, resume, early data is attempted but will not work
+ server.restart()
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ use_session=True, data=edata)
+ assert cr.returncode == 0
+ assert cr.early_data_rejected
+ cr.assert_non_resume_handshake()
+ # restart again, sent data, but not as early data
+ server.restart()
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ use_session=True, data=edata,
+ extra_args=['--disable-early-data'])
+ assert cr.returncode == 0
+ # we see no rejection, since it was not used
+ assert not cr.early_data_rejected
+ cr.assert_non_resume_handshake()
diff --git a/examples/tests/test_04_clientcert.py b/examples/tests/test_04_clientcert.py
new file mode 100644
index 0000000..bde1b18
--- /dev/null
+++ b/examples/tests/test_04_clientcert.py
@@ -0,0 +1,57 @@
+import pytest
+
+from .ngtcp2test import ExampleClient
+from .ngtcp2test import ExampleServer
+from .ngtcp2test import Env
+
+
+@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
+ reason="no crypto lib examples configured")
+class TestClientCert:
+
+ @pytest.fixture(scope='class', params=Env.get_crypto_libs())
+ def server(self, env, request) -> ExampleServer:
+ s = ExampleServer(env=env, crypto_lib=request.param,
+ verify_client=True)
+ assert s.exists(), f'server not found: {s.path}'
+ assert s.start()
+ yield s
+ s.stop()
+
+ @pytest.fixture(scope='function', params=Env.get_crypto_libs())
+ def client(self, env, request) -> ExampleClient:
+ client = ExampleClient(env=env, crypto_lib=request.param)
+ assert client.exists()
+ yield client
+
+ def test_04_01(self, env: Env, server, client):
+ # run GET with a server requesting a cert, client has none to offer
+ cr = client.http_get(server, url=f'https://{env.example_domain}/')
+ assert cr.returncode == 0
+ cr.assert_verify_null_handshake()
+ creqs = [r for r in cr.handshake if r.hsid == 13] # CertificateRequest
+ assert len(creqs) == 1
+ creq = creqs[0].to_json()
+ certs = [r for r in cr.server.handshake if r.hsid == 11] # Certificate
+ assert len(certs) == 1
+ crec = certs[0].to_json()
+ assert len(crec['certificate_list']) == 0
+ assert creq['context'] == crec['context']
+ # TODO: check that GET had no answer
+
+ def test_04_02(self, env: Env, server, client):
+ # run GET with a server requesting a cert, client has cert to offer
+ credentials = env.ca.get_first("clientsX")
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ credentials=credentials)
+ assert cr.returncode == 0
+ cr.assert_verify_cert_handshake()
+ creqs = [r for r in cr.handshake if r.hsid == 13] # CertificateRequest
+ assert len(creqs) == 1
+ creq = creqs[0].to_json()
+ certs = [r for r in cr.server.handshake if r.hsid == 11] # Certificate
+ assert len(certs) == 1
+ crec = certs[0].to_json()
+ assert len(crec['certificate_list']) == 1
+ assert creq['context'] == crec['context']
+ # TODO: check that GET indeed gave a response
diff --git a/examples/tests/test_05_ciphers.py b/examples/tests/test_05_ciphers.py
new file mode 100644
index 0000000..27f326e
--- /dev/null
+++ b/examples/tests/test_05_ciphers.py
@@ -0,0 +1,46 @@
+import sys
+
+import pytest
+
+from .ngtcp2test import ExampleClient
+from .ngtcp2test import ExampleServer
+from .ngtcp2test import Env
+
+
+@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
+ reason="no crypto lib examples configured")
+class TestCiphers:
+
+ @pytest.fixture(scope='class', params=Env.get_crypto_libs())
+ def server(self, env, request) -> ExampleServer:
+ s = ExampleServer(env=env, crypto_lib=request.param)
+ assert s.exists(), f'server not found: {s.path}'
+ assert s.start()
+ yield s
+ s.stop()
+
+ @pytest.fixture(scope='function',
+ params=Env.get_crypto_libs(configurable_ciphers=True))
+ def client(self, env, request) -> ExampleClient:
+ client = ExampleClient(env=env, crypto_lib=request.param)
+ assert client.exists()
+ yield client
+
+ @pytest.mark.parametrize('cipher', [
+ 'TLS_AES_128_GCM_SHA256',
+ 'TLS_AES_256_GCM_SHA384',
+ 'TLS_CHACHA20_POLY1305_SHA256',
+ 'TLS_AES_128_CCM_SHA256',
+ ])
+ def test_05_01_get(self, env: Env, server, client, cipher):
+ if not client.uses_cipher_config:
+ pytest.skip(f'client {client.crypto_lib} ignores cipher config\n')
+ # run simple GET, no sessions, needs to give full handshake
+ if not client.supports_cipher(cipher):
+ pytest.skip(f'client {client.crypto_lib} does not support {cipher}\n')
+ if not server.supports_cipher(cipher):
+ pytest.skip(f'server {server.crypto_lib} does not support {cipher}\n')
+ cr = client.http_get(server, url=f'https://{env.example_domain}/',
+ ciphers=cipher)
+ assert cr.returncode == 0
+ cr.assert_non_resume_handshake()
diff --git a/examples/tls_client_context.h b/examples/tls_client_context.h
new file mode 100644
index 0000000..31e2d47
--- /dev/null
+++ b/examples/tls_client_context.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_CLIENT_CONTEXT_H
+#define TLS_CLIENT_CONTEXT_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#if defined(ENABLE_EXAMPLE_OPENSSL) && defined(WITH_EXAMPLE_OPENSSL)
+# include "tls_client_context_openssl.h"
+#endif // ENABLE_EXAMPLE_OPENSSL && WITH_EXAMPLE_OPENSSL
+
+#if defined(ENABLE_EXAMPLE_GNUTLS) && defined(WITH_EXAMPLE_GNUTLS)
+# include "tls_client_context_gnutls.h"
+#endif // ENABLE_EXAMPLE_GNUTLS && WITH_EXAMPLE_GNUTLS
+
+#if defined(ENABLE_EXAMPLE_BORINGSSL) && defined(WITH_EXAMPLE_BORINGSSL)
+# include "tls_client_context_boringssl.h"
+#endif // ENABLE_EXAMPLE_BORINGSSL && WITH_EXAMPLE_BORINGSSL
+
+#if defined(ENABLE_EXAMPLE_PICOTLS) && defined(WITH_EXAMPLE_PICOTLS)
+# include "tls_client_context_picotls.h"
+#endif // ENABLE_EXAMPLE_PICOTLS && WITH_EXAMPLE_PICOTLS
+
+#if defined(ENABLE_EXAMPLE_WOLFSSL) && defined(WITH_EXAMPLE_WOLFSSL)
+# include "tls_client_context_wolfssl.h"
+#endif // ENABLE_EXAMPLE_WOLFSSL && WITH_EXAMPLE_WOLFSSL
+
+#endif // TLS_CLIENT_CONTEXT_H
diff --git a/examples/tls_client_context_boringssl.cc b/examples/tls_client_context_boringssl.cc
new file mode 100644
index 0000000..bfdc525
--- /dev/null
+++ b/examples/tls_client_context_boringssl.cc
@@ -0,0 +1,126 @@
+/*
+ * 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 "tls_client_context_boringssl.h"
+
+#include <iostream>
+#include <fstream>
+
+#include <ngtcp2/ngtcp2_crypto_boringssl.h>
+
+#include <openssl/err.h>
+
+#include "client_base.h"
+#include "template.h"
+
+extern Config config;
+
+TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {}
+
+TLSClientContext::~TLSClientContext() {
+ if (ssl_ctx_) {
+ SSL_CTX_free(ssl_ctx_);
+ }
+}
+
+SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; }
+
+namespace {
+int new_session_cb(SSL *ssl, SSL_SESSION *session) {
+ auto f = BIO_new_file(config.session_file, "w");
+ if (f == nullptr) {
+ std::cerr << "Could not write TLS session in " << config.session_file
+ << std::endl;
+ return 0;
+ }
+
+ if (!PEM_write_bio_SSL_SESSION(f, session)) {
+ std::cerr << "Unable to write TLS session to file" << std::endl;
+ }
+
+ BIO_free(f);
+
+ return 0;
+}
+} // namespace
+
+int TLSClientContext::init(const char *private_key_file,
+ const char *cert_file) {
+ ssl_ctx_ = SSL_CTX_new(TLS_client_method());
+ if (!ssl_ctx_) {
+ std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx_) != 0) {
+ std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed"
+ << std::endl;
+ return -1;
+ }
+
+ SSL_CTX_set_default_verify_paths(ssl_ctx_);
+
+ if (SSL_CTX_set1_curves_list(ssl_ctx_, config.groups) != 1) {
+ std::cerr << "SSL_CTX_set1_curves_list failed" << std::endl;
+ return -1;
+ }
+
+ if (private_key_file && cert_file) {
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
+ SSL_FILETYPE_PEM) != 1) {
+ std::cerr << "SSL_CTX_use_PrivateKey_file: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
+ std::cerr << "SSL_CTX_use_certificate_chain_file: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+ }
+
+ if (config.session_file) {
+ SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT |
+ SSL_SESS_CACHE_NO_INTERNAL);
+ SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb);
+ }
+
+ return 0;
+}
+
+extern std::ofstream keylog_file;
+
+namespace {
+void keylog_callback(const SSL *ssl, const char *line) {
+ keylog_file.write(line, strlen(line));
+ keylog_file.put('\n');
+ keylog_file.flush();
+}
+} // namespace
+
+void TLSClientContext::enable_keylog() {
+ SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
+}
diff --git a/examples/tls_client_context_boringssl.h b/examples/tls_client_context_boringssl.h
new file mode 100644
index 0000000..22b581a
--- /dev/null
+++ b/examples/tls_client_context_boringssl.h
@@ -0,0 +1,49 @@
+/*
+ * 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 TLS_CLIENT_CONTEXT_BORINGSSL_H
+#define TLS_CLIENT_CONTEXT_BORINGSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <openssl/ssl.h>
+
+class TLSClientContext {
+public:
+ TLSClientContext();
+ ~TLSClientContext();
+
+ int init(const char *private_key_file, const char *cert_file);
+
+ SSL_CTX *get_native_handle() const;
+
+ void enable_keylog();
+
+private:
+ SSL_CTX *ssl_ctx_;
+};
+
+#endif // TLS_CLIENT_CONTEXT_BORINGSSL_H
diff --git a/examples/tls_client_context_gnutls.cc b/examples/tls_client_context_gnutls.cc
new file mode 100644
index 0000000..1fa03a8
--- /dev/null
+++ b/examples/tls_client_context_gnutls.cc
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+#include "tls_client_context_gnutls.h"
+
+#include <iostream>
+
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+
+#include "client_base.h"
+#include "template.h"
+
+// Based on https://github.com/ueno/ngtcp2-gnutls-examples
+
+extern Config config;
+
+TLSClientContext::TLSClientContext() : cred_{nullptr} {}
+
+TLSClientContext::~TLSClientContext() {
+ gnutls_certificate_free_credentials(cred_);
+}
+
+gnutls_certificate_credentials_t TLSClientContext::get_native_handle() const {
+ return cred_;
+}
+
+int TLSClientContext::init(const char *private_key_file,
+ const char *cert_file) {
+
+ if (auto rv = gnutls_certificate_allocate_credentials(&cred_); rv != 0) {
+ std::cerr << "gnutls_certificate_allocate_credentials failed: "
+ << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (auto rv = gnutls_certificate_set_x509_system_trust(cred_); rv < 0) {
+ std::cerr << "gnutls_certificate_set_x509_system_trust failed: "
+ << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (private_key_file != nullptr && cert_file != nullptr) {
+ if (auto rv = gnutls_certificate_set_x509_key_file(
+ cred_, cert_file, private_key_file, GNUTLS_X509_FMT_PEM);
+ rv != 0) {
+ std::cerr << "gnutls_certificate_set_x509_key_file failed: "
+ << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/examples/tls_client_context_gnutls.h b/examples/tls_client_context_gnutls.h
new file mode 100644
index 0000000..f637a15
--- /dev/null
+++ b/examples/tls_client_context_gnutls.h
@@ -0,0 +1,50 @@
+/*
+ * 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 TLS_CLIENT_CONTEXT_GNUTLS_H
+#define TLS_CLIENT_CONTEXT_GNUTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <gnutls/gnutls.h>
+
+class TLSClientContext {
+public:
+ TLSClientContext();
+ ~TLSClientContext();
+
+ int init(const char *private_key_file, const char *cert_file);
+
+ gnutls_certificate_credentials_t get_native_handle() const;
+
+ // Keylog is enabled per session.
+ void enable_keylog() {}
+
+private:
+ gnutls_certificate_credentials_t cred_;
+};
+
+#endif // TLS_CLIENT_CONTEXT_GNUTLS_H
diff --git a/examples/tls_client_context_openssl.cc b/examples/tls_client_context_openssl.cc
new file mode 100644
index 0000000..06c8af1
--- /dev/null
+++ b/examples/tls_client_context_openssl.cc
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+#include "tls_client_context_openssl.h"
+
+#include <iostream>
+#include <fstream>
+#include <limits>
+
+#include <ngtcp2/ngtcp2_crypto_openssl.h>
+
+#include <openssl/err.h>
+
+#include "client_base.h"
+#include "template.h"
+
+extern Config config;
+
+TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {}
+
+TLSClientContext::~TLSClientContext() {
+ if (ssl_ctx_) {
+ SSL_CTX_free(ssl_ctx_);
+ }
+}
+
+SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; }
+
+namespace {
+int new_session_cb(SSL *ssl, SSL_SESSION *session) {
+ if (SSL_SESSION_get_max_early_data(session) !=
+ std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "max_early_data_size is not 0xffffffff" << std::endl;
+ }
+ auto f = BIO_new_file(config.session_file, "w");
+ if (f == nullptr) {
+ std::cerr << "Could not write TLS session in " << config.session_file
+ << std::endl;
+ return 0;
+ }
+
+ if (!PEM_write_bio_SSL_SESSION(f, session)) {
+ std::cerr << "Unable to write TLS session to file" << std::endl;
+ }
+
+ BIO_free(f);
+
+ return 0;
+}
+} // namespace
+
+int TLSClientContext::init(const char *private_key_file,
+ const char *cert_file) {
+ ssl_ctx_ = SSL_CTX_new(TLS_client_method());
+ if (!ssl_ctx_) {
+ std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (ngtcp2_crypto_openssl_configure_client_context(ssl_ctx_) != 0) {
+ std::cerr << "ngtcp2_crypto_openssl_configure_client_context failed"
+ << std::endl;
+ return -1;
+ }
+
+ SSL_CTX_set_default_verify_paths(ssl_ctx_);
+
+ if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) {
+ std::cerr << "SSL_CTX_set_ciphersuites: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) {
+ std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
+ return -1;
+ }
+
+ if (private_key_file && cert_file) {
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
+ SSL_FILETYPE_PEM) != 1) {
+ std::cerr << "SSL_CTX_use_PrivateKey_file: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
+ std::cerr << "SSL_CTX_use_certificate_chain_file: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+ }
+
+ if (config.session_file) {
+ SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT |
+ SSL_SESS_CACHE_NO_INTERNAL);
+ SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb);
+ }
+
+ return 0;
+}
+
+extern std::ofstream keylog_file;
+
+namespace {
+void keylog_callback(const SSL *ssl, const char *line) {
+ keylog_file.write(line, strlen(line));
+ keylog_file.put('\n');
+ keylog_file.flush();
+}
+} // namespace
+
+void TLSClientContext::enable_keylog() {
+ SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
+}
diff --git a/examples/tls_client_context_openssl.h b/examples/tls_client_context_openssl.h
new file mode 100644
index 0000000..a6d0114
--- /dev/null
+++ b/examples/tls_client_context_openssl.h
@@ -0,0 +1,49 @@
+/*
+ * 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 TLS_CLIENT_CONTEXT_OPENSSL_H
+#define TLS_CLIENT_CONTEXT_OPENSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <openssl/ssl.h>
+
+class TLSClientContext {
+public:
+ TLSClientContext();
+ ~TLSClientContext();
+
+ int init(const char *private_key_file, const char *cert_file);
+
+ SSL_CTX *get_native_handle() const;
+
+ void enable_keylog();
+
+private:
+ SSL_CTX *ssl_ctx_;
+};
+
+#endif // TLS_CLIENT_CONTEXT_OPENSSL_H
diff --git a/examples/tls_client_context_picotls.cc b/examples/tls_client_context_picotls.cc
new file mode 100644
index 0000000..363f6c7
--- /dev/null
+++ b/examples/tls_client_context_picotls.cc
@@ -0,0 +1,155 @@
+/*
+ * 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 "tls_client_context_picotls.h"
+
+#include <iostream>
+
+#include <ngtcp2/ngtcp2_crypto_picotls.h>
+
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+
+#include "client_base.h"
+#include "template.h"
+
+extern Config config;
+
+namespace {
+int save_ticket_cb(ptls_save_ticket_t *self, ptls_t *ptls, ptls_iovec_t input) {
+ auto f = BIO_new_file(config.session_file, "w");
+ if (f == nullptr) {
+ std::cerr << "Could not write TLS session in " << config.session_file
+ << std::endl;
+ return 0;
+ }
+
+ if (!PEM_write_bio(f, "PICOTLS SESSION PARAMETERS", "", input.base,
+ input.len)) {
+ std::cerr << "Unable to write TLS session to file" << std::endl;
+ }
+
+ BIO_free(f);
+
+ return 0;
+}
+
+ptls_save_ticket_t save_ticket = {save_ticket_cb};
+} // namespace
+
+namespace {
+ptls_key_exchange_algorithm_t *key_exchanges[] = {
+ &ptls_openssl_x25519,
+ &ptls_openssl_secp256r1,
+ &ptls_openssl_secp384r1,
+ &ptls_openssl_secp521r1,
+ nullptr,
+};
+} // namespace
+
+namespace {
+ptls_cipher_suite_t *cipher_suites[] = {
+ &ptls_openssl_aes128gcmsha256,
+ &ptls_openssl_aes256gcmsha384,
+ &ptls_openssl_chacha20poly1305sha256,
+ nullptr,
+};
+} // namespace
+
+TLSClientContext::TLSClientContext()
+ : ctx_{
+ .random_bytes = ptls_openssl_random_bytes,
+ .get_time = &ptls_get_time,
+ .key_exchanges = key_exchanges,
+ .cipher_suites = cipher_suites,
+ .require_dhe_on_psk = 1,
+ } {}
+
+TLSClientContext::~TLSClientContext() {
+ if (sign_cert_.key) {
+ ptls_openssl_dispose_sign_certificate(&sign_cert_);
+ }
+
+ for (size_t i = 0; i < ctx_.certificates.count; ++i) {
+ free(ctx_.certificates.list[i].base);
+ }
+ free(ctx_.certificates.list);
+}
+
+ptls_context_t *TLSClientContext::get_native_handle() { return &ctx_; }
+
+int TLSClientContext::init(const char *private_key_file,
+ const char *cert_file) {
+ if (ngtcp2_crypto_picotls_configure_client_context(&ctx_) != 0) {
+ std::cerr << "ngtcp2_crypto_picotls_configure_client_context failed"
+ << std::endl;
+ return -1;
+ }
+
+ if (config.session_file) {
+ ctx_.save_ticket = &save_ticket;
+ }
+
+ if (private_key_file && cert_file) {
+ if (ptls_load_certificates(&ctx_, cert_file) != 0) {
+ std::cerr << "ptls_load_certificates failed" << std::endl;
+ return -1;
+ }
+
+ if (load_private_key(private_key_file) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int TLSClientContext::load_private_key(const char *private_key_file) {
+ auto fp = fopen(private_key_file, "rb");
+ if (fp == nullptr) {
+ std::cerr << "Could not open private key file " << private_key_file << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ auto fp_d = defer(fclose, fp);
+
+ auto pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr);
+ if (pkey == nullptr) {
+ std::cerr << "Could not read private key file " << private_key_file
+ << std::endl;
+ return -1;
+ }
+
+ auto pkey_d = defer(EVP_PKEY_free, pkey);
+
+ if (ptls_openssl_init_sign_certificate(&sign_cert_, pkey) != 0) {
+ std::cerr << "ptls_openssl_init_sign_certificate failed" << std::endl;
+ return -1;
+ }
+
+ ctx_.sign_certificate = &sign_cert_.super;
+
+ return 0;
+}
diff --git a/examples/tls_client_context_picotls.h b/examples/tls_client_context_picotls.h
new file mode 100644
index 0000000..aada78e
--- /dev/null
+++ b/examples/tls_client_context_picotls.h
@@ -0,0 +1,54 @@
+/*
+ * 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 TLS_CLIENT_CONTEXT_PICOTLS_H
+#define TLS_CLIENT_CONTEXT_PICOTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <picotls.h>
+#include <picotls/openssl.h>
+
+class TLSClientContext {
+public:
+ TLSClientContext();
+ ~TLSClientContext();
+
+ int init(const char *private_key_file, const char *cert_file);
+
+ ptls_context_t *get_native_handle();
+
+ // TODO Implement keylog.
+ void enable_keylog() {}
+
+private:
+ int load_private_key(const char *private_key_file);
+
+ ptls_context_t ctx_;
+ ptls_openssl_sign_certificate_t sign_cert_;
+};
+
+#endif // TLS_CLIENT_CONTEXT_PICOTLS_H
diff --git a/examples/tls_client_context_wolfssl.cc b/examples/tls_client_context_wolfssl.cc
new file mode 100644
index 0000000..0141df5
--- /dev/null
+++ b/examples/tls_client_context_wolfssl.cc
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+#include "tls_client_context_wolfssl.h"
+
+#include <iostream>
+#include <fstream>
+#include <limits>
+
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+
+#include "client_base.h"
+#include "template.h"
+
+extern Config config;
+
+TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {}
+
+TLSClientContext::~TLSClientContext() {
+ if (ssl_ctx_) {
+ wolfSSL_CTX_free(ssl_ctx_);
+ }
+}
+
+WOLFSSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; }
+
+namespace {
+int new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) {
+ std::cerr << "new_session_cb called" << std::endl;
+#ifdef HAVE_SESSION_TICKET
+ if (wolfSSL_SESSION_get_max_early_data(session) !=
+ std::numeric_limits<uint32_t>::max()) {
+ std::cerr << "max_early_data_size is not 0xffffffff" << std::endl;
+ }
+
+ unsigned char sbuffer[16 * 1024], *data;
+ unsigned int sz;
+ sz = wolfSSL_i2d_SSL_SESSION(session, nullptr);
+ if (sz <= 0) {
+ std::cerr << "Could not export TLS session in " << config.session_file
+ << std::endl;
+ return 0;
+ }
+ if (static_cast<size_t>(sz) > sizeof(sbuffer)) {
+ std::cerr << "Exported TLS session too large" << std::endl;
+ return 0;
+ }
+ data = sbuffer;
+ sz = wolfSSL_i2d_SSL_SESSION(session, &data);
+
+ auto f = wolfSSL_BIO_new_file(config.session_file, "w");
+ if (f == nullptr) {
+ std::cerr << "Could not write TLS session in " << config.session_file
+ << std::endl;
+ return 0;
+ }
+
+ auto f_d = defer(wolfSSL_BIO_free, f);
+
+ if (!wolfSSL_PEM_write_bio(f, "WOLFSSL SESSION PARAMETERS", "", sbuffer,
+ sz)) {
+ std::cerr << "Unable to write TLS session to file" << std::endl;
+ return 0;
+ }
+ std::cerr << "new_session_cb: wrote " << sz << " of session data"
+ << std::endl;
+#else
+ std::cerr << "TLS session tickets not enabled in wolfSSL " << std::endl;
+#endif
+ return 0;
+}
+} // namespace
+
+int TLSClientContext::init(const char *private_key_file,
+ const char *cert_file) {
+ ssl_ctx_ = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
+ if (!ssl_ctx_) {
+ std::cerr << "wolfSSL_CTX_new: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx_) != 0) {
+ std::cerr << "ngtcp2_crypto_wolfssl_configure_client_context failed"
+ << std::endl;
+ return -1;
+ }
+
+ if (wolfSSL_CTX_set_default_verify_paths(ssl_ctx_) ==
+ WOLFSSL_NOT_IMPLEMENTED) {
+ /* hmm, not verifying the server cert for now */
+ wolfSSL_CTX_set_verify(ssl_ctx_, WOLFSSL_VERIFY_NONE, 0);
+ }
+
+ if (wolfSSL_CTX_set_cipher_list(ssl_ctx_, config.ciphers) !=
+ WOLFSSL_SUCCESS) {
+ std::cerr << "wolfSSL_CTX_set_cipher_list: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (wolfSSL_CTX_set1_curves_list(
+ ssl_ctx_, const_cast<char *>(config.groups)) != WOLFSSL_SUCCESS) {
+ std::cerr << "wolfSSL_CTX_set1_curves_list(" << config.groups << ") failed"
+ << std::endl;
+ return -1;
+ }
+
+ if (private_key_file && cert_file) {
+ if (wolfSSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
+ SSL_FILETYPE_PEM) != WOLFSSL_SUCCESS) {
+ std::cerr << "wolfSSL_CTX_use_PrivateKey_file: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (wolfSSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) !=
+ WOLFSSL_SUCCESS) {
+ std::cerr << "wolfSSL_CTX_use_certificate_chain_file: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+ }
+
+ if (config.session_file) {
+ wolfSSL_CTX_UseSessionTicket(ssl_ctx_);
+ wolfSSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb);
+ }
+
+ return 0;
+}
+
+extern std::ofstream keylog_file;
+
+#ifdef HAVE_SECRET_CALLBACK
+namespace {
+void keylog_callback(const WOLFSSL *ssl, const char *line) {
+ keylog_file.write(line, strlen(line));
+ keylog_file.put('\n');
+ keylog_file.flush();
+}
+} // namespace
+#endif
+
+void TLSClientContext::enable_keylog() {
+#ifdef HAVE_SECRET_CALLBACK
+ wolfSSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
+#endif
+}
diff --git a/examples/tls_client_context_wolfssl.h b/examples/tls_client_context_wolfssl.h
new file mode 100644
index 0000000..f2ed14e
--- /dev/null
+++ b/examples/tls_client_context_wolfssl.h
@@ -0,0 +1,51 @@
+/*
+ * 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 TLS_CLIENT_CONTEXT_WOLFSSL_H
+#define TLS_CLIENT_CONTEXT_WOLFSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+
+class TLSClientContext {
+public:
+ TLSClientContext();
+ ~TLSClientContext();
+
+ int init(const char *private_key_file, const char *cert_file);
+
+ WOLFSSL_CTX *get_native_handle() const;
+
+ void enable_keylog();
+
+private:
+ WOLFSSL_CTX *ssl_ctx_;
+};
+
+#endif // TLS_CLIENT_CONTEXT_WOLFSSL_H
diff --git a/examples/tls_client_session.h b/examples/tls_client_session.h
new file mode 100644
index 0000000..e4fd0f2
--- /dev/null
+++ b/examples/tls_client_session.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_CLIENT_SESSION_H
+#define TLS_CLIENT_SESSION_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#if defined(ENABLE_EXAMPLE_OPENSSL) && defined(WITH_EXAMPLE_OPENSSL)
+# include "tls_client_session_openssl.h"
+#endif // ENABLE_EXAMPLE_OPENSSL && WITH_EXAMPLE_OPENSSL
+
+#if defined(ENABLE_EXAMPLE_GNUTLS) && defined(WITH_EXAMPLE_GNUTLS)
+# include "tls_client_session_gnutls.h"
+#endif // ENABLE_EXAMPLE_GNUTLS && WITH_EXAMPLE_GNUTLS
+
+#if defined(ENABLE_EXAMPLE_BORINGSSL) && defined(WITH_EXAMPLE_BORINGSSL)
+# include "tls_client_session_boringssl.h"
+#endif // ENABLE_EXAMPLE_BORINGSSL && WITH_EXAMPLE_BORINGSSL
+
+#if defined(ENABLE_EXAMPLE_PICOTLS) && defined(WITH_EXAMPLE_PICOTLS)
+# include "tls_client_session_picotls.h"
+#endif // ENABLE_EXAMPLE_PICOTLS && WITH_EXAMPLE_PICOTLS
+
+#if defined(ENABLE_EXAMPLE_WOLFSSL) && defined(WITH_EXAMPLE_WOLFSSL)
+# include "tls_client_session_wolfssl.h"
+#endif // ENABLE_EXAMPLE_WOLFSSL && WITH_EXAMPLE_WOLFSSL
+
+#endif // TLS_CLIENT_SESSION_H
diff --git a/examples/tls_client_session_boringssl.cc b/examples/tls_client_session_boringssl.cc
new file mode 100644
index 0000000..95b9834
--- /dev/null
+++ b/examples/tls_client_session_boringssl.cc
@@ -0,0 +1,110 @@
+/*
+ * 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 "tls_client_session_boringssl.h"
+
+#include <cassert>
+#include <iostream>
+
+#include "tls_client_context_boringssl.h"
+#include "client_base.h"
+#include "template.h"
+#include "util.h"
+
+TLSClientSession::TLSClientSession() {}
+
+TLSClientSession::~TLSClientSession() {}
+
+extern Config config;
+
+int TLSClientSession::init(bool &early_data_enabled,
+ const TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client,
+ uint32_t quic_version, AppProtocol app_proto) {
+ early_data_enabled = false;
+
+ auto ssl_ctx = tls_ctx.get_native_handle();
+
+ ssl_ = SSL_new(ssl_ctx);
+ if (!ssl_) {
+ std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ SSL_set_app_data(ssl_, client->conn_ref());
+ SSL_set_connect_state(ssl_);
+
+ SSL_set_quic_use_legacy_codepoint(ssl_,
+ (quic_version & 0xff000000) == 0xff000000);
+
+ switch (app_proto) {
+ case AppProtocol::H3:
+ SSL_set_alpn_protos(ssl_, H3_ALPN, str_size(H3_ALPN));
+ break;
+ case AppProtocol::HQ:
+ SSL_set_alpn_protos(ssl_, HQ_ALPN, str_size(HQ_ALPN));
+ break;
+ }
+
+ if (!config.sni.empty()) {
+ SSL_set_tlsext_host_name(ssl_, config.sni.data());
+ } else if (util::numeric_host(remote_addr)) {
+ // If remote host is numeric address, just send "localhost" as SNI
+ // for now.
+ SSL_set_tlsext_host_name(ssl_, "localhost");
+ } else {
+ SSL_set_tlsext_host_name(ssl_, remote_addr);
+ }
+
+ if (config.session_file) {
+ auto f = BIO_new_file(config.session_file, "r");
+ if (f == nullptr) {
+ std::cerr << "Could not read TLS session file " << config.session_file
+ << std::endl;
+ } else {
+ auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr);
+ BIO_free(f);
+ if (session == nullptr) {
+ std::cerr << "Could not read TLS session file " << config.session_file
+ << std::endl;
+ } else {
+ if (!SSL_set_session(ssl_, session)) {
+ std::cerr << "Could not set session" << std::endl;
+ } else if (!config.disable_early_data &&
+ SSL_SESSION_early_data_capable(session)) {
+ early_data_enabled = true;
+ SSL_set_early_data_enabled(ssl_, 1);
+ }
+ SSL_SESSION_free(session);
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool TLSClientSession::get_early_data_accepted() const {
+ return SSL_early_data_accepted(ssl_);
+}
diff --git a/examples/tls_client_session_boringssl.h b/examples/tls_client_session_boringssl.h
new file mode 100644
index 0000000..27ce9ab
--- /dev/null
+++ b/examples/tls_client_session_boringssl.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_CLIENT_SESSION_BORINGSSL_H
+#define TLS_CLIENT_SESSION_BORINGSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_openssl.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSClientContext;
+class ClientBase;
+
+class TLSClientSession : public TLSSessionBase {
+public:
+ TLSClientSession();
+ ~TLSClientSession();
+
+ int init(bool &early_data_enabled, const TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client, uint32_t quic_version,
+ AppProtocol app_proto);
+
+ bool get_early_data_accepted() const;
+};
+
+#endif // TLS_CLIENT_SESSION_BORINGSSL_H
diff --git a/examples/tls_client_session_gnutls.cc b/examples/tls_client_session_gnutls.cc
new file mode 100644
index 0000000..c77394f
--- /dev/null
+++ b/examples/tls_client_session_gnutls.cc
@@ -0,0 +1,190 @@
+/*
+ * 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.
+ */
+#include "tls_client_session_gnutls.h"
+
+#include <iostream>
+#include <fstream>
+#include <array>
+
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+
+#include <gnutls/crypto.h>
+
+#include "tls_client_context_gnutls.h"
+#include "client_base.h"
+#include "template.h"
+#include "util.h"
+
+// Based on https://github.com/ueno/ngtcp2-gnutls-examples
+
+extern Config config;
+
+TLSClientSession::TLSClientSession() {}
+
+TLSClientSession::~TLSClientSession() {}
+
+namespace {
+int hook_func(gnutls_session_t session, unsigned int htype, unsigned when,
+ unsigned int incoming, const gnutls_datum_t *msg) {
+ if (config.session_file && htype == GNUTLS_HANDSHAKE_NEW_SESSION_TICKET) {
+ gnutls_datum_t data;
+ if (auto rv = gnutls_session_get_data2(session, &data); rv != 0) {
+ std::cerr << "gnutls_session_get_data2 failed: " << gnutls_strerror(rv)
+ << std::endl;
+ return rv;
+ }
+ auto f = std::ofstream(config.session_file);
+ if (!f) {
+ return -1;
+ }
+
+ gnutls_datum_t d;
+ if (auto rv =
+ gnutls_pem_base64_encode2("GNUTLS SESSION PARAMETERS", &data, &d);
+ rv < 0) {
+ std::cerr << "Could not encode session in " << config.session_file
+ << std::endl;
+ return -1;
+ }
+
+ f.write(reinterpret_cast<const char *>(d.data), d.size);
+ if (!f) {
+ std::cerr << "Unable to write TLS session to file" << std::endl;
+ }
+ gnutls_free(d.data);
+ gnutls_free(data.data);
+ }
+
+ return 0;
+}
+} // namespace
+
+int TLSClientSession::init(bool &early_data_enabled,
+ const TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client,
+ uint32_t quic_version, AppProtocol app_proto) {
+ early_data_enabled = false;
+
+ if (auto rv =
+ gnutls_init(&session_, GNUTLS_CLIENT | GNUTLS_ENABLE_EARLY_DATA |
+ GNUTLS_NO_END_OF_EARLY_DATA);
+ rv != 0) {
+ std::cerr << "gnutls_init failed: " << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ std::string priority = "%DISABLE_TLS13_COMPAT_MODE:";
+ priority += config.ciphers;
+ priority += ':';
+ priority += config.groups;
+
+ if (auto rv = gnutls_priority_set_direct(session_, priority.c_str(), nullptr);
+ rv != 0) {
+ std::cerr << "gnutls_priority_set_direct failed: " << gnutls_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ gnutls_handshake_set_hook_function(session_, GNUTLS_HANDSHAKE_ANY,
+ GNUTLS_HOOK_POST, hook_func);
+
+ if (ngtcp2_crypto_gnutls_configure_client_session(session_) != 0) {
+ std::cerr << "ngtcp2_crypto_gnutls_configure_client_session failed"
+ << std::endl;
+ return -1;
+ }
+
+ if (config.session_file) {
+ auto f = std::ifstream(config.session_file);
+ if (f) {
+ f.seekg(0, std::ios::end);
+ auto pos = f.tellg();
+ std::vector<char> content(pos);
+ f.seekg(0, std::ios::beg);
+ f.read(content.data(), pos);
+
+ gnutls_datum_t s{
+ .data = reinterpret_cast<unsigned char *>(content.data()),
+ .size = static_cast<unsigned int>(content.size()),
+ };
+
+ gnutls_datum_t d;
+ if (auto rv =
+ gnutls_pem_base64_decode2("GNUTLS SESSION PARAMETERS", &s, &d);
+ rv < 0) {
+ std::cerr << "Could not read session in " << config.session_file
+ << std::endl;
+ return -1;
+ }
+
+ auto d_d = defer(gnutls_free, d.data);
+
+ if (auto rv = gnutls_session_set_data(session_, d.data, d.size);
+ rv != 0) {
+ std::cerr << "gnutls_session_set_data failed: " << gnutls_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ if (!config.disable_early_data) {
+ early_data_enabled = true;
+ }
+ }
+ }
+
+ gnutls_session_set_ptr(session_, client->conn_ref());
+
+ if (auto rv = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE,
+ tls_ctx.get_native_handle());
+ rv != 0) {
+ std::cerr << "gnutls_credentials_set failed: " << gnutls_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ // strip the first byte from H3_ALPN_V1
+ gnutls_datum_t alpn{
+ .data = const_cast<uint8_t *>(&H3_ALPN_V1[1]),
+ .size = H3_ALPN_V1[0],
+ };
+
+ gnutls_alpn_set_protocols(session_, &alpn, 1, GNUTLS_ALPN_MANDATORY);
+
+ if (util::numeric_host(remote_addr)) {
+ // If remote host is numeric address, just send "localhost" as SNI
+ // for now.
+ gnutls_server_name_set(session_, GNUTLS_NAME_DNS, "localhost",
+ strlen("localhost"));
+ } else {
+ gnutls_server_name_set(session_, GNUTLS_NAME_DNS, remote_addr,
+ strlen(remote_addr));
+ }
+
+ return 0;
+}
+
+bool TLSClientSession::get_early_data_accepted() const {
+ return gnutls_session_get_flags(session_) & GNUTLS_SFLAGS_EARLY_DATA;
+}
diff --git a/examples/tls_client_session_gnutls.h b/examples/tls_client_session_gnutls.h
new file mode 100644
index 0000000..a76db49
--- /dev/null
+++ b/examples/tls_client_session_gnutls.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_CLIENT_SESSION_GNUTLS_H
+#define TLS_CLIENT_SESSION_GNUTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_gnutls.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSClientContext;
+class ClientBase;
+
+class TLSClientSession : public TLSSessionBase {
+public:
+ TLSClientSession();
+ ~TLSClientSession();
+
+ int init(bool &early_data_enabled, const TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client, uint32_t quic_version,
+ AppProtocol app_proto);
+
+ bool get_early_data_accepted() const;
+};
+
+#endif // TLS_CLIENT_SESSION_GNUTLS_H
diff --git a/examples/tls_client_session_openssl.cc b/examples/tls_client_session_openssl.cc
new file mode 100644
index 0000000..dd6bb5d
--- /dev/null
+++ b/examples/tls_client_session_openssl.cc
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+#include "tls_client_session_openssl.h"
+
+#include <cassert>
+#include <iostream>
+
+#include <openssl/err.h>
+
+#include "tls_client_context_openssl.h"
+#include "client_base.h"
+#include "template.h"
+#include "util.h"
+
+TLSClientSession::TLSClientSession() {}
+
+TLSClientSession::~TLSClientSession() {}
+
+extern Config config;
+
+int TLSClientSession::init(bool &early_data_enabled,
+ const TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client,
+ uint32_t quic_version, AppProtocol app_proto) {
+ early_data_enabled = false;
+
+ auto ssl_ctx = tls_ctx.get_native_handle();
+
+ ssl_ = SSL_new(ssl_ctx);
+ if (!ssl_) {
+ std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ SSL_set_app_data(ssl_, client->conn_ref());
+ SSL_set_connect_state(ssl_);
+
+ SSL_set_quic_use_legacy_codepoint(ssl_,
+ (quic_version & 0xff000000) == 0xff000000);
+
+ switch (app_proto) {
+ case AppProtocol::H3:
+ SSL_set_alpn_protos(ssl_, H3_ALPN, str_size(H3_ALPN));
+ break;
+ case AppProtocol::HQ:
+ SSL_set_alpn_protos(ssl_, HQ_ALPN, str_size(HQ_ALPN));
+ break;
+ }
+
+ if (!config.sni.empty()) {
+ SSL_set_tlsext_host_name(ssl_, config.sni.data());
+ } else if (util::numeric_host(remote_addr)) {
+ // If remote host is numeric address, just send "localhost" as SNI
+ // for now.
+ SSL_set_tlsext_host_name(ssl_, "localhost");
+ } else {
+ SSL_set_tlsext_host_name(ssl_, remote_addr);
+ }
+
+ if (config.session_file) {
+ auto f = BIO_new_file(config.session_file, "r");
+ if (f == nullptr) {
+ std::cerr << "Could not read TLS session file " << config.session_file
+ << std::endl;
+ } else {
+ auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr);
+ BIO_free(f);
+ if (session == nullptr) {
+ std::cerr << "Could not read TLS session file " << config.session_file
+ << std::endl;
+ } else {
+ if (!SSL_set_session(ssl_, session)) {
+ std::cerr << "Could not set session" << std::endl;
+ } else if (!config.disable_early_data &&
+ SSL_SESSION_get_max_early_data(session)) {
+ early_data_enabled = true;
+ SSL_set_quic_early_data_enabled(ssl_, 1);
+ }
+ SSL_SESSION_free(session);
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool TLSClientSession::get_early_data_accepted() const {
+ // SSL_get_early_data_status works after handshake completes.
+ return SSL_get_early_data_status(ssl_) == SSL_EARLY_DATA_ACCEPTED;
+}
diff --git a/examples/tls_client_session_openssl.h b/examples/tls_client_session_openssl.h
new file mode 100644
index 0000000..06ecb41
--- /dev/null
+++ b/examples/tls_client_session_openssl.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_CLIENT_SESSION_OPENSSL_H
+#define TLS_CLIENT_SESSION_OPENSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_openssl.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSClientContext;
+class ClientBase;
+
+class TLSClientSession : public TLSSessionBase {
+public:
+ TLSClientSession();
+ ~TLSClientSession();
+
+ int init(bool &early_data_enabled, const TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client, uint32_t quic_version,
+ AppProtocol app_proto);
+
+ bool get_early_data_accepted() const;
+};
+
+#endif // TLS_CLIENT_SESSION_OPENSSL_H
diff --git a/examples/tls_client_session_picotls.cc b/examples/tls_client_session_picotls.cc
new file mode 100644
index 0000000..8f5bdfc
--- /dev/null
+++ b/examples/tls_client_session_picotls.cc
@@ -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.
+ */
+#include "tls_client_session_picotls.h"
+
+#include <iostream>
+#include <memory>
+
+#include <ngtcp2/ngtcp2_crypto_picotls.h>
+
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+
+#include <picotls.h>
+
+#include "tls_client_context_picotls.h"
+#include "client_base.h"
+#include "template.h"
+#include "util.h"
+
+using namespace std::literals;
+
+extern Config config;
+
+TLSClientSession::TLSClientSession() {}
+
+TLSClientSession::~TLSClientSession() {
+ auto &hsprops = cptls_.handshake_properties;
+
+ delete[] hsprops.client.session_ticket.base;
+}
+
+namespace {
+auto negotiated_protocols = std::array<ptls_iovec_t, 1>{{
+ {
+ .base = const_cast<uint8_t *>(&H3_ALPN_V1[1]),
+ .len = H3_ALPN_V1[0],
+ },
+}};
+} // namespace
+
+int TLSClientSession::init(bool &early_data_enabled, TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client,
+ uint32_t quic_version, AppProtocol app_proto) {
+ cptls_.ptls = ptls_client_new(tls_ctx.get_native_handle());
+ if (!cptls_.ptls) {
+ std::cerr << "ptls_client_new failed" << std::endl;
+ return -1;
+ }
+
+ *ptls_get_data_ptr(cptls_.ptls) = client->conn_ref();
+
+ auto conn = client->conn();
+ auto &hsprops = cptls_.handshake_properties;
+
+ hsprops.additional_extensions = new ptls_raw_extension_t[2]{
+ {
+ .type = UINT16_MAX,
+ },
+ {
+ .type = UINT16_MAX,
+ },
+ };
+
+ if (ngtcp2_crypto_picotls_configure_client_session(&cptls_, conn) != 0) {
+ std::cerr << "ngtcp2_crypto_picotls_configure_client_session failed"
+ << std::endl;
+ return -1;
+ }
+
+ hsprops.client.negotiated_protocols.list = negotiated_protocols.data();
+ hsprops.client.negotiated_protocols.count = negotiated_protocols.size();
+
+ if (util::numeric_host(remote_addr)) {
+ // If remote host is numeric address, just send "localhost" as SNI
+ // for now.
+ ptls_set_server_name(cptls_.ptls, "localhost", strlen("localhost"));
+ } else {
+ ptls_set_server_name(cptls_.ptls, remote_addr, strlen(remote_addr));
+ }
+
+ if (config.session_file) {
+ auto f = BIO_new_file(config.session_file, "r");
+ if (f == nullptr) {
+ std::cerr << "Could not read TLS session file " << config.session_file
+ << std::endl;
+ } else {
+ auto f_d = defer(BIO_free, f);
+
+ char *name, *header;
+ unsigned char *data;
+ long datalen;
+
+ if (PEM_read_bio(f, &name, &header, &data, &datalen) != 1) {
+ std::cerr << "Could not read TLS session file " << config.session_file
+ << std::endl;
+ } else {
+ if ("PICOTLS SESSION PARAMETERS"sv != name) {
+ std::cerr << "TLS session file contains unexpected name: " << name
+ << std::endl;
+ } else {
+ hsprops.client.session_ticket.base = new uint8_t[datalen];
+ hsprops.client.session_ticket.len = datalen;
+ memcpy(hsprops.client.session_ticket.base, data, datalen);
+
+ if (!config.disable_early_data) {
+ // No easy way to check max_early_data from ticket. We
+ // need to run ptls_handle_message.
+ early_data_enabled = true;
+ }
+ }
+
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+ OPENSSL_free(data);
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool TLSClientSession::get_early_data_accepted() const {
+ return cptls_.handshake_properties.client.early_data_acceptance ==
+ PTLS_EARLY_DATA_ACCEPTED;
+}
diff --git a/examples/tls_client_session_picotls.h b/examples/tls_client_session_picotls.h
new file mode 100644
index 0000000..75a376e
--- /dev/null
+++ b/examples/tls_client_session_picotls.h
@@ -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 TLS_CLIENT_SESSION_PICOTLS_H
+#define TLS_CLIENT_SESSION_PICOTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_picotls.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSClientContext;
+class ClientBase;
+
+class TLSClientSession : public TLSSessionBase {
+public:
+ TLSClientSession();
+ ~TLSClientSession();
+
+ int init(bool &early_data_enabled, TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client, uint32_t quic_version,
+ AppProtocol app_proto);
+
+ bool get_early_data_accepted() const;
+};
+
+#endif // TLS_CLIENT_SESSION_PICOTLS_H
diff --git a/examples/tls_client_session_wolfssl.cc b/examples/tls_client_session_wolfssl.cc
new file mode 100644
index 0000000..87fb809
--- /dev/null
+++ b/examples/tls_client_session_wolfssl.cc
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ */
+#include "tls_client_session_wolfssl.h"
+
+#include <cassert>
+#include <iostream>
+
+#include "tls_client_context_wolfssl.h"
+#include "client_base.h"
+#include "template.h"
+#include "util.h"
+
+using namespace std::literals;
+
+TLSClientSession::TLSClientSession() {}
+
+TLSClientSession::~TLSClientSession() {}
+
+extern Config config;
+
+namespace {
+int wolfssl_session_ticket_cb(WOLFSSL *ssl, const unsigned char *ticket,
+ int ticketSz, void *cb_ctx) {
+ std::cerr << "session ticket calback invoked" << std::endl;
+ return 0;
+}
+} // namespace
+
+int TLSClientSession::init(bool &early_data_enabled,
+ const TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client,
+ uint32_t quic_version, AppProtocol app_proto) {
+ early_data_enabled = false;
+
+ auto ssl_ctx = tls_ctx.get_native_handle();
+
+ ssl_ = wolfSSL_new(ssl_ctx);
+ if (!ssl_) {
+ std::cerr << "wolfSSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ wolfSSL_set_app_data(ssl_, client->conn_ref());
+ wolfSSL_set_connect_state(ssl_);
+
+ wolfSSL_set_quic_use_legacy_codepoint(ssl_, (quic_version & 0xff000000) ==
+ 0xff000000);
+
+ switch (app_proto) {
+ case AppProtocol::H3:
+ wolfSSL_set_alpn_protos(ssl_, H3_ALPN, str_size(H3_ALPN));
+ break;
+ case AppProtocol::HQ:
+ wolfSSL_set_alpn_protos(ssl_, HQ_ALPN, str_size(HQ_ALPN));
+ break;
+ }
+
+ if (!config.sni.empty()) {
+ wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, config.sni.data(),
+ config.sni.length());
+ } else if (util::numeric_host(remote_addr)) {
+ // If remote host is numeric address, just send "localhost" as SNI
+ // for now.
+ wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, "localhost",
+ sizeof("localhost") - 1);
+ } else {
+ wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, remote_addr,
+ strlen(remote_addr));
+ }
+
+ if (config.session_file) {
+#ifdef HAVE_SESSION_TICKET
+ auto f = wolfSSL_BIO_new_file(config.session_file, "r");
+ if (f == nullptr) {
+ std::cerr << "Could not open TLS session file " << config.session_file
+ << std::endl;
+ } else {
+ char *name, *header;
+ unsigned char *data;
+ const unsigned char *pdata;
+ long datalen;
+ unsigned int ret;
+ WOLFSSL_SESSION *session;
+
+ if (wolfSSL_PEM_read_bio(f, &name, &header, &data, &datalen) != 1) {
+ std::cerr << "Could not read TLS session file " << config.session_file
+ << std::endl;
+ } else {
+ if ("WOLFSSL SESSION PARAMETERS"sv != name) {
+ std::cerr << "TLS session file contains unexpected name: " << name
+ << std::endl;
+ } else {
+ pdata = data;
+ session = wolfSSL_d2i_SSL_SESSION(nullptr, &pdata, datalen);
+ if (session == nullptr) {
+ std::cerr << "Could not parse TLS session from file "
+ << config.session_file << std::endl;
+ } else {
+ ret = wolfSSL_set_session(ssl_, session);
+ if (ret != WOLFSSL_SUCCESS) {
+ std::cerr << "Could not install TLS session from file "
+ << config.session_file << std::endl;
+ } else {
+ if (!config.disable_early_data &&
+ wolfSSL_SESSION_get_max_early_data(session)) {
+ early_data_enabled = true;
+ wolfSSL_set_quic_early_data_enabled(ssl_, 1);
+ }
+ }
+ wolfSSL_SESSION_free(session);
+ }
+ }
+
+ wolfSSL_OPENSSL_free(name);
+ wolfSSL_OPENSSL_free(header);
+ wolfSSL_OPENSSL_free(data);
+ }
+ wolfSSL_BIO_free(f);
+ }
+ wolfSSL_UseSessionTicket(ssl_);
+ wolfSSL_set_SessionTicket_cb(ssl_, wolfssl_session_ticket_cb, nullptr);
+#else
+ std::cerr << "TLS session im-/export not enabled in wolfSSL" << std::endl;
+#endif
+ }
+
+ return 0;
+}
+
+bool TLSClientSession::get_early_data_accepted() const {
+ // wolfSSL_get_early_data_status works after handshake completes.
+#ifdef WOLFSSL_EARLY_DATA
+ return wolfSSL_get_early_data_status(ssl_) == SSL_EARLY_DATA_ACCEPTED;
+#else
+ return 0;
+#endif
+}
diff --git a/examples/tls_client_session_wolfssl.h b/examples/tls_client_session_wolfssl.h
new file mode 100644
index 0000000..1686e14
--- /dev/null
+++ b/examples/tls_client_session_wolfssl.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_CLIENT_SESSION_WOLFSSL_H
+#define TLS_CLIENT_SESSION_WOLFSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_wolfssl.h"
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSClientContext;
+class ClientBase;
+
+class TLSClientSession : public TLSSessionBase {
+public:
+ TLSClientSession();
+ ~TLSClientSession();
+
+ int init(bool &early_data_enabled, const TLSClientContext &tls_ctx,
+ const char *remote_addr, ClientBase *client, uint32_t quic_version,
+ AppProtocol app_proto);
+
+ bool get_early_data_accepted() const;
+};
+
+#endif // TLS_CLIENT_SESSION_WOLFSSL_H
diff --git a/examples/tls_server_context.h b/examples/tls_server_context.h
new file mode 100644
index 0000000..1f4c574
--- /dev/null
+++ b/examples/tls_server_context.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_SERVER_CONTEXT_H
+#define TLS_SERVER_CONTEXT_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#if defined(ENABLE_EXAMPLE_OPENSSL) && defined(WITH_EXAMPLE_OPENSSL)
+# include "tls_server_context_openssl.h"
+#endif // ENABLE_EXAMPLE_OPENSSL && WITH_EXAMPLE_OPENSSL
+
+#if defined(ENABLE_EXAMPLE_GNUTLS) && defined(WITH_EXAMPLE_GNUTLS)
+# include "tls_server_context_gnutls.h"
+#endif // ENABLE_EXAMPLE_GNUTLS && WITH_EXAMPLE_GNUTLS
+
+#if defined(ENABLE_EXAMPLE_BORINGSSL) && defined(WITH_EXAMPLE_BORINGSSL)
+# include "tls_server_context_boringssl.h"
+#endif // ENABLE_EXAMPLE_BORINGSSL && WITH_EXAMPLE_BORINGSSL
+
+#if defined(ENABLE_EXAMPLE_PICOTLS) && defined(WITH_EXAMPLE_PICOTLS)
+# include "tls_server_context_picotls.h"
+#endif // ENABLE_EXAMPLE_PICOTLS && WITH_EXAMPLE_PICOTLS
+
+#if defined(ENABLE_EXAMPLE_WOLFSSL) && defined(WITH_EXAMPLE_WOLFSSL)
+# include "tls_server_context_wolfssl.h"
+#endif // ENABLE_EXAMPLE_WOLFSSL && WITH_EXAMPLE_WOLFSSL
+
+#endif // TLS_SERVER_CONTEXT_H
diff --git a/examples/tls_server_context_boringssl.cc b/examples/tls_server_context_boringssl.cc
new file mode 100644
index 0000000..9b95583
--- /dev/null
+++ b/examples/tls_server_context_boringssl.cc
@@ -0,0 +1,257 @@
+/*
+ * 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 "tls_server_context_boringssl.h"
+
+#include <iostream>
+#include <fstream>
+
+#include <ngtcp2/ngtcp2_crypto_boringssl.h>
+
+#include <openssl/err.h>
+
+#include "server_base.h"
+#include "template.h"
+
+extern Config config;
+
+TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {}
+
+TLSServerContext::~TLSServerContext() {
+ if (ssl_ctx_) {
+ SSL_CTX_free(ssl_ctx_);
+ }
+}
+
+SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; }
+
+namespace {
+int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+ const uint8_t *alpn;
+ size_t alpnlen;
+ // This should be the negotiated version, but we have not set the
+ // negotiated version when this callback is called.
+ auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
+
+ switch (version) {
+ case QUIC_VER_DRAFT29:
+ alpn = H3_ALPN_DRAFT29;
+ alpnlen = str_size(H3_ALPN_DRAFT29);
+ break;
+ case QUIC_VER_DRAFT30:
+ alpn = H3_ALPN_DRAFT30;
+ alpnlen = str_size(H3_ALPN_DRAFT30);
+ break;
+ case QUIC_VER_DRAFT31:
+ alpn = H3_ALPN_DRAFT31;
+ alpnlen = str_size(H3_ALPN_DRAFT31);
+ break;
+ case QUIC_VER_DRAFT32:
+ alpn = H3_ALPN_DRAFT32;
+ alpnlen = str_size(H3_ALPN_DRAFT32);
+ break;
+ case NGTCP2_PROTO_VER_V1:
+ case NGTCP2_PROTO_VER_V2_DRAFT:
+ alpn = H3_ALPN_V1;
+ alpnlen = str_size(H3_ALPN_V1);
+ break;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
+ << version << std::dec << std::endl;
+ }
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) {
+ if (std::equal(alpn, alpn + alpnlen, p)) {
+ *out = p + 1;
+ *outlen = *p;
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl;
+ }
+
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // namespace
+
+namespace {
+int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+ const uint8_t *alpn;
+ size_t alpnlen;
+ // This should be the negotiated version, but we have not set the
+ // negotiated version when this callback is called.
+ auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
+
+ switch (version) {
+ case QUIC_VER_DRAFT29:
+ alpn = HQ_ALPN_DRAFT29;
+ alpnlen = str_size(HQ_ALPN_DRAFT29);
+ break;
+ case QUIC_VER_DRAFT30:
+ alpn = HQ_ALPN_DRAFT30;
+ alpnlen = str_size(HQ_ALPN_DRAFT30);
+ break;
+ case QUIC_VER_DRAFT31:
+ alpn = HQ_ALPN_DRAFT31;
+ alpnlen = str_size(HQ_ALPN_DRAFT31);
+ break;
+ case QUIC_VER_DRAFT32:
+ alpn = HQ_ALPN_DRAFT32;
+ alpnlen = str_size(HQ_ALPN_DRAFT32);
+ break;
+ case NGTCP2_PROTO_VER_V1:
+ case NGTCP2_PROTO_VER_V2_DRAFT:
+ alpn = HQ_ALPN_V1;
+ alpnlen = str_size(HQ_ALPN_V1);
+ break;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
+ << version << std::dec << std::endl;
+ }
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) {
+ if (std::equal(alpn, alpn + alpnlen, p)) {
+ *out = p + 1;
+ *outlen = *p;
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl;
+ }
+
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // namespace
+
+namespace {
+int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) {
+ // We don't verify the client certificate. Just request it for the
+ // testing purpose.
+ return 1;
+}
+} // namespace
+
+int TLSServerContext::init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto) {
+ constexpr static unsigned char sid_ctx[] = "ngtcp2 server";
+
+ ssl_ctx_ = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx_) {
+ std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_SINGLE_ECDH_USE |
+ SSL_OP_CIPHER_SERVER_PREFERENCE;
+
+ SSL_CTX_set_options(ssl_ctx_, ssl_opts);
+
+ if (SSL_CTX_set1_curves_list(ssl_ctx_, config.groups) != 1) {
+ std::cerr << "SSL_CTX_set1_curves_list failed" << std::endl;
+ return -1;
+ }
+
+ SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS);
+
+ if (ngtcp2_crypto_boringssl_configure_server_context(ssl_ctx_) != 0) {
+ std::cerr << "ngtcp2_crypto_boringssl_configure_server_context failed"
+ << std::endl;
+ return -1;
+ }
+
+ switch (app_proto) {
+ case AppProtocol::H3:
+ SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr);
+ break;
+ case AppProtocol::HQ:
+ SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr);
+ break;
+ }
+
+ SSL_CTX_set_default_verify_paths(ssl_ctx_);
+
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
+ SSL_FILETYPE_PEM) != 1) {
+ std::cerr << "SSL_CTX_use_PrivateKey_file: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
+ std::cerr << "SSL_CTX_use_certificate_chain_file: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_check_private_key(ssl_ctx_) != 1) {
+ std::cerr << "SSL_CTX_check_private_key: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1);
+
+ if (config.verify_client) {
+ SSL_CTX_set_verify(ssl_ctx_,
+ SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ verify_cb);
+ }
+
+ return 0;
+}
+
+extern std::ofstream keylog_file;
+
+namespace {
+void keylog_callback(const SSL *ssl, const char *line) {
+ keylog_file.write(line, strlen(line));
+ keylog_file.put('\n');
+ keylog_file.flush();
+}
+} // namespace
+
+void TLSServerContext::enable_keylog() {
+ SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
+}
diff --git a/examples/tls_server_context_boringssl.h b/examples/tls_server_context_boringssl.h
new file mode 100644
index 0000000..d7d3dfb
--- /dev/null
+++ b/examples/tls_server_context_boringssl.h
@@ -0,0 +1,54 @@
+/*
+ * 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 TLS_SERVER_CONTEXT_BORINGSSL_H
+#define TLS_SERVER_CONTEXT_BORINGSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <openssl/ssl.h>
+
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSServerContext {
+public:
+ TLSServerContext();
+ ~TLSServerContext();
+
+ int init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto);
+
+ SSL_CTX *get_native_handle() const;
+
+ void enable_keylog();
+
+private:
+ SSL_CTX *ssl_ctx_;
+};
+
+#endif // TLS_SERVER_CONTEXT_BORINGSSL_H
diff --git a/examples/tls_server_context_gnutls.cc b/examples/tls_server_context_gnutls.cc
new file mode 100644
index 0000000..5b2be55
--- /dev/null
+++ b/examples/tls_server_context_gnutls.cc
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+#include "tls_server_context_gnutls.h"
+
+#include <iostream>
+
+#include "server_base.h"
+#include "template.h"
+
+// Based on https://github.com/ueno/ngtcp2-gnutls-examples
+
+extern Config config;
+
+namespace {
+int anti_replay_db_add_func(void *dbf, time_t exp_time,
+ const gnutls_datum_t *key,
+ const gnutls_datum_t *data) {
+ return 0;
+}
+} // namespace
+
+TLSServerContext::TLSServerContext() : cred_{nullptr}, session_ticket_key_{} {
+ gnutls_anti_replay_init(&anti_replay_);
+ gnutls_anti_replay_set_add_function(anti_replay_, anti_replay_db_add_func);
+ gnutls_anti_replay_set_ptr(anti_replay_, nullptr);
+}
+
+TLSServerContext::~TLSServerContext() {
+ gnutls_anti_replay_deinit(anti_replay_);
+ gnutls_free(session_ticket_key_.data);
+ gnutls_certificate_free_credentials(cred_);
+}
+
+gnutls_certificate_credentials_t
+TLSServerContext::get_certificate_credentials() const {
+ return cred_;
+}
+
+const gnutls_datum_t *TLSServerContext::get_session_ticket_key() const {
+ return &session_ticket_key_;
+}
+
+gnutls_anti_replay_t TLSServerContext::get_anti_replay() const {
+ return anti_replay_;
+}
+
+int TLSServerContext::init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto) {
+ if (auto rv = gnutls_certificate_allocate_credentials(&cred_); rv != 0) {
+ std::cerr << "gnutls_certificate_allocate_credentials failed: "
+ << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (auto rv = gnutls_certificate_set_x509_system_trust(cred_); rv < 0) {
+ std::cerr << "gnutls_certificate_set_x509_system_trust failed: "
+ << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (auto rv = gnutls_certificate_set_x509_key_file(
+ cred_, cert_file, private_key_file, GNUTLS_X509_FMT_PEM);
+ rv != 0) {
+ std::cerr << "gnutls_certificate_set_x509_key_file failed: "
+ << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ if (auto rv = gnutls_session_ticket_key_generate(&session_ticket_key_);
+ rv != 0) {
+ std::cerr << "gnutls_session_ticket_key_generate failed: "
+ << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/examples/tls_server_context_gnutls.h b/examples/tls_server_context_gnutls.h
new file mode 100644
index 0000000..21ed109
--- /dev/null
+++ b/examples/tls_server_context_gnutls.h
@@ -0,0 +1,59 @@
+/*
+ * 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 TLS_SERVER_CONTEXT_GNUTLS_H
+#define TLS_SERVER_CONTEXT_GNUTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <gnutls/gnutls.h>
+
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSServerContext {
+public:
+ TLSServerContext();
+ ~TLSServerContext();
+
+ int init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto);
+
+ gnutls_certificate_credentials_t get_certificate_credentials() const;
+ const gnutls_datum_t *get_session_ticket_key() const;
+ gnutls_anti_replay_t get_anti_replay() const;
+
+ // Keylog is enabled per session.
+ void enable_keylog() {}
+
+private:
+ gnutls_certificate_credentials_t cred_;
+ gnutls_datum_t session_ticket_key_;
+ gnutls_anti_replay_t anti_replay_;
+};
+
+#endif // TLS_SERVER_CONTEXT_GNUTLS_H
diff --git a/examples/tls_server_context_openssl.cc b/examples/tls_server_context_openssl.cc
new file mode 100644
index 0000000..bdc35a3
--- /dev/null
+++ b/examples/tls_server_context_openssl.cc
@@ -0,0 +1,338 @@
+/*
+ * 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.
+ */
+#include "tls_server_context_openssl.h"
+
+#include <cstring>
+#include <iostream>
+#include <fstream>
+#include <limits>
+
+#include <ngtcp2/ngtcp2_crypto_openssl.h>
+
+#include <openssl/err.h>
+
+#include "server_base.h"
+#include "template.h"
+
+extern Config config;
+
+TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {}
+
+TLSServerContext::~TLSServerContext() {
+ if (ssl_ctx_) {
+ SSL_CTX_free(ssl_ctx_);
+ }
+}
+
+SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; }
+
+namespace {
+int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+ const uint8_t *alpn;
+ size_t alpnlen;
+ // This should be the negotiated version, but we have not set the
+ // negotiated version when this callback is called.
+ auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
+
+ switch (version) {
+ case QUIC_VER_DRAFT29:
+ alpn = H3_ALPN_DRAFT29;
+ alpnlen = str_size(H3_ALPN_DRAFT29);
+ break;
+ case QUIC_VER_DRAFT30:
+ alpn = H3_ALPN_DRAFT30;
+ alpnlen = str_size(H3_ALPN_DRAFT30);
+ break;
+ case QUIC_VER_DRAFT31:
+ alpn = H3_ALPN_DRAFT31;
+ alpnlen = str_size(H3_ALPN_DRAFT31);
+ break;
+ case QUIC_VER_DRAFT32:
+ alpn = H3_ALPN_DRAFT32;
+ alpnlen = str_size(H3_ALPN_DRAFT32);
+ break;
+ case NGTCP2_PROTO_VER_V1:
+ case NGTCP2_PROTO_VER_V2_DRAFT:
+ alpn = H3_ALPN_V1;
+ alpnlen = str_size(H3_ALPN_V1);
+ break;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
+ << version << std::dec << std::endl;
+ }
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) {
+ if (std::equal(alpn, alpn + alpnlen, p)) {
+ *out = p + 1;
+ *outlen = *p;
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl;
+ }
+
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // namespace
+
+namespace {
+int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+ const uint8_t *alpn;
+ size_t alpnlen;
+ // This should be the negotiated version, but we have not set the
+ // negotiated version when this callback is called.
+ auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
+
+ switch (version) {
+ case QUIC_VER_DRAFT29:
+ alpn = HQ_ALPN_DRAFT29;
+ alpnlen = str_size(HQ_ALPN_DRAFT29);
+ break;
+ case QUIC_VER_DRAFT30:
+ alpn = HQ_ALPN_DRAFT30;
+ alpnlen = str_size(HQ_ALPN_DRAFT30);
+ break;
+ case QUIC_VER_DRAFT31:
+ alpn = HQ_ALPN_DRAFT31;
+ alpnlen = str_size(HQ_ALPN_DRAFT31);
+ break;
+ case QUIC_VER_DRAFT32:
+ alpn = HQ_ALPN_DRAFT32;
+ alpnlen = str_size(HQ_ALPN_DRAFT32);
+ break;
+ case NGTCP2_PROTO_VER_V1:
+ case NGTCP2_PROTO_VER_V2_DRAFT:
+ alpn = HQ_ALPN_V1;
+ alpnlen = str_size(HQ_ALPN_V1);
+ break;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
+ << version << std::dec << std::endl;
+ }
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) {
+ if (std::equal(alpn, alpn + alpnlen, p)) {
+ *out = p + 1;
+ *outlen = *p;
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl;
+ }
+
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // namespace
+
+namespace {
+int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) {
+ // We don't verify the client certificate. Just request it for the
+ // testing purpose.
+ return 1;
+}
+} // namespace
+
+namespace {
+int gen_ticket_cb(SSL *ssl, void *arg) {
+ auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+ auto ver = htonl(ngtcp2_conn_get_negotiated_version(h->conn()));
+
+ if (!SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl), &ver,
+ sizeof(ver))) {
+ return 0;
+ }
+
+ return 1;
+}
+} // namespace
+
+namespace {
+SSL_TICKET_RETURN decrypt_ticket_cb(SSL *ssl, SSL_SESSION *session,
+ const unsigned char *keyname,
+ size_t keynamelen, SSL_TICKET_STATUS status,
+ void *arg) {
+ switch (status) {
+ case SSL_TICKET_EMPTY:
+ case SSL_TICKET_NO_DECRYPT:
+ return SSL_TICKET_RETURN_IGNORE_RENEW;
+ }
+
+ uint8_t *pver;
+ uint32_t ver;
+ size_t verlen;
+
+ if (!SSL_SESSION_get0_ticket_appdata(
+ session, reinterpret_cast<void **>(&pver), &verlen) ||
+ verlen != sizeof(ver)) {
+ switch (status) {
+ case SSL_TICKET_SUCCESS:
+ return SSL_TICKET_RETURN_IGNORE;
+ case SSL_TICKET_SUCCESS_RENEW:
+ default:
+ return SSL_TICKET_RETURN_IGNORE_RENEW;
+ }
+ }
+
+ memcpy(&ver, pver, sizeof(ver));
+
+ auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+
+ if (ngtcp2_conn_get_client_chosen_version(h->conn()) != ntohl(ver)) {
+ switch (status) {
+ case SSL_TICKET_SUCCESS:
+ return SSL_TICKET_RETURN_IGNORE;
+ case SSL_TICKET_SUCCESS_RENEW:
+ default:
+ return SSL_TICKET_RETURN_IGNORE_RENEW;
+ }
+ }
+
+ switch (status) {
+ case SSL_TICKET_SUCCESS:
+ return SSL_TICKET_RETURN_USE;
+ case SSL_TICKET_SUCCESS_RENEW:
+ default:
+ return SSL_TICKET_RETURN_USE_RENEW;
+ }
+}
+} // namespace
+
+int TLSServerContext::init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto) {
+ constexpr static unsigned char sid_ctx[] = "ngtcp2 server";
+
+ ssl_ctx_ = SSL_CTX_new(TLS_server_method());
+ if (!ssl_ctx_) {
+ std::cerr << "SSSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (ngtcp2_crypto_openssl_configure_server_context(ssl_ctx_) != 0) {
+ std::cerr << "ngtcp2_crypto_openssl_configure_server_context failed"
+ << std::endl;
+ return -1;
+ }
+
+ SSL_CTX_set_max_early_data(ssl_ctx_, UINT32_MAX);
+
+ constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ SSL_OP_SINGLE_ECDH_USE |
+ SSL_OP_CIPHER_SERVER_PREFERENCE |
+ SSL_OP_NO_ANTI_REPLAY;
+
+ SSL_CTX_set_options(ssl_ctx_, ssl_opts);
+
+ if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) {
+ std::cerr << "SSL_CTX_set_ciphersuites: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) {
+ std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
+ return -1;
+ }
+
+ SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS);
+
+ switch (app_proto) {
+ case AppProtocol::H3:
+ SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr);
+ break;
+ case AppProtocol::HQ:
+ SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr);
+ break;
+ }
+
+ SSL_CTX_set_default_verify_paths(ssl_ctx_);
+
+ if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
+ SSL_FILETYPE_PEM) != 1) {
+ std::cerr << "SSL_CTX_use_PrivateKey_file: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
+ std::cerr << "SSL_CTX_use_certificate_chain_file: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (SSL_CTX_check_private_key(ssl_ctx_) != 1) {
+ std::cerr << "SSL_CTX_check_private_key: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1);
+
+ if (config.verify_client) {
+ SSL_CTX_set_verify(ssl_ctx_,
+ SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ verify_cb);
+ }
+
+ SSL_CTX_set_session_ticket_cb(ssl_ctx_, gen_ticket_cb, decrypt_ticket_cb,
+ nullptr);
+
+ return 0;
+}
+
+extern std::ofstream keylog_file;
+
+namespace {
+void keylog_callback(const SSL *ssl, const char *line) {
+ keylog_file.write(line, strlen(line));
+ keylog_file.put('\n');
+ keylog_file.flush();
+}
+} // namespace
+
+void TLSServerContext::enable_keylog() {
+ SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
+}
diff --git a/examples/tls_server_context_openssl.h b/examples/tls_server_context_openssl.h
new file mode 100644
index 0000000..94c7561
--- /dev/null
+++ b/examples/tls_server_context_openssl.h
@@ -0,0 +1,54 @@
+/*
+ * 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 TLS_SERVER_CONTEXT_OPENSSL_H
+#define TLS_SERVER_CONTEXT_OPENSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <openssl/ssl.h>
+
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSServerContext {
+public:
+ TLSServerContext();
+ ~TLSServerContext();
+
+ int init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto);
+
+ SSL_CTX *get_native_handle() const;
+
+ void enable_keylog();
+
+private:
+ SSL_CTX *ssl_ctx_;
+};
+
+#endif // TLS_SERVER_CONTEXT_OPENSSL_H
diff --git a/examples/tls_server_context_picotls.cc b/examples/tls_server_context_picotls.cc
new file mode 100644
index 0000000..51d41b6
--- /dev/null
+++ b/examples/tls_server_context_picotls.cc
@@ -0,0 +1,318 @@
+/*
+ * 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 "tls_server_context_picotls.h"
+
+#include <iostream>
+#include <memory>
+
+#include <ngtcp2/ngtcp2_crypto_picotls.h>
+
+#include <openssl/pem.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+# include <openssl/core_names.h>
+#endif // OPENSSL_VERSION_NUMBER >= 0x30000000L
+
+#include "server_base.h"
+#include "template.h"
+
+extern Config config;
+
+namespace {
+int on_client_hello_cb(ptls_on_client_hello_t *self, ptls_t *ptls,
+ ptls_on_client_hello_parameters_t *params) {
+ auto &negprotos = params->negotiated_protocols;
+
+ for (size_t i = 0; i < negprotos.count; ++i) {
+ auto &proto = negprotos.list[i];
+ if (H3_ALPN_V1[0] == proto.len &&
+ memcmp(&H3_ALPN_V1[1], proto.base, proto.len) == 0) {
+ if (ptls_set_negotiated_protocol(
+ ptls, reinterpret_cast<char *>(proto.base), proto.len) != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+ }
+
+ return PTLS_ALERT_NO_APPLICATION_PROTOCOL;
+}
+
+ptls_on_client_hello_t on_client_hello = {on_client_hello_cb};
+} // namespace
+
+namespace {
+auto ticket_hmac = EVP_sha256();
+
+template <size_t N> void random_bytes(std::array<uint8_t, N> &dest) {
+ ptls_openssl_random_bytes(dest.data(), dest.size());
+}
+
+const std::array<uint8_t, 16> &get_ticket_key_name() {
+ static std::array<uint8_t, 16> key_name;
+ random_bytes(key_name);
+ return key_name;
+}
+
+const std::array<uint8_t, 32> &get_ticket_key() {
+ static std::array<uint8_t, 32> key;
+ random_bytes(key);
+ return key;
+}
+
+const std::array<uint8_t, 32> &get_ticket_hmac_key() {
+ static std::array<uint8_t, 32> hmac_key;
+ random_bytes(hmac_key);
+ return hmac_key;
+}
+} // namespace
+
+namespace {
+int ticket_key_cb(unsigned char *key_name, unsigned char *iv,
+ EVP_CIPHER_CTX *ctx,
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ EVP_MAC_CTX *hctx,
+#else // OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_CTX *hctx,
+#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
+ int enc) {
+ static const auto &static_key_name = get_ticket_key_name();
+ static const auto &static_key = get_ticket_key();
+ static const auto &static_hmac_key = get_ticket_hmac_key();
+
+ if (enc) {
+ ptls_openssl_random_bytes(iv, EVP_MAX_IV_LENGTH);
+
+ memcpy(key_name, static_key_name.data(), static_key_name.size());
+
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, static_key.data(), iv);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ std::array<OSSL_PARAM, 3> params{
+ OSSL_PARAM_construct_octet_string(
+ OSSL_MAC_PARAM_KEY, const_cast<uint8_t *>(static_hmac_key.data()),
+ static_hmac_key.size()),
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_MAC_PARAM_DIGEST,
+ const_cast<char *>(EVP_MD_get0_name(ticket_hmac)), 0),
+ OSSL_PARAM_construct_end(),
+ };
+ if (!EVP_MAC_CTX_set_params(hctx, params.data())) {
+ /* TODO Which value should we return on error? */
+ return 0;
+ }
+#else // OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_Init_ex(hctx, static_hmac_key.data(), static_hmac_key.size(),
+ ticket_hmac, nullptr);
+#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
+
+ return 1;
+ }
+
+ if (memcmp(key_name, static_key_name.data(), static_key_name.size()) != 0) {
+ return 0;
+ }
+
+ EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, static_key.data(), iv);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ std::array<OSSL_PARAM, 3> params{
+ OSSL_PARAM_construct_octet_string(
+ OSSL_MAC_PARAM_KEY, const_cast<uint8_t *>(static_hmac_key.data()),
+ static_hmac_key.size()),
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_MAC_PARAM_DIGEST,
+ const_cast<char *>(EVP_MD_get0_name(ticket_hmac)), 0),
+ OSSL_PARAM_construct_end(),
+ };
+ if (!EVP_MAC_CTX_set_params(hctx, params.data())) {
+ /* TODO Which value should we return on error? */
+ return 0;
+ }
+#else // OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_Init_ex(hctx, static_hmac_key.data(), static_hmac_key.size(),
+ ticket_hmac, nullptr);
+#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
+
+ return 1;
+}
+} // namespace
+
+namespace {
+int encrypt_ticket_cb(ptls_encrypt_ticket_t *encrypt_ticket, ptls_t *ptls,
+ int is_encrypt, ptls_buffer_t *dst, ptls_iovec_t src) {
+ int rv;
+ auto conn_ref =
+ static_cast<ngtcp2_crypto_conn_ref *>(*ptls_get_data_ptr(ptls));
+ auto conn = conn_ref->get_conn(conn_ref);
+ uint32_t ver;
+
+ if (is_encrypt) {
+ ver = htonl(ngtcp2_conn_get_negotiated_version(conn));
+ // TODO Replace std::make_unique with
+ // std::make_unique_for_overwrite when it is available.
+ auto buf = std::make_unique<uint8_t[]>(src.len + sizeof(ver));
+ auto p = std::copy_n(src.base, src.len, buf.get());
+ p = std::copy_n(reinterpret_cast<uint8_t *>(&ver), sizeof(ver), p);
+
+ src.base = buf.get();
+ src.len = p - buf.get();
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ rv = ptls_openssl_encrypt_ticket_evp(dst, src, ticket_key_cb);
+#else // OPENSSL_VERSION_NUMBER < 0x30000000L
+ rv = ptls_openssl_encrypt_ticket(dst, src, ticket_key_cb);
+#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
+ if (rv != 0) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ rv = ptls_openssl_decrypt_ticket_evp(dst, src, ticket_key_cb);
+#else // OPENSSL_VERSION_NUMBER < 0x30000000L
+ rv = ptls_openssl_decrypt_ticket(dst, src, ticket_key_cb);
+#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (dst->off < sizeof(ver)) {
+ return -1;
+ }
+
+ memcpy(&ver, dst->base + dst->off - sizeof(ver), sizeof(ver));
+
+ if (ngtcp2_conn_get_client_chosen_version(conn) != ntohl(ver)) {
+ return -1;
+ }
+
+ dst->off -= sizeof(ver);
+
+ return 0;
+}
+
+ptls_encrypt_ticket_t encrypt_ticket = {encrypt_ticket_cb};
+} // namespace
+
+namespace {
+ptls_key_exchange_algorithm_t *key_exchanges[] = {
+ &ptls_openssl_x25519,
+ &ptls_openssl_secp256r1,
+ &ptls_openssl_secp384r1,
+ &ptls_openssl_secp521r1,
+ nullptr,
+};
+} // namespace
+
+namespace {
+ptls_cipher_suite_t *cipher_suites[] = {
+ &ptls_openssl_aes128gcmsha256,
+ &ptls_openssl_aes256gcmsha384,
+ &ptls_openssl_chacha20poly1305sha256,
+ nullptr,
+};
+} // namespace
+
+TLSServerContext::TLSServerContext()
+ : ctx_{
+ .random_bytes = ptls_openssl_random_bytes,
+ .get_time = &ptls_get_time,
+ .key_exchanges = key_exchanges,
+ .cipher_suites = cipher_suites,
+ .on_client_hello = &on_client_hello,
+ .ticket_lifetime = 86400,
+ .require_dhe_on_psk = 1,
+ .server_cipher_preference = 1,
+ .encrypt_ticket = &encrypt_ticket,
+ },
+ sign_cert_{}
+{}
+
+TLSServerContext::~TLSServerContext() {
+ if (sign_cert_.key) {
+ ptls_openssl_dispose_sign_certificate(&sign_cert_);
+ }
+
+ for (size_t i = 0; i < ctx_.certificates.count; ++i) {
+ free(ctx_.certificates.list[i].base);
+ }
+ free(ctx_.certificates.list);
+}
+
+ptls_context_t *TLSServerContext::get_native_handle() { return &ctx_; }
+
+int TLSServerContext::init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto) {
+ if (ngtcp2_crypto_picotls_configure_server_context(&ctx_) != 0) {
+ std::cerr << "ngtcp2_crypto_picotls_configure_server_context failed"
+ << std::endl;
+ return -1;
+ }
+
+ if (ptls_load_certificates(&ctx_, cert_file) != 0) {
+ std::cerr << "ptls_load_certificates failed" << std::endl;
+ return -1;
+ }
+
+ if (load_private_key(private_key_file) != 0) {
+ return -1;
+ }
+
+ if (config.verify_client) {
+ ctx_.require_client_authentication = 1;
+ }
+
+ return 0;
+}
+
+int TLSServerContext::load_private_key(const char *private_key_file) {
+ auto fp = fopen(private_key_file, "rb");
+ if (fp == nullptr) {
+ std::cerr << "Could not open private key file " << private_key_file << ": "
+ << strerror(errno) << std::endl;
+ return -1;
+ }
+
+ auto fp_d = defer(fclose, fp);
+
+ auto pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr);
+ if (pkey == nullptr) {
+ std::cerr << "Could not read private key file " << private_key_file
+ << std::endl;
+ return -1;
+ }
+
+ auto pkey_d = defer(EVP_PKEY_free, pkey);
+
+ if (ptls_openssl_init_sign_certificate(&sign_cert_, pkey) != 0) {
+ std::cerr << "ptls_openssl_init_sign_certificate failed" << std::endl;
+ return -1;
+ }
+
+ ctx_.sign_certificate = &sign_cert_.super;
+
+ return 0;
+}
diff --git a/examples/tls_server_context_picotls.h b/examples/tls_server_context_picotls.h
new file mode 100644
index 0000000..c9dc489
--- /dev/null
+++ b/examples/tls_server_context_picotls.h
@@ -0,0 +1,59 @@
+/*
+ * 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 TLS_SERVER_CONTEXT_PICOTLS_H
+#define TLS_SERVER_CONTEXT_PICOTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <picotls.h>
+#include <picotls/openssl.h>
+
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSServerContext {
+public:
+ TLSServerContext();
+ ~TLSServerContext();
+
+ int init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto);
+
+ ptls_context_t *get_native_handle();
+
+ // TODO Implement keylog.
+ void enable_keylog() {}
+
+private:
+ int load_private_key(const char *private_key_file);
+
+ ptls_context_t ctx_;
+ ptls_openssl_sign_certificate_t sign_cert_;
+};
+
+#endif // TLS_SERVER_CONTEXT_PICOTLS_H
diff --git a/examples/tls_server_context_wolfssl.cc b/examples/tls_server_context_wolfssl.cc
new file mode 100644
index 0000000..ed09b72
--- /dev/null
+++ b/examples/tls_server_context_wolfssl.cc
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+#include "tls_server_context_wolfssl.h"
+
+#include <iostream>
+#include <fstream>
+#include <limits>
+
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+
+#include "server_base.h"
+#include "template.h"
+
+extern Config config;
+
+TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {}
+
+TLSServerContext::~TLSServerContext() {
+ if (ssl_ctx_) {
+ wolfSSL_CTX_free(ssl_ctx_);
+ }
+}
+
+WOLFSSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; }
+
+namespace {
+int alpn_select_proto_h3_cb(WOLFSSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ auto conn_ref =
+ static_cast<ngtcp2_crypto_conn_ref *>(wolfSSL_get_app_data(ssl));
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+ const uint8_t *alpn;
+ size_t alpnlen;
+ // This should be the negotiated version, but we have not set the
+ // negotiated version when this callback is called.
+ auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
+
+ switch (version) {
+ case QUIC_VER_DRAFT29:
+ alpn = H3_ALPN_DRAFT29;
+ alpnlen = str_size(H3_ALPN_DRAFT29);
+ break;
+ case QUIC_VER_DRAFT30:
+ alpn = H3_ALPN_DRAFT30;
+ alpnlen = str_size(H3_ALPN_DRAFT30);
+ break;
+ case QUIC_VER_DRAFT31:
+ alpn = H3_ALPN_DRAFT31;
+ alpnlen = str_size(H3_ALPN_DRAFT31);
+ break;
+ case QUIC_VER_DRAFT32:
+ alpn = H3_ALPN_DRAFT32;
+ alpnlen = str_size(H3_ALPN_DRAFT32);
+ break;
+ case NGTCP2_PROTO_VER_V1:
+ case NGTCP2_PROTO_VER_V2_DRAFT:
+ alpn = H3_ALPN_V1;
+ alpnlen = str_size(H3_ALPN_V1);
+ break;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
+ << version << std::dec << std::endl;
+ }
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) {
+ if (std::equal(alpn, alpn + alpnlen, p)) {
+ *out = p + 1;
+ *outlen = *p;
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl;
+ }
+
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // namespace
+
+namespace {
+int alpn_select_proto_hq_cb(WOLFSSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ auto conn_ref =
+ static_cast<ngtcp2_crypto_conn_ref *>(wolfSSL_get_app_data(ssl));
+ auto h = static_cast<HandlerBase *>(conn_ref->user_data);
+ const uint8_t *alpn;
+ size_t alpnlen;
+ // This should be the negotiated version, but we have not set the
+ // negotiated version when this callback is called.
+ auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
+
+ switch (version) {
+ case QUIC_VER_DRAFT29:
+ alpn = HQ_ALPN_DRAFT29;
+ alpnlen = str_size(HQ_ALPN_DRAFT29);
+ break;
+ case QUIC_VER_DRAFT30:
+ alpn = HQ_ALPN_DRAFT30;
+ alpnlen = str_size(HQ_ALPN_DRAFT30);
+ break;
+ case QUIC_VER_DRAFT31:
+ alpn = HQ_ALPN_DRAFT31;
+ alpnlen = str_size(HQ_ALPN_DRAFT31);
+ break;
+ case QUIC_VER_DRAFT32:
+ alpn = HQ_ALPN_DRAFT32;
+ alpnlen = str_size(HQ_ALPN_DRAFT32);
+ break;
+ case NGTCP2_PROTO_VER_V1:
+ case NGTCP2_PROTO_VER_V2_DRAFT:
+ alpn = HQ_ALPN_V1;
+ alpnlen = str_size(HQ_ALPN_V1);
+ break;
+ default:
+ if (!config.quiet) {
+ std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
+ << version << std::dec << std::endl;
+ }
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) {
+ if (std::equal(alpn, alpn + alpnlen, p)) {
+ *out = p + 1;
+ *outlen = *p;
+ return SSL_TLSEXT_ERR_OK;
+ }
+ }
+
+ if (!config.quiet) {
+ std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl;
+ }
+
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+} // namespace
+
+namespace {
+int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) {
+ // We don't verify the client certificate. Just request it for the
+ // testing purpose.
+ return 1;
+}
+} // namespace
+
+int TLSServerContext::init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto) {
+ constexpr static unsigned char sid_ctx[] = "ngtcp2 server";
+
+#if defined(DEBUG_WOLFSSL)
+ if (!config.quiet) {
+ /*wolfSSL_Debugging_ON();*/
+ }
+#endif
+
+ ssl_ctx_ = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
+ if (!ssl_ctx_) {
+ std::cerr << "wolfSSL_CTX_new: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (ngtcp2_crypto_wolfssl_configure_server_context(ssl_ctx_) != 0) {
+ std::cerr << "ngtcp2_crypto_wolfssl_configure_server_context failed"
+ << std::endl;
+ return -1;
+ }
+
+#ifdef WOLFSSL_EARLY_DATA
+ wolfSSL_CTX_set_max_early_data(ssl_ctx_, UINT32_MAX);
+#endif
+
+ constexpr auto ssl_opts =
+ (WOLFSSL_OP_ALL & ~WOLFSSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
+ WOLFSSL_OP_SINGLE_ECDH_USE | WOLFSSL_OP_CIPHER_SERVER_PREFERENCE;
+
+ wolfSSL_CTX_set_options(ssl_ctx_, ssl_opts);
+
+ if (wolfSSL_CTX_set_cipher_list(ssl_ctx_, config.ciphers) != 1) {
+ std::cerr << "wolfSSL_CTX_set_cipher_list: "
+ << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+ return -1;
+ }
+
+ if (wolfSSL_CTX_set1_curves_list(ssl_ctx_,
+ const_cast<char *>(config.groups)) != 1) {
+ std::cerr << "wolfSSL_CTX_set1_curves_list(" << config.groups << ") failed"
+ << std::endl;
+ return -1;
+ }
+
+ wolfSSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS);
+
+ switch (app_proto) {
+ case AppProtocol::H3:
+ wolfSSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr);
+ break;
+ case AppProtocol::HQ:
+ wolfSSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr);
+ break;
+ }
+
+ wolfSSL_CTX_set_default_verify_paths(ssl_ctx_);
+
+ if (wolfSSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
+ SSL_FILETYPE_PEM) != 1) {
+ std::cerr << "wolfSSL_CTX_use_PrivateKey_file: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (wolfSSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
+ std::cerr << "wolfSSL_CTX_use_certificate_chain_file: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ if (wolfSSL_CTX_check_private_key(ssl_ctx_) != 1) {
+ std::cerr << "wolfSSL_CTX_check_private_key: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ wolfSSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1);
+
+ if (config.verify_client) {
+ wolfSSL_CTX_set_verify(ssl_ctx_,
+ WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_CLIENT_ONCE |
+ WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ verify_cb);
+ }
+
+ return 0;
+}
+
+extern std::ofstream keylog_file;
+
+#ifdef HAVE_SECRET_CALLBACK
+namespace {
+void keylog_callback(const WOLFSSL *ssl, const char *line) {
+ keylog_file.write(line, strlen(line));
+ keylog_file.put('\n');
+ keylog_file.flush();
+}
+} // namespace
+#endif
+
+void TLSServerContext::enable_keylog() {
+#ifdef HAVE_SECRET_CALLBACK
+ wolfSSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
+#endif
+}
diff --git a/examples/tls_server_context_wolfssl.h b/examples/tls_server_context_wolfssl.h
new file mode 100644
index 0000000..e0b3c38
--- /dev/null
+++ b/examples/tls_server_context_wolfssl.h
@@ -0,0 +1,55 @@
+/*
+ * 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 TLS_SERVER_CONTEXT_WOLFSSL_H
+#define TLS_SERVER_CONTEXT_WOLFSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+
+#include "shared.h"
+
+using namespace ngtcp2;
+
+class TLSServerContext {
+public:
+ TLSServerContext();
+ ~TLSServerContext();
+
+ int init(const char *private_key_file, const char *cert_file,
+ AppProtocol app_proto);
+
+ WOLFSSL_CTX *get_native_handle() const;
+
+ void enable_keylog();
+
+private:
+ WOLFSSL_CTX *ssl_ctx_;
+};
+
+#endif // TLS_SERVER_CONTEXT_WOLFSSL_H
diff --git a/examples/tls_server_session.h b/examples/tls_server_session.h
new file mode 100644
index 0000000..85b76e4
--- /dev/null
+++ b/examples/tls_server_session.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_SERVER_SESSION_H
+#define TLS_SERVER_SESSION_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#if defined(ENABLE_EXAMPLE_OPENSSL) && defined(WITH_EXAMPLE_OPENSSL)
+# include "tls_server_session_openssl.h"
+#endif // ENABLE_EXAMPLE_OPENSSL && WITH_EXAMPLE_OPENSSL
+
+#if defined(ENABLE_EXAMPLE_GNUTLS) && defined(WITH_EXAMPLE_GNUTLS)
+# include "tls_server_session_gnutls.h"
+#endif // ENABLE_EXAMPLE_GNUTLS && WITH_EXAMPLE_GNUTLS
+
+#if defined(ENABLE_EXAMPLE_BORINGSSL) && defined(WITH_EXAMPLE_BORINGSSL)
+# include "tls_server_session_boringssl.h"
+#endif // ENABLE_EXAMPLE_BORINGSSL && WITH_EXAMPLE_BORINGSSL
+
+#if defined(ENABLE_EXAMPLE_PICOTLS) && defined(WITH_EXAMPLE_PICOTLS)
+# include "tls_server_session_picotls.h"
+#endif // ENABLE_EXAMPLE_PICOTLS && WITH_EXAMPLE_PICOTLS
+
+#if defined(ENABLE_EXAMPLE_WOLFSSL) && defined(WITH_EXAMPLE_WOLFSSL)
+# include "tls_server_session_wolfssl.h"
+#endif // ENABLE_EXAMPLE_WOLFSSL && WITH_EXAMPLE_WOLFSSL
+
+#endif // TLS_SERVER_SESSION_H
diff --git a/examples/tls_server_session_boringssl.cc b/examples/tls_server_session_boringssl.cc
new file mode 100644
index 0000000..133f4d0
--- /dev/null
+++ b/examples/tls_server_session_boringssl.cc
@@ -0,0 +1,84 @@
+/*
+ * 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 "tls_server_session_boringssl.h"
+
+#include <cassert>
+#include <iostream>
+
+#include <ngtcp2/ngtcp2.h>
+
+#include "tls_server_context_boringssl.h"
+#include "server_base.h"
+
+extern Config config;
+
+TLSServerSession::TLSServerSession() {}
+
+TLSServerSession::~TLSServerSession() {}
+
+int TLSServerSession::init(const TLSServerContext &tls_ctx,
+ HandlerBase *handler) {
+ auto ssl_ctx = tls_ctx.get_native_handle();
+
+ ssl_ = SSL_new(ssl_ctx);
+ if (!ssl_) {
+ std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ SSL_set_app_data(ssl_, handler->conn_ref());
+ SSL_set_accept_state(ssl_);
+ SSL_set_early_data_enabled(ssl_, 1);
+ SSL_set_quic_use_legacy_codepoint(ssl_, 0);
+
+ std::array<uint8_t, 128> quic_early_data_ctx;
+ ngtcp2_transport_params params;
+ memset(&params, 0, sizeof(params));
+ params.initial_max_streams_bidi = config.max_streams_bidi;
+ params.initial_max_streams_uni = config.max_streams_uni;
+ params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
+ params.initial_max_stream_data_bidi_remote =
+ config.max_stream_data_bidi_remote;
+ params.initial_max_stream_data_uni = config.max_stream_data_uni;
+ params.initial_max_data = config.max_data;
+
+ auto quic_early_data_ctxlen = ngtcp2_encode_transport_params(
+ quic_early_data_ctx.data(), quic_early_data_ctx.size(),
+ NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, &params);
+ if (quic_early_data_ctxlen < 0) {
+ std::cerr << "ngtcp2_encode_transport_params: "
+ << ngtcp2_strerror(quic_early_data_ctxlen) << std::endl;
+ return -1;
+ }
+
+ if (SSL_set_quic_early_data_context(ssl_, quic_early_data_ctx.data(),
+ quic_early_data_ctxlen) != 1) {
+ std::cerr << "SSL_set_quic_early_data_context failed" << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/examples/tls_server_session_boringssl.h b/examples/tls_server_session_boringssl.h
new file mode 100644
index 0000000..eebf18a
--- /dev/null
+++ b/examples/tls_server_session_boringssl.h
@@ -0,0 +1,47 @@
+/*
+ * 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 TLS_SERVER_SESSION_BORINGSSL_H
+#define TLS_SERVER_SESSION_BORINGSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_openssl.h"
+
+class TLSServerContext;
+class HandlerBase;
+
+class TLSServerSession : public TLSSessionBase {
+public:
+ TLSServerSession();
+ ~TLSServerSession();
+
+ int init(const TLSServerContext &tls_ctx, HandlerBase *handler);
+ // ticket is sent automatically.
+ int send_session_ticket() { return 0; }
+};
+
+#endif // TLS_SERVER_SESSION_BORINGSSL_H
diff --git a/examples/tls_server_session_gnutls.cc b/examples/tls_server_session_gnutls.cc
new file mode 100644
index 0000000..ee776c4
--- /dev/null
+++ b/examples/tls_server_session_gnutls.cc
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+#include "tls_server_session_gnutls.h"
+
+#include <cassert>
+#include <iostream>
+#include <fstream>
+#include <array>
+
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+
+#include "tls_server_context_gnutls.h"
+#include "server_base.h"
+#include "util.h"
+
+// Based on https://github.com/ueno/ngtcp2-gnutls-examples
+
+using namespace ngtcp2;
+
+extern Config config;
+
+TLSServerSession::TLSServerSession() {}
+
+TLSServerSession::~TLSServerSession() {}
+
+namespace {
+int client_hello_cb(gnutls_session_t session, unsigned int htype, unsigned when,
+ unsigned int incoming, const gnutls_datum_t *msg) {
+ assert(htype == GNUTLS_HANDSHAKE_CLIENT_HELLO);
+ assert(when == GNUTLS_HOOK_POST);
+ assert(incoming == 1);
+
+ // check if ALPN extension is present and properly selected h3
+ gnutls_datum_t alpn;
+ if (auto rv = gnutls_alpn_get_selected_protocol(session, &alpn); rv != 0) {
+ return rv;
+ }
+
+ // TODO Fix this to properly select ALPN based on app_proto.
+
+ // strip the first byte from H3_ALPN_V1
+ auto h3 = reinterpret_cast<const char *>(&H3_ALPN_V1[1]);
+ if (static_cast<size_t>(H3_ALPN_V1[0]) != alpn.size ||
+ !std::equal(alpn.data, alpn.data + alpn.size, h3)) {
+ return -1;
+ }
+
+ return 0;
+}
+} // namespace
+
+int TLSServerSession::init(const TLSServerContext &tls_ctx,
+ HandlerBase *handler) {
+ if (auto rv =
+ gnutls_init(&session_, GNUTLS_SERVER | GNUTLS_ENABLE_EARLY_DATA |
+ GNUTLS_NO_AUTO_SEND_TICKET |
+ GNUTLS_NO_END_OF_EARLY_DATA);
+ rv != 0) {
+ std::cerr << "gnutls_init failed: " << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ std::string priority = "%DISABLE_TLS13_COMPAT_MODE:";
+ priority += config.ciphers;
+ priority += ':';
+ priority += config.groups;
+
+ if (auto rv = gnutls_priority_set_direct(session_, priority.c_str(), nullptr);
+ rv != 0) {
+ std::cerr << "gnutls_priority_set_direct failed: " << gnutls_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ auto rv = gnutls_session_ticket_enable_server(
+ session_, tls_ctx.get_session_ticket_key());
+ if (rv != 0) {
+ std::cerr << "gnutls_session_ticket_enable_server failed: "
+ << gnutls_strerror(rv) << std::endl;
+ return -1;
+ }
+
+ gnutls_handshake_set_hook_function(session_, GNUTLS_HANDSHAKE_CLIENT_HELLO,
+ GNUTLS_HOOK_POST, client_hello_cb);
+
+ if (ngtcp2_crypto_gnutls_configure_server_session(session_) != 0) {
+ std::cerr << "ngtcp2_crypto_gnutls_configure_server_session failed"
+ << std::endl;
+ return -1;
+ }
+
+ gnutls_anti_replay_enable(session_, tls_ctx.get_anti_replay());
+
+ gnutls_record_set_max_early_data_size(session_, 0xffffffffu);
+
+ gnutls_session_set_ptr(session_, handler->conn_ref());
+
+ if (auto rv = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE,
+ tls_ctx.get_certificate_credentials());
+ rv != 0) {
+ std::cerr << "gnutls_credentials_set failed: " << gnutls_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ // TODO Set all available ALPN based on app_proto.
+
+ // strip the first byte from H3_ALPN_V1
+ gnutls_datum_t alpn{
+ .data = const_cast<uint8_t *>(&H3_ALPN_V1[1]),
+ .size = H3_ALPN_V1[0],
+ };
+ gnutls_alpn_set_protocols(session_, &alpn, 1,
+ GNUTLS_ALPN_MANDATORY |
+ GNUTLS_ALPN_SERVER_PRECEDENCE);
+
+ if (config.verify_client) {
+ gnutls_certificate_server_set_request(session_, GNUTLS_CERT_REQUIRE);
+ gnutls_certificate_send_x509_rdn_sequence(session_, 1);
+ }
+
+ return 0;
+}
+
+int TLSServerSession::send_session_ticket() {
+ if (auto rv = gnutls_session_ticket_send(session_, 1, 0); rv != 0) {
+ std::cerr << "gnutls_session_ticket_send failed: " << gnutls_strerror(rv)
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/examples/tls_server_session_gnutls.h b/examples/tls_server_session_gnutls.h
new file mode 100644
index 0000000..994c643
--- /dev/null
+++ b/examples/tls_server_session_gnutls.h
@@ -0,0 +1,46 @@
+/*
+ * 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 TLS_SERVER_SESSION_GNUTLS_H
+#define TLS_SERVER_SESSION_GNUTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_gnutls.h"
+
+class TLSServerContext;
+class HandlerBase;
+
+class TLSServerSession : public TLSSessionBase {
+public:
+ TLSServerSession();
+ ~TLSServerSession();
+
+ int init(const TLSServerContext &tls_ctx, HandlerBase *handler);
+ int send_session_ticket();
+};
+
+#endif // TLS_SERVER_SESSION_GNUTLS_H
diff --git a/examples/tls_server_session_openssl.cc b/examples/tls_server_session_openssl.cc
new file mode 100644
index 0000000..5e93e41
--- /dev/null
+++ b/examples/tls_server_session_openssl.cc
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+#include "tls_server_session_openssl.h"
+
+#include <iostream>
+
+#include <openssl/err.h>
+
+#include "tls_server_context_openssl.h"
+#include "server_base.h"
+
+TLSServerSession::TLSServerSession() {}
+
+TLSServerSession::~TLSServerSession() {}
+
+int TLSServerSession::init(const TLSServerContext &tls_ctx,
+ HandlerBase *handler) {
+ auto ssl_ctx = tls_ctx.get_native_handle();
+
+ ssl_ = SSL_new(ssl_ctx);
+ if (!ssl_) {
+ std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ SSL_set_app_data(ssl_, handler->conn_ref());
+ SSL_set_accept_state(ssl_);
+ SSL_set_quic_early_data_enabled(ssl_, 1);
+
+ return 0;
+}
diff --git a/examples/tls_server_session_openssl.h b/examples/tls_server_session_openssl.h
new file mode 100644
index 0000000..ef84b39
--- /dev/null
+++ b/examples/tls_server_session_openssl.h
@@ -0,0 +1,47 @@
+/*
+ * 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 TLS_SERVER_SESSION_OPENSSL_H
+#define TLS_SERVER_SESSION_OPENSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_openssl.h"
+
+class TLSServerContext;
+class HandlerBase;
+
+class TLSServerSession : public TLSSessionBase {
+public:
+ TLSServerSession();
+ ~TLSServerSession();
+
+ int init(const TLSServerContext &tls_ctx, HandlerBase *handler);
+ // ticket is sent automatically.
+ int send_session_ticket() { return 0; }
+};
+
+#endif // TLS_SERVER_SESSION_OPENSSL_H
diff --git a/examples/tls_server_session_picotls.cc b/examples/tls_server_session_picotls.cc
new file mode 100644
index 0000000..f1124aa
--- /dev/null
+++ b/examples/tls_server_session_picotls.cc
@@ -0,0 +1,70 @@
+/*
+ * 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 "tls_server_session_picotls.h"
+
+#include <cassert>
+#include <iostream>
+
+#include <ngtcp2/ngtcp2_crypto_picotls.h>
+
+#include "tls_server_context_picotls.h"
+#include "server_base.h"
+#include "util.h"
+
+using namespace ngtcp2;
+
+extern Config config;
+
+TLSServerSession::TLSServerSession() {}
+
+TLSServerSession::~TLSServerSession() {}
+
+int TLSServerSession::init(TLSServerContext &tls_ctx, HandlerBase *handler) {
+ cptls_.ptls = ptls_server_new(tls_ctx.get_native_handle());
+ if (!cptls_.ptls) {
+ std::cerr << "ptls_server_new failed" << std::endl;
+ return -1;
+ }
+
+ *ptls_get_data_ptr(cptls_.ptls) = handler->conn_ref();
+
+ cptls_.handshake_properties.additional_extensions =
+ new ptls_raw_extension_t[2]{
+ {
+ .type = UINT16_MAX,
+ },
+ {
+ .type = UINT16_MAX,
+ },
+ };
+
+ if (ngtcp2_crypto_picotls_configure_server_session(&cptls_) != 0) {
+ std::cerr << "ngtcp2_crypto_picotls_configure_server_session failed"
+ << std::endl;
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/examples/tls_server_session_picotls.h b/examples/tls_server_session_picotls.h
new file mode 100644
index 0000000..ea919a2
--- /dev/null
+++ b/examples/tls_server_session_picotls.h
@@ -0,0 +1,47 @@
+/*
+ * 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 TLS_SERVER_SESSION_PICOTLS_H
+#define TLS_SERVER_SESSION_PICOTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_picotls.h"
+
+class TLSServerContext;
+class HandlerBase;
+
+class TLSServerSession : public TLSSessionBase {
+public:
+ TLSServerSession();
+ ~TLSServerSession();
+
+ int init(TLSServerContext &tls_ctx, HandlerBase *handler);
+ // ticket is sent automatically.
+ int send_session_ticket() { return 0; }
+};
+
+#endif // TLS_SERVER_SESSION_PICOTLS_H
diff --git a/examples/tls_server_session_wolfssl.cc b/examples/tls_server_session_wolfssl.cc
new file mode 100644
index 0000000..68497ad
--- /dev/null
+++ b/examples/tls_server_session_wolfssl.cc
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+#include "tls_server_session_wolfssl.h"
+
+#include <iostream>
+
+#include "tls_server_context_wolfssl.h"
+#include "server_base.h"
+
+TLSServerSession::TLSServerSession() {}
+
+TLSServerSession::~TLSServerSession() {}
+
+int TLSServerSession::init(const TLSServerContext &tls_ctx,
+ HandlerBase *handler) {
+ auto ssl_ctx = tls_ctx.get_native_handle();
+
+ ssl_ = wolfSSL_new(ssl_ctx);
+ if (!ssl_) {
+ std::cerr << "wolfSSL_new: "
+ << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
+ << std::endl;
+ return -1;
+ }
+
+ wolfSSL_set_app_data(ssl_, handler->conn_ref());
+ wolfSSL_set_accept_state(ssl_);
+#ifdef WOLFSSL_EARLY_DATA
+ wolfSSL_set_quic_early_data_enabled(ssl_, 1);
+#endif
+
+ return 0;
+}
diff --git a/examples/tls_server_session_wolfssl.h b/examples/tls_server_session_wolfssl.h
new file mode 100644
index 0000000..db32441
--- /dev/null
+++ b/examples/tls_server_session_wolfssl.h
@@ -0,0 +1,47 @@
+/*
+ * 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 TLS_SERVER_SESSION_WOLFSSL_H
+#define TLS_SERVER_SESSION_WOLFSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include "tls_session_base_wolfssl.h"
+
+class TLSServerContext;
+class HandlerBase;
+
+class TLSServerSession : public TLSSessionBase {
+public:
+ TLSServerSession();
+ ~TLSServerSession();
+
+ int init(const TLSServerContext &tls_ctx, HandlerBase *handler);
+ // ticket is sent automatically.
+ int send_session_ticket() { return 0; }
+};
+
+#endif // TLS_SERVER_SESSION_WOLFSSL_H
diff --git a/examples/tls_session_base_gnutls.cc b/examples/tls_session_base_gnutls.cc
new file mode 100644
index 0000000..51460e9
--- /dev/null
+++ b/examples/tls_session_base_gnutls.cc
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+#include "tls_session_base_gnutls.h"
+
+#include <fstream>
+
+#include "util.h"
+
+// Based on https://github.com/ueno/ngtcp2-gnutls-examples
+
+using namespace ngtcp2;
+
+TLSSessionBase::TLSSessionBase() : session_{nullptr} {}
+
+TLSSessionBase::~TLSSessionBase() { gnutls_deinit(session_); }
+
+gnutls_session_t TLSSessionBase::get_native_handle() const { return session_; }
+
+std::string TLSSessionBase::get_cipher_name() const {
+ return gnutls_cipher_get_name(gnutls_cipher_get(session_));
+}
+
+std::string TLSSessionBase::get_selected_alpn() const {
+ gnutls_datum_t alpn;
+
+ if (auto rv = gnutls_alpn_get_selected_protocol(session_, &alpn); rv == 0) {
+ return std::string{alpn.data, alpn.data + alpn.size};
+ }
+
+ return {};
+}
+
+extern std::ofstream keylog_file;
+
+namespace {
+int keylog_callback(gnutls_session_t session, const char *label,
+ const gnutls_datum_t *secret) {
+ keylog_file.write(label, strlen(label));
+ keylog_file.put(' ');
+
+ gnutls_datum_t crandom;
+ gnutls_datum_t srandom;
+
+ gnutls_session_get_random(session, &crandom, &srandom);
+ if (crandom.size != 32) {
+ return -1;
+ }
+
+ auto crandom_hex =
+ util::format_hex(reinterpret_cast<unsigned char *>(crandom.data), 32);
+ keylog_file << crandom_hex << " ";
+
+ auto secret_hex = util::format_hex(
+ reinterpret_cast<unsigned char *>(secret->data), secret->size);
+ keylog_file << secret_hex << " ";
+
+ keylog_file.put('\n');
+ keylog_file.flush();
+ return 0;
+}
+} // namespace
+
+void TLSSessionBase::enable_keylog() {
+ gnutls_session_set_keylog_function(session_, keylog_callback);
+}
diff --git a/examples/tls_session_base_gnutls.h b/examples/tls_session_base_gnutls.h
new file mode 100644
index 0000000..7a23418
--- /dev/null
+++ b/examples/tls_session_base_gnutls.h
@@ -0,0 +1,51 @@
+/*
+ * 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 TLS_SESSION_BASE_GNUTLS_H
+#define TLS_SESSION_BASE_GNUTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <string>
+
+#include <gnutls/gnutls.h>
+
+class TLSSessionBase {
+public:
+ TLSSessionBase();
+ ~TLSSessionBase();
+
+ gnutls_session_t get_native_handle() const;
+
+ std::string get_cipher_name() const;
+ std::string get_selected_alpn() const;
+ void enable_keylog();
+
+protected:
+ gnutls_session_t session_;
+};
+
+#endif // TLS_SESSION_BASE_GNUTLS_H
diff --git a/examples/tls_session_base_openssl.cc b/examples/tls_session_base_openssl.cc
new file mode 100644
index 0000000..2de47dc
--- /dev/null
+++ b/examples/tls_session_base_openssl.cc
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+#include "tls_session_base_openssl.h"
+
+#include <array>
+
+#include "util.h"
+
+using namespace ngtcp2;
+
+TLSSessionBase::TLSSessionBase() : ssl_{nullptr} {}
+
+TLSSessionBase::~TLSSessionBase() {
+ if (ssl_) {
+ SSL_free(ssl_);
+ }
+}
+
+SSL *TLSSessionBase::get_native_handle() const { return ssl_; }
+
+std::string TLSSessionBase::get_cipher_name() const {
+ return SSL_get_cipher_name(ssl_);
+}
+
+std::string TLSSessionBase::get_selected_alpn() const {
+ const unsigned char *alpn = nullptr;
+ unsigned int alpnlen;
+
+ SSL_get0_alpn_selected(ssl_, &alpn, &alpnlen);
+
+ return std::string{alpn, alpn + alpnlen};
+}
diff --git a/examples/tls_session_base_openssl.h b/examples/tls_session_base_openssl.h
new file mode 100644
index 0000000..ee63b39
--- /dev/null
+++ b/examples/tls_session_base_openssl.h
@@ -0,0 +1,52 @@
+/*
+ * 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 TLS_SESSION_BASE_OPENSSL_H
+#define TLS_SESSION_BASE_OPENSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <string>
+
+#include <openssl/ssl.h>
+
+class TLSSessionBase {
+public:
+ TLSSessionBase();
+ ~TLSSessionBase();
+
+ SSL *get_native_handle() const;
+
+ std::string get_cipher_name() const;
+ std::string get_selected_alpn() const;
+ // Keylog is enabled per SSL_CTX.
+ void enable_keylog() {}
+
+protected:
+ SSL *ssl_;
+};
+
+#endif // TLS_SESSION_BASE_OPENSSL_H
diff --git a/examples/tls_session_base_picotls.cc b/examples/tls_session_base_picotls.cc
new file mode 100644
index 0000000..b8413b4
--- /dev/null
+++ b/examples/tls_session_base_picotls.cc
@@ -0,0 +1,56 @@
+/*
+ * 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 "tls_session_base_picotls.h"
+
+TLSSessionBase::TLSSessionBase() { ngtcp2_crypto_picotls_ctx_init(&cptls_); }
+
+TLSSessionBase::~TLSSessionBase() {
+ ngtcp2_crypto_picotls_deconfigure_session(&cptls_);
+
+ delete[] cptls_.handshake_properties.additional_extensions;
+
+ if (cptls_.ptls) {
+ ptls_free(cptls_.ptls);
+ }
+}
+
+ngtcp2_crypto_picotls_ctx *TLSSessionBase::get_native_handle() {
+ return &cptls_;
+}
+
+std::string TLSSessionBase::get_cipher_name() const {
+ auto cs = ptls_get_cipher(cptls_.ptls);
+ return cs->aead->name;
+}
+
+std::string TLSSessionBase::get_selected_alpn() const {
+ auto alpn = ptls_get_negotiated_protocol(cptls_.ptls);
+
+ if (!alpn) {
+ return {};
+ }
+
+ return alpn;
+}
diff --git a/examples/tls_session_base_picotls.h b/examples/tls_session_base_picotls.h
new file mode 100644
index 0000000..e59ccbc
--- /dev/null
+++ b/examples/tls_session_base_picotls.h
@@ -0,0 +1,54 @@
+/*
+ * 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 TLS_SESSION_BASE_PICOTLS_H
+#define TLS_SESSION_BASE_PICOTLS_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <string>
+
+#include <ngtcp2/ngtcp2_crypto_picotls.h>
+
+#include <picotls.h>
+
+class TLSSessionBase {
+public:
+ TLSSessionBase();
+ ~TLSSessionBase();
+
+ ngtcp2_crypto_picotls_ctx *get_native_handle();
+
+ std::string get_cipher_name() const;
+ std::string get_selected_alpn() const;
+ // TODO make keylog work with picotls
+ void enable_keylog(){};
+
+protected:
+ ngtcp2_crypto_picotls_ctx cptls_;
+};
+
+#endif // TLS_SESSION_BASE_PICOTLS_H
diff --git a/examples/tls_session_base_wolfssl.cc b/examples/tls_session_base_wolfssl.cc
new file mode 100644
index 0000000..4620182
--- /dev/null
+++ b/examples/tls_session_base_wolfssl.cc
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+#include "tls_session_base_wolfssl.h"
+
+#include <array>
+
+#include "util.h"
+
+using namespace ngtcp2;
+
+TLSSessionBase::TLSSessionBase() : ssl_{nullptr} {}
+
+TLSSessionBase::~TLSSessionBase() {
+ if (ssl_) {
+ wolfSSL_free(ssl_);
+ }
+}
+
+WOLFSSL *TLSSessionBase::get_native_handle() const { return ssl_; }
+
+std::string TLSSessionBase::get_cipher_name() const {
+ return wolfSSL_get_cipher_name(ssl_);
+}
+
+std::string TLSSessionBase::get_selected_alpn() const {
+ char *alpn = nullptr;
+ unsigned short alpnlen;
+
+ wolfSSL_ALPN_GetProtocol(ssl_, &alpn, &alpnlen);
+
+ return std::string{alpn, alpn + alpnlen};
+}
diff --git a/examples/tls_session_base_wolfssl.h b/examples/tls_session_base_wolfssl.h
new file mode 100644
index 0000000..c61eee8
--- /dev/null
+++ b/examples/tls_session_base_wolfssl.h
@@ -0,0 +1,54 @@
+/*
+ * 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 TLS_SESSION_BASE_WOLFSSL_H
+#define TLS_SESSION_BASE_WOLFSSL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <string>
+
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+
+class TLSSessionBase {
+public:
+ TLSSessionBase();
+ ~TLSSessionBase();
+
+ WOLFSSL *get_native_handle() const;
+
+ std::string get_cipher_name() const;
+ std::string get_selected_alpn() const;
+ // Keylog is enabled per SSL_CTX.
+ void enable_keylog() {}
+
+protected:
+ WOLFSSL *ssl_;
+};
+
+#endif // TLS_SESSION_BASE_WOLFSSL_H
diff --git a/examples/util.cc b/examples/util.cc
new file mode 100644
index 0000000..f8401d4
--- /dev/null
+++ b/examples/util.cc
@@ -0,0 +1,646 @@
+/*
+ * 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 "util.h"
+
+#ifdef HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif // HAVE_ARPA_INET_H
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netdb.h>
+
+#include <cassert>
+#include <cstring>
+#include <chrono>
+#include <array>
+#include <iostream>
+#include <fstream>
+#include <algorithm>
+#include <limits>
+#include <charconv>
+
+#include "template.h"
+
+using namespace std::literals;
+
+namespace ngtcp2 {
+
+namespace util {
+
+namespace {
+constexpr char LOWER_XDIGITS[] = "0123456789abcdef";
+} // namespace
+
+std::string format_hex(uint8_t c) {
+ std::string s;
+ s.resize(2);
+
+ s[0] = LOWER_XDIGITS[c >> 4];
+ s[1] = LOWER_XDIGITS[c & 0xf];
+
+ return s;
+}
+
+std::string format_hex(const uint8_t *s, size_t len) {
+ std::string res;
+ res.resize(len * 2);
+
+ for (size_t i = 0; i < len; ++i) {
+ auto c = s[i];
+
+ res[i * 2] = LOWER_XDIGITS[c >> 4];
+ res[i * 2 + 1] = LOWER_XDIGITS[c & 0x0f];
+ }
+ return res;
+}
+
+std::string format_hex(const std::string_view &s) {
+ return format_hex(reinterpret_cast<const uint8_t *>(s.data()), s.size());
+}
+
+std::string decode_hex(const std::string_view &s) {
+ assert(s.size() % 2 == 0);
+ std::string res(s.size() / 2, '0');
+ auto p = std::begin(res);
+ for (auto it = std::begin(s); it != std::end(s); it += 2) {
+ *p++ = (hex_to_uint(*it) << 4) | hex_to_uint(*(it + 1));
+ }
+ return res;
+}
+
+namespace {
+// format_fraction2 formats |n| as fraction part of integer. |n| is
+// considered as fraction, and its precision is 3 digits. The last
+// digit is ignored. The precision of the resulting fraction is 2
+// digits.
+std::string format_fraction2(uint32_t n) {
+ n /= 10;
+
+ if (n < 10) {
+ return {'.', '0', static_cast<char>('0' + n)};
+ }
+ return {'.', static_cast<char>('0' + n / 10),
+ static_cast<char>('0' + (n % 10))};
+}
+} // namespace
+
+namespace {
+// round2even rounds the last digit of |n| so that the n / 10 becomes
+// even.
+uint64_t round2even(uint64_t n) {
+ if (n % 10 == 5) {
+ if ((n / 10) & 1) {
+ n += 10;
+ }
+ } else {
+ n += 5;
+ }
+ return n;
+}
+} // namespace
+
+std::string format_durationf(uint64_t ns) {
+ static constexpr const std::string_view units[] = {"us"sv, "ms"sv, "s"sv};
+ if (ns < 1000) {
+ return format_uint(ns) + "ns";
+ }
+ auto unit = 0;
+ if (ns < 1000000) {
+ // do nothing
+ } else if (ns < 1000000000) {
+ ns /= 1000;
+ unit = 1;
+ } else {
+ ns /= 1000000;
+ unit = 2;
+ }
+
+ ns = round2even(ns);
+
+ if (ns / 1000 >= 1000 && unit < 2) {
+ ns /= 1000;
+ ++unit;
+ }
+
+ auto res = format_uint(ns / 1000);
+ res += format_fraction2(ns % 1000);
+ res += units[unit];
+
+ return res;
+}
+
+std::mt19937 make_mt19937() {
+ std::random_device rd;
+ return std::mt19937(rd());
+}
+
+ngtcp2_tstamp timestamp(struct ev_loop *loop) {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count();
+}
+
+bool numeric_host(const char *hostname) {
+ return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6);
+}
+
+bool numeric_host(const char *hostname, int family) {
+ int rv;
+ std::array<uint8_t, sizeof(struct in6_addr)> dst;
+
+ rv = inet_pton(family, hostname, dst.data());
+
+ return rv == 1;
+}
+
+namespace {
+void hexdump8(FILE *out, const uint8_t *first, const uint8_t *last) {
+ auto stop = std::min(first + 8, last);
+ for (auto k = first; k != stop; ++k) {
+ fprintf(out, "%02x ", *k);
+ }
+ // each byte needs 3 spaces (2 hex value and space)
+ for (; stop != first + 8; ++stop) {
+ fputs(" ", out);
+ }
+ // we have extra space after 8 bytes
+ fputc(' ', out);
+}
+} // namespace
+
+void hexdump(FILE *out, const uint8_t *src, size_t len) {
+ if (len == 0) {
+ return;
+ }
+ size_t buflen = 0;
+ auto repeated = false;
+ std::array<uint8_t, 16> buf{};
+ auto end = src + len;
+ auto i = src;
+ for (;;) {
+ auto nextlen =
+ std::min(static_cast<size_t>(16), static_cast<size_t>(end - i));
+ if (nextlen == buflen &&
+ std::equal(std::begin(buf), std::begin(buf) + buflen, i)) {
+ // as long as adjacent 16 bytes block are the same, we just
+ // print single '*'.
+ if (!repeated) {
+ repeated = true;
+ fputs("*\n", out);
+ }
+ i += nextlen;
+ continue;
+ }
+ repeated = false;
+ fprintf(out, "%08lx", static_cast<unsigned long>(i - src));
+ if (i == end) {
+ fputc('\n', out);
+ break;
+ }
+ fputs(" ", out);
+ hexdump8(out, i, end);
+ hexdump8(out, i + 8, std::max(i + 8, end));
+ fputc('|', out);
+ auto stop = std::min(i + 16, end);
+ buflen = stop - i;
+ auto p = buf.data();
+ for (; i != stop; ++i) {
+ *p++ = *i;
+ if (0x20 <= *i && *i <= 0x7e) {
+ fputc(*i, out);
+ } else {
+ fputc('.', out);
+ }
+ }
+ fputs("|\n", out);
+ }
+}
+
+std::string make_cid_key(const ngtcp2_cid *cid) {
+ return std::string(cid->data, cid->data + cid->datalen);
+}
+
+std::string make_cid_key(const uint8_t *cid, size_t cidlen) {
+ return std::string(cid, cid + cidlen);
+}
+
+std::string straddr(const sockaddr *sa, socklen_t salen) {
+ std::array<char, NI_MAXHOST> host;
+ std::array<char, NI_MAXSERV> port;
+
+ auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
+ port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rv != 0) {
+ std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
+ return "";
+ }
+ std::string res = "[";
+ res.append(host.data(), strlen(host.data()));
+ res += "]:";
+ res.append(port.data(), strlen(port.data()));
+ return res;
+}
+
+std::string_view strccalgo(ngtcp2_cc_algo cc_algo) {
+ switch (cc_algo) {
+ case NGTCP2_CC_ALGO_RENO:
+ return "reno"sv;
+ case NGTCP2_CC_ALGO_CUBIC:
+ return "cubic"sv;
+ case NGTCP2_CC_ALGO_BBR:
+ return "bbr"sv;
+ case NGTCP2_CC_ALGO_BBR2:
+ return "bbr2"sv;
+ default:
+ assert(0);
+ abort();
+ }
+}
+
+namespace {
+constexpr bool rws(char c) { return c == '\t' || c == ' '; }
+} // namespace
+
+std::optional<std::unordered_map<std::string, std::string>>
+read_mime_types(const std::string_view &filename) {
+ std::ifstream f(filename.data());
+ if (!f) {
+ return {};
+ }
+
+ std::unordered_map<std::string, std::string> dest;
+
+ std::string line;
+ while (std::getline(f, line)) {
+ if (line.empty() || line[0] == '#') {
+ continue;
+ }
+
+ auto p = std::find_if(std::begin(line), std::end(line), rws);
+ if (p == std::begin(line) || p == std::end(line)) {
+ continue;
+ }
+
+ auto media_type = std::string{std::begin(line), p};
+ for (;;) {
+ auto ext = std::find_if_not(p, std::end(line), rws);
+ if (ext == std::end(line)) {
+ break;
+ }
+
+ p = std::find_if(ext, std::end(line), rws);
+ dest.emplace(std::string{ext, p}, media_type);
+ }
+ }
+
+ return dest;
+}
+
+std::string format_duration(ngtcp2_duration n) {
+ if (n >= 3600 * NGTCP2_SECONDS && (n % (3600 * NGTCP2_SECONDS)) == 0) {
+ return format_uint(n / (3600 * NGTCP2_SECONDS)) + 'h';
+ }
+ if (n >= 60 * NGTCP2_SECONDS && (n % (60 * NGTCP2_SECONDS)) == 0) {
+ return format_uint(n / (60 * NGTCP2_SECONDS)) + 'm';
+ }
+ if (n >= NGTCP2_SECONDS && (n % NGTCP2_SECONDS) == 0) {
+ return format_uint(n / NGTCP2_SECONDS) + 's';
+ }
+ if (n >= NGTCP2_MILLISECONDS && (n % NGTCP2_MILLISECONDS) == 0) {
+ return format_uint(n / NGTCP2_MILLISECONDS) + "ms";
+ }
+ if (n >= NGTCP2_MICROSECONDS && (n % NGTCP2_MICROSECONDS) == 0) {
+ return format_uint(n / NGTCP2_MICROSECONDS) + "us";
+ }
+ return format_uint(n) + "ns";
+}
+
+namespace {
+std::optional<std::pair<uint64_t, size_t>>
+parse_uint_internal(const std::string_view &s) {
+ uint64_t res = 0;
+
+ if (s.empty()) {
+ return {};
+ }
+
+ for (size_t i = 0; i < s.size(); ++i) {
+ auto c = s[i];
+ if (c < '0' || '9' < c) {
+ return {{res, i}};
+ }
+
+ auto d = c - '0';
+ if (res > (std::numeric_limits<uint64_t>::max() - d) / 10) {
+ return {};
+ }
+
+ res *= 10;
+ res += d;
+ }
+
+ return {{res, s.size()}};
+}
+} // namespace
+
+std::optional<uint64_t> parse_uint(const std::string_view &s) {
+ auto o = parse_uint_internal(s);
+ if (!o) {
+ return {};
+ }
+ auto [res, idx] = *o;
+ if (idx != s.size()) {
+ return {};
+ }
+ return res;
+}
+
+std::optional<uint64_t> parse_uint_iec(const std::string_view &s) {
+ auto o = parse_uint_internal(s);
+ if (!o) {
+ return {};
+ }
+ auto [res, idx] = *o;
+ if (idx == s.size()) {
+ return res;
+ }
+ if (idx + 1 != s.size()) {
+ return {};
+ }
+
+ uint64_t m;
+ switch (s[idx]) {
+ case 'G':
+ case 'g':
+ m = 1 << 30;
+ break;
+ case 'M':
+ case 'm':
+ m = 1 << 20;
+ break;
+ case 'K':
+ case 'k':
+ m = 1 << 10;
+ break;
+ default:
+ return {};
+ }
+
+ if (res > std::numeric_limits<uint64_t>::max() / m) {
+ return {};
+ }
+
+ return res * m;
+}
+
+std::optional<uint64_t> parse_duration(const std::string_view &s) {
+ auto o = parse_uint_internal(s);
+ if (!o) {
+ return {};
+ }
+ auto [res, idx] = *o;
+ if (idx == s.size()) {
+ return res * NGTCP2_SECONDS;
+ }
+
+ uint64_t m;
+ if (idx + 1 == s.size()) {
+ switch (s[idx]) {
+ case 'H':
+ case 'h':
+ m = 3600 * NGTCP2_SECONDS;
+ break;
+ case 'M':
+ case 'm':
+ m = 60 * NGTCP2_SECONDS;
+ break;
+ case 'S':
+ case 's':
+ m = NGTCP2_SECONDS;
+ break;
+ default:
+ return {};
+ }
+ } else if (idx + 2 == s.size() && (s[idx + 1] == 's' || s[idx + 1] == 'S')) {
+ switch (s[idx]) {
+ case 'M':
+ case 'm':
+ m = NGTCP2_MILLISECONDS;
+ break;
+ case 'U':
+ case 'u':
+ m = NGTCP2_MICROSECONDS;
+ break;
+ case 'N':
+ case 'n':
+ return res;
+ default:
+ return {};
+ }
+ } else {
+ return {};
+ }
+
+ if (res > std::numeric_limits<uint64_t>::max() / m) {
+ return {};
+ }
+
+ return res * m;
+}
+
+namespace {
+template <typename InputIt> InputIt eat_file(InputIt first, InputIt last) {
+ if (first == last) {
+ *first++ = '/';
+ return first;
+ }
+
+ if (*(last - 1) == '/') {
+ return last;
+ }
+
+ auto p = last;
+ for (; p != first && *(p - 1) != '/'; --p)
+ ;
+ if (p == first) {
+ // this should not happened in normal case, where we expect path
+ // starts with '/'
+ *first++ = '/';
+ return first;
+ }
+
+ return p;
+}
+} // namespace
+
+namespace {
+template <typename InputIt> InputIt eat_dir(InputIt first, InputIt last) {
+ auto p = eat_file(first, last);
+
+ --p;
+
+ assert(*p == '/');
+
+ return eat_file(first, p);
+}
+} // namespace
+
+std::string normalize_path(const std::string_view &path) {
+ assert(path.size() <= 1024);
+ assert(path.size() > 0);
+ assert(path[0] == '/');
+
+ std::array<char, 1024> res;
+ auto p = res.data();
+
+ auto first = std::begin(path);
+ auto last = std::end(path);
+
+ *p++ = '/';
+ ++first;
+ for (; first != last && *first == '/'; ++first)
+ ;
+
+ for (; first != last;) {
+ if (*first == '.') {
+ if (first + 1 == last) {
+ break;
+ }
+ if (*(first + 1) == '/') {
+ first += 2;
+ continue;
+ }
+ if (*(first + 1) == '.') {
+ if (first + 2 == last) {
+ p = eat_dir(res.data(), p);
+ break;
+ }
+ if (*(first + 2) == '/') {
+ p = eat_dir(res.data(), p);
+ first += 3;
+ continue;
+ }
+ }
+ }
+ if (*(p - 1) != '/') {
+ p = eat_file(res.data(), p);
+ }
+ auto slash = std::find(first, last, '/');
+ if (slash == last) {
+ p = std::copy(first, last, p);
+ break;
+ }
+ p = std::copy(first, slash + 1, p);
+ first = slash + 1;
+ for (; first != last && *first == '/'; ++first)
+ ;
+ }
+ return std::string{res.data(), p};
+}
+
+int make_socket_nonblocking(int fd) {
+ int rv;
+ int flags;
+
+ while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
+ ;
+ if (flags == -1) {
+ return -1;
+ }
+
+ while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
+ ;
+
+ return rv;
+}
+
+int create_nonblock_socket(int domain, int type, int protocol) {
+#ifdef SOCK_NONBLOCK
+ auto fd = socket(domain, type | SOCK_NONBLOCK, protocol);
+ if (fd == -1) {
+ return -1;
+ }
+#else // !SOCK_NONBLOCK
+ auto fd = socket(domain, type, protocol);
+ if (fd == -1) {
+ return -1;
+ }
+
+ make_socket_nonblocking(fd);
+#endif // !SOCK_NONBLOCK
+
+ return fd;
+}
+
+std::vector<std::string_view> split_str(const std::string_view &s, char delim) {
+ size_t len = 1;
+ auto last = std::end(s);
+ std::string_view::const_iterator d;
+ for (auto first = std::begin(s); (d = std::find(first, last, delim)) != last;
+ ++len, first = d + 1)
+ ;
+
+ auto list = std::vector<std::string_view>(len);
+
+ len = 0;
+ for (auto first = std::begin(s);; ++len) {
+ auto stop = std::find(first, last, delim);
+ // xcode clang does not understand std::string_view{first, stop}.
+ list[len] = std::string_view{first, static_cast<size_t>(stop - first)};
+ if (stop == last) {
+ break;
+ }
+ first = stop + 1;
+ }
+ return list;
+}
+
+std::optional<uint32_t> parse_version(const std::string_view &s) {
+ auto k = s;
+ if (!util::istarts_with(k, "0x"sv)) {
+ return {};
+ }
+ k = k.substr(2);
+ uint32_t v;
+ auto rv = std::from_chars(k.data(), k.data() + k.size(), v, 16);
+ if (rv.ptr != k.data() + k.size() || rv.ec != std::errc{}) {
+ return {};
+ }
+
+ return v;
+}
+
+} // namespace util
+
+std::ostream &operator<<(std::ostream &os, const ngtcp2_cid &cid) {
+ return os << "0x" << util::format_hex(cid.data, cid.datalen);
+}
+
+} // namespace ngtcp2
diff --git a/examples/util.h b/examples/util.h
new file mode 100644
index 0000000..c83449d
--- /dev/null
+++ b/examples/util.h
@@ -0,0 +1,361 @@
+/*
+ * 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 UTIL_H
+#define UTIL_H
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <sys/socket.h>
+
+#include <optional>
+#include <string>
+#include <random>
+#include <unordered_map>
+#include <string_view>
+
+#include <ngtcp2/ngtcp2.h>
+#include <nghttp3/nghttp3.h>
+
+#include <ev.h>
+
+namespace ngtcp2 {
+
+namespace util {
+
+inline nghttp3_nv make_nv(const std::string_view &name,
+ const std::string_view &value, uint8_t flags) {
+ return nghttp3_nv{
+ reinterpret_cast<uint8_t *>(const_cast<char *>(std::data(name))),
+ reinterpret_cast<uint8_t *>(const_cast<char *>(std::data(value))),
+ name.size(),
+ value.size(),
+ flags,
+ };
+}
+
+inline nghttp3_nv make_nv_cc(const std::string_view &name,
+ const std::string_view &value) {
+ return make_nv(name, value, NGHTTP3_NV_FLAG_NONE);
+}
+
+inline nghttp3_nv make_nv_nc(const std::string_view &name,
+ const std::string_view &value) {
+ return make_nv(name, value, NGHTTP3_NV_FLAG_NO_COPY_NAME);
+}
+
+inline nghttp3_nv make_nv_nn(const std::string_view &name,
+ const std::string_view &value) {
+ return make_nv(name, value,
+ NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE);
+}
+
+std::string format_hex(uint8_t c);
+
+std::string format_hex(const uint8_t *s, size_t len);
+
+std::string format_hex(const std::string_view &s);
+
+template <size_t N> std::string format_hex(const uint8_t (&s)[N]) {
+ return format_hex(s, N);
+}
+
+std::string decode_hex(const std::string_view &s);
+
+// format_durationf formats |ns| in human readable manner. |ns| must
+// be nanoseconds resolution. This function uses the largest unit so
+// that the integral part is strictly more than zero, and the
+// precision is at most 2 digits. For example, 1234 is formatted as
+// "1.23us". The largest unit is seconds.
+std::string format_durationf(uint64_t ns);
+
+std::mt19937 make_mt19937();
+
+ngtcp2_tstamp timestamp(struct ev_loop *loop);
+
+bool numeric_host(const char *hostname);
+
+bool numeric_host(const char *hostname, int family);
+
+// Dumps |src| of length |len| in the format similar to `hexdump -C`.
+void hexdump(FILE *out, const uint8_t *src, size_t len);
+
+inline char lowcase(char c) {
+ constexpr static unsigned char tbl[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
+ 'z', 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+ 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+ 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+ 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
+ 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+ 255,
+ };
+ return tbl[static_cast<unsigned char>(c)];
+}
+
+struct CaseCmp {
+ bool operator()(char lhs, char rhs) const {
+ return lowcase(lhs) == lowcase(rhs);
+ }
+};
+
+template <typename InputIterator1, typename InputIterator2>
+bool istarts_with(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2) {
+ if (last1 - first1 < last2 - first2) {
+ return false;
+ }
+ return std::equal(first2, last2, first1, CaseCmp());
+}
+
+template <typename S, typename T> bool istarts_with(const S &a, const T &b) {
+ return istarts_with(a.begin(), a.end(), b.begin(), b.end());
+}
+
+// make_cid_key returns the key for |cid|.
+std::string make_cid_key(const ngtcp2_cid *cid);
+std::string make_cid_key(const uint8_t *cid, size_t cidlen);
+
+// straddr stringifies |sa| of length |salen| in a format "[IP]:PORT".
+std::string straddr(const sockaddr *sa, socklen_t salen);
+
+// strccalgo stringifies |cc_algo|.
+std::string_view strccalgo(ngtcp2_cc_algo cc_algo);
+
+template <typename T, size_t N>
+bool streq_l(const T (&a)[N], const nghttp3_vec &b) {
+ return N - 1 == b.len && memcmp(a, b.base, N - 1) == 0;
+}
+
+namespace {
+constexpr char B64_CHARS[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+};
+} // namespace
+
+template <typename InputIt> std::string b64encode(InputIt first, InputIt last) {
+ std::string res;
+ size_t len = last - first;
+ if (len == 0) {
+ return res;
+ }
+ size_t r = len % 3;
+ res.resize((len + 2) / 3 * 4);
+ auto j = last - r;
+ auto p = std::begin(res);
+ while (first != j) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ n += static_cast<uint8_t>(*first++);
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = B64_CHARS[n & 0x3fu];
+ }
+
+ if (r == 2) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ n += static_cast<uint8_t>(*first++) << 8;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = B64_CHARS[(n >> 6) & 0x3fu];
+ *p++ = '=';
+ } else if (r == 1) {
+ uint32_t n = static_cast<uint8_t>(*first++) << 16;
+ *p++ = B64_CHARS[n >> 18];
+ *p++ = B64_CHARS[(n >> 12) & 0x3fu];
+ *p++ = '=';
+ *p++ = '=';
+ }
+ return res;
+}
+
+// read_mime_types reads "MIME media types and the extensions" file
+// denoted by |filename| and returns the mapping of extension to MIME
+// media type.
+std::optional<std::unordered_map<std::string, std::string>>
+read_mime_types(const std::string_view &filename);
+
+// format_uint converts |n| into string.
+template <typename T> std::string format_uint(T n) {
+ std::string res;
+ if (n == 0) {
+ res = "0";
+ return res;
+ }
+ size_t nlen = 0;
+ for (auto t = n; t; t /= 10, ++nlen)
+ ;
+ res.resize(nlen);
+ for (; n; n /= 10) {
+ res[--nlen] = (n % 10) + '0';
+ }
+ return res;
+}
+
+// format_uint_iec converts |n| into string with the IEC unit (either
+// "G", "M", or "K"). It chooses the largest unit which does not drop
+// precision.
+template <typename T> std::string format_uint_iec(T n) {
+ if (n >= (1 << 30) && (n & ((1 << 30) - 1)) == 0) {
+ return format_uint(n / (1 << 30)) + 'G';
+ }
+ if (n >= (1 << 20) && (n & ((1 << 20) - 1)) == 0) {
+ return format_uint(n / (1 << 20)) + 'M';
+ }
+ if (n >= (1 << 10) && (n & ((1 << 10) - 1)) == 0) {
+ return format_uint(n / (1 << 10)) + 'K';
+ }
+ return format_uint(n);
+}
+
+// format_duration converts |n| into string with the unit in either
+// "h" (hours), "m" (minutes), "s" (seconds), "ms" (milliseconds),
+// "us" (microseconds) or "ns" (nanoseconds). It chooses the largest
+// unit which does not drop precision. |n| is in nanosecond
+// resolution.
+std::string format_duration(ngtcp2_duration n);
+
+// parse_uint parses |s| as 64-bit unsigned integer. If it cannot
+// parse |s|, the return value does not contain a value.
+std::optional<uint64_t> parse_uint(const std::string_view &s);
+
+// parse_uint_iec parses |s| as 64-bit unsigned integer. It accepts
+// IEC unit letter (either "G", "M", or "K") in |s|. If it cannot
+// parse |s|, the return value does not contain a value.
+std::optional<uint64_t> parse_uint_iec(const std::string_view &s);
+
+// parse_duration parses |s| as 64-bit unsigned integer. It accepts a
+// unit (either "h", "m", "s", "ms", "us", or "ns") in |s|. If no
+// unit is present, the unit "s" is assumed. If it cannot parse |s|,
+// the return value does not contain a value.
+std::optional<uint64_t> parse_duration(const std::string_view &s);
+
+// generate_secure_random generates a cryptographically secure pseudo
+// random data of |datalen| bytes and stores to the buffer pointed by
+// |data|.
+int generate_secure_random(uint8_t *data, size_t datalen);
+
+// generate_secret generates secret and writes it to the buffer
+// pointed by |secret| of length |secretlen|. Currently, |secretlen|
+// must be 32.
+int generate_secret(uint8_t *secret, size_t secretlen);
+
+// normalize_path removes ".." by consuming a previous path component.
+// It also removes ".". It assumes that |path| starts with "/". If
+// it cannot consume a previous path component, it just removes "..".
+std::string normalize_path(const std::string_view &path);
+
+constexpr bool is_digit(const char c) { return '0' <= c && c <= '9'; }
+
+constexpr bool is_hex_digit(const char c) {
+ return is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
+}
+
+// Returns integer corresponding to hex notation |c|. If
+// is_hex_digit(c) is false, it returns 256.
+constexpr uint32_t hex_to_uint(char c) {
+ if (c <= '9') {
+ return c - '0';
+ }
+ if (c <= 'Z') {
+ return c - 'A' + 10;
+ }
+ if (c <= 'z') {
+ return c - 'a' + 10;
+ }
+ return 256;
+}
+
+template <typename InputIt>
+std::string percent_decode(InputIt first, InputIt last) {
+ std::string result;
+ result.resize(last - first);
+ auto p = std::begin(result);
+ for (; first != last; ++first) {
+ if (*first != '%') {
+ *p++ = *first;
+ continue;
+ }
+
+ if (first + 1 != last && first + 2 != last && is_hex_digit(*(first + 1)) &&
+ is_hex_digit(*(first + 2))) {
+ *p++ = (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
+ first += 2;
+ continue;
+ }
+
+ *p++ = *first;
+ }
+ result.resize(p - std::begin(result));
+ return result;
+}
+
+int make_socket_nonblocking(int fd);
+
+int create_nonblock_socket(int domain, int type, int protocol);
+
+std::optional<std::string> read_token(const std::string_view &filename);
+int write_token(const std::string_view &filename, const uint8_t *token,
+ size_t tokenlen);
+
+const char *crypto_default_ciphers();
+
+const char *crypto_default_groups();
+
+// split_str parses delimited strings in |s| and returns substrings
+// delimited by |delim|. The any white spaces around substring are
+// treated as a part of substring.
+std::vector<std::string_view> split_str(const std::string_view &s,
+ char delim = ',');
+
+// parse_version parses |s| to get 4 byte QUIC version. |s| must be a
+// hex string and must start with "0x" (.e.g, 0x00000001).
+std::optional<uint32_t> parse_version(const std::string_view &s);
+
+} // namespace util
+
+std::ostream &operator<<(std::ostream &os, const ngtcp2_cid &cid);
+
+} // namespace ngtcp2
+
+#endif // UTIL_H
diff --git a/examples/util_gnutls.cc b/examples/util_gnutls.cc
new file mode 100644
index 0000000..a33ca6e
--- /dev/null
+++ b/examples/util_gnutls.cc
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+#include "util.h"
+
+#include <cassert>
+#include <iostream>
+#include <fstream>
+#include <array>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include <gnutls/crypto.h>
+
+#include "template.h"
+
+// Based on https://github.com/ueno/ngtcp2-gnutls-examples
+
+namespace ngtcp2 {
+
+namespace util {
+
+int generate_secure_random(uint8_t *data, size_t datalen) {
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, data, datalen) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_secret(uint8_t *secret, size_t secretlen) {
+ std::array<uint8_t, 16> rand;
+ std::array<uint8_t, 32> md;
+
+ assert(md.size() == secretlen);
+
+ if (generate_secure_random(rand.data(), rand.size()) != 0) {
+ return -1;
+ }
+
+ if (gnutls_hash_fast(GNUTLS_DIG_SHA256, rand.data(), rand.size(),
+ md.data()) != 0) {
+ return -1;
+ }
+
+ std::copy_n(std::begin(md), secretlen, secret);
+ return 0;
+}
+
+std::optional<std::string> read_token(const std::string_view &filename) {
+ auto f = std::ifstream(filename.data());
+ if (!f) {
+ std::cerr << "Could not read token file " << filename << std::endl;
+ return {};
+ }
+
+ auto pos = f.tellg();
+ std::vector<char> content(pos);
+ f.seekg(0, std::ios::beg);
+ f.read(content.data(), pos);
+
+ gnutls_datum_t s;
+ s.data = reinterpret_cast<unsigned char *>(content.data());
+ s.size = content.size();
+
+ gnutls_datum_t d;
+ if (auto rv = gnutls_pem_base64_decode2("QUIC TOKEN", &s, &d); rv < 0) {
+ std::cerr << "Could not read token in " << filename << std::endl;
+ return {};
+ }
+
+ auto res = std::string{d.data, d.data + d.size};
+
+ gnutls_free(d.data);
+
+ return res;
+}
+
+int write_token(const std::string_view &filename, const uint8_t *token,
+ size_t tokenlen) {
+ auto f = std::ofstream(filename.data());
+ if (!f) {
+ std::cerr << "Could not write token in " << filename << std::endl;
+ return -1;
+ }
+
+ gnutls_datum_t s;
+ s.data = const_cast<uint8_t *>(token);
+ s.size = tokenlen;
+
+ gnutls_datum_t d;
+ if (auto rv = gnutls_pem_base64_encode2("QUIC TOKEN", &s, &d); rv < 0) {
+ std::cerr << "Could not encode token in " << filename << std::endl;
+ return -1;
+ }
+
+ f.write(reinterpret_cast<const char *>(d.data), d.size);
+ gnutls_free(d.data);
+
+ return 0;
+}
+
+const char *crypto_default_ciphers() {
+ return "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:"
+ "+CHACHA20-POLY1305:+AES-128-CCM";
+}
+
+const char *crypto_default_groups() {
+ return "-GROUP-ALL:+GROUP-X25519:+GROUP-SECP256R1:+GROUP-SECP384R1:"
+ "+GROUP-SECP521R1";
+}
+
+} // namespace util
+
+} // namespace ngtcp2
diff --git a/examples/util_openssl.cc b/examples/util_openssl.cc
new file mode 100644
index 0000000..116505b
--- /dev/null
+++ b/examples/util_openssl.cc
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+#include "util.h"
+
+#include <cassert>
+#include <iostream>
+#include <array>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+#include "template.h"
+
+namespace ngtcp2 {
+
+namespace util {
+
+int generate_secure_random(uint8_t *data, size_t datalen) {
+ if (RAND_bytes(data, static_cast<int>(datalen)) != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_secret(uint8_t *secret, size_t secretlen) {
+ std::array<uint8_t, 16> rand;
+ std::array<uint8_t, 32> md;
+
+ assert(md.size() == secretlen);
+
+ if (generate_secure_random(rand.data(), rand.size()) != 0) {
+ return -1;
+ }
+
+ auto ctx = EVP_MD_CTX_new();
+ if (ctx == nullptr) {
+ return -1;
+ }
+
+ auto ctx_deleter = defer(EVP_MD_CTX_free, ctx);
+
+ unsigned int mdlen = md.size();
+ if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) ||
+ !EVP_DigestUpdate(ctx, rand.data(), rand.size()) ||
+ !EVP_DigestFinal_ex(ctx, md.data(), &mdlen)) {
+ return -1;
+ }
+
+ std::copy_n(std::begin(md), secretlen, secret);
+ return 0;
+}
+
+std::optional<std::string> read_token(const std::string_view &filename) {
+ auto f = BIO_new_file(filename.data(), "r");
+ if (f == nullptr) {
+ std::cerr << "Could not open token file " << filename << std::endl;
+ return {};
+ }
+
+ auto f_d = defer(BIO_free, f);
+
+ char *name, *header;
+ unsigned char *data;
+ long datalen;
+ std::string token;
+ if (PEM_read_bio(f, &name, &header, &data, &datalen) != 1) {
+ std::cerr << "Could not read token file " << filename << std::endl;
+ return {};
+ }
+
+ OPENSSL_free(name);
+ OPENSSL_free(header);
+
+ auto res = std::string{data, data + datalen};
+
+ OPENSSL_free(data);
+
+ return res;
+}
+
+int write_token(const std::string_view &filename, const uint8_t *token,
+ size_t tokenlen) {
+ auto f = BIO_new_file(filename.data(), "w");
+ if (f == nullptr) {
+ std::cerr << "Could not write token in " << filename << std::endl;
+ return -1;
+ }
+
+ PEM_write_bio(f, "QUIC TOKEN", "", token, tokenlen);
+ BIO_free(f);
+
+ return 0;
+}
+
+const char *crypto_default_ciphers() {
+ return "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_"
+ "SHA256:TLS_AES_128_CCM_SHA256";
+}
+
+const char *crypto_default_groups() { return "X25519:P-256:P-384:P-521"; }
+
+} // namespace util
+
+} // namespace ngtcp2
diff --git a/examples/util_test.cc b/examples/util_test.cc
new file mode 100644
index 0000000..d59aab2
--- /dev/null
+++ b/examples/util_test.cc
@@ -0,0 +1,237 @@
+/*
+ * 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 "util_test.h"
+
+#include <limits>
+
+#include <CUnit/CUnit.h>
+
+#include "util.h"
+
+namespace ngtcp2 {
+
+void test_util_format_durationf() {
+ CU_ASSERT("0ns" == util::format_durationf(0));
+ CU_ASSERT("999ns" == util::format_durationf(999));
+ CU_ASSERT("1.00us" == util::format_durationf(1000));
+ CU_ASSERT("1.00us" == util::format_durationf(1004));
+ CU_ASSERT("1.00us" == util::format_durationf(1005));
+ CU_ASSERT("1.02us" == util::format_durationf(1015));
+ CU_ASSERT("2.00us" == util::format_durationf(1999));
+ CU_ASSERT("1.00ms" == util::format_durationf(999999));
+ CU_ASSERT("3.50ms" == util::format_durationf(3500111));
+ CU_ASSERT("9999.99s" == util::format_durationf(9999990000000llu));
+}
+
+void test_util_format_uint() {
+ CU_ASSERT("0" == util::format_uint(0));
+ CU_ASSERT("18446744073709551615" ==
+ util::format_uint(18446744073709551615ull));
+}
+
+void test_util_format_uint_iec() {
+ CU_ASSERT("0" == util::format_uint_iec(0));
+ CU_ASSERT("1023" == util::format_uint_iec((1 << 10) - 1));
+ CU_ASSERT("1K" == util::format_uint_iec(1 << 10));
+ CU_ASSERT("1M" == util::format_uint_iec(1 << 20));
+ CU_ASSERT("1G" == util::format_uint_iec(1 << 30));
+ CU_ASSERT("18446744073709551615" ==
+ util::format_uint_iec(std::numeric_limits<uint64_t>::max()));
+ CU_ASSERT("1025K" == util::format_uint_iec((1 << 20) + (1 << 10)));
+}
+
+void test_util_format_duration() {
+ CU_ASSERT("0ns" == util::format_duration(0));
+ CU_ASSERT("999ns" == util::format_duration(999));
+ CU_ASSERT("1us" == util::format_duration(1000));
+ CU_ASSERT("1ms" == util::format_duration(1000000));
+ CU_ASSERT("1s" == util::format_duration(1000000000));
+ CU_ASSERT("1m" == util::format_duration(60000000000ull));
+ CU_ASSERT("1h" == util::format_duration(3600000000000ull));
+ CU_ASSERT("18446744073709551615ns" ==
+ util::format_duration(std::numeric_limits<uint64_t>::max()));
+ CU_ASSERT("61s" == util::format_duration(61000000000ull));
+}
+
+void test_util_parse_uint() {
+ {
+ auto res = util::parse_uint("0");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(0 == *res);
+ }
+ {
+ auto res = util::parse_uint("1");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(1 == *res);
+ }
+ {
+ auto res = util::parse_uint("18446744073709551615");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(18446744073709551615ull == *res);
+ }
+ {
+ auto res = util::parse_uint("18446744073709551616");
+ CU_ASSERT(!res.has_value());
+ }
+ {
+ auto res = util::parse_uint("a");
+ CU_ASSERT(!res.has_value());
+ }
+ {
+ auto res = util::parse_uint("1a");
+ CU_ASSERT(!res.has_value());
+ }
+}
+
+void test_util_parse_uint_iec() {
+ {
+ auto res = util::parse_uint_iec("0");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(0 == *res);
+ }
+ {
+ auto res = util::parse_uint_iec("1023");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(1023 == *res);
+ }
+ {
+ auto res = util::parse_uint_iec("1K");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(1 << 10 == *res);
+ }
+ {
+ auto res = util::parse_uint_iec("1M");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(1 << 20 == *res);
+ }
+ {
+ auto res = util::parse_uint_iec("1G");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(1 << 30 == *res);
+ }
+ {
+ auto res = util::parse_uint_iec("11G");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT((1ull << 30) * 11 == *res);
+ }
+ {
+ auto res = util::parse_uint_iec("18446744073709551616");
+ CU_ASSERT(!res.has_value());
+ }
+ {
+ auto res = util::parse_uint_iec("1x");
+ CU_ASSERT(!res.has_value());
+ }
+ {
+ auto res = util::parse_uint_iec("1Gx");
+ CU_ASSERT(!res.has_value());
+ }
+}
+
+void test_util_parse_duration() {
+ {
+ auto res = util::parse_duration("0");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(0 == *res);
+ }
+ {
+ auto res = util::parse_duration("1");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(NGTCP2_SECONDS == *res);
+ }
+ {
+ auto res = util::parse_duration("0ns");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(0 == *res);
+ }
+ {
+ auto res = util::parse_duration("1ns");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(1 == *res);
+ }
+ {
+ auto res = util::parse_duration("1us");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(NGTCP2_MICROSECONDS == *res);
+ }
+ {
+ auto res = util::parse_duration("1ms");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(NGTCP2_MILLISECONDS == *res);
+ }
+ {
+ auto res = util::parse_duration("1s");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(NGTCP2_SECONDS == *res);
+ }
+ {
+ auto res = util::parse_duration("1m");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(60 * NGTCP2_SECONDS == *res);
+ }
+ {
+ auto res = util::parse_duration("1h");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(3600 * NGTCP2_SECONDS == *res);
+ }
+ {
+ auto res = util::parse_duration("2h");
+ CU_ASSERT(res.has_value());
+ CU_ASSERT(2 * 3600 * NGTCP2_SECONDS == *res);
+ }
+ {
+ auto res = util::parse_duration("18446744073709551616");
+ CU_ASSERT(!res.has_value());
+ }
+ {
+ auto res = util::parse_duration("1x");
+ CU_ASSERT(!res.has_value());
+ }
+ {
+ auto res = util::parse_duration("1mx");
+ CU_ASSERT(!res.has_value());
+ }
+ {
+ auto res = util::parse_duration("1mxy");
+ CU_ASSERT(!res.has_value());
+ }
+}
+
+void test_util_normalize_path() {
+ CU_ASSERT("/" == util::normalize_path("/"));
+ CU_ASSERT("/" == util::normalize_path("//"));
+ CU_ASSERT("/foo" == util::normalize_path("/foo"));
+ CU_ASSERT("/foo/bar/" == util::normalize_path("/foo/bar/"));
+ CU_ASSERT("/foo/bar/" == util::normalize_path("/foo/abc/../bar/"));
+ CU_ASSERT("/foo/bar/" == util::normalize_path("/../foo/abc/../bar/"));
+ CU_ASSERT("/foo/bar/" ==
+ util::normalize_path("/./foo/././abc///.././bar/./"));
+ CU_ASSERT("/foo/" == util::normalize_path("/foo/."));
+ CU_ASSERT("/foo/bar" == util::normalize_path("/foo/./bar"));
+ CU_ASSERT("/bar" == util::normalize_path("/foo/./../bar"));
+ CU_ASSERT("/bar" == util::normalize_path("/../../bar"));
+}
+
+} // namespace ngtcp2
diff --git a/examples/util_test.h b/examples/util_test.h
new file mode 100644
index 0000000..376d3a9
--- /dev/null
+++ b/examples/util_test.h
@@ -0,0 +1,45 @@
+/*
+ * 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 UTIL_TEST_H
+#define UTIL_TEST_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif // HAVE_CONFIG_H
+
+namespace ngtcp2 {
+
+void test_util_format_durationf();
+void test_util_format_uint();
+void test_util_format_uint_iec();
+void test_util_format_duration();
+void test_util_parse_uint();
+void test_util_parse_uint_iec();
+void test_util_parse_duration();
+void test_util_normalize_path();
+
+} // namespace ngtcp2
+
+#endif // UTIL_TEST_H
diff --git a/examples/util_wolfssl.cc b/examples/util_wolfssl.cc
new file mode 100644
index 0000000..80eb096
--- /dev/null
+++ b/examples/util_wolfssl.cc
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+#include "util.h"
+
+#include <cassert>
+#include <iostream>
+#include <array>
+
+#include <ngtcp2/ngtcp2_crypto.h>
+
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/openssl/pem.h>
+
+#include "template.h"
+
+namespace ngtcp2 {
+
+namespace util {
+
+int generate_secure_random(uint8_t *data, size_t datalen) {
+ if (wolfSSL_RAND_bytes(data, static_cast<int>(datalen)) != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int generate_secret(uint8_t *secret, size_t secretlen) {
+ std::array<uint8_t, 16> rand;
+ std::array<uint8_t, 32> md;
+
+ assert(md.size() == secretlen);
+
+ if (generate_secure_random(rand.data(), rand.size()) != 0) {
+ return -1;
+ }
+
+ auto ctx = wolfSSL_EVP_MD_CTX_new();
+ if (ctx == nullptr) {
+ return -1;
+ }
+
+ unsigned int mdlen = md.size();
+ if (!wolfSSL_EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) ||
+ !wolfSSL_EVP_DigestUpdate(ctx, rand.data(), rand.size()) ||
+ !wolfSSL_EVP_DigestFinal_ex(ctx, md.data(), &mdlen)) {
+ wolfSSL_EVP_MD_CTX_free(ctx);
+ return -1;
+ }
+
+ std::copy_n(std::begin(md), secretlen, secret);
+ wolfSSL_EVP_MD_CTX_free(ctx);
+ return 0;
+}
+
+std::optional<std::string> read_token(const std::string_view &filename) {
+ auto f = wolfSSL_BIO_new_file(filename.data(), "r");
+ if (f == nullptr) {
+ std::cerr << "Could not open token file " << filename << std::endl;
+ return {};
+ }
+
+ char *name, *header;
+ unsigned char *data;
+ long datalen;
+ std::string token;
+ if (wolfSSL_PEM_read_bio(f, &name, &header, &data, &datalen) != 1) {
+ std::cerr << "Could not read token file " << filename << std::endl;
+ wolfSSL_BIO_free(f);
+ return {};
+ }
+ wolfSSL_BIO_free(f);
+
+ wolfSSL_OPENSSL_free(name);
+ wolfSSL_OPENSSL_free(header);
+
+ auto res = std::string{data, data + datalen};
+
+ wolfSSL_OPENSSL_free(data);
+
+ return res;
+}
+
+int write_token(const std::string_view &filename, const uint8_t *token,
+ size_t tokenlen) {
+ auto f = wolfSSL_BIO_new_file(filename.data(), "w");
+ if (f == nullptr) {
+ std::cerr << "Could not write token in " << filename << std::endl;
+ return -1;
+ }
+
+ wolfSSL_PEM_write_bio(f, "QUIC TOKEN", "", token, tokenlen);
+ wolfSSL_BIO_free(f);
+
+ return 0;
+}
+
+const char *crypto_default_ciphers() {
+ return "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_"
+ "SHA256:TLS_AES_128_CCM_SHA256";
+}
+
+const char *crypto_default_groups() { return "X25519:P-256:P-384:P-521"; }
+
+} // namespace util
+
+} // namespace ngtcp2