diff --git a/CMakeLists.txt b/CMakeLists.txt index 420ef82803c..41f8bf1e20b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -563,6 +563,15 @@ add_subdirectory(${FLB_PATH_LIB_CMETRICS} EXCLUDE_FROM_ALL) # CTraces add_subdirectory(${FLB_PATH_LIB_CTRACES} EXCLUDE_FROM_ALL) +# Nghttp2 options +FLB_OPTION(ENABLE_LIB_ONLY ON) +FLB_OPTION(ENABLE_STATIC_LIB ON) +FLB_OPTION(ENABLE_SHARED_LIB OFF) + +FLB_DEFINITION(NGHTTP2_STATICLIB) + +add_subdirectory(${FLB_PATH_LIB_NGHTTP2} EXCLUDE_FROM_ALL) + # C-Ares (DNS library) FLB_OPTION(CARES_STATIC ON) FLB_OPTION(CARES_SHARED OFF) diff --git a/cmake/headers.cmake b/cmake/headers.cmake old mode 100644 new mode 100755 index 3e814066c40..45a1394ca7f --- a/cmake/headers.cmake +++ b/cmake/headers.cmake @@ -46,6 +46,10 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR}/lib/monkey/include/ ${CMAKE_CURRENT_BINARY_DIR}/lib/monkey/include/monkey/ + + ${CMAKE_CURRENT_BINARY_DIR}/lib/nghttp2 + ${CMAKE_CURRENT_BINARY_DIR}/lib/nghttp2/lib/includes/ + ${FLB_PATH_ROOT_SOURCE}/${FLB_PATH_LIB_NGHTTP2}/lib/includes/ ) if(FLB_IN_KAFKA OR FLB_OUT_KAFKA) diff --git a/cmake/libraries.cmake b/cmake/libraries.cmake index 8ac71e7964a..37bfe24c4fd 100644 --- a/cmake/libraries.cmake +++ b/cmake/libraries.cmake @@ -7,6 +7,7 @@ set(FLB_PATH_LIB_CTRACES "lib/ctraces") set(FLB_PATH_LIB_CO "lib/flb_libco") set(FLB_PATH_LIB_RBTREE "lib/rbtree") set(FLB_PATH_LIB_MSGPACK "lib/msgpack-c") +set(FLB_PATH_LIB_NGHTTP2 "lib/nghttp2") set(FLB_PATH_LIB_AVRO "lib/avro") set(FLB_PATH_LIB_CHUNKIO "lib/chunkio") set(FLB_PATH_LIB_LUAJIT "lib/luajit-3065c9") diff --git a/include/fluent-bit/flb_http_client.h b/include/fluent-bit/flb_http_client.h index 674b91213c1..f843be77818 100644 --- a/include/fluent-bit/flb_http_client.h +++ b/include/fluent-bit/flb_http_client.h @@ -61,7 +61,7 @@ #define FLB_HTTP_HEADER_CONNECTION "Connection" #define FLB_HTTP_HEADER_KA "keep-alive" -struct flb_http_response { +struct flb_http_client_response { int status; /* HTTP response status */ int content_length; /* Content length set by headers */ int chunked_encoding; /* Chunked transfer encoding ? */ @@ -131,7 +131,7 @@ struct flb_http_client { struct flb_http_proxy proxy; /* Response */ - struct flb_http_response resp; + struct flb_http_client_response resp; /* Reference to Callback context */ void *cb_ctx; diff --git a/include/fluent-bit/flb_http_common.h b/include/fluent-bit/flb_http_common.h new file mode 100644 index 00000000000..e87cfe68db6 --- /dev/null +++ b/include/fluent-bit/flb_http_common.h @@ -0,0 +1,176 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_HTTP_COMMON +#define FLB_HTTP_COMMON + +#include + +#include +#include + +/* These definitions are temporary and should be moved + * to monkey. + * This fallback has been added to be able to merge this + * feature with the current monkey version. + */ + +#ifndef MK_HTTP_PROTOCOL_20 +#define MK_HTTP_PROTOCOL_20 (20) +#endif + +#ifndef MK_HTTP_PROTOCOL_20_STR +#define MK_HTTP_PROTOCOL_20_STR "HTTP/2" +#endif + +#define HTTP_PROTOCOL_AUTODETECT -1 +#define HTTP_PROTOCOL_HTTP0 0 +#define HTTP_PROTOCOL_HTTP1 1 +#define HTTP_PROTOCOL_HTTP2 2 + +#define HTTP_PROTOCOL_VERSION_09 MK_HTTP_PROTOCOL_09 +#define HTTP_PROTOCOL_VERSION_10 MK_HTTP_PROTOCOL_10 +#define HTTP_PROTOCOL_VERSION_11 MK_HTTP_PROTOCOL_11 +#define HTTP_PROTOCOL_VERSION_20 MK_HTTP_PROTOCOL_20 + +#define HTTP_METHOD_GET MK_METHOD_GET +#define HTTP_METHOD_POST MK_METHOD_POST +#define HTTP_METHOD_HEAD MK_METHOD_HEAD +#define HTTP_METHOD_PUT MK_METHOD_PUT +#define HTTP_METHOD_DELETE MK_METHOD_DELETE +#define HTTP_METHOD_OPTIONS MK_METHOD_OPTIONS +#define HTTP_METHOD_UNKNOWN MK_METHOD_UNKNOWN + +#define HTTP_STREAM_ROLE_SERVER 0 +#define HTTP_STREAM_ROLE_CLIENT 1 + +#define HTTP_STREAM_STATUS_RECEIVING_HEADERS 0 +#define HTTP_STREAM_STATUS_RECEIVING_DATA 1 +#define HTTP_STREAM_STATUS_READY 2 +#define HTTP_STREAM_STATUS_PROCESSING 3 +#define HTTP_STREAM_STATUS_CLOSED 4 +#define HTTP_STREAM_STATUS_ERROR 5 + +struct flb_http_stream; +struct flb_http_server_session; + +struct flb_http_request { + int protocol_version; + int method; + cfl_sds_t path; + cfl_sds_t host; + cfl_sds_t query_string; + struct flb_hash_table *headers; + size_t content_length; + char *content_type; + cfl_sds_t body; + + struct flb_http_stream *stream; + + struct cfl_list _head; +}; + +struct flb_http_response { + int status; + cfl_sds_t message; + struct flb_hash_table *headers; + struct flb_hash_table *trailer_headers; + size_t content_length; + cfl_sds_t body; + size_t body_read_offset; + + struct flb_http_stream *stream; +}; + +struct flb_http_stream { + int32_t id; + int role; + int status; + + struct flb_http_request request; + struct flb_http_response response; + + void *parent; + void *user_data; + + int releasable; + struct cfl_list _head; +}; + +/* HTTP REQUEST */ + +int flb_http_request_init(struct flb_http_request *request); + +void flb_http_request_destroy(struct flb_http_request *request); + +char *flb_http_request_get_header(struct flb_http_request *request, + char *name); + +int flb_http_request_set_header(struct flb_http_request *request, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http_request_unset_header(struct flb_http_request *request, + char *name); + +/* HTTP RESPONSE */ + +int flb_http_response_init(struct flb_http_response *response); + +void flb_http_response_destroy(struct flb_http_response *response); + +struct flb_http_response *flb_http_response_begin( + struct flb_http_server_session *session, + void *stream); + +int flb_http_response_commit(struct flb_http_response *response); + +int flb_http_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http_response_set_trailer_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http_response_set_status(struct flb_http_response *response, + int status); + +int flb_http_response_set_message(struct flb_http_response *response, + char *message); + +int flb_http_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length); + +/* HTTP STREAM */ + +int flb_http_stream_init(struct flb_http_stream *stream, + void *parent, + int32_t stream_id, + int role, + void *user_data); + +struct flb_http_stream *flb_http_stream_create(void *parent, + int32_t stream_id, + int role, + void *user_data); + +void flb_http_stream_destroy(struct flb_http_stream *stream); + +#endif diff --git a/include/fluent-bit/http_server/flb_http_server.h b/include/fluent-bit/http_server/flb_http_server.h new file mode 100755 index 00000000000..d4fb5d17e29 --- /dev/null +++ b/include/fluent-bit/http_server/flb_http_server.h @@ -0,0 +1,141 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_HTTP_SERVER +#define FLB_HTTP_SERVER + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#define HTTP_SERVER_INITIAL_BUFFER_SIZE (10 * 1024) +#define HTTP_SERVER_MAXIMUM_BUFFER_SIZE (10 * (1000 * 1024)) + +#define FLB_HTTP_SERVER_FLAG_KEEPALIVE (((uint64_t) 1) << 0) +#define FLB_HTTP_SERVER_FLAG_AUTO_INFLATE (((uint64_t) 1) << 1) + +#define HTTP_SERVER_SUCCESS 0 +#define HTTP_SERVER_PROVIDER_ERROR -1 +#define HTTP_SERVER_ALLOCATION_ERROR -2 + +#define HTTP_SERVER_UNINITIALIZED 0 +#define HTTP_SERVER_INITIALIZED 1 +#define HTTP_SERVER_RUNNING 2 +#define HTTP_SERVER_STOPPED 3 + +typedef int (*flb_http_server_request_processor_callback)( + struct flb_http_request *request, + struct flb_http_response *response); + +struct flb_http_server { + /* Internal */ + struct mk_event listener_event; + char *address; + unsigned short int port; + struct flb_tls *tls_provider; + int networking_flags; + struct flb_net_setup *networking_setup; + struct mk_event_loop *event_loop; + struct flb_config *system_context; + /* Internal */ + + uint64_t flags; + int status; + int protocol_version; + struct flb_downstream *downstream; + struct cfl_list clients; + flb_http_server_request_processor_callback + request_callback; + void *user_data; +}; + +struct flb_http_server_session { + struct flb_http1_server_session http1; + struct flb_http2_server_session http2; + + int version; + struct cfl_list request_queue; + + cfl_sds_t incoming_data; + cfl_sds_t outgoing_data; + + int releasable; + + struct flb_connection *connection; + struct flb_http_server *parent; + struct cfl_list _head; +}; + +/* COMMON */ + +char *flb_http_server_convert_string_to_lowercase(char *input_buffer, + size_t length); + +int flb_http_server_strncasecmp(const uint8_t *first_buffer, + size_t first_length, + const char *second_buffer, + size_t second_length); + +/* HTTP SERVER */ + +int flb_http_server_init(struct flb_http_server *session, + int protocol_version, + uint64_t flags, + flb_http_server_request_processor_callback + request_callback, + char *address, + unsigned short int port, + struct flb_tls *tls_provider, + int networking_flags, + struct flb_net_setup *networking_setup, + struct mk_event_loop *event_loop, + struct flb_config *system_context, + void *user_data); + +int flb_http_server_start(struct flb_http_server *session); + +int flb_http_server_stop(struct flb_http_server *session); + +int flb_http_server_destroy(struct flb_http_server *session); + +/* HTTP SESSION */ + +int flb_http_server_session_init(struct flb_http_server_session *session, int version); + +struct flb_http_server_session *flb_http_server_session_create(int version); + +void flb_http_server_session_destroy(struct flb_http_server_session *session); + +int flb_http_server_session_ingest(struct flb_http_server_session *session, + unsigned char *buffer, + size_t length); + +#endif diff --git a/include/fluent-bit/http_server/flb_http_server_http1.h b/include/fluent-bit/http_server/flb_http_server_http1.h new file mode 100644 index 00000000000..1f16d2424af --- /dev/null +++ b/include/fluent-bit/http_server/flb_http_server_http1.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_HTTP_SERVER_HTTP1 +#define FLB_HTTP_SERVER_HTTP1 + +#include +#include +#include + +struct flb_http_server_session; +struct flb_http_stream; + +struct flb_http1_server_session { + struct mk_http_session inner_session; + struct mk_http_request inner_request; + struct mk_http_parser inner_parser; + struct mk_server inner_server; + int initialized; + struct flb_http_stream stream; + struct flb_http_server_session *parent; +}; + +/* RESPONSE */ + +struct flb_http_response *flb_http1_response_begin( + struct flb_http1_server_session *session, + struct flb_http_stream *stream); + +int flb_http1_response_commit(struct flb_http_response *response); + +int flb_http1_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http1_response_set_status(struct flb_http_response *response, + int status); + +int flb_http1_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length); + +/* SESSION */ + +int flb_http1_server_session_init(struct flb_http1_server_session *session, + struct flb_http_server_session *parent); + +void flb_http1_server_session_destroy(struct flb_http1_server_session *session); + +int flb_http1_server_session_ingest(struct flb_http1_server_session *session, + unsigned char *buffer, + size_t length); + +#endif diff --git a/include/fluent-bit/http_server/flb_http_server_http2.h b/include/fluent-bit/http_server/flb_http_server_http2.h new file mode 100644 index 00000000000..f8225d348f3 --- /dev/null +++ b/include/fluent-bit/http_server/flb_http_server_http2.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_HTTP_SERVER_HTTP2 +#define FLB_HTTP_SERVER_HTTP2 + +#include + +#include + +struct flb_http_server_session; +struct flb_http_stream; + +struct flb_http2_server_session { + nghttp2_session *inner_session; + int initialized; + struct cfl_list streams; + struct flb_http_server_session *parent; +}; + +/* RESPONSE */ + +struct flb_http_response *flb_http2_response_begin( + struct flb_http2_server_session *session, + struct flb_http_stream *stream); + +int flb_http2_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length); + +int flb_http2_response_set_status(struct flb_http_response *response, + int status); + +int flb_http2_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length); + +int flb_http2_response_commit(struct flb_http_response *response); + +/* SESSION */ + +int flb_http2_server_session_init(struct flb_http2_server_session *session, + struct flb_http_server_session *parent); + +void flb_http2_server_session_destroy(struct flb_http2_server_session *session); + +int flb_http2_server_session_ingest(struct flb_http2_server_session *session, + unsigned char *buffer, + size_t length); + +#endif diff --git a/include/fluent-bit/tls/flb_tls.h b/include/fluent-bit/tls/flb_tls.h index d463361ce12..70a865f15cf 100644 --- a/include/fluent-bit/tls/flb_tls.h +++ b/include/fluent-bit/tls/flb_tls.h @@ -72,6 +72,9 @@ struct flb_tls_backend { /* destroy backend context */ void (*context_destroy) (void *); + /* Additional settings */ + int (*context_alpn_set) (void *, const char *); + /* Session management */ void *(*session_create) (struct flb_tls *, int); int (*session_destroy) (void *); @@ -106,6 +109,9 @@ struct flb_tls *flb_tls_create(int mode, const char *key_file, const char *key_passwd); int flb_tls_destroy(struct flb_tls *tls); + +int flb_tls_set_alpn(struct flb_tls *tls, const char *alpn); + int flb_tls_load_system_certificates(struct flb_tls *tls); struct mk_list *flb_tls_get_config_map(struct flb_config *config); diff --git a/lib/nghttp2/AUTHORS b/lib/nghttp2/AUTHORS new file mode 100644 index 00000000000..bb1ae74f12c --- /dev/null +++ b/lib/nghttp2/AUTHORS @@ -0,0 +1,155 @@ +nghttp2 project was started as a fork of spdylay project [1]. Both +projects were started by Tatsuhiro Tsujikawa, who is still the main +author of these projects. Meanwhile, we have many contributions, and +we are not here without them. We sincerely thank you to all who made +a contribution. Here is the all individuals/organizations who +contributed to nghttp2 and spdylay project at which we forked. These +names are retrieved from git commit log. If you have made a +contribution, but you are missing in the list, please let us know via +github issues [2]. + +[1] https://github.com/tatsuhiro-t/spdylay +[2] https://github.com/nghttp2/nghttp2/issues + +-------- + +187j3x1 +Adam Gołębiowski +Alek Storm +Alex Nalivko +Alexandr Vlasov +Alexandros Konstantinakis-Karmis +Alexis La Goutte +Amir Livneh +Amir Pakdel +Anders Bakken +Andreas Pohl +Andrew Penkrat +Andy Davies +Angus Gratton +Anna Henningsen +Ant Bryan +Asra Ali +Benedikt Christoph Wolters +Benjamin Peterson +Bernard Spil +Brendan Heinonen +Brian Card +Brian Suh +Daniel Bevenius +Daniel Evers +Daniel Stenberg +Dave Reisner +David Beitey +David Korczynski +David Weekly +Dimitris Apostolou +Dmitri Tikhonov +Dmitriy Vetutnev +Don +Dylan Plecki +Etienne Cimon +Fabian Möller +Fabian Wiesel +Fred Sundvik +Gabi Davar +Gaël PORTAY +Geoff Hill +George Liu +Gitai +Google Inc. +Hajime Fujita +Jacky Tian +Jacky_Yin +Jacob Champion +James M Snell +Jan Kundrát +Jan-E +Janusz Dziemidowicz +Jay Satiro +Jeff 'Raid' Baitis +Jianqing Wang +Jim Morrison +Josh Braegger +José F. Calcerrada +Kamil Dudka +Kazuho Oku +Kenny (kang-yen) Peng +Kenny Peng +Kit Chan +Kyle Schomp +LazyHamster +Leo Neat +Lorenz Nickel +Lucas Pardue +MATSUMOTO Ryosuke +Marc Bachmann +Marcelo Trylesinski +Matt Rudary +Matt Way +Michael Kaufmann +Mike Conlen +Mike Frysinger +Mike Lothian +Nicholas Hurley +Nora Shoemaker +Paweł Wegner +Pedro Santos +Peeyush Aggarwal +Peter Wu +Piotr Sikora +PufferOverflow +Raul Gutierrez Segales +Remo E +Renaud +Reza Tavakoli +Richard Wolfert +Rick Lei +Ross Smith II +Rudi Heitbaum +Ryo Ota +Scott Mitchell +Sebastiaan Deckers +Shelley Vohr +Simon Frankenberger +Simone Basso +Soham Sinha +Stefan Eissing +Stephen Ludin +Sunpoet Po-Chuan Hsieh +Svante Signell +Syohei YOSHIDA +Tapanito +Tatsuhiko Kubo +Tatsuhiro Tsujikawa +Tobias Geerinckx-Rice +Tom Harwood +Tomas Krizek +Tomasz Buchert +Tomasz Torcz +Vernon Tang +Viacheslav Biriukov +Viktor Szakats +Viktor Szépe +Wenfeng Liu +William A Rowe Jr +Xiaoguang Sun +Zhuoyun Wei +acesso +ayanamist +bxshi +clemahieu +dalf +dawg +es +fangdingjun +jwchoi +kumagi +lhuang04 +lstefani +makovich +mod-h2-dev +moparisthebest +robaho +snnn +yuuki-kodama diff --git a/lib/nghttp2/CMakeLists.txt b/lib/nghttp2/CMakeLists.txt new file mode 100644 index 00000000000..4681db75346 --- /dev/null +++ b/lib/nghttp2/CMakeLists.txt @@ -0,0 +1,497 @@ +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2012, 2013, 2014, 2015 Tatsuhiro Tsujikawa +# Copyright (c) 2016 Peter Wu +# +# 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. + +cmake_minimum_required(VERSION 3.0) +# XXX using 1.8.90 instead of 1.9.0-DEV +project(nghttp2 VERSION 1.58.90) + +# See versioning rule: +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +set(LT_CURRENT 39) +set(LT_REVISION 1) +set(LT_AGE 25) + +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +include(Version) + +math(EXPR LT_SOVERSION "${LT_CURRENT} - ${LT_AGE}") +set(LT_VERSION "${LT_SOVERSION}.${LT_AGE}.${LT_REVISION}") +set(PACKAGE_VERSION "${PROJECT_VERSION}") +HexVersion(PACKAGE_VERSION_NUM ${PROJECT_VERSION_MAJOR} ${PROJECT_VERSION_MINOR} ${PROJECT_VERSION_PATCH}) + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the build type" FORCE) + + # Include "None" as option to disable any additional (optimization) flags, + # relying on just CMAKE_C_FLAGS and CMAKE_CXX_FLAGS (which are empty by + # default). These strings are presented in cmake-gui. + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "None" "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() + +include(GNUInstallDirs) + +# This is necessary to ensure that we link openssl statically +set(OPENSSL_USE_STATIC_LIBS ON) +set(OPENSSL_MSVC_STATIC_RT ON) + +# For documentation +find_package(Python3 COMPONENTS Interpreter) + +# Auto-detection of features that can be toggled +find_package(OpenSSL 1.0.1) +find_package(Libev 4.11) +find_package(Libcares 1.7.5) +find_package(ZLIB 1.2.3) +find_package(Libngtcp2 1.0.0) +find_package(Libngtcp2_crypto_quictls 1.0.0) +if(LIBNGTCP2_CRYPTO_QUICTLS_FOUND) + set(HAVE_LIBNGTCP2_CRYPTO_QUICTLS 1) +endif() +find_package(Libnghttp3 1.1.0) +if(WITH_LIBBPF) + find_package(Libbpf 0.7.0) + if(NOT LIBBPF_FOUND) + message(FATAL_ERROR "libbpf was requested (WITH_LIBBPF=1) but not found.") + endif() +endif() +if(OPENSSL_FOUND AND LIBEV_FOUND AND ZLIB_FOUND) + set(ENABLE_APP_DEFAULT ON) +else() + set(ENABLE_APP_DEFAULT OFF) +endif() +find_package(Systemd 209) +find_package(Jansson 2.5) +set(ENABLE_HPACK_TOOLS_DEFAULT ${JANSSON_FOUND}) +# 2.0.8 is required because we use evconnlistener_set_error_cb() +find_package(Libevent 2.0.8 COMPONENTS core extra openssl) +set(ENABLE_EXAMPLES_DEFAULT ${LIBEVENT_OPENSSL_FOUND}) + +find_package(LibXml2 2.6.26) +set(WITH_LIBXML2_DEFAULT ${LIBXML2_FOUND}) +find_package(Jemalloc) +set(WITH_JEMALLOC_DEFAULT ${JEMALLOC_FOUND}) + +include(CMakeOptions.txt) + +if(ENABLE_LIB_ONLY AND (ENABLE_APP OR ENABLE_HPACK_TOOLS OR ENABLE_EXAMPLES)) + # Remember when disabled options are disabled for later diagnostics. + set(ENABLE_LIB_ONLY_DISABLED_OTHERS 1) +else() + set(ENABLE_LIB_ONLY_DISABLED_OTHERS 0) +endif() +if(ENABLE_LIB_ONLY) + set(ENABLE_APP OFF) + set(ENABLE_HPACK_TOOLS OFF) + set(ENABLE_EXAMPLES OFF) +endif() + +# Do not disable assertions based on CMAKE_BUILD_TYPE. +foreach(_build_type "Release" "MinSizeRel" "RelWithDebInfo") + foreach(_lang C CXX) + string(TOUPPER "CMAKE_${_lang}_FLAGS_${_build_type}" _var) + string(REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " " ${_var} "${${_var}}") + endforeach() +endforeach() + +if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") + set(HINT_NORETURN "__attribute__((noreturn))") +else() + set(HINT_NORETURN) +endif() + +include(ExtractValidFlags) +foreach(_cxx1x_flag -std=c++14) + extract_valid_cxx_flags(_cxx1x_flag_supported ${_cxx1x_flag}) + if(_cxx1x_flag_supported) + set(CXX1XCXXFLAGS ${_cxx1x_flag}) + break() + endif() +endforeach() + +include(CMakePushCheckState) +include(CheckCXXSourceCompiles) +cmake_push_check_state() +set(CMAKE_REQUIRED_DEFINITIONS "${CXX1XCXXFLAGS}") +# Check that std::future is available. +check_cxx_source_compiles(" +#include +#include +int main() { std::vector> v; }" HAVE_STD_FUTURE) +# Check that std::map::emplace is available for g++-4.7. +check_cxx_source_compiles(" +#include +int main() { std::map().emplace(1, 2); }" HAVE_STD_MAP_EMPLACE) +cmake_pop_check_state() + + +# Checks for libraries. +# Additional libraries required for programs under src directory. +set(APP_LIBRARIES) + +set(CMAKE_THREAD_PREFER_PTHREAD 1) +find_package(Threads) +if(CMAKE_USE_PTHREADS_INIT) + list(APPEND APP_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) +endif() +# XXX android and C++, is this still needed in cmake? +# case "$host" in +# *android*) +# android_build=yes +# # android does not need -pthread, but needs following 3 libs for C++ +# APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic -lsupc++" + +# dl: openssl requires libdl when it is statically linked. +# XXX shouldn't ${CMAKE_DL_LIBS} be appended to OPENSSL_LIBRARIES instead of +# APP_LIBRARIES if it is really specific to OpenSSL? + +find_package(CUnit 2.1) +enable_testing() +set(HAVE_CUNIT ${CUNIT_FOUND}) +if(HAVE_CUNIT) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}) +endif() + +# openssl (for src) +include(CheckSymbolExists) +set(HAVE_OPENSSL ${OPENSSL_FOUND}) +if(OPENSSL_FOUND) + set(OPENSSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + cmake_push_check_state() + set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") + set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") + if(WIN32) + set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}" "ws2_32" "bcrypt") + endif() + check_symbol_exists(SSL_provide_quic_data "openssl/ssl.h" HAVE_SSL_PROVIDE_QUIC_DATA) + if(NOT HAVE_SSL_PROVIDE_QUIC_DATA) + message(WARNING "OpenSSL in ${OPENSSL_LIBRARIES} does not have SSL_provide_quic_data. HTTP/3 support cannot be enabled") + endif() + cmake_pop_check_state() +else() + set(OPENSSL_INCLUDE_DIRS "") + set(OPENSSL_LIBRARIES "") +endif() +# libev (for src) +set(HAVE_LIBEV ${LIBEV_FOUND}) +set(HAVE_ZLIB ${ZLIB_FOUND}) +set(HAVE_SYSTEMD ${SYSTEMD_FOUND}) +set(HAVE_LIBEVENT_OPENSSL ${LIBEVENT_FOUND}) +if(LIBEVENT_FOUND) + # Must both link the core and openssl libraries. + set(LIBEVENT_OPENSSL_LIBRARIES ${LIBEVENT_LIBRARIES}) +endif() +# libc-ares (for src) +set(HAVE_LIBCARES ${LIBCARES_FOUND}) +if(LIBCARES_FOUND) + set(LIBCARES_INCLUDE_DIRS ${LIBCARES_INCLUDE_DIR}) +else() + set(LIBCARES_INCLUDE_DIRS "") + set(LIBCARES_LIBRARIES "") +endif() +# jansson (for src/nghttp, src/deflatehd and src/inflatehd) +set(HAVE_JANSSON ${JANSSON_FOUND}) +# libxml2 (for src/nghttp) +set(HAVE_LIBXML2 ${LIBXML2_FOUND}) +if(LIBXML2_FOUND) + set(LIBXML2_INCLUDE_DIRS ${LIBXML2_INCLUDE_DIR}) +else() + set(LIBXML2_INCLUDE_DIRS "") + set(LIBXML2_LIBRARIES "") +endif() +# jemalloc +set(HAVE_JEMALLOC ${JEMALLOC_FOUND}) + +# libbpf (for bpf) +set(HAVE_LIBBPF ${LIBBPF_FOUND}) +if(LIBBPF_FOUND) + set(BPFCFLAGS -Wall -O2 -g) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # For Debian/Ubuntu + set(EXTRABPFCFLAGS -I/usr/include/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu) + endif() + + check_c_source_compiles(" +#include +int main() { enum bpf_stats_type foo; (void)foo; }" HAVE_BPF_STATS_TYPE) +endif() + +# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL and libev +if(ENABLE_APP AND NOT (ZLIB_FOUND AND OPENSSL_FOUND AND LIBEV_FOUND)) + message(FATAL_ERROR "Applications were requested (ENABLE_APP=1) but dependencies are not met.") +endif() + +# HTTP/3 requires quictls/openssl, libngtcp2, libngtcp2_crypto_quictls +# and libnghttp3. +if(ENABLE_HTTP3 AND NOT (HAVE_SSL_PROVIDE_QUIC_DATA AND LIBNGTCP2_FOUND AND LIBNGTCP2_CRYPTO_QUICTLS_FOUND AND LIBNGHTTP3_FOUND)) + message(FATAL_ERROR "HTTP/3 was requested (ENABLE_HTTP3=1) but dependencies are not met.") +endif() + +# HPACK tools requires jansson +if(ENABLE_HPACK_TOOLS AND NOT HAVE_JANSSON) + message(FATAL_ERROR "HPACK tools were requested (ENABLE_HPACK_TOOLS=1) but dependencies are not met.") +endif() + +# examples +if(ENABLE_EXAMPLES AND NOT (OPENSSL_FOUND AND LIBEVENT_OPENSSL_FOUND)) + message(FATAL_ERROR "examples were requested (ENABLE_EXAMPLES=1) but dependencies are not met.") +endif() + +# third-party http-parser only be built when needed +if(ENABLE_EXAMPLES OR ENABLE_APP OR ENABLE_HPACK_TOOLS) + set(ENABLE_THIRD_PARTY 1) + # mruby (for src/nghttpx) + set(HAVE_MRUBY ${WITH_MRUBY}) + set(HAVE_NEVERBLEED ${WITH_NEVERBLEED}) +else() + set(HAVE_MRUBY 0) + set(HAVE_NEVERBLEED 0) +endif() + +# Checks for header files. +include(CheckIncludeFile) +check_include_file("arpa/inet.h" HAVE_ARPA_INET_H) +check_include_file("fcntl.h" HAVE_FCNTL_H) +check_include_file("inttypes.h" HAVE_INTTYPES_H) +check_include_file("limits.h" HAVE_LIMITS_H) +check_include_file("netdb.h" HAVE_NETDB_H) +check_include_file("netinet/in.h" HAVE_NETINET_IN_H) +check_include_file("netinet/ip.h" HAVE_NETINET_IP_H) +check_include_file("pwd.h" HAVE_PWD_H) +check_include_file("sys/socket.h" HAVE_SYS_SOCKET_H) +check_include_file("sys/time.h" HAVE_SYS_TIME_H) +check_include_file("syslog.h" HAVE_SYSLOG_H) +check_include_file("time.h" HAVE_TIME_H) +check_include_file("unistd.h" HAVE_UNISTD_H) +check_include_file("windows.h" HAVE_WINDOWS_H) + +include(CheckTypeSize) +# Checks for typedefs, structures, and compiler characteristics. +# AC_TYPE_SIZE_T +check_type_size("ssize_t" SIZEOF_SSIZE_T) +if(SIZEOF_SSIZE_T STREQUAL "") + # ssize_t is a signed type in POSIX storing at least -1. + # Set it to "int" to match the behavior of AC_TYPE_SSIZE_T (autotools). + set(ssize_t int) +endif() +# AC_TYPE_UINT8_T +# AC_TYPE_UINT16_T +# AC_TYPE_UINT32_T +# AC_TYPE_UINT64_T +# AC_TYPE_INT8_T +# AC_TYPE_INT16_T +# AC_TYPE_INT32_T +# AC_TYPE_INT64_T +# AC_TYPE_OFF_T +# AC_TYPE_PID_T +# AC_TYPE_UID_T +# XXX To support inline for crappy compilers, see https://cmake.org/Wiki/CMakeTestInline +# AC_C_INLINE +# XXX is AC_SYS_LARGEFILE still needed for modern systems? +# add_definitions(-D_FILE_OFFSET_BITS=64) + +include(CheckStructHasMember) +check_struct_has_member("struct tm" tm_gmtoff time.h HAVE_STRUCT_TM_TM_GMTOFF) + +# Check size of pointer to decide we need 8 bytes alignment adjustment. +check_type_size("int *" SIZEOF_INT_P) +check_type_size("time_t" SIZEOF_TIME_T) + +# Checks for library functions. +include(CheckFunctionExists) +check_function_exists(_Exit HAVE__EXIT) +check_function_exists(accept4 HAVE_ACCEPT4) +check_function_exists(clock_gettime HAVE_CLOCK_GETTIME) +check_function_exists(mkostemp HAVE_MKOSTEMP) + +check_symbol_exists(GetTickCount64 sysinfoapi.h HAVE_GETTICKCOUNT64) + +include(CheckSymbolExists) +# XXX does this correctly detect initgroups (un)availability on cygwin? +check_symbol_exists(initgroups grp.h HAVE_DECL_INITGROUPS) +if(NOT HAVE_DECL_INITGROUPS AND HAVE_UNISTD_H) + # FreeBSD declares initgroups() in unistd.h + check_symbol_exists(initgroups unistd.h HAVE_DECL_INITGROUPS2) + if(HAVE_DECL_INITGROUPS2) + set(HAVE_DECL_INITGROUPS 1) + endif() +endif() + +check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_DECL_CLOCK_MONOTONIC) + +set(WARNCFLAGS) +set(WARNCXXFLAGS) +if(CMAKE_C_COMPILER_ID MATCHES "MSVC") + if(ENABLE_WERROR) + set(WARNCFLAGS /WX) + set(WARNCXXFLAGS /WX) + endif() +else() + if(ENABLE_WERROR) + set(WARNCFLAGS "-Werror") + set(WARNCXXFLAGS "-Werror") + endif() + + include(PickyWarningsC) + include(PickyWarningsCXX) +endif() + +if(ENABLE_STATIC_CRT) + foreach(lang C CXX) + foreach(suffix "" _DEBUG _MINSIZEREL _RELEASE _RELWITHDEBINFO) + set(var "CMAKE_${lang}_FLAGS${suffix}") + string(REPLACE "/MD" "/MT" ${var} "${${var}}") + endforeach() + endforeach() +endif() + +if(ENABLE_DEBUG) + set(DEBUGBUILD 1) +endif() + +# Some platform does not have working std::future. We disable +# threading for those platforms. +if(NOT ENABLE_THREADS OR NOT HAVE_STD_FUTURE) + set(NOTHREADS 1) +endif() + +add_definitions(-DHAVE_CONFIG_H) +configure_file(cmakeconfig.h.in config.h) +# autotools-compatible names +# Sphinx expects relative paths in the .rst files. Use the fact that the files +# below are all one directory level deep. +file(RELATIVE_PATH top_srcdir "${CMAKE_CURRENT_BINARY_DIR}/dir" "${CMAKE_CURRENT_SOURCE_DIR}") +file(RELATIVE_PATH top_builddir "${CMAKE_CURRENT_BINARY_DIR}/dir" "${CMAKE_CURRENT_BINARY_DIR}") +set(abs_top_srcdir "${CMAKE_CURRENT_SOURCE_DIR}") +set(abs_top_builddir "${CMAKE_CURRENT_BINARY_DIR}") +# libnghttp2.pc (pkg-config file) +set(prefix "${CMAKE_INSTALL_PREFIX}") +set(exec_prefix "${CMAKE_INSTALL_PREFIX}") +set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}") +set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}") +set(VERSION "${PACKAGE_VERSION}") +# For init scripts and systemd service file (in contrib/) +set(bindir "${CMAKE_INSTALL_FULL_BINDIR}") +set(sbindir "${CMAKE_INSTALL_FULL_SBINDIR}") +foreach(name + lib/libnghttp2.pc + lib/includes/nghttp2/nghttp2ver.h + integration-tests/config.go + integration-tests/setenv + doc/conf.py + doc/index.rst + doc/package_README.rst + doc/tutorial-client.rst + doc/tutorial-server.rst + doc/tutorial-hpack.rst + doc/nghttpx-howto.rst + doc/h2load-howto.rst + doc/building-android-binary.rst + doc/nghttp2.h.rst + doc/nghttp2ver.h.rst + doc/contribute.rst +) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +if(APPLE) + add_definitions(-D__APPLE_USE_RFC_3542) +endif() + +include_directories( + BEFORE "${CMAKE_CURRENT_BINARY_DIR}" # for config.h +) +# For use in src/CMakeLists.txt +set(PKGDATADIR "${CMAKE_INSTALL_FULL_DATADIR}/${CMAKE_PROJECT_NAME}") +set(PKGLIBDIR "${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_PROJECT_NAME}") + +install(FILES README.rst DESTINATION "${CMAKE_INSTALL_DOCDIR}") + +add_subdirectory(lib) +#add_subdirectory(lib/includes) +add_subdirectory(third-party) +add_subdirectory(src) +add_subdirectory(examples) +add_subdirectory(tests) +#add_subdirectory(tests/testdata) +add_subdirectory(integration-tests) +if(ENABLE_DOC) + add_subdirectory(doc) +endif() +add_subdirectory(contrib) +add_subdirectory(script) +add_subdirectory(bpf) + + +string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type) +message(STATUS "summary of build options: + + Package version: ${VERSION} + Library version: ${LT_CURRENT}:${LT_REVISION}:${LT_AGE} + Install prefix: ${CMAKE_INSTALL_PREFIX} + Target system: ${CMAKE_SYSTEM_NAME} + Compiler: + Build type: ${CMAKE_BUILD_TYPE} + C compiler: ${CMAKE_C_COMPILER} + CFLAGS: ${CMAKE_C_FLAGS_${_build_type}} ${CMAKE_C_FLAGS} + C++ compiler: ${CMAKE_CXX_COMPILER} + CXXFLAGS: ${CMAKE_CXX_FLAGS_${_build_type}} ${CMAKE_CXX_FLAGS} + WARNCFLAGS: ${WARNCFLAGS} + CXX1XCXXFLAGS: ${CXX1XCXXFLAGS} + WARNCXXFLAGS: ${WARNCXXFLAGS} + Python: + Python: ${Python3_EXECUTABLE} + Python3_VERSION: ${Python3_VERSION} + Test: + CUnit: ${HAVE_CUNIT} (LIBS='${CUNIT_LIBRARIES}') + Failmalloc: ${ENABLE_FAILMALLOC} + Libs: + OpenSSL: ${HAVE_OPENSSL} (LIBS='${OPENSSL_LIBRARIES}') + Libxml2: ${HAVE_LIBXML2} (LIBS='${LIBXML2_LIBRARIES}') + Libev: ${HAVE_LIBEV} (LIBS='${LIBEV_LIBRARIES}') + Libc-ares: ${HAVE_LIBCARES} (LIBS='${LIBCARES_LIBRARIES}') + Libngtcp2: ${HAVE_LIBNGTCP2} (LIBS='${LIBNGTCP2_LIBRARIES}') + Libngtcp2_crypto_quictls: ${HAVE_LIBNGTCP2_CRYPTO_QUICTLS} (LIBS='${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES}') + Libnghttp3: ${HAVE_LIBNGHTTP3} (LIBS='${LIBNGHTTP3_LIBRARIES}') + Libbpf: ${HAVE_LIBBPF} (LIBS='${LIBBPF_LIBRARIES}') + Libevent(SSL): ${HAVE_LIBEVENT_OPENSSL} (LIBS='${LIBEVENT_OPENSSL_LIBRARIES}') + Jansson: ${HAVE_JANSSON} (LIBS='${JANSSON_LIBRARIES}') + Jemalloc: ${HAVE_JEMALLOC} (LIBS='${JEMALLOC_LIBRARIES}') + Zlib: ${HAVE_ZLIB} (LIBS='${ZLIB_LIBRARIES}') + Systemd: ${HAVE_SYSTEMD} (LIBS='${SYSTEMD_LIBRARIES}') + Third-party: + http-parser: ${ENABLE_THIRD_PARTY} + MRuby: ${HAVE_MRUBY} + Neverbleed: ${HAVE_NEVERBLEED} + Features: + Applications: ${ENABLE_APP} + HPACK tools: ${ENABLE_HPACK_TOOLS} + Examples: ${ENABLE_EXAMPLES} + Threading: ${ENABLE_THREADS} + HTTP/3(EXPERIMENTAL): ${ENABLE_HTTP3} +") +if(ENABLE_LIB_ONLY_DISABLED_OTHERS) + message("Only the library will be built. To build other components " + "(such as applications and examples), set ENABLE_LIB_ONLY=OFF.") +endif() diff --git a/lib/nghttp2/CMakeOptions.txt b/lib/nghttp2/CMakeOptions.txt new file mode 100644 index 00000000000..238d5b1b2b3 --- /dev/null +++ b/lib/nghttp2/CMakeOptions.txt @@ -0,0 +1,28 @@ +# Features that can be enabled for cmake (see CMakeLists.txt) + +option(ENABLE_WERROR "Turn on compile time warnings") +option(ENABLE_DEBUG "Turn on debug output") +option(ENABLE_THREADS "Turn on threading in apps" ON) +option(ENABLE_APP "Build applications (nghttp, nghttpd, nghttpx and h2load)" + ${ENABLE_APP_DEFAULT}) +option(ENABLE_HPACK_TOOLS "Build HPACK tools" + ${ENABLE_HPACK_TOOLS_DEFAULT}) +option(ENABLE_EXAMPLES "Build examples" + ${ENABLE_EXAMPLES_DEFAULT}) +option(ENABLE_FAILMALLOC "Build failmalloc test program" ON) +option(ENABLE_LIB_ONLY "Build libnghttp2 only. This is a short hand for -DENABLE_APP=0 -DENABLE_EXAMPLES=0 -DENABLE_HPACK_TOOLS=0") +option(ENABLE_STATIC_LIB "Build libnghttp2 in static mode also") +option(ENABLE_SHARED_LIB "Build libnghttp2 as a shared library" ON) +option(ENABLE_STATIC_CRT "Build libnghttp2 against the MS LIBCMT[d]") +option(ENABLE_HTTP3 "Enable HTTP/3 support" OFF) +option(ENABLE_DOC "Build documentation" ON) + +option(WITH_LIBXML2 "Use libxml2" + ${WITH_LIBXML2_DEFAULT}) +option(WITH_JEMALLOC "Use jemalloc" + ${WITH_JEMALLOC_DEFAULT}) +option(WITH_MRUBY "Use mruby") +option(WITH_NEVERBLEED "Use neverbleed") +option(WITH_LIBBPF "Use libbpf") + +# vim: ft=cmake: diff --git a/lib/nghttp2/CONTRIBUTION b/lib/nghttp2/CONTRIBUTION new file mode 100644 index 00000000000..8a2aae39e31 --- /dev/null +++ b/lib/nghttp2/CONTRIBUTION @@ -0,0 +1,18 @@ +[The text below was composed based on 1.2. License section of +curl/libcurl project.] + +When contributing with code, you agree to put your changes and new +code under the same license nghttp2 is already using unless stated and +agreed otherwise. + +When changing existing source code, you do not alter the copyright of +the original file(s). The copyright will still be owned by the +original creator(s) or those who have been assigned copyright by the +original author(s). + +By submitting a patch to the nghttp2 project, you are assumed to have +the right to the code and to be allowed by your employer or whatever +to hand over that patch/code to us. We will credit you for your +changes as far as possible, to give credit but also to keep a trace +back to who made what changes. Please always provide us with your +full real name when contributing! diff --git a/lib/nghttp2/COPYING b/lib/nghttp2/COPYING new file mode 100644 index 00000000000..80201792ec7 --- /dev/null +++ b/lib/nghttp2/COPYING @@ -0,0 +1,23 @@ +The MIT License + +Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa +Copyright (c) 2012, 2014, 2015, 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. diff --git a/lib/nghttp2/ChangeLog b/lib/nghttp2/ChangeLog new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/nghttp2/Dockerfile.android b/lib/nghttp2/Dockerfile.android new file mode 100644 index 00000000000..96ce7c4670d --- /dev/null +++ b/lib/nghttp2/Dockerfile.android @@ -0,0 +1,121 @@ +# vim: ft=dockerfile: +# Dockerfile to build nghttp2 android binary +# +# $ sudo docker build -t nghttp2-android - < Dockerfile.android +# +# After successful build, android binaries are located under +# /root/build/nghttp2. You can copy the binary using docker cp. For +# example, to copy nghttpx binary to host file system location +# /path/to/dest, do this: +# +# $ sudo docker run -v /path/to/dest:/out nghttp2-android cp /root/build/nghttp2/src/nghttpx /out + + +# Only use standalone-toolchain for reduce size +FROM ubuntu:22.04 +MAINTAINER Tatsuhiro Tsujikawa + +ENV NDK_VERSION r25b +ENV NDK /root/android-ndk-$NDK_VERSION +ENV TOOLCHAIN $NDK/toolchains/llvm/prebuilt/linux-x86_64 +ENV TARGET aarch64-linux-android +ENV API 33 +ENV AR $TOOLCHAIN/bin/llvm-ar +ENV CC $TOOLCHAIN/bin/$TARGET$API-clang +ENV CXX $TOOLCHAIN/bin/$TARGET$API-clang++ +ENV LD $TOOLCHAIN/bin/ld +ENV RANDLIB $TOOLCHAIN/bin/llvm-ranlib +ENV STRIP $TOOLCHAIN/bin/llvm-strip +ENV PREFIX /root/usr/local + +WORKDIR /root +RUN apt-get update && \ + apt-get install -y unzip make binutils autoconf \ + automake autotools-dev libtool pkg-config git \ + curl dpkg-dev libxml2-dev genisoimage libc6-i386 \ + lib32stdc++6 && \ + rm -rf /var/cache/apt/* + +# Download NDK +RUN curl -L -O https://dl.google.com/android/repository/android-ndk-$NDK_VERSION-linux.zip && \ + unzip -q android-ndk-$NDK_VERSION-linux.zip && \ + rm android-ndk-$NDK_VERSION-linux.zip + +# Setup version of libraries +ENV OPENSSL_VERSION 1.1.1q +ENV LIBEV_VERSION 4.33 +ENV ZLIB_VERSION 1.2.13 +ENV CARES_VERSION 1.18.1 +ENV NGHTTP2_VERSION master + +WORKDIR /root/build +RUN curl -L -O https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz && \ + tar xf openssl-$OPENSSL_VERSION.tar.gz && \ + rm openssl-$OPENSSL_VERSION.tar.gz + +WORKDIR /root/build/openssl-$OPENSSL_VERSION +RUN export ANDROID_NDK_HOME=$NDK PATH=$TOOLCHAIN/bin:$PATH && \ + ./Configure no-shared --prefix=$PREFIX android-arm64 && \ + make && make install_sw + +WORKDIR /root/build +RUN curl -L -O http://dist.schmorp.de/libev/Attic/libev-$LIBEV_VERSION.tar.gz && \ + tar xf libev-$LIBEV_VERSION.tar.gz && \ + rm libev-$LIBEV_VERSION.tar.gz + +WORKDIR /root/build/libev-$LIBEV_VERSION +RUN ./configure \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --prefix=$PREFIX \ + --disable-shared \ + --enable-static \ + CPPFLAGS=-I$PREFIX/include \ + LDFLAGS=-L$PREFIX/lib && \ + make install + +WORKDIR /root/build +RUN curl -L -O https://zlib.net/zlib-$ZLIB_VERSION.tar.gz && \ + tar xf zlib-$ZLIB_VERSION.tar.gz && \ + rm zlib-$ZLIB_VERSION.tar.gz + +WORKDIR /root/build/zlib-$ZLIB_VERSION +RUN HOST=$TARGET \ + ./configure \ + --prefix=$PREFIX \ + --libdir=$PREFIX/lib \ + --includedir=$PREFIX/include \ + --static && \ + make install + + +WORKDIR /root/build +RUN curl -L -O https://c-ares.haxx.se/download/c-ares-$CARES_VERSION.tar.gz && \ + tar xf c-ares-$CARES_VERSION.tar.gz && \ + rm c-ares-$CARES_VERSION.tar.gz + +WORKDIR /root/build/c-ares-$CARES_VERSION +RUN ./configure \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --prefix=$PREFIX \ + --disable-shared && \ + make install + +WORKDIR /root/build +RUN git clone https://github.com/nghttp2/nghttp2 -b $NGHTTP2_VERSION --depth 1 +WORKDIR /root/build/nghttp2 +RUN autoreconf -i && \ + ./configure \ + --enable-app \ + --disable-shared \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --without-libxml2 \ + --disable-examples \ + --disable-threads \ + CPPFLAGS="-fPIE -I$PREFIX/include" \ + PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \ + LDFLAGS="-fPIE -pie -L$PREFIX/lib" && \ + make && \ + $STRIP src/nghttpx src/nghttpd src/nghttp diff --git a/lib/nghttp2/LICENSE b/lib/nghttp2/LICENSE new file mode 100644 index 00000000000..45d9408ffa5 --- /dev/null +++ b/lib/nghttp2/LICENSE @@ -0,0 +1 @@ +See COPYING diff --git a/lib/nghttp2/Makefile.am b/lib/nghttp2/Makefile.am new file mode 100644 index 00000000000..683b9896b09 --- /dev/null +++ b/lib/nghttp2/Makefile.am @@ -0,0 +1,61 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = lib third-party src bpf examples tests integration-tests \ + doc contrib script + +ACLOCAL_AMFLAGS = -I m4 + +dist_doc_DATA = README.rst + +EXTRA_DIST = nghttpx.conf.sample proxy.pac.sample android-config android-env \ + Dockerfile.android \ + cmakeconfig.h.in \ + CMakeLists.txt \ + CMakeOptions.txt \ + cmake/ExtractValidFlags.cmake \ + cmake/FindJemalloc.cmake \ + cmake/FindLibev.cmake \ + cmake/FindCUnit.cmake \ + cmake/Version.cmake \ + cmake/FindLibevent.cmake \ + cmake/FindJansson.cmake \ + cmake/FindLibcares.cmake \ + cmake/FindSystemd.cmake \ + cmake/FindLibbpf.cmake \ + cmake/FindLibnghttp3.cmake \ + cmake/FindLibngtcp2.cmake \ + cmake/FindLibngtcp2_crypto_quictls.cmake \ + cmake/PickyWarningsC.cmake \ + cmake/PickyWarningsCXX.cmake + +.PHONY: clang-format + +# Format source files using clang-format. Don't format source files +# under third-party directory since we are not responsible for their +# coding style. +clang-format: + CLANGFORMAT=`git config --get clangformat.binary`; \ + test -z $${CLANGFORMAT} && CLANGFORMAT="clang-format"; \ + $${CLANGFORMAT} -i lib/*.{c,h} lib/includes/nghttp2/*.h \ + src/*.{c,cc,h} examples/*.c \ + tests/*.{c,h} bpf/*.c fuzz/*.cc diff --git a/lib/nghttp2/NEWS b/lib/nghttp2/NEWS new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/nghttp2/README b/lib/nghttp2/README new file mode 100644 index 00000000000..5ccc0ea36b5 --- /dev/null +++ b/lib/nghttp2/README @@ -0,0 +1 @@ +See README.rst diff --git a/lib/nghttp2/README.rst b/lib/nghttp2/README.rst new file mode 100644 index 00000000000..596d5615753 --- /dev/null +++ b/lib/nghttp2/README.rst @@ -0,0 +1,1473 @@ +nghttp2 - HTTP/2 C Library +========================== + +This is an implementation of the Hypertext Transfer Protocol version 2 +in C. + +The framing layer of HTTP/2 is implemented as a reusable C library. +On top of that, we have implemented an HTTP/2 client, server and +proxy. We have also developed load test and benchmarking tools for +HTTP/2. + +An HPACK encoder and decoder are available as a public API. + +Development Status +------------------ + +nghttp2 was originally developed based on `RFC 7540 +`_ HTTP/2 and `RFC 7541 +`_ HPACK - Header Compression for +HTTP/2. Now we are updating our code to implement `RFC 9113 +`_. + +The nghttp2 code base was forked from the spdylay +(https://github.com/tatsuhiro-t/spdylay) project. + +Public Test Server +------------------ + +The following endpoints are available to try out our nghttp2 +implementation. + +* https://nghttp2.org/ (TLS + ALPN/NPN and HTTP/3) + + This endpoint supports ``h2``, ``h2-16``, ``h2-14``, and + ``http/1.1`` via ALPN/NPN and requires TLSv1.2 for HTTP/2 + connection. + + It also supports HTTP/3. + +* http://nghttp2.org/ (HTTP Upgrade and HTTP/2 Direct) + + ``h2c`` and ``http/1.1``. + +Requirements +------------ + +The following package is required to build the libnghttp2 library: + +* pkg-config >= 0.20 + +To build and run the unit test programs, the following package is +required: + +* cunit >= 2.1 + +To build the documentation, you need to install: + +* sphinx (http://sphinx-doc.org/) + +If you need libnghttp2 (C library) only, then the above packages are +all you need. Use ``--enable-lib-only`` to ensure that only +libnghttp2 is built. This avoids potential build error related to +building bundled applications. + +To build and run the application programs (``nghttp``, ``nghttpd``, +``nghttpx`` and ``h2load``) in the ``src`` directory, the following packages +are required: + +* OpenSSL >= 1.0.1 +* libev >= 4.11 +* zlib >= 1.2.3 +* libc-ares >= 1.7.5 + +ALPN support requires OpenSSL >= 1.0.2 (released 22 January 2015). +LibreSSL >= 2.2.0 can be used instead of OpenSSL, but OpenSSL has more +features than LibreSSL at the time of this writing. + +To enable ``-a`` option (getting linked assets from the downloaded +resource) in ``nghttp``, the following package is required: + +* libxml2 >= 2.6.26 + +To enable systemd support in nghttpx, the following package is +required: + +* libsystemd-dev >= 209 + +The HPACK tools require the following package: + +* jansson >= 2.5 + +To build sources under the examples directory, libevent is required: + +* libevent-openssl >= 2.0.8 + +To mitigate heap fragmentation in long running server programs +(``nghttpd`` and ``nghttpx``), jemalloc is recommended: + +* jemalloc + + .. note:: + + Alpine Linux currently does not support malloc replacement + due to musl limitations. See details in issue `#762 `_. + +To enable mruby support for nghttpx, `mruby +`_ is required. We need to build +mruby with C++ ABI explicitly turned on, and probably need other +mrgems, mruby is manged by git submodule under third-party/mruby +directory. Currently, mruby support for nghttpx is disabled by +default. To enable mruby support, use ``--with-mruby`` configure +option. Note that at the time of this writing, libmruby-dev and mruby +packages in Debian/Ubuntu are not usable for nghttp2, since they do +not enable C++ ABI. To build mruby, the following packages are +required: + +* ruby +* bison + +nghttpx supports `neverbleed `_, +privilege separation engine for OpenSSL / LibreSSL. In short, it +minimizes the risk of private key leakage when serious bug like +Heartbleed is exploited. The neverbleed is disabled by default. To +enable it, use ``--with-neverbleed`` configure option. + +To enable the experimental HTTP/3 support for h2load and nghttpx, the +following libraries are required: + +* `OpenSSL with QUIC support + `_; or + `BoringSSL `_ (commit + 6ca49385b168f47a50e7172d82a590b218f55e4d) +* `ngtcp2 `_ >= 1.0.0 +* `nghttp3 `_ >= 1.1.0 + +Use ``--enable-http3`` configure option to enable HTTP/3 feature for +h2load and nghttpx. + +In order to build optional eBPF program to direct an incoming QUIC UDP +datagram to a correct socket for nghttpx, the following libraries are +required: + +* libbpf-dev >= 0.7.0 + +Use ``--with-libbpf`` configure option to build eBPF program. +libelf-dev is needed to build libbpf. + +For Ubuntu 20.04, you can build libbpf from `the source code +`_. nghttpx +requires eBPF program for reloading its configuration and hot swapping +its executable. + +Compiling libnghttp2 C source code requires a C99 compiler. gcc 4.8 +is known to be adequate. In order to compile the C++ source code, gcc +>= 6.0 or clang >= 6.0 is required. C++ source code requires C++14 +language features. + +.. note:: + + To enable mruby support in nghttpx, and use ``--with-mruby`` + configure option. + +.. note:: + + Mac OS X users may need the ``--disable-threads`` configure option to + disable multi-threading in nghttpd, nghttpx and h2load to prevent + them from crashing. A patch is welcome to make multi threading work + on Mac OS X platform. + +.. note:: + + To compile the associated applications (nghttp, nghttpd, nghttpx + and h2load), you must use the ``--enable-app`` configure option and + ensure that the specified requirements above are met. Normally, + configure script checks required dependencies to build these + applications, and enable ``--enable-app`` automatically, so you + don't have to use it explicitly. But if you found that + applications were not built, then using ``--enable-app`` may find + that cause, such as the missing dependency. + +.. note:: + + In order to detect third party libraries, pkg-config is used + (however we don't use pkg-config for some libraries (e.g., libev)). + By default, pkg-config searches ``*.pc`` file in the standard + locations (e.g., /usr/lib/pkgconfig). If it is necessary to use + ``*.pc`` file in the custom location, specify paths to + ``PKG_CONFIG_PATH`` environment variable, and pass it to configure + script, like so: + + .. code-block:: text + + $ ./configure PKG_CONFIG_PATH=/path/to/pkgconfig + + For pkg-config managed libraries, ``*_CFLAG`` and ``*_LIBS`` + environment variables are defined (e.g., ``OPENSSL_CFLAGS``, + ``OPENSSL_LIBS``). Specifying non-empty string to these variables + completely overrides pkg-config. In other words, if they are + specified, pkg-config is not used for detection, and user is + responsible to specify the correct values to these variables. For + complete list of these variables, run ``./configure -h``. + +If you are using Ubuntu 22.04 LTS, run the following to install the +required packages: + +.. code-block:: text + + sudo apt-get install g++ clang make binutils autoconf automake \ + autotools-dev libtool pkg-config \ + zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev \ + libevent-dev libjansson-dev \ + libc-ares-dev libjemalloc-dev libsystemd-dev \ + ruby-dev bison libelf-dev + +Building nghttp2 from release tar archive +----------------------------------------- + +The nghttp2 project regularly releases tar archives which includes +nghttp2 source code, and generated build files. They can be +downloaded from `Releases +`_ page. + +Building nghttp2 from git requires autotools development packages. +Building from tar archives does not require them, and thus it is much +easier. The usual build step is as follows: + +.. code-block:: text + + $ tar xf nghttp2-X.Y.Z.tar.bz2 + $ cd nghttp2-X.Y.Z + $ ./configure + $ make + +Building from git +----------------- + +Building from git is easy, but please be sure that at least autoconf 2.68 is +used: + +.. code-block:: text + + $ git submodule update --init + $ autoreconf -i + $ automake + $ autoconf + $ ./configure + $ make + +Notes for building on Windows (MSVC) +------------------------------------ + +The easiest way to build native Windows nghttp2 dll is use `cmake +`_. The free version of `Visual C++ Build Tools +`_ works +fine. + +1. Install cmake for windows +2. Open "Visual C++ ... Native Build Tool Command Prompt", and inside + nghttp2 directly, run ``cmake``. +3. Then run ``cmake --build`` to build library. +4. nghttp2.dll, nghttp2.lib, nghttp2.exp are placed under lib directory. + +Note that the above steps most likely produce nghttp2 library only. +No bundled applications are compiled. + +Notes for building on Windows (Mingw/Cygwin) +-------------------------------------------- + +Under Mingw environment, you can only compile the library, it's +``libnghttp2-X.dll`` and ``libnghttp2.a``. + +If you want to compile the applications(``h2load``, ``nghttp``, +``nghttpx``, ``nghttpd``), you need to use the Cygwin environment. + +Under Cygwin environment, to compile the applications you need to +compile and install the libev first. + +Secondly, you need to undefine the macro ``__STRICT_ANSI__``, if you +not, the functions ``fdopen``, ``fileno`` and ``strptime`` will not +available. + +the sample command like this: + +.. code-block:: text + + $ export CFLAGS="-U__STRICT_ANSI__ -I$libev_PREFIX/include -L$libev_PREFIX/lib" + $ export CXXFLAGS=$CFLAGS + $ ./configure + $ make + +If you want to compile the applications under ``examples/``, you need +to remove or rename the ``event.h`` from libev's installation, because +it conflicts with libevent's installation. + +Notes for installation on Linux systems +-------------------------------------------- +After installing nghttp2 tool suite with ``make install`` one might experience a similar error: + +.. code-block:: text + + nghttpx: error while loading shared libraries: libnghttp2.so.14: cannot open shared object file: No such file or directory + +This means that the tool is unable to locate the ``libnghttp2.so`` shared library. + +To update the shared library cache run ``sudo ldconfig``. + +Building the documentation +-------------------------- + +.. note:: + + Documentation is still incomplete. + +To build the documentation, run: + +.. code-block:: text + + $ make html + +The documents will be generated under ``doc/manual/html/``. + +The generated documents will not be installed with ``make install``. + +The online documentation is available at +https://nghttp2.org/documentation/ + +Build HTTP/3 enabled h2load and nghttpx +--------------------------------------- + +To build h2load and nghttpx with HTTP/3 feature enabled, run the +configure script with ``--enable-http3``. + +For nghttpx to reload configurations and swapping its executable while +gracefully terminating old worker processes, eBPF is required. Run +the configure script with ``--enable-http3 --with-libbpf`` to build +eBPF program. The QUIC keying material must be set with +``--frontend-quic-secret-file`` in order to keep the existing +connections alive during reload. + +The detailed steps to build HTTP/3 enabled h2load and nghttpx follow. + +Build custom OpenSSL: + +.. code-block:: text + + $ git clone --depth 1 -b OpenSSL_1_1_1w+quic https://github.com/quictls/openssl + $ cd openssl + $ ./config --prefix=$PWD/build --openssldir=/etc/ssl + $ make -j$(nproc) + $ make install_sw + $ cd .. + +Build nghttp3: + +.. code-block:: text + + $ git clone --depth 1 -b v1.1.0 https://github.com/ngtcp2/nghttp3 + $ cd nghttp3 + $ autoreconf -i + $ ./configure --prefix=$PWD/build --enable-lib-only + $ make -j$(nproc) + $ make install + $ cd .. + +Build ngtcp2: + +.. code-block:: text + + $ git clone --depth 1 -b v1.1.0 https://github.com/ngtcp2/ngtcp2 + $ cd ngtcp2 + $ autoreconf -i + $ ./configure --prefix=$PWD/build --enable-lib-only \ + PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig" + $ make -j$(nproc) + $ make install + $ cd .. + +If your Linux distribution does not have libbpf-dev >= 0.7.0, build +from source: + +.. code-block:: text + + $ git clone --depth 1 -b v1.3.0 https://github.com/libbpf/libbpf + $ cd libbpf + $ PREFIX=$PWD/build make -C src install + $ cd .. + +Build nghttp2: + +.. code-block:: text + + $ git clone https://github.com/nghttp2/nghttp2 + $ cd nghttp2 + $ git submodule update --init + $ autoreconf -i + $ ./configure --with-mruby --with-neverbleed --enable-http3 --with-libbpf \ + CC=clang-14 CXX=clang++-14 \ + PKG_CONFIG_PATH="$PWD/../openssl/build/lib/pkgconfig:$PWD/../nghttp3/build/lib/pkgconfig:$PWD/../ngtcp2/build/lib/pkgconfig:$PWD/../libbpf/build/lib64/pkgconfig" \ + LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/../openssl/build/lib -Wl,-rpath,$PWD/../libbpf/build/lib64" + $ make -j$(nproc) + +The eBPF program ``reuseport_kern.o`` should be found under bpf +directory. Pass ``--quic-bpf-program-file=bpf/reuseport_kern.o`` +option to nghttpx to load it. See also `HTTP/3 section in nghttpx - +HTTP/2 proxy - HOW-TO +`_. + +Unit tests +---------- + +Unit tests are done by simply running ``make check``. + +Integration tests +----------------- + +We have the integration tests for the nghttpx proxy server. The tests are +written in the `Go programming language `_ and uses +its testing framework. We depend on the following libraries: + +* golang.org/x/net/http2 +* golang.org/x/net/websocket +* https://github.com/tatsuhiro-t/go-nghttp2 + +Go modules will download these dependencies automatically. + +To run the tests, run the following command under +``integration-tests`` directory: + +.. code-block:: text + + $ make it + +Inside the tests, we use port 3009 to run the test subject server. + +Migration from v0.7.15 or earlier +--------------------------------- + +nghttp2 v1.0.0 introduced several backward incompatible changes. In +this section, we describe these changes and how to migrate to v1.0.0. + +ALPN protocol ID is now ``h2`` and ``h2c`` +++++++++++++++++++++++++++++++++++++++++++ + +Previously we announced ``h2-14`` and ``h2c-14``. v1.0.0 implements +final protocol version, and we changed ALPN ID to ``h2`` and ``h2c``. +The macros ``NGHTTP2_PROTO_VERSION_ID``, +``NGHTTP2_PROTO_VERSION_ID_LEN``, +``NGHTTP2_CLEARTEXT_PROTO_VERSION_ID``, and +``NGHTTP2_CLEARTEXT_PROTO_VERSION_ID_LEN`` have been updated to +reflect this change. + +Basically, existing applications do not have to do anything, just +recompiling is enough for this change. + +Use word "client magic" where we use "client connection preface" +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +We use "client connection preface" to mean first 24 bytes of client +connection preface. This is technically not correct, since client +connection preface is composed of 24 bytes client magic byte string +followed by SETTINGS frame. For clarification, we call "client magic" +for this 24 bytes byte string and updated API. + +* ``NGHTTP2_CLIENT_CONNECTION_PREFACE`` was replaced with + ``NGHTTP2_CLIENT_MAGIC``. +* ``NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN`` was replaced with + ``NGHTTP2_CLIENT_MAGIC_LEN``. +* ``NGHTTP2_BAD_PREFACE`` was renamed as ``NGHTTP2_BAD_CLIENT_MAGIC`` + +The already deprecated ``NGHTTP2_CLIENT_CONNECTION_HEADER`` and +``NGHTTP2_CLIENT_CONNECTION_HEADER_LEN`` were removed. + +If application uses these macros, just replace old ones with new ones. +Since v1.0.0, client magic is sent by library (see next subsection), +so client application may just remove these macro use. + +Client magic is sent by library ++++++++++++++++++++++++++++++++ + +Previously nghttp2 library did not send client magic, which is first +24 bytes byte string of client connection preface, and client +applications have to send it by themselves. Since v1.0.0, client +magic is sent by library via first call of ``nghttp2_session_send()`` +or ``nghttp2_session_mem_send()``. + +The client applications which send client magic must remove the +relevant code. + +Remove HTTP Alternative Services (Alt-Svc) related code ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Alt-Svc specification is not finalized yet. To make our API stable, +we have decided to remove all Alt-Svc related API from nghttp2. + +* ``NGHTTP2_EXT_ALTSVC`` was removed. +* ``nghttp2_ext_altsvc`` was removed. + +We have already removed the functionality of Alt-Svc in v0.7 series +and they have been essentially noop. The application using these +macro and struct, remove those lines. + +Use nghttp2_error in nghttp2_on_invalid_frame_recv_callback ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Previously ``nghttp2_on_invalid_frame_recv_cb_called`` took the +``error_code``, defined in ``nghttp2_error_code``, as parameter. But +they are not detailed enough to debug. Therefore, we decided to use +more detailed ``nghttp2_error`` values instead. + +The application using this callback should update the callback +signature. If it treats ``error_code`` as HTTP/2 error code, update +the code so that it is treated as ``nghttp2_error``. + +Receive client magic by default ++++++++++++++++++++++++++++++++ + +Previously nghttp2 did not process client magic (24 bytes byte +string). To make it deal with it, we had to use +``nghttp2_option_set_recv_client_preface()``. Since v1.0.0, nghttp2 +processes client magic by default and +``nghttp2_option_set_recv_client_preface()`` was removed. + +Some application may want to disable this behaviour, so we added +``nghttp2_option_set_no_recv_client_magic()`` to achieve this. + +The application using ``nghttp2_option_set_recv_client_preface()`` +with nonzero value, just remove it. + +The application using ``nghttp2_option_set_recv_client_preface()`` +with zero value or not using it must use +``nghttp2_option_set_no_recv_client_magic()`` with nonzero value. + +Client, Server and Proxy programs +--------------------------------- + +The ``src`` directory contains the HTTP/2 client, server and proxy programs. + +nghttp - client ++++++++++++++++ + +``nghttp`` is a HTTP/2 client. It can connect to the HTTP/2 server +with prior knowledge, HTTP Upgrade and NPN/ALPN TLS extension. + +It has verbose output mode for framing information. Here is sample +output from ``nghttp`` client: + +.. code-block:: text + + $ nghttp -nv https://nghttp2.org + [ 0.190] Connected + The negotiated protocol: h2 + [ 0.212] recv SETTINGS frame + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [ 0.212] send SETTINGS frame + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [ 0.212] send SETTINGS frame + ; ACK + (niv=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=0, weight=201, exclusive=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=0, weight=101, exclusive=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=0, weight=1, exclusive=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=7, weight=1, exclusive=0) + [ 0.212] send PRIORITY frame + (dep_stream_id=3, weight=1, exclusive=0) + [ 0.212] send HEADERS frame + ; END_STREAM | END_HEADERS | PRIORITY + (padlen=0, dep_stream_id=11, weight=16, exclusive=0) + ; Open new stream + :method: GET + :path: / + :scheme: https + :authority: nghttp2.org + accept: */* + accept-encoding: gzip, deflate + user-agent: nghttp2/1.0.1-DEV + [ 0.221] recv SETTINGS frame + ; ACK + (niv=0) + [ 0.221] recv (stream_id=13) :method: GET + [ 0.221] recv (stream_id=13) :scheme: https + [ 0.221] recv (stream_id=13) :path: /stylesheets/screen.css + [ 0.221] recv (stream_id=13) :authority: nghttp2.org + [ 0.221] recv (stream_id=13) accept-encoding: gzip, deflate + [ 0.222] recv (stream_id=13) user-agent: nghttp2/1.0.1-DEV + [ 0.222] recv PUSH_PROMISE frame + ; END_HEADERS + (padlen=0, promised_stream_id=2) + [ 0.222] recv (stream_id=13) :status: 200 + [ 0.222] recv (stream_id=13) date: Thu, 21 May 2015 16:38:14 GMT + [ 0.222] recv (stream_id=13) content-type: text/html + [ 0.222] recv (stream_id=13) last-modified: Fri, 15 May 2015 15:38:06 GMT + [ 0.222] recv (stream_id=13) etag: W/"555612de-19f6" + [ 0.222] recv (stream_id=13) link: ; rel=preload; as=stylesheet + [ 0.222] recv (stream_id=13) content-encoding: gzip + [ 0.222] recv (stream_id=13) server: nghttpx nghttp2/1.0.1-DEV + [ 0.222] recv (stream_id=13) via: 1.1 nghttpx + [ 0.222] recv (stream_id=13) strict-transport-security: max-age=31536000 + [ 0.222] recv HEADERS frame + ; END_HEADERS + (padlen=0) + ; First response header + [ 0.222] recv DATA frame + ; END_STREAM + [ 0.222] recv (stream_id=2) :status: 200 + [ 0.222] recv (stream_id=2) date: Thu, 21 May 2015 16:38:14 GMT + [ 0.222] recv (stream_id=2) content-type: text/css + [ 0.222] recv (stream_id=2) last-modified: Fri, 15 May 2015 15:38:06 GMT + [ 0.222] recv (stream_id=2) etag: W/"555612de-9845" + [ 0.222] recv (stream_id=2) content-encoding: gzip + [ 0.222] recv (stream_id=2) server: nghttpx nghttp2/1.0.1-DEV + [ 0.222] recv (stream_id=2) via: 1.1 nghttpx + [ 0.222] recv (stream_id=2) strict-transport-security: max-age=31536000 + [ 0.222] recv HEADERS frame + ; END_HEADERS + (padlen=0) + ; First push response header + [ 0.228] recv DATA frame + ; END_STREAM + [ 0.228] send GOAWAY frame + (last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[]) + +The HTTP Upgrade is performed like so: + +.. code-block:: text + + $ nghttp -nvu http://nghttp2.org + [ 0.011] Connected + [ 0.011] HTTP Upgrade request + GET / HTTP/1.1 + Host: nghttp2.org + Connection: Upgrade, HTTP2-Settings + Upgrade: h2c + HTTP2-Settings: AAMAAABkAAQAAP__ + Accept: */* + User-Agent: nghttp2/1.0.1-DEV + + + [ 0.018] HTTP Upgrade response + HTTP/1.1 101 Switching Protocols + Connection: Upgrade + Upgrade: h2c + + + [ 0.018] HTTP Upgrade success + [ 0.018] recv SETTINGS frame + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [ 0.018] send SETTINGS frame + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [ 0.018] send SETTINGS frame + ; ACK + (niv=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=0, weight=201, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=0, weight=101, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=0, weight=1, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=7, weight=1, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=3, weight=1, exclusive=0) + [ 0.018] send PRIORITY frame + (dep_stream_id=11, weight=16, exclusive=0) + [ 0.019] recv (stream_id=1) :method: GET + [ 0.019] recv (stream_id=1) :scheme: http + [ 0.019] recv (stream_id=1) :path: /stylesheets/screen.css + [ 0.019] recv (stream_id=1) host: nghttp2.org + [ 0.019] recv (stream_id=1) user-agent: nghttp2/1.0.1-DEV + [ 0.019] recv PUSH_PROMISE frame + ; END_HEADERS + (padlen=0, promised_stream_id=2) + [ 0.019] recv (stream_id=1) :status: 200 + [ 0.019] recv (stream_id=1) date: Thu, 21 May 2015 16:39:16 GMT + [ 0.019] recv (stream_id=1) content-type: text/html + [ 0.019] recv (stream_id=1) content-length: 6646 + [ 0.019] recv (stream_id=1) last-modified: Fri, 15 May 2015 15:38:06 GMT + [ 0.019] recv (stream_id=1) etag: "555612de-19f6" + [ 0.019] recv (stream_id=1) link: ; rel=preload; as=stylesheet + [ 0.019] recv (stream_id=1) accept-ranges: bytes + [ 0.019] recv (stream_id=1) server: nghttpx nghttp2/1.0.1-DEV + [ 0.019] recv (stream_id=1) via: 1.1 nghttpx + [ 0.019] recv HEADERS frame + ; END_HEADERS + (padlen=0) + ; First response header + [ 0.019] recv DATA frame + ; END_STREAM + [ 0.019] recv (stream_id=2) :status: 200 + [ 0.019] recv (stream_id=2) date: Thu, 21 May 2015 16:39:16 GMT + [ 0.019] recv (stream_id=2) content-type: text/css + [ 0.019] recv (stream_id=2) content-length: 38981 + [ 0.019] recv (stream_id=2) last-modified: Fri, 15 May 2015 15:38:06 GMT + [ 0.019] recv (stream_id=2) etag: "555612de-9845" + [ 0.019] recv (stream_id=2) accept-ranges: bytes + [ 0.019] recv (stream_id=2) server: nghttpx nghttp2/1.0.1-DEV + [ 0.019] recv (stream_id=2) via: 1.1 nghttpx + [ 0.019] recv HEADERS frame + ; END_HEADERS + (padlen=0) + ; First push response header + [ 0.026] recv DATA frame + [ 0.027] recv DATA frame + [ 0.027] send WINDOW_UPDATE frame + (window_size_increment=33343) + [ 0.032] send WINDOW_UPDATE frame + (window_size_increment=33707) + [ 0.032] recv DATA frame + ; END_STREAM + [ 0.032] recv SETTINGS frame + ; ACK + (niv=0) + [ 0.032] send GOAWAY frame + (last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[]) + +Using the ``-s`` option, ``nghttp`` prints out some timing information for +requests, sorted by completion time: + +.. code-block:: text + + $ nghttp -nas https://nghttp2.org/ + ***** Statistics ***** + + Request timing: + responseEnd: the time when last byte of response was received + relative to connectEnd + requestStart: the time just before first byte of request was sent + relative to connectEnd. If '*' is shown, this was + pushed by server. + process: responseEnd - requestStart + code: HTTP status code + size: number of bytes received as response body without + inflation. + URI: request URI + + see http://www.w3.org/TR/resource-timing/#processing-model + + sorted by 'complete' + + id responseEnd requestStart process code size request path + 13 +37.19ms +280us 36.91ms 200 2K / + 2 +72.65ms * +36.38ms 36.26ms 200 8K /stylesheets/screen.css + 17 +77.43ms +38.67ms 38.75ms 200 3K /javascripts/octopress.js + 15 +78.12ms +38.66ms 39.46ms 200 3K /javascripts/modernizr-2.0.js + +Using the ``-r`` option, ``nghttp`` writes more detailed timing data to +the given file in HAR format. + +nghttpd - server +++++++++++++++++ + +``nghttpd`` is a multi-threaded static web server. + +By default, it uses SSL/TLS connection. Use ``--no-tls`` option to +disable it. + +``nghttpd`` only accepts HTTP/2 connections via NPN/ALPN or direct +HTTP/2 connections. No HTTP Upgrade is supported. + +The ``-p`` option allows users to configure server push. + +Just like ``nghttp``, it has a verbose output mode for framing +information. Here is sample output from ``nghttpd``: + +.. code-block:: text + + $ nghttpd --no-tls -v 8080 + IPv4: listen 0.0.0.0:8080 + IPv6: listen :::8080 + [id=1] [ 1.521] send SETTINGS frame + (niv=1) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [id=1] [ 1.521] recv SETTINGS frame + (niv=2) + [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] + [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535] + [id=1] [ 1.521] recv SETTINGS frame + ; ACK + (niv=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=0, weight=201, exclusive=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=0, weight=101, exclusive=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=0, weight=1, exclusive=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=7, weight=1, exclusive=0) + [id=1] [ 1.521] recv PRIORITY frame + (dep_stream_id=3, weight=1, exclusive=0) + [id=1] [ 1.521] recv (stream_id=13) :method: GET + [id=1] [ 1.521] recv (stream_id=13) :path: / + [id=1] [ 1.521] recv (stream_id=13) :scheme: http + [id=1] [ 1.521] recv (stream_id=13) :authority: localhost:8080 + [id=1] [ 1.521] recv (stream_id=13) accept: */* + [id=1] [ 1.521] recv (stream_id=13) accept-encoding: gzip, deflate + [id=1] [ 1.521] recv (stream_id=13) user-agent: nghttp2/1.0.0-DEV + [id=1] [ 1.521] recv HEADERS frame + ; END_STREAM | END_HEADERS | PRIORITY + (padlen=0, dep_stream_id=11, weight=16, exclusive=0) + ; Open new stream + [id=1] [ 1.521] send SETTINGS frame + ; ACK + (niv=0) + [id=1] [ 1.521] send HEADERS frame + ; END_HEADERS + (padlen=0) + ; First response header + :status: 200 + server: nghttpd nghttp2/1.0.0-DEV + content-length: 10 + cache-control: max-age=3600 + date: Fri, 15 May 2015 14:49:04 GMT + last-modified: Tue, 30 Sep 2014 12:40:52 GMT + [id=1] [ 1.522] send DATA frame + ; END_STREAM + [id=1] [ 1.522] stream_id=13 closed + [id=1] [ 1.522] recv GOAWAY frame + (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[]) + [id=1] [ 1.522] closed + +nghttpx - proxy ++++++++++++++++ + +``nghttpx`` is a multi-threaded reverse proxy for HTTP/3, HTTP/2, and +HTTP/1.1, and powers http://nghttp2.org and supports HTTP/2 server +push. + +We reworked ``nghttpx`` command-line interface, and as a result, there +are several incompatibles from 1.8.0 or earlier. This is necessary to +extend its capability, and secure the further feature enhancements in +the future release. Please read `Migration from nghttpx v1.8.0 or +earlier +`_ +to know how to migrate from earlier releases. + +``nghttpx`` implements `important performance-oriented features +`_ in TLS, such as +session IDs, session tickets (with automatic key rotation), OCSP +stapling, dynamic record sizing, ALPN/NPN, forward secrecy and HTTP/2. +``nghttpx`` also offers the functionality to share session cache and +ticket keys among multiple ``nghttpx`` instances via memcached. + +``nghttpx`` has 2 operation modes: + +================== ======================== ================ ============= +Mode option Frontend Backend Note +================== ======================== ================ ============= +default mode HTTP/3, HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Reverse proxy +``--http2-proxy`` HTTP/3, HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Forward proxy +================== ======================== ================ ============= + +The interesting mode at the moment is the default mode. It works like +a reverse proxy and listens for HTTP/3, HTTP/2, and HTTP/1.1 and can +be deployed as a SSL/TLS terminator for existing web server. + +In all modes, the frontend connections are encrypted by SSL/TLS by +default. To disable encryption, use the ``no-tls`` keyword in +``--frontend`` option. If encryption is disabled, incoming HTTP/1.1 +connections can be upgraded to HTTP/2 through HTTP Upgrade. On the +other hard, backend connections are not encrypted by default. To +encrypt backend connections, use ``tls`` keyword in ``--backend`` +option. + +``nghttpx`` supports a configuration file. See the ``--conf`` option and +sample configuration file ``nghttpx.conf.sample``. + +In the default mode, ``nghttpx`` works as reverse proxy to the backend +server: + +.. code-block:: text + + Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server + [reverse proxy] + +With the ``--http2-proxy`` option, it works as forward proxy, and it +is so called secure HTTP/2 proxy: + +.. code-block:: text + + Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy + [secure proxy] (e.g., Squid, ATS) + +The ``Client`` in the above example needs to be configured to use +``nghttpx`` as secure proxy. + +At the time of this writing, both Chrome and Firefox support secure +HTTP/2 proxy. One way to configure Chrome to use a secure proxy is to +create a proxy.pac script like this: + +.. code-block:: javascript + + function FindProxyForURL(url, host) { + return "HTTPS SERVERADDR:PORT"; + } + +``SERVERADDR`` and ``PORT`` is the hostname/address and port of the +machine nghttpx is running on. Please note that Chrome requires a valid +certificate for secure proxy. + +Then run Chrome with the following arguments: + +.. code-block:: text + + $ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn + +The backend HTTP/2 connections can be tunneled through an HTTP proxy. +The proxy is specified using ``--backend-http-proxy-uri``. The +following figure illustrates how nghttpx talks to the outside HTTP/2 +proxy through an HTTP proxy: + +.. code-block:: text + + Client <-- (HTTP/3, HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) -- + + --===================---> HTTP/2 Proxy + (HTTP proxy tunnel) (e.g., nghttpx -s) + +Benchmarking tool +----------------- + +The ``h2load`` program is a benchmarking tool for HTTP/3, HTTP/2, and +HTTP/1.1. The UI of ``h2load`` is heavily inspired by ``weighttp`` +(https://github.com/lighttpd/weighttp). The typical usage is as +follows: + +.. code-block:: text + + $ h2load -n100000 -c100 -m100 https://localhost:8443/ + starting benchmark... + spawning thread #0: 100 concurrent clients, 100000 total requests + Protocol: TLSv1.2 + Cipher: ECDHE-RSA-AES128-GCM-SHA256 + Server Temp Key: ECDH P-256 256 bits + progress: 10% done + progress: 20% done + progress: 30% done + progress: 40% done + progress: 50% done + progress: 60% done + progress: 70% done + progress: 80% done + progress: 90% done + progress: 100% done + + finished in 771.26ms, 129658 req/s, 4.71MB/s + requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored + status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx + traffic: 3812300 bytes total, 1009900 bytes headers, 1000000 bytes data + min max mean sd +/- sd + time for request: 25.12ms 124.55ms 51.07ms 15.36ms 84.87% + time for connect: 208.94ms 254.67ms 241.38ms 7.95ms 63.00% + time to 1st byte: 209.11ms 254.80ms 241.51ms 7.94ms 63.00% + +The above example issued total 100,000 requests, using 100 concurrent +clients (in other words, 100 HTTP/2 sessions), and a maximum of 100 streams +per client. With the ``-t`` option, ``h2load`` will use multiple native +threads to avoid saturating a single core on client side. + +.. warning:: + + **Don't use this tool against publicly available servers.** That is + considered a DOS attack. Please only use it against your private + servers. + +If the experimental HTTP/3 is enabled, h2load can send requests to +HTTP/3 server. To do this, specify ``h3`` to ``--npn-list`` option +like so: + +.. code-block:: text + + $ h2load --npn-list h3 https://127.0.0.1:4433 + +HPACK tools +----------- + +The ``src`` directory contains the HPACK tools. The ``deflatehd`` program is a +command-line header compression tool. The ``inflatehd`` program is a +command-line header decompression tool. Both tools read input from +stdin and write output to stdout. Errors are written to stderr. +They take JSON as input and output. We (mostly) use the same JSON data +format described at https://github.com/http2jp/hpack-test-case. + +deflatehd - header compressor ++++++++++++++++++++++++++++++ + +The ``deflatehd`` program reads JSON data or HTTP/1-style header fields from +stdin and outputs compressed header block in JSON. + +For the JSON input, the root JSON object must include a ``cases`` key. +Its value has to include the sequence of input header set. They share +the same compression context and are processed in the order they +appear. Each item in the sequence is a JSON object and it must +include a ``headers`` key. Its value is an array of JSON objects, +which includes exactly one name/value pair. + +Example: + +.. code-block:: json + + { + "cases": + [ + { + "headers": [ + { ":method": "GET" }, + { ":path": "/" } + ] + }, + { + "headers": [ + { ":method": "POST" }, + { ":path": "/" } + ] + } + ] + } + + +With the ``-t`` option, the program can accept more familiar HTTP/1 style +header field blocks. Each header set is delimited by an empty line: + +Example: + +.. code-block:: text + + :method: GET + :scheme: https + :path: / + + :method: POST + user-agent: nghttp2 + +The output is in JSON object. It should include a ``cases`` key and its +value is an array of JSON objects, which has at least the following keys: + +seq + The index of header set in the input. + +input_length + The sum of the length of the name/value pairs in the input. + +output_length + The length of the compressed header block. + +percentage_of_original_size + ``output_length`` / ``input_length`` * 100 + +wire + The compressed header block as a hex string. + +headers + The input header set. + +header_table_size + The header table size adjusted before deflating the header set. + +Examples: + +.. code-block:: json + + { + "cases": + [ + { + "seq": 0, + "input_length": 66, + "output_length": 20, + "percentage_of_original_size": 30.303030303030305, + "wire": "01881f3468e5891afcbf83868a3d856659c62e3f", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "GET" + }, + { + ":path": "/" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096 + } + , + { + "seq": 1, + "input_length": 74, + "output_length": 10, + "percentage_of_original_size": 13.513513513513514, + "wire": "88448504252dd5918485", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "POST" + }, + { + ":path": "/account" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096 + } + ] + } + + +The output can be used as the input for ``inflatehd`` and +``deflatehd``. + +With the ``-d`` option, the extra ``header_table`` key is added and its +associated value includes the state of dynamic header table after the +corresponding header set was processed. The value includes at least +the following keys: + +entries + The entry in the header table. If ``referenced`` is ``true``, it + is in the reference set. The ``size`` includes the overhead (32 + bytes). The ``index`` corresponds to the index of header table. + The ``name`` is the header field name and the ``value`` is the + header field value. + +size + The sum of the spaces entries occupied, this includes the + entry overhead. + +max_size + The maximum header table size. + +deflate_size + The sum of the spaces entries occupied within + ``max_deflate_size``. + +max_deflate_size + The maximum header table size the encoder uses. This can be smaller + than ``max_size``. In this case, the encoder only uses up to first + ``max_deflate_size`` buffer. Since the header table size is still + ``max_size``, the encoder has to keep track of entries outside the + ``max_deflate_size`` but inside the ``max_size`` and make sure + that they are no longer referenced. + +Example: + +.. code-block:: json + + { + "cases": + [ + { + "seq": 0, + "input_length": 66, + "output_length": 20, + "percentage_of_original_size": 30.303030303030305, + "wire": "01881f3468e5891afcbf83868a3d856659c62e3f", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "GET" + }, + { + ":path": "/" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096, + "header_table": { + "entries": [ + { + "index": 1, + "name": "user-agent", + "value": "nghttp2", + "referenced": true, + "size": 49 + }, + { + "index": 2, + "name": ":scheme", + "value": "https", + "referenced": true, + "size": 44 + }, + { + "index": 3, + "name": ":path", + "value": "/", + "referenced": true, + "size": 38 + }, + { + "index": 4, + "name": ":method", + "value": "GET", + "referenced": true, + "size": 42 + }, + { + "index": 5, + "name": ":authority", + "value": "example.org", + "referenced": true, + "size": 53 + } + ], + "size": 226, + "max_size": 4096, + "deflate_size": 226, + "max_deflate_size": 4096 + } + } + , + { + "seq": 1, + "input_length": 74, + "output_length": 10, + "percentage_of_original_size": 13.513513513513514, + "wire": "88448504252dd5918485", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "POST" + }, + { + ":path": "/account" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096, + "header_table": { + "entries": [ + { + "index": 1, + "name": ":method", + "value": "POST", + "referenced": true, + "size": 43 + }, + { + "index": 2, + "name": "user-agent", + "value": "nghttp2", + "referenced": true, + "size": 49 + }, + { + "index": 3, + "name": ":scheme", + "value": "https", + "referenced": true, + "size": 44 + }, + { + "index": 4, + "name": ":path", + "value": "/", + "referenced": false, + "size": 38 + }, + { + "index": 5, + "name": ":method", + "value": "GET", + "referenced": false, + "size": 42 + }, + { + "index": 6, + "name": ":authority", + "value": "example.org", + "referenced": true, + "size": 53 + } + ], + "size": 269, + "max_size": 4096, + "deflate_size": 269, + "max_deflate_size": 4096 + } + } + ] + } + +inflatehd - header decompressor ++++++++++++++++++++++++++++++++ + +The ``inflatehd`` program reads JSON data from stdin and outputs decompressed +name/value pairs in JSON. + +The root JSON object must include the ``cases`` key. Its value has to +include the sequence of compressed header blocks. They share the same +compression context and are processed in the order they appear. Each +item in the sequence is a JSON object and it must have at least a +``wire`` key. Its value is a compressed header block as a hex string. + +Example: + +.. code-block:: json + + { + "cases": + [ + { "wire": "8285" }, + { "wire": "8583" } + ] + } + +The output is a JSON object. It should include a ``cases`` key and its +value is an array of JSON objects, which has at least following keys: + +seq + The index of the header set in the input. + +headers + A JSON array that includes decompressed name/value pairs. + +wire + The compressed header block as a hex string. + +header_table_size + The header table size adjusted before inflating compressed header + block. + +Example: + +.. code-block:: json + + { + "cases": + [ + { + "seq": 0, + "wire": "01881f3468e5891afcbf83868a3d856659c62e3f", + "headers": [ + { + ":authority": "example.org" + }, + { + ":method": "GET" + }, + { + ":path": "/" + }, + { + ":scheme": "https" + }, + { + "user-agent": "nghttp2" + } + ], + "header_table_size": 4096 + } + , + { + "seq": 1, + "wire": "88448504252dd5918485", + "headers": [ + { + ":method": "POST" + }, + { + ":path": "/account" + }, + { + "user-agent": "nghttp2" + }, + { + ":scheme": "https" + }, + { + ":authority": "example.org" + } + ], + "header_table_size": 4096 + } + ] + } + +The output can be used as the input for ``deflatehd`` and +``inflatehd``. + +With the ``-d`` option, the extra ``header_table`` key is added and its +associated value includes the state of the dynamic header table after the +corresponding header set was processed. The format is the same as +``deflatehd``. + +Contribution +------------ + +[This text was composed based on 1.2. License section of curl/libcurl +project.] + +When contributing with code, you agree to put your changes and new +code under the same license nghttp2 is already using unless stated and +agreed otherwise. + +When changing existing source code, do not alter the copyright of +the original file(s). The copyright will still be owned by the +original creator(s) or those who have been assigned copyright by the +original author(s). + +By submitting a patch to the nghttp2 project, you (or your employer, as +the case may be) agree to assign the copyright of your submission to us. +.. the above really needs to be reworded to pass legal muster. +We will credit you for your +changes as far as possible, to give credit but also to keep a trace +back to who made what changes. Please always provide us with your +full real name when contributing! + +See `Contribution Guidelines +`_ for more +details. + +Reporting vulnerability +----------------------- + +If you find a vulnerability in our software, please send the email to +"tatsuhiro.t at gmail dot com" about its details instead of submitting +issues on github issue page. It is a standard practice not to +disclose vulnerability information publicly until a fixed version is +released, or mitigation is worked out. + +In the future, we may setup a dedicated mail address for this purpose. + +Versioning +---------- + +In general, we follow `Semantic Versioning `_. + +We may release PATCH releases between the regular releases, mainly for +severe security bug fixes. + +We have no plan to break API compatibility changes involving soname +bump, so MAJOR version will stay 1 for the foreseeable future. + +License +------- + +The MIT License diff --git a/lib/nghttp2/android-config b/lib/nghttp2/android-config new file mode 100755 index 00000000000..4a137dea748 --- /dev/null +++ b/lib/nghttp2/android-config @@ -0,0 +1,37 @@ +#!/bin/sh +# +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2013 Tatsuhiro Tsujikawa +# +# 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. + +. ./android-env + +./configure \ + --disable-shared \ + --host=$TARGET \ + --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \ + --without-libxml2 \ + --disable-examples \ + --disable-threads \ + CPPFLAGS="-fPIE -I$PREFIX/include" \ + PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \ + LDFLAGS="-fPIE -pie -L$PREFIX/lib" diff --git a/lib/nghttp2/android-env b/lib/nghttp2/android-env new file mode 100755 index 00000000000..4412fcf90c2 --- /dev/null +++ b/lib/nghttp2/android-env @@ -0,0 +1,40 @@ +#!/bin/sh +# +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2022 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. + +if [ -z "$NDK" ]; then + echo 'No $NDK specified.' + exit 1 +fi + +export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64 +export TARGET=aarch64-linux-android +export API=33 +export AR=$TOOLCHAIN/bin/llvm-ar +export CC=$TOOLCHAIN/bin/$TARGET$API-clang +export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++ +export LD=$TOOLCHAIN/bin/ld +export RANDLIB=$TOOLCHAIN/bin/llvm-ranlib +export STRIP=$TOOLCHAIN/bin/llvm-strip +export PREFIX=$NDK/usr/local diff --git a/lib/nghttp2/author.py b/lib/nghttp2/author.py new file mode 100755 index 00000000000..4bf97c4964c --- /dev/null +++ b/lib/nghttp2/author.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# script to extract commit author's name from standard input. The +# input should be :, one per line. +# This script expects the input is created by git-log command: +# +# git log --format=%aN:%aE +# +# This script removes duplicates based on email address, breaking a +# tie with longer author name. Among the all author names extract the +# previous step, we remove duplicate by case-insensitive match. +# +# So we can do this in one line: +# +# git log --format=%aN:%aE | sort | uniq | ./author.py > authors + +import sys + +edict = {} + +for line in sys.stdin: + author, email = line.strip().split(':', 1) + if email in edict: + an = edict[email] + if len(an) < len(author) or an > author: + sys.stderr.write( + 'eliminated {} in favor of {}\n'.format(an, author)) + edict[email] = author + else: + sys.stderr.write( + 'eliminated {} in favor of {}\n'.format(author, an)) + else: + edict[email] = author + +names = list(sorted(edict.values())) + +ndict = {} + +for name in names: + lowname = name.lower() + if lowname in ndict: + an = ndict[lowname] + if an > name: + sys.stderr.write('eliminated {} in favor of {}\n'.format(an, name)) + ndict[lowname] = name + else: + sys.stderr.write('eliminated {} in favor of {}\n'.format(name, an)) + else: + ndict[lowname] = name + +for name in sorted(ndict.values()): + print(name) diff --git a/lib/nghttp2/bpf/CMakeLists.txt b/lib/nghttp2/bpf/CMakeLists.txt new file mode 100644 index 00000000000..904b82184c0 --- /dev/null +++ b/lib/nghttp2/bpf/CMakeLists.txt @@ -0,0 +1,13 @@ +if(LIBBPF_FOUND) + add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o" + COMMAND ${CMAKE_C_COMPILER} ${BPFCFLAGS} ${EXTRABPFCFLAGS} -I${LIBBPF_INCLUDE_DIRS} -target bpf -c reuseport_kern.c -o "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + VERBATIM) + + add_custom_target(bpf ALL + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o" + VERBATIM) + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/reuseport_kern.o" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${CMAKE_PROJECT_NAME}") +endif() diff --git a/lib/nghttp2/bpf/Makefile.am b/lib/nghttp2/bpf/Makefile.am new file mode 100644 index 00000000000..9017fd95e16 --- /dev/null +++ b/lib/nghttp2/bpf/Makefile.am @@ -0,0 +1,40 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2021 Tatsuhiro Tsujikawa + +# 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 reuseport_kern.c + +if HAVE_LIBBPF + +bpf_pkglibdir = $(pkglibdir) +bpf_pkglib_DATA = reuseport_kern.o + +all: $(builddir)/reuseport_kern.o + +$(builddir)/reuseport_kern.o: reuseport_kern.c + $(CC) @LIBBPF_CFLAGS@ @BPFCFLAGS@ @EXTRABPFCFLAGS@ \ + -target bpf -c $< -o $@ + +clean-local: + -rm -f reuseport_kern.o + +endif # HAVE_LIBBPF diff --git a/lib/nghttp2/bpf/reuseport_kern.c b/lib/nghttp2/bpf/reuseport_kern.c new file mode 100644 index 00000000000..74c08c5aae1 --- /dev/null +++ b/lib/nghttp2/bpf/reuseport_kern.c @@ -0,0 +1,663 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 +#include + +#include + +/* + * How to compile: + * + * clang-12 -O2 -Wall -target bpf -g -c reuseport_kern.c -o reuseport_kern.o \ + * -I/path/to/kernel/include + * + * See + * https://www.kernel.org/doc/Documentation/kbuild/headers_install.txt + * how to install kernel header files. + */ + +/* AES_CBC_decrypt_buffer: https://github.com/kokke/tiny-AES-c + License is Public Domain. Commit hash: + 12e7744b4919e9d55de75b7ab566326a1c8e7a67 */ + +#define AES_BLOCKLEN \ + 16 /* Block length in bytes - AES is 128b block \ + only */ + +#define AES_KEYLEN 16 /* Key length in bytes */ +#define AES_keyExpSize 176 + +struct AES_ctx { + __u8 RoundKey[AES_keyExpSize]; +}; + +/* The number of columns comprising a state in AES. This is a constant + in AES. Value=4 */ +#define Nb 4 + +#define Nk 4 /* The number of 32 bit words in a key. */ +#define Nr 10 /* The number of rounds in AES Cipher. */ + +/* state - array holding the intermediate results during + decryption. */ +typedef __u8 state_t[4][4]; + +/* The lookup-tables are marked const so they can be placed in + read-only storage instead of RAM The numbers below can be computed + dynamically trading ROM for RAM - This can be useful in (embedded) + bootloader applications, where ROM is often limited. */ +static const __u8 sbox[256] = { + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, + 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, + 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, + 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, + 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, + 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, + 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, + 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, + 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, + 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, + 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, + 0xb0, 0x54, 0xbb, 0x16}; + +static const __u8 rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, + 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, + 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, + 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, + 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, + 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, + 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, + 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, + 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, + 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, + 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, + 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, + 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, + 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, + 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, + 0x55, 0x21, 0x0c, 0x7d}; + +/* The round constant word array, Rcon[i], contains the values given + by x to the power (i-1) being powers of x (x is denoted as {02}) in + the field GF(2^8) */ +static const __u8 Rcon[11] = {0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, + 0x20, 0x40, 0x80, 0x1b, 0x36}; + +#define getSBoxValue(num) (sbox[(num)]) + +/* This function produces Nb(Nr+1) round keys. The round keys are used + in each round to decrypt the states. */ +static void KeyExpansion(__u8 *RoundKey, const __u8 *Key) { + unsigned i, j, k; + __u8 tempa[4]; /* Used for the column/row operations */ + + /* The first round key is the key itself. */ + for (i = 0; i < Nk; ++i) { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + /* All other round keys are found from the previous round keys. */ + for (i = Nk; i < Nb * (Nr + 1); ++i) { + { + k = (i - 1) * 4; + tempa[0] = RoundKey[k + 0]; + tempa[1] = RoundKey[k + 1]; + tempa[2] = RoundKey[k + 2]; + tempa[3] = RoundKey[k + 3]; + } + + if (i % Nk == 0) { + /* This function shifts the 4 bytes in a word to the left once. + [a0,a1,a2,a3] becomes [a1,a2,a3,a0] */ + + /* Function RotWord() */ + { + const __u8 u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + /* SubWord() is a function that takes a four-byte input word and + applies the S-box to each of the four bytes to produce an + output word. */ + + /* Function Subword() */ + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i / Nk]; + } + j = i * 4; + k = (i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +static void AES_init_ctx(struct AES_ctx *ctx, const __u8 *key) { + KeyExpansion(ctx->RoundKey, key); +} + +/* This function adds the round key to state. The round key is added + to the state by an XOR function. */ +static void AddRoundKey(__u8 round, state_t *state, const __u8 *RoundKey) { + __u8 i, j; + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +static __u8 xtime(__u8 x) { return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); } + +#define Multiply(x, y) \ + (((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ \ + ((y >> 2 & 1) * xtime(xtime(x))) ^ \ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x)))))) + +#define getSBoxInvert(num) (rsbox[(num)]) + +/* MixColumns function mixes the columns of the state matrix. The + method used to multiply may be difficult to understand for the + inexperienced. Please use the references to gain more + information. */ +static void InvMixColumns(state_t *state) { + int i; + __u8 a, b, c, d; + for (i = 0; i < 4; ++i) { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ + Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ + Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ + Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ + Multiply(d, 0x0e); + } +} + +extern __u32 LINUX_KERNEL_VERSION __kconfig; + +/* The SubBytes Function Substitutes the values in the state matrix + with values in an S-box. */ +static void InvSubBytes(state_t *state) { + __u8 i, j; + if (LINUX_KERNEL_VERSION < KERNEL_VERSION(5, 10, 0)) { + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { + /* Ubuntu 20.04 LTS kernel 5.4.0 needs this workaround + otherwise "math between map_value pointer and register with + unbounded min value is not allowed". 5.10.0 is a kernel + version that works but it might not be the minimum + version. */ + __u8 k = (*state)[j][i]; + (*state)[j][i] = k ? getSBoxInvert(k) : getSBoxInvert(0); + } + } + } else { + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } + } +} + +static void InvShiftRows(state_t *state) { + __u8 temp; + + /* Rotate first row 1 columns to right */ + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + /* Rotate second row 2 columns to right */ + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + /* Rotate third row 3 columns to right */ + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} + +static void InvCipher(state_t *state, const __u8 *RoundKey) { + /* Add the First round key to the state before starting the + rounds. */ + AddRoundKey(Nr, state, RoundKey); + + /* There will be Nr rounds. The first Nr-1 rounds are identical. + These Nr rounds are executed in the loop below. Last one without + InvMixColumn() */ + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 1, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 2, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 3, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 4, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 5, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 6, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 7, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 8, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 9, state, RoundKey); + InvMixColumns(state); + + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(Nr - 10, state, RoundKey); +} + +static void AES_ECB_decrypt(const struct AES_ctx *ctx, __u8 *buf) { + /* The next function call decrypts the PlainText with the Key using + AES algorithm. */ + InvCipher((state_t *)buf, ctx->RoundKey); +} + +/* rol32: From linux kernel source code */ + +/** + * rol32 - rotate a 32-bit value left + * @word: value to rotate + * @shift: bits to roll + */ +static inline __u32 rol32(__u32 word, unsigned int shift) { + return (word << shift) | (word >> ((-shift) & 31)); +} + +/* jhash.h: Jenkins hash support. + * + * Copyright (C) 2006. Bob Jenkins (bob_jenkins@burtleburtle.net) + * + * https://burtleburtle.net/bob/hash/ + * + * These are the credits from Bob's sources: + * + * lookup3.c, by Bob Jenkins, May 2006, Public Domain. + * + * These are functions for producing 32-bit hashes for hash table lookup. + * hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() + * are externally useful functions. Routines to test the hash are included + * if SELF_TEST is defined. You can use this free for any purpose. It's in + * the public domain. It has no warranty. + * + * Copyright (C) 2009-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * I've modified Bob's hash to be useful in the Linux kernel, and + * any bugs present are my fault. + * Jozsef + */ + +/* __jhash_final - final mixing of 3 32-bit values (a,b,c) into c */ +#define __jhash_final(a, b, c) \ + { \ + c ^= b; \ + c -= rol32(b, 14); \ + a ^= c; \ + a -= rol32(c, 11); \ + b ^= a; \ + b -= rol32(a, 25); \ + c ^= b; \ + c -= rol32(b, 16); \ + a ^= c; \ + a -= rol32(c, 4); \ + b ^= a; \ + b -= rol32(a, 14); \ + c ^= b; \ + c -= rol32(b, 24); \ + } + +/* __jhash_nwords - hash exactly 3, 2 or 1 word(s) */ +static inline __u32 __jhash_nwords(__u32 a, __u32 b, __u32 c, __u32 initval) { + a += initval; + b += initval; + c += initval; + + __jhash_final(a, b, c); + + return c; +} + +/* An arbitrary initial parameter */ +#define JHASH_INITVAL 0xdeadbeef + +static inline __u32 jhash_2words(__u32 a, __u32 b, __u32 initval) { + return __jhash_nwords(a, b, 0, initval + JHASH_INITVAL + (2 << 2)); +} + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 255); + __type(key, __u64); + __type(value, __u32); +} cid_prefix_map SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY); + __uint(max_entries, 255); + __type(key, __u32); + __type(value, __u32); +} reuseport_array SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 3); + __type(key, __u32); + __type(value, __u64); +} sk_info SEC(".maps"); + +typedef struct quic_hd { + __u8 *dcid; + __u32 dcidlen; + __u32 dcid_offset; + __u8 type; +} quic_hd; + +#define SV_DCIDLEN 20 +#define MAX_DCIDLEN 20 +#define MIN_DCIDLEN 8 +#define CID_PREFIXLEN 8 +#define CID_PREFIX_OFFSET 1 + +enum { + NGTCP2_PKT_INITIAL = 0x0, + NGTCP2_PKT_0RTT = 0x1, + NGTCP2_PKT_HANDSHAKE = 0x2, + NGTCP2_PKT_SHORT = 0x40, +}; + +static inline int parse_quic(quic_hd *qhd, __u8 *data, __u8 *data_end) { + __u8 *p; + __u64 dcidlen; + + if (*data & 0x80) { + p = data + 1 + 4; + + /* Do not check the actual DCID length because we might not buffer + entire DCID here. */ + dcidlen = *p; + + if (dcidlen > MAX_DCIDLEN || dcidlen < MIN_DCIDLEN) { + return -1; + } + + ++p; + + qhd->type = (*data & 0x30) >> 4; + qhd->dcid = p; + qhd->dcidlen = dcidlen; + qhd->dcid_offset = 6; + } else { + qhd->type = NGTCP2_PKT_SHORT; + qhd->dcid = data + 1; + qhd->dcidlen = SV_DCIDLEN; + qhd->dcid_offset = 1; + } + + return 0; +} + +static __u32 hash(const __u8 *data, __u32 datalen, __u32 initval) { + __u32 a, b; + + a = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; + b = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7]; + + return jhash_2words(a, b, initval); +} + +static __u32 sk_index_from_dcid(const quic_hd *qhd, + const struct sk_reuseport_md *reuse_md, + __u64 num_socks) { + __u32 len = qhd->dcidlen; + __u32 h = reuse_md->hash; + __u8 hbuf[8]; + + if (len > 16) { + __builtin_memset(hbuf, 0, sizeof(hbuf)); + + switch (len) { + case 20: + __builtin_memcpy(hbuf, qhd->dcid + 16, 4); + break; + case 19: + __builtin_memcpy(hbuf, qhd->dcid + 16, 3); + break; + case 18: + __builtin_memcpy(hbuf, qhd->dcid + 16, 2); + break; + case 17: + __builtin_memcpy(hbuf, qhd->dcid + 16, 1); + break; + } + + h = hash(hbuf, sizeof(hbuf), h); + len = 16; + } + + if (len > 8) { + __builtin_memset(hbuf, 0, sizeof(hbuf)); + + switch (len) { + case 16: + __builtin_memcpy(hbuf, qhd->dcid + 8, 8); + break; + case 15: + __builtin_memcpy(hbuf, qhd->dcid + 8, 7); + break; + case 14: + __builtin_memcpy(hbuf, qhd->dcid + 8, 6); + break; + case 13: + __builtin_memcpy(hbuf, qhd->dcid + 8, 5); + break; + case 12: + __builtin_memcpy(hbuf, qhd->dcid + 8, 4); + break; + case 11: + __builtin_memcpy(hbuf, qhd->dcid + 8, 3); + break; + case 10: + __builtin_memcpy(hbuf, qhd->dcid + 8, 2); + break; + case 9: + __builtin_memcpy(hbuf, qhd->dcid + 8, 1); + break; + } + + h = hash(hbuf, sizeof(hbuf), h); + len = 8; + } + + return hash(qhd->dcid, len, h) % num_socks; +} + +SEC("sk_reuseport") +int select_reuseport(struct sk_reuseport_md *reuse_md) { + __u32 sk_index, *psk_index; + __u64 *pnum_socks, *pkey; + __u32 zero = 0, key_high_idx = 1, key_low_idx = 2; + int rv; + quic_hd qhd; + __u8 qpktbuf[6 + MAX_DCIDLEN]; + struct AES_ctx aes_ctx; + __u8 key[AES_KEYLEN]; + __u8 *cid_prefix; + + if (bpf_skb_load_bytes(reuse_md, sizeof(struct udphdr), qpktbuf, + sizeof(qpktbuf)) != 0) { + return SK_DROP; + } + + pnum_socks = bpf_map_lookup_elem(&sk_info, &zero); + if (pnum_socks == NULL) { + return SK_DROP; + } + + pkey = bpf_map_lookup_elem(&sk_info, &key_high_idx); + if (pkey == NULL) { + return SK_DROP; + } + + __builtin_memcpy(key, pkey, sizeof(*pkey)); + + pkey = bpf_map_lookup_elem(&sk_info, &key_low_idx); + if (pkey == NULL) { + return SK_DROP; + } + + __builtin_memcpy(key + sizeof(*pkey), pkey, sizeof(*pkey)); + + rv = parse_quic(&qhd, qpktbuf, qpktbuf + sizeof(qpktbuf)); + if (rv != 0) { + return SK_DROP; + } + + AES_init_ctx(&aes_ctx, key); + + switch (qhd.type) { + case NGTCP2_PKT_INITIAL: + case NGTCP2_PKT_0RTT: + if (qhd.dcidlen == SV_DCIDLEN) { + cid_prefix = qhd.dcid + CID_PREFIX_OFFSET; + AES_ECB_decrypt(&aes_ctx, cid_prefix); + + psk_index = bpf_map_lookup_elem(&cid_prefix_map, cid_prefix); + if (psk_index != NULL) { + sk_index = *psk_index; + + break; + } + } + + sk_index = sk_index_from_dcid(&qhd, reuse_md, *pnum_socks); + + break; + case NGTCP2_PKT_HANDSHAKE: + case NGTCP2_PKT_SHORT: + if (qhd.dcidlen != SV_DCIDLEN) { + return SK_DROP; + } + + cid_prefix = qhd.dcid + CID_PREFIX_OFFSET; + AES_ECB_decrypt(&aes_ctx, cid_prefix); + + psk_index = bpf_map_lookup_elem(&cid_prefix_map, cid_prefix); + if (psk_index == NULL) { + sk_index = sk_index_from_dcid(&qhd, reuse_md, *pnum_socks); + + break; + } + + sk_index = *psk_index; + + break; + default: + return SK_DROP; + } + + rv = bpf_sk_select_reuseport(reuse_md, &reuseport_array, &sk_index, 0); + if (rv != 0) { + return SK_DROP; + } + + return SK_PASS; +} diff --git a/lib/nghttp2/cmake/ExtractValidFlags.cmake b/lib/nghttp2/cmake/ExtractValidFlags.cmake new file mode 100644 index 00000000000..ccd57dc8133 --- /dev/null +++ b/lib/nghttp2/cmake/ExtractValidFlags.cmake @@ -0,0 +1,31 @@ +# Convenience function that checks the availability of certain +# C or C++ compiler flags and returns valid ones as a string. + +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) + +function(extract_valid_c_flags varname) + set(valid_flags) + foreach(flag IN LISTS ARGN) + string(REGEX REPLACE "[^a-zA-Z0-9_]+" "_" flag_var ${flag}) + set(flag_var "C_FLAG_${flag_var}") + check_c_compiler_flag("${flag}" "${flag_var}") + if(${flag_var}) + set(valid_flags "${valid_flags} ${flag}") + endif() + endforeach() + set(${varname} "${valid_flags}" PARENT_SCOPE) +endfunction() + +function(extract_valid_cxx_flags varname) + set(valid_flags) + foreach(flag IN LISTS ARGN) + string(REGEX REPLACE "[^a-zA-Z0-9_]+" "_" flag_var ${flag}) + set(flag_var "CXX_FLAG_${flag_var}") + check_cxx_compiler_flag("${flag}" "${flag_var}") + if(${flag_var}) + set(valid_flags "${valid_flags} ${flag}") + endif() + endforeach() + set(${varname} "${valid_flags}" PARENT_SCOPE) +endfunction() diff --git a/lib/nghttp2/cmake/FindCUnit.cmake b/lib/nghttp2/cmake/FindCUnit.cmake new file mode 100644 index 00000000000..ada87c16531 --- /dev/null +++ b/lib/nghttp2/cmake/FindCUnit.cmake @@ -0,0 +1,40 @@ +# - Try to find cunit +# Once done this will define +# CUNIT_FOUND - System has cunit +# CUNIT_INCLUDE_DIRS - The cunit include directories +# CUNIT_LIBRARIES - The libraries needed to use cunit + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_CUNIT QUIET cunit) + +find_path(CUNIT_INCLUDE_DIR + NAMES CUnit/CUnit.h + HINTS ${PC_CUNIT_INCLUDE_DIRS} +) +find_library(CUNIT_LIBRARY + NAMES cunit + HINTS ${PC_CUNIT_LIBRARY_DIRS} +) + +if(CUNIT_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+CU_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${CUNIT_INCLUDE_DIR}/CUnit/CUnit.h" + CUNIT_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + CUNIT_VERSION "${CUNIT_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set CUNIT_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(CUnit REQUIRED_VARS + CUNIT_LIBRARY CUNIT_INCLUDE_DIR + VERSION_VAR CUNIT_VERSION) + +if(CUNIT_FOUND) + set(CUNIT_LIBRARIES ${CUNIT_LIBRARY}) + set(CUNIT_INCLUDE_DIRS ${CUNIT_INCLUDE_DIR}) +endif() + +mark_as_advanced(CUNIT_INCLUDE_DIR CUNIT_LIBRARY) diff --git a/lib/nghttp2/cmake/FindJansson.cmake b/lib/nghttp2/cmake/FindJansson.cmake new file mode 100644 index 00000000000..4c4bcb73d87 --- /dev/null +++ b/lib/nghttp2/cmake/FindJansson.cmake @@ -0,0 +1,40 @@ +# - Try to find jansson +# Once done this will define +# JANSSON_FOUND - System has jansson +# JANSSON_INCLUDE_DIRS - The jansson include directories +# JANSSON_LIBRARIES - The libraries needed to use jansson + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_JANSSON QUIET jansson) + +find_path(JANSSON_INCLUDE_DIR + NAMES jansson.h + HINTS ${PC_JANSSON_INCLUDE_DIRS} +) +find_library(JANSSON_LIBRARY + NAMES jansson + HINTS ${PC_JANSSON_LIBRARY_DIRS} +) + +if(JANSSON_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+JANSSON_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${JANSSON_INCLUDE_DIR}/jansson.h" + JANSSON_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + JANSSON_VERSION "${JANSSON_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set JANSSON_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Jansson REQUIRED_VARS + JANSSON_LIBRARY JANSSON_INCLUDE_DIR + VERSION_VAR JANSSON_VERSION) + +if(JANSSON_FOUND) + set(JANSSON_LIBRARIES ${JANSSON_LIBRARY}) + set(JANSSON_INCLUDE_DIRS ${JANSSON_INCLUDE_DIR}) +endif() + +mark_as_advanced(JANSSON_INCLUDE_DIR JANSSON_LIBRARY) diff --git a/lib/nghttp2/cmake/FindJemalloc.cmake b/lib/nghttp2/cmake/FindJemalloc.cmake new file mode 100644 index 00000000000..b7815fa0fa7 --- /dev/null +++ b/lib/nghttp2/cmake/FindJemalloc.cmake @@ -0,0 +1,40 @@ +# - Try to find jemalloc +# Once done this will define +# JEMALLOC_FOUND - System has jemalloc +# JEMALLOC_INCLUDE_DIRS - The jemalloc include directories +# JEMALLOC_LIBRARIES - The libraries needed to use jemalloc + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_JEMALLOC QUIET jemalloc) + +find_path(JEMALLOC_INCLUDE_DIR + NAMES jemalloc/jemalloc.h + HINTS ${PC_JEMALLOC_INCLUDE_DIRS} +) +find_library(JEMALLOC_LIBRARY + NAMES jemalloc + HINTS ${PC_JEMALLOC_LIBRARY_DIRS} +) + +if(JEMALLOC_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+JEMALLOC_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${JEMALLOC_INCLUDE_DIR}/jemalloc/jemalloc.h" + JEMALLOC_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + JEMALLOC_VERSION "${JEMALLOC_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set JEMALLOC_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Jemalloc REQUIRED_VARS + JEMALLOC_LIBRARY JEMALLOC_INCLUDE_DIR + VERSION_VAR JEMALLOC_VERSION) + +if(JEMALLOC_FOUND) + set(JEMALLOC_LIBRARIES ${JEMALLOC_LIBRARY}) + set(JEMALLOC_INCLUDE_DIRS ${JEMALLOC_INCLUDE_DIR}) +endif() + +mark_as_advanced(JEMALLOC_INCLUDE_DIR JEMALLOC_LIBRARY) diff --git a/lib/nghttp2/cmake/FindLibbpf.cmake b/lib/nghttp2/cmake/FindLibbpf.cmake new file mode 100644 index 00000000000..7f76255e51a --- /dev/null +++ b/lib/nghttp2/cmake/FindLibbpf.cmake @@ -0,0 +1,32 @@ +# - Try to find libbpf +# Once done this will define +# LIBBPF_FOUND - System has libbpf +# LIBBPF_INCLUDE_DIRS - The libbpf include directories +# LIBBPF_LIBRARIES - The libraries needed to use libbpf + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBBPF QUIET libbpf) + +find_path(LIBBPF_INCLUDE_DIR + NAMES bpf/bpf.h + HINTS ${PC_LIBBPF_INCLUDE_DIRS} +) +find_library(LIBBPF_LIBRARY + NAMES bpf + HINTS ${PC_LIBBPF_LIBRARY_DIRS} +) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBBPF_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libbpf REQUIRED_VARS + LIBBPF_LIBRARY LIBBPF_INCLUDE_DIR + VERSION_VAR LIBBPF_VERSION) + +if(LIBBPF_FOUND) + set(LIBBPF_LIBRARIES ${LIBBPF_LIBRARY}) + set(LIBBPF_INCLUDE_DIRS ${LIBBPF_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBBPF_INCLUDE_DIR LIBBPF_LIBRARY) diff --git a/lib/nghttp2/cmake/FindLibcares.cmake b/lib/nghttp2/cmake/FindLibcares.cmake new file mode 100644 index 00000000000..1fe56ce7021 --- /dev/null +++ b/lib/nghttp2/cmake/FindLibcares.cmake @@ -0,0 +1,40 @@ +# - Try to find libcares +# Once done this will define +# LIBCARES_FOUND - System has libcares +# LIBCARES_INCLUDE_DIRS - The libcares include directories +# LIBCARES_LIBRARIES - The libraries needed to use libcares + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBCARES QUIET libcares) + +find_path(LIBCARES_INCLUDE_DIR + NAMES ares.h + HINTS ${PC_LIBCARES_INCLUDE_DIRS} +) +find_library(LIBCARES_LIBRARY + NAMES cares + HINTS ${PC_LIBCARES_LIBRARY_DIRS} +) + +if(LIBCARES_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+ARES_VERSION_STR[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBCARES_INCLUDE_DIR}/ares_version.h" + LIBCARES_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBCARES_VERSION "${LIBCARES_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBCARES_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Libcares REQUIRED_VARS + LIBCARES_LIBRARY LIBCARES_INCLUDE_DIR + VERSION_VAR LIBCARES_VERSION) + +if(LIBCARES_FOUND) + set(LIBCARES_LIBRARIES ${LIBCARES_LIBRARY}) + set(LIBCARES_INCLUDE_DIRS ${LIBCARES_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBCARES_INCLUDE_DIR LIBCARES_LIBRARY) diff --git a/lib/nghttp2/cmake/FindLibev.cmake b/lib/nghttp2/cmake/FindLibev.cmake new file mode 100644 index 00000000000..71e45082b3b --- /dev/null +++ b/lib/nghttp2/cmake/FindLibev.cmake @@ -0,0 +1,38 @@ +# - Try to find libev +# Once done this will define +# LIBEV_FOUND - System has libev +# LIBEV_INCLUDE_DIRS - The libev include directories +# LIBEV_LIBRARIES - The libraries needed to use libev + +find_path(LIBEV_INCLUDE_DIR + NAMES ev.h +) +find_library(LIBEV_LIBRARY + NAMES ev +) + +if(LIBEV_INCLUDE_DIR) + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MAJOR REGEX "^#define[ \t]+EV_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MINOR REGEX "^#define[ \t]+EV_VERSION_MINOR[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MAJOR "${LIBEV_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MINOR "${LIBEV_VERSION_MINOR}") + set(LIBEV_VERSION "${LIBEV_VERSION_MAJOR}.${LIBEV_VERSION_MINOR}") + unset(LIBEV_VERSION_MINOR) + unset(LIBEV_VERSION_MAJOR) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBEV_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Libev REQUIRED_VARS + LIBEV_LIBRARY LIBEV_INCLUDE_DIR + VERSION_VAR LIBEV_VERSION) + +if(LIBEV_FOUND) + set(LIBEV_LIBRARIES ${LIBEV_LIBRARY}) + set(LIBEV_INCLUDE_DIRS ${LIBEV_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBEV_INCLUDE_DIR LIBEV_LIBRARY) diff --git a/lib/nghttp2/cmake/FindLibevent.cmake b/lib/nghttp2/cmake/FindLibevent.cmake new file mode 100644 index 00000000000..e8a3cef223f --- /dev/null +++ b/lib/nghttp2/cmake/FindLibevent.cmake @@ -0,0 +1,97 @@ +# - Try to find libevent +#.rst +# FindLibevent +# ------------ +# +# Find Libevent include directories and libraries. Invoke as:: +# +# find_package(Libevent +# [version] [EXACT] # Minimum or exact version +# [REQUIRED] # Fail if Libevent is not found +# [COMPONENT ...]) # Libraries to look for +# +# Valid components are one or more of:: libevent core extra pthreads openssl. +# Note that 'libevent' contains both core and extra. You must specify one of +# them for the other components. +# +# This module will define the following variables:: +# +# LIBEVENT_FOUND - True if headers and requested libraries were found +# LIBEVENT_INCLUDE_DIRS - Libevent include directories +# LIBEVENT_LIBRARIES - Libevent libraries to be linked +# LIBEVENT__FOUND - Component was found ( is uppercase) +# LIBEVENT__LIBRARY - Library to be linked for Libevent component . + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBEVENT QUIET libevent) + +# Look for the Libevent 2.0 or 1.4 headers +find_path(LIBEVENT_INCLUDE_DIR + NAMES + event2/event-config.h + event-config.h + HINTS + ${PC_LIBEVENT_INCLUDE_DIRS} +) + +if(LIBEVENT_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+_EVENT_VERSION[ \t]+\"([^\"]+)\".*") + if(EXISTS "${LIBEVENT_INCLUDE_DIR}/event2/event-config.h") + # Libevent 2.0 + file(STRINGS "${LIBEVENT_INCLUDE_DIR}/event2/event-config.h" + LIBEVENT_VERSION REGEX "${_version_regex}") + if("${LIBEVENT_VERSION}" STREQUAL "") + set(LIBEVENT_VERSION ${PC_LIBEVENT_VERSION}) + endif() + else() + # Libevent 1.4 + file(STRINGS "${LIBEVENT_INCLUDE_DIR}/event-config.h" + LIBEVENT_VERSION REGEX "${_version_regex}") + endif() + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBEVENT_VERSION "${LIBEVENT_VERSION}") + unset(_version_regex) +endif() + +set(_LIBEVENT_REQUIRED_VARS) +foreach(COMPONENT ${Libevent_FIND_COMPONENTS}) + set(_LIBEVENT_LIBNAME libevent) + # Note: compare two variables to avoid a CMP0054 policy warning + if(COMPONENT STREQUAL _LIBEVENT_LIBNAME) + set(_LIBEVENT_LIBNAME event) + else() + set(_LIBEVENT_LIBNAME "event_${COMPONENT}") + endif() + string(TOUPPER "${COMPONENT}" COMPONENT_UPPER) + find_library(LIBEVENT_${COMPONENT_UPPER}_LIBRARY + NAMES ${_LIBEVENT_LIBNAME} + HINTS ${PC_LIBEVENT_LIBRARY_DIRS} + ) + if(LIBEVENT_${COMPONENT_UPPER}_LIBRARY) + set(Libevent_${COMPONENT}_FOUND 1) + endif() + list(APPEND _LIBEVENT_REQUIRED_VARS LIBEVENT_${COMPONENT_UPPER}_LIBRARY) +endforeach() +unset(_LIBEVENT_LIBNAME) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBEVENT_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Libevent REQUIRED_VARS + ${_LIBEVENT_REQUIRED_VARS} + LIBEVENT_INCLUDE_DIR + VERSION_VAR LIBEVENT_VERSION + HANDLE_COMPONENTS) + +if(LIBEVENT_FOUND) + set(LIBEVENT_INCLUDE_DIRS ${LIBEVENT_INCLUDE_DIR}) + set(LIBEVENT_LIBRARIES) + foreach(COMPONENT ${Libevent_FIND_COMPONENTS}) + string(TOUPPER "${COMPONENT}" COMPONENT_UPPER) + list(APPEND LIBEVENT_LIBRARIES ${LIBEVENT_${COMPONENT_UPPER}_LIBRARY}) + set(LIBEVENT_${COMPONENT_UPPER}_FOUND ${Libevent_${COMPONENT}_FOUND}) + endforeach() +endif() + +mark_as_advanced(LIBEVENT_INCLUDE_DIR ${_LIBEVENT_REQUIRED_VARS}) +unset(_LIBEVENT_REQUIRED_VARS) diff --git a/lib/nghttp2/cmake/FindLibnghttp3.cmake b/lib/nghttp2/cmake/FindLibnghttp3.cmake new file mode 100644 index 00000000000..ecd01f6c715 --- /dev/null +++ b/lib/nghttp2/cmake/FindLibnghttp3.cmake @@ -0,0 +1,41 @@ +# - Try to find libnghttp3 +# Once done this will define +# LIBNGHTTP3_FOUND - System has libnghttp3 +# LIBNGHTTP3_INCLUDE_DIRS - The libnghttp3 include directories +# LIBNGHTTP3_LIBRARIES - The libraries needed to use libnghttp3 + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGHTTP3 QUIET libnghttp3) + +find_path(LIBNGHTTP3_INCLUDE_DIR + NAMES nghttp3/nghttp3.h + HINTS ${PC_LIBNGHTTP3_INCLUDE_DIRS} +) +find_library(LIBNGHTTP3_LIBRARY + NAMES nghttp3 + HINTS ${PC_LIBNGHTTP3_LIBRARY_DIRS} +) + +if(LIBNGHTTP3_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGHTTP3_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGHTTP3_INCLUDE_DIR}/nghttp3/version.h" + LIBNGHTTP3_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGHTTP3_VERSION "${LIBNGHTTP3_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBNGHTTP3_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libnghttp3 REQUIRED_VARS + LIBNGHTTP3_LIBRARY LIBNGHTTP3_INCLUDE_DIR + VERSION_VAR LIBNGHTTP3_VERSION) + +if(LIBNGHTTP3_FOUND) + set(LIBNGHTTP3_LIBRARIES ${LIBNGHTTP3_LIBRARY}) + set(LIBNGHTTP3_INCLUDE_DIRS ${LIBNGHTTP3_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGHTTP3_INCLUDE_DIR LIBNGHTTP3_LIBRARY) diff --git a/lib/nghttp2/cmake/FindLibngtcp2.cmake b/lib/nghttp2/cmake/FindLibngtcp2.cmake new file mode 100644 index 00000000000..c670114928a --- /dev/null +++ b/lib/nghttp2/cmake/FindLibngtcp2.cmake @@ -0,0 +1,41 @@ +# - Try to find libngtcp2 +# Once done this will define +# LIBNGTCP2_FOUND - System has libngtcp2 +# LIBNGTCP2_INCLUDE_DIRS - The libngtcp2 include directories +# LIBNGTCP2_LIBRARIES - The libraries needed to use libngtcp2 + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGTCP2 QUIET libngtcp2) + +find_path(LIBNGTCP2_INCLUDE_DIR + NAMES ngtcp2/ngtcp2.h + HINTS ${PC_LIBNGTCP2_INCLUDE_DIRS} +) +find_library(LIBNGTCP2_LIBRARY + NAMES ngtcp2 + HINTS ${PC_LIBNGTCP2_LIBRARY_DIRS} +) + +if(LIBNGTCP2_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGTCP2_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGTCP2_INCLUDE_DIR}/ngtcp2/version.h" + LIBNGTCP2_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGTCP2_VERSION "${LIBNGTCP2_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBNGTCP2_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libngtcp2 REQUIRED_VARS + LIBNGTCP2_LIBRARY LIBNGTCP2_INCLUDE_DIR + VERSION_VAR LIBNGTCP2_VERSION) + +if(LIBNGTCP2_FOUND) + set(LIBNGTCP2_LIBRARIES ${LIBNGTCP2_LIBRARY}) + set(LIBNGTCP2_INCLUDE_DIRS ${LIBNGTCP2_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGTCP2_INCLUDE_DIR LIBNGTCP2_LIBRARY) diff --git a/lib/nghttp2/cmake/FindLibngtcp2_crypto_quictls.cmake b/lib/nghttp2/cmake/FindLibngtcp2_crypto_quictls.cmake new file mode 100644 index 00000000000..3d55b63abf2 --- /dev/null +++ b/lib/nghttp2/cmake/FindLibngtcp2_crypto_quictls.cmake @@ -0,0 +1,43 @@ +# - Try to find libngtcp2_crypto_quictls +# Once done this will define +# LIBNGTCP2_CRYPTO_QUICTLS_FOUND - System has libngtcp2_crypto_quictls +# LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS - The libngtcp2_crypto_quictls include directories +# LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES - The libraries needed to use libngtcp2_crypto_quictls + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGTCP2_CRYPTO_QUICTLS QUIET libngtcp2_crypto_quictls) + +find_path(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR + NAMES ngtcp2/ngtcp2_crypto_quictls.h + HINTS ${PC_LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS} +) +find_library(LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY + NAMES ngtcp2_crypto_quictls + HINTS ${PC_LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY_DIRS} +) + +if(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGTCP2_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR}/ngtcp2/version.h" + LIBNGTCP2_CRYPTO_QUICTLS_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGTCP2_CRYPTO_QUICTLS_VERSION "${LIBNGTCP2_CRYPTO_QUICTLS_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set +# LIBNGTCP2_CRYPTO_QUICTLS_FOUND to TRUE if all listed variables are +# TRUE and the requested version matches. +find_package_handle_standard_args(Libngtcp2_crypto_quictls REQUIRED_VARS + LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY + LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR + VERSION_VAR LIBNGTCP2_CRYPTO_QUICTLS_VERSION) + +if(LIBNGTCP2_CRYPTO_QUICTLS_FOUND) + set(LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES ${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY}) + set(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS ${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIR + LIBNGTCP2_CRYPTO_QUICTLS_LIBRARY) diff --git a/lib/nghttp2/cmake/FindSystemd.cmake b/lib/nghttp2/cmake/FindSystemd.cmake new file mode 100644 index 00000000000..e7534e5fe67 --- /dev/null +++ b/lib/nghttp2/cmake/FindSystemd.cmake @@ -0,0 +1,19 @@ +# - Try to find systemd +# Once done this will define +# SYSTEMD_FOUND - System has systemd +# SYSTEMD_INCLUDE_DIRS - The systemd include directories +# SYSTEMD_LIBRARIES - The libraries needed to use systemd + +include(FeatureSummary) +set_package_properties(Systemd PROPERTIES + URL "http://freedesktop.org/wiki/Software/systemd/" + DESCRIPTION "System and Service Manager") + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_SYSTEMD QUIET libsystemd) +find_library(SYSTEMD_LIBRARIES NAMES systemd ${PC_SYSTEMD_LIBRARY_DIRS}) +find_path(SYSTEMD_INCLUDE_DIRS systemd/sd-login.h HINTS ${PC_SYSTEMD_INCLUDE_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Systemd DEFAULT_MSG SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) +mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) diff --git a/lib/nghttp2/cmake/PickyWarningsC.cmake b/lib/nghttp2/cmake/PickyWarningsC.cmake new file mode 100644 index 00000000000..50eb7891802 --- /dev/null +++ b/lib/nghttp2/cmake/PickyWarningsC.cmake @@ -0,0 +1,163 @@ +# nghttp2 +# +# Copyright (c) 2023 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. + +# C + +include(CheckCCompilerFlag) + +if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_C_COMPILER_ID MATCHES "Clang") + + # https://clang.llvm.org/docs/DiagnosticsReference.html + # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html + + # WPICKY_ENABLE = Options we want to enable as-is. + # WPICKY_DETECT = Options we want to test first and enable if available. + + # Prefer the -Wextra alias with clang. + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + set(WPICKY_ENABLE "-Wextra") + else() + set(WPICKY_ENABLE "-W") + endif() + + list(APPEND WPICKY_ENABLE + -Wall + ) + + # ---------------------------------- + # Add new options here, if in doubt: + # ---------------------------------- + set(WPICKY_DETECT + ) + + # Assume these options always exist with both clang and gcc. + # Require clang 3.0 / gcc 2.95 or later. + list(APPEND WPICKY_ENABLE + -Wconversion # clang 3.0 gcc 2.95 + -Winline # clang 1.0 gcc 1.0 + -Wmissing-declarations # clang 1.0 gcc 2.7 + -Wmissing-prototypes # clang 1.0 gcc 1.0 + -Wnested-externs # clang 1.0 gcc 2.7 + -Wpointer-arith # clang 1.0 gcc 1.4 + -Wshadow # clang 1.0 gcc 2.95 + -Wundef # clang 1.0 gcc 2.95 + -Wwrite-strings # clang 1.0 gcc 1.4 + ) + + # Always enable with clang, version dependent with gcc + set(WPICKY_COMMON_OLD + -Waddress # clang 3.0 gcc 4.3 + -Wattributes # clang 3.0 gcc 4.1 + -Wcast-align # clang 1.0 gcc 4.2 + -Wdeclaration-after-statement # clang 1.0 gcc 3.4 + -Wdiv-by-zero # clang 3.0 gcc 4.1 + -Wempty-body # clang 3.0 gcc 4.3 + -Wendif-labels # clang 1.0 gcc 3.3 + -Wfloat-equal # clang 1.0 gcc 2.96 (3.0) + -Wformat-nonliteral # clang 3.0 gcc 4.1 + -Wformat-security # clang 3.0 gcc 4.1 + -Wmissing-field-initializers # clang 3.0 gcc 4.1 + -Wmissing-noreturn # clang 3.0 gcc 4.1 + -Wno-format-nonliteral # clang 1.0 gcc 2.96 (3.0) # This is required because we pass format string as "const char*" + # -Wpadded # clang 3.0 gcc 4.1 # Not used because we cannot change public structs + -Wredundant-decls # clang 3.0 gcc 4.1 + -Wsign-conversion # clang 3.0 gcc 4.3 + -Wstrict-prototypes # clang 1.0 gcc 3.3 + # -Wswitch-enum # clang 3.0 gcc 4.1 # Not used because this basically disallows default case + -Wunreachable-code # clang 3.0 gcc 4.1 + -Wunused-macros # clang 3.0 gcc 4.1 + -Wunused-parameter # clang 3.0 gcc 4.1 + -Wvla # clang 2.8 gcc 4.3 + ) + + set(WPICKY_COMMON + -Wpragmas # clang 3.5 gcc 4.1 appleclang 6.0 + ) + + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON_OLD} + -Wshorten-64-to-32 # clang 1.0 + -Wlanguage-extension-token # clang 3.0 + ) + # Enable based on compiler version + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.3)) + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON} + -Wunreachable-code-break # clang 3.5 appleclang 6.0 + -Wheader-guard # clang 3.4 appleclang 5.1 + ) + endif() + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 8.3)) + list(APPEND WPICKY_ENABLE + -Wmissing-variable-declarations # clang 3.2 appleclang 4.6 + ) + endif() + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 9.4)) + list(APPEND WPICKY_ENABLE + ) + endif() + if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0) OR + (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.3)) + list(APPEND WPICKY_ENABLE + ) + endif() + else() # gcc + list(APPEND WPICKY_DETECT + ${WPICKY_COMMON} + ) + # Enable based on compiler version + if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.3) + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON_OLD} + -Wclobbered # gcc 4.3 + ) + endif() + endif() + + # + + unset(_wpicky) + + foreach(_CCOPT IN LISTS WPICKY_ENABLE) + set(_wpicky "${_wpicky} ${_CCOPT}") + endforeach() + + foreach(_CCOPT IN LISTS WPICKY_DETECT) + # surprisingly, CHECK_C_COMPILER_FLAG needs a new variable to store each new + # test result in. + string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname) + # GCC only warns about unknown -Wno- options if there are also other diagnostic messages, + # so test for the positive form instead + string(REPLACE "-Wno-" "-W" _CCOPT_ON "${_CCOPT}") + check_c_compiler_flag(${_CCOPT_ON} ${_optvarname}) + if(${_optvarname}) + set(_wpicky "${_wpicky} ${_CCOPT}") + endif() + endforeach() + + set(WARNCFLAGS "${WARNCFLAGS} ${_wpicky}") +endif() diff --git a/lib/nghttp2/cmake/PickyWarningsCXX.cmake b/lib/nghttp2/cmake/PickyWarningsCXX.cmake new file mode 100644 index 00000000000..4699733d25a --- /dev/null +++ b/lib/nghttp2/cmake/PickyWarningsCXX.cmake @@ -0,0 +1,117 @@ +# nghttp2 +# +# Copyright (c) 2023 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. + +# C++ + +include(CheckCXXCompilerFlag) + +if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + + # https://clang.llvm.org/docs/DiagnosticsReference.html + # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html + + # WPICKY_ENABLE = Options we want to enable as-is. + # WPICKY_DETECT = Options we want to test first and enable if available. + + set(WPICKY_ENABLE "-Wall") + + # ---------------------------------- + # Add new options here, if in doubt: + # ---------------------------------- + set(WPICKY_DETECT + ) + + # Assume these options always exist with both clang and gcc. + # Require clang 3.0 / gcc 2.95 or later. + list(APPEND WPICKY_ENABLE + ) + + # Always enable with clang, version dependent with gcc + set(WPICKY_COMMON_OLD + -Wformat-security # clang 3.0 gcc 4.1 + ) + + set(WPICKY_COMMON + ) + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON_OLD} + ) + # Enable based on compiler version + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.6) OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.3)) + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON} + ) + endif() + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.9) OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.3)) + list(APPEND WPICKY_ENABLE + ) + endif() + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.4)) + list(APPEND WPICKY_ENABLE + ) + endif() + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.3)) + list(APPEND WPICKY_ENABLE + ) + endif() + else() # gcc + list(APPEND WPICKY_DETECT + ${WPICKY_COMMON} + ) + # Enable based on compiler version + if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.3) + list(APPEND WPICKY_ENABLE + ${WPICKY_COMMON_OLD} + ) + endif() + endif() + + # + + unset(_wpicky) + + foreach(_CCOPT IN LISTS WPICKY_ENABLE) + set(_wpicky "${_wpicky} ${_CCOPT}") + endforeach() + + foreach(_CCOPT IN LISTS WPICKY_DETECT) + # surprisingly, CHECK_CXX_COMPILER_FLAG needs a new variable to store each new + # test result in. + string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname) + # GCC only warns about unknown -Wno- options if there are also other diagnostic messages, + # so test for the positive form instead + string(REPLACE "-Wno-" "-W" _CCOPT_ON "${_CCOPT}") + check_cxx_compiler_flag(${_CCOPT_ON} ${_optvarname}) + if(${_optvarname}) + set(_wpicky "${_wpicky} ${_CCOPT}") + endif() + endforeach() + + set(WARNCXXFLAGS "${WARNCXXFLAGS} ${_wpicky}") +endif() diff --git a/lib/nghttp2/cmake/Version.cmake b/lib/nghttp2/cmake/Version.cmake new file mode 100644 index 00000000000..8ac4849c972 --- /dev/null +++ b/lib/nghttp2/cmake/Version.cmake @@ -0,0 +1,11 @@ +# Converts a version such as 1.2.255 to 0x0102ff +function(HexVersion version_hex_var major minor patch) + math(EXPR version_dec "${major} * 256 * 256 + ${minor} * 256 + ${patch}") + set(version_hex "0x") + foreach(i RANGE 5 0 -1) + math(EXPR num "(${version_dec} >> (4 * ${i})) & 15") + string(SUBSTRING "0123456789abcdef" ${num} 1 num_hex) + set(version_hex "${version_hex}${num_hex}") + endforeach() + set(${version_hex_var} "${version_hex}" PARENT_SCOPE) +endfunction() diff --git a/lib/nghttp2/cmakeconfig.h.in b/lib/nghttp2/cmakeconfig.h.in new file mode 100644 index 00000000000..765a72a5c84 --- /dev/null +++ b/lib/nghttp2/cmakeconfig.h.in @@ -0,0 +1,110 @@ +/* Hint to the compiler that a function never returns */ +#define NGHTTP2_NORETURN @HINT_NORETURN@ + +/* Define to `int' if does not define. */ +#cmakedefine ssize_t @ssize_t@ + +/* Define to 1 if you have the `std::map::emplace`. */ +#cmakedefine HAVE_STD_MAP_EMPLACE 1 + +/* Define to 1 if you have `libjansson` library. */ +#cmakedefine HAVE_JANSSON 1 + +/* Define to 1 if you have `libxml2` library. */ +#cmakedefine HAVE_LIBXML2 1 + +/* Define to 1 if you have `mruby` library. */ +#cmakedefine HAVE_MRUBY 1 + +/* Define to 1 if you have `neverbleed` library. */ +#cmakedefine HAVE_NEVERBLEED 1 + +/* sizeof(int *) */ +#cmakedefine SIZEOF_INT_P @SIZEOF_INT_P@ + +/* sizeof(time_t) */ +#cmakedefine SIZEOF_TIME_T @SIZEOF_TIME_T@ + +/* Define to 1 if you have the `_Exit` function. */ +#cmakedefine HAVE__EXIT 1 + +/* Define to 1 if you have the `accept4` function. */ +#cmakedefine HAVE_ACCEPT4 1 + +/* Define to 1 if you have the `clock_gettime` function. */ +#cmakedefine HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if you have the `mkostemp` function. */ +#cmakedefine HAVE_MKOSTEMP 1 + +/* Define to 1 if you have the `GetTickCount64` function. */ +#cmakedefine HAVE_GETTICKCOUNT64 1 + +/* Define to 1 if you have the `initgroups` function. */ +#cmakedefine01 HAVE_DECL_INITGROUPS + +/* Define to 1 if you have the `CLOCK_MONOTONIC` defined. */ +#cmakedefine01 HAVE_DECL_CLOCK_MONOTONIC + +/* Define to 1 to enable debug output. */ +#cmakedefine DEBUGBUILD 1 + +/* Define to 1 if you want to disable threads. */ +#cmakedefine NOTHREADS 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_FCNTL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_IP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PWD_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WINDOWS_H 1 + +/* Define to 1 if HTTP/3 is enabled. */ +#cmakedefine ENABLE_HTTP3 1 + +/* Define to 1 if you have `libbpf` library. */ +#cmakedefine HAVE_LIBBPF 1 + +/* Define to 1 if you have enum bpf_stats_type in linux/bpf.h. */ +#cmakedefine HAVE_BPF_STATS_TYPE 1 + +/* Define to 1 if you have `libngtcp2_crypto_quictls` library. */ +#cmakedefine HAVE_LIBNGTCP2_CRYPTO_QUICTLS + +/* Define to 1 if you have `libev` library. */ +#cmakedefine HAVE_LIBEV 1 diff --git a/lib/nghttp2/configure.ac b/lib/nghttp2/configure.ac new file mode 100644 index 00000000000..0376e21fa92 --- /dev/null +++ b/lib/nghttp2/configure.ac @@ -0,0 +1,1192 @@ +dnl nghttp2 - HTTP/2 C Library + +dnl Copyright (c) 2012, 2013, 2014, 2015 Tatsuhiro Tsujikawa + +dnl Permission is hereby granted, free of charge, to any person obtaining +dnl a copy of this software and associated documentation files (the +dnl "Software"), to deal in the Software without restriction, including +dnl without limitation the rights to use, copy, modify, merge, publish, +dnl distribute, sublicense, and/or sell copies of the Software, and to +dnl permit persons to whom the Software is furnished to do so, subject to +dnl the following conditions: + +dnl The above copyright notice and this permission notice shall be +dnl included in all copies or substantial portions of the Software. + +dnl THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +dnl EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +dnl MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +dnl NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +dnl LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +dnl OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +dnl WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +dnl Do not change user variables! +dnl https://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html + +AC_PREREQ(2.61) +AC_INIT([nghttp2], [1.59.0-DEV], [t-tujikawa@users.sourceforge.net]) +AC_CONFIG_AUX_DIR([.]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) +AC_USE_SYSTEM_EXTENSIONS + +LT_PREREQ([2.2.6]) +LT_INIT() + +AC_CANONICAL_BUILD +AC_CANONICAL_HOST +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE([subdir-objects]) + +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl See versioning rule: +dnl https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +AC_SUBST(LT_CURRENT, 39) +AC_SUBST(LT_REVISION, 1) +AC_SUBST(LT_AGE, 25) + +major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"` +minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"` +patch=`echo $PACKAGE_VERSION |cut -d. -f3 | cut -d- -f1 | sed -e "s/[^0-9]//g"` + +PACKAGE_VERSION_NUM=`printf "0x%02x%02x%02x" "$major" "$minor" "$patch"` + +AC_SUBST(PACKAGE_VERSION_NUM) + +dnl Checks for command-line options +AC_ARG_ENABLE([werror], + [AS_HELP_STRING([--enable-werror], + [Turn on compile time warnings])], + [werror=$enableval], [werror=no]) + +AC_ARG_ENABLE([debug], + [AS_HELP_STRING([--enable-debug], + [Turn on debug output])], + [debug=$enableval], [debug=no]) + +AC_ARG_ENABLE([threads], + [AS_HELP_STRING([--disable-threads], + [Turn off threading in apps])], + [threads=$enableval], [threads=yes]) + +AC_ARG_ENABLE([app], + [AS_HELP_STRING([--enable-app], + [Build applications (nghttp, nghttpd, nghttpx and h2load) [default=check]])], + [request_app=$enableval], [request_app=check]) + +AC_ARG_ENABLE([hpack-tools], + [AS_HELP_STRING([--enable-hpack-tools], + [Build HPACK tools [default=check]])], + [request_hpack_tools=$enableval], [request_hpack_tools=check]) + +AC_ARG_ENABLE([examples], + [AS_HELP_STRING([--enable-examples], + [Build examples [default=check]])], + [request_examples=$enableval], [request_examples=check]) + +AC_ARG_ENABLE([failmalloc], + [AS_HELP_STRING([--disable-failmalloc], + [Do not build failmalloc test program])], + [request_failmalloc=$enableval], [request_failmalloc=yes]) + +AC_ARG_ENABLE([lib-only], + [AS_HELP_STRING([--enable-lib-only], + [Build libnghttp2 only. This is a short hand for --disable-app --disable-examples --disable-hpack-tools])], + [request_lib_only=$enableval], [request_lib_only=no]) + +AC_ARG_ENABLE([http3], + [AS_HELP_STRING([--enable-http3], + [(EXPERIMENTAL) Enable HTTP/3. This requires ngtcp2, nghttp3, and a custom OpenSSL.])], + [request_http3=$enableval], [request_http3=no]) + +AC_ARG_WITH([libxml2], + [AS_HELP_STRING([--with-libxml2], + [Use libxml2 [default=check]])], + [request_libxml2=$withval], [request_libxml2=check]) + +AC_ARG_WITH([jansson], + [AS_HELP_STRING([--with-jansson], + [Use jansson [default=check]])], + [request_jansson=$withval], [request_jansson=check]) + +AC_ARG_WITH([zlib], + [AS_HELP_STRING([--with-zlib], + [Use zlib [default=check]])], + [request_zlib=$withval], [request_zlib=check]) + +AC_ARG_WITH([libevent-openssl], + [AS_HELP_STRING([--with-libevent-openssl], + [Use libevent_openssl [default=check]])], + [request_libevent_openssl=$withval], [request_libevent_openssl=check]) + +AC_ARG_WITH([libcares], + [AS_HELP_STRING([--with-libcares], + [Use libc-ares [default=check]])], + [request_libcares=$withval], [request_libcares=check]) + +AC_ARG_WITH([openssl], + [AS_HELP_STRING([--with-openssl], + [Use openssl [default=check]])], + [request_openssl=$withval], [request_openssl=check]) + +AC_ARG_WITH([libev], + [AS_HELP_STRING([--with-libev], + [Use libev [default=check]])], + [request_libev=$withval], [request_libev=check]) + +AC_ARG_WITH([cunit], + [AS_HELP_STRING([--with-cunit], + [Use cunit [default=check]])], + [request_cunit=$withval], [request_cunit=check]) + +AC_ARG_WITH([jemalloc], + [AS_HELP_STRING([--with-jemalloc], + [Use jemalloc [default=check]])], + [request_jemalloc=$withval], [request_jemalloc=check]) + +AC_ARG_WITH([systemd], + [AS_HELP_STRING([--with-systemd], + [Enable systemd support in nghttpx [default=check]])], + [request_systemd=$withval], [request_systemd=check]) + +AC_ARG_WITH([mruby], + [AS_HELP_STRING([--with-mruby], + [Use mruby [default=no]])], + [request_mruby=$withval], [request_mruby=no]) + +AC_ARG_WITH([neverbleed], + [AS_HELP_STRING([--with-neverbleed], + [Use neverbleed [default=no]])], + [request_neverbleed=$withval], [request_neverbleed=no]) + +AC_ARG_WITH([libngtcp2], + [AS_HELP_STRING([--with-libngtcp2], + [Use libngtcp2 [default=check]])], + [request_libngtcp2=$withval], [request_libngtcp2=check]) + +AC_ARG_WITH([libnghttp3], + [AS_HELP_STRING([--with-libnghttp3], + [Use libnghttp3 [default=check]])], + [request_libnghttp3=$withval], [request_libnghttp3=check]) + +AC_ARG_WITH([libbpf], + [AS_HELP_STRING([--with-libbpf], + [Use libbpf [default=no]])], + [request_libbpf=$withval], [request_libbpf=no]) + +dnl Define variables +AC_ARG_VAR([LIBEV_CFLAGS], [C compiler flags for libev, skipping any checks]) +AC_ARG_VAR([LIBEV_LIBS], [linker flags for libev, skipping any checks]) + +AC_ARG_VAR([JEMALLOC_CFLAGS], + [C compiler flags for jemalloc, skipping any checks]) +AC_ARG_VAR([JEMALLOC_LIBS], [linker flags for jemalloc, skipping any checks]) + +AC_ARG_VAR([LIBTOOL_LDFLAGS], + [libtool specific flags (e.g., -static-libtool-libs)]) + +AC_ARG_VAR([BPFCFLAGS], [C compiler flags for bpf program]) + +dnl Checks for programs +AC_PROG_CC +AC_PROG_CXX +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_PROG_MKDIR_P + +PKG_PROG_PKG_CONFIG([0.20]) + +AM_PATH_PYTHON([3.8],, [:]) + +if [test "x$request_lib_only" = "xyes"]; then + request_app=no + request_hpack_tools=no + request_examples=no + request_http3=no + request_libxml2=no + request_jansson=no + request_zlib=no + request_libevent_openssl=no + request_libcares=no + request_openssl=no + request_libev=no + request_jemalloc=no + request_systemd=no + request_mruby=no + request_neverbleed=no + request_libngtcp2=no + request_libnghttp3=no + request_libbpf=no +fi + +if test "x$GCC" = "xyes" -o "x$CC" = "xclang" ; then + AC_DEFINE([NGHTTP2_NORETURN], [__attribute__((noreturn))], [Hint to the compiler that a function never return]) +else + AC_DEFINE([NGHTTP2_NORETURN], , [Hint to the compiler that a function never return]) +fi + +save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS= + +AX_CXX_COMPILE_STDCXX([14], [], [optional]) + +CXX1XCXXFLAGS="$CXXFLAGS" +CXXFLAGS="$save_CXXFLAGS" +AC_SUBST([CXX1XCXXFLAGS]) + +AC_LANG_PUSH(C++) + +save_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $CXX1XCXXFLAGS" + +# Check that std::future is available. +AC_MSG_CHECKING([whether std::future is available]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( +[[ +#include +#include +]], +[[ +std::vector> v; +(void)v; +]])], + [AC_DEFINE([HAVE_STD_FUTURE], [1], + [Define to 1 if you have the `std::future`.]) + have_std_future=yes + AC_MSG_RESULT([yes])], + [have_std_future=no + AC_MSG_RESULT([no])]) + +# Check that std::map::emplace is available for g++-4.7. +AC_MSG_CHECKING([whether std::map::emplace is available]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( +[[ +#include +]], +[[ +std::map().emplace(1, 2); +]])], + [AC_DEFINE([HAVE_STD_MAP_EMPLACE], [1], + [Define to 1 if you have the `std::map::emplace`.]) + have_std_map_emplace=yes + AC_MSG_RESULT([yes])], + [have_std_map_emplace=no + AC_MSG_RESULT([no])]) + +# Check that std::atomic_* overloads for std::shared_ptr are +# available. +AC_MSG_CHECKING([whether std::atomic_* overloads for std::shared_ptr are available]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( +[[ +#include +]], +[[ +auto a = std::make_shared(1000000007); +auto p = std::atomic_load(&a); +++*p; +std::atomic_store(&a, p); +]])], + [AC_DEFINE([HAVE_ATOMIC_STD_SHARED_PTR], [1], + [Define to 1 if you have the std::atomic_* overloads for std::shared_ptr.]) + have_atomic_std_shared_ptr=yes + AC_MSG_RESULT([yes])], + [have_atomic_std_shared_ptr=no + AC_MSG_RESULT([no])]) + +# Check that thread_local storage specifier is available +AC_MSG_CHECKING([whether thread_local storage class specifier is available.]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( +, +[[ +thread_local int a = 0; +(void)a; +]])], + [AC_DEFINE([HAVE_THREAD_LOCAL], [1], + [Define to 1 if you have thread_local storage specifier.]) + have_thread_local=yes + AC_MSG_RESULT([yes])], + [have_Thread_local=no + AC_MSG_RESULT([no])]) + +CXXFLAGS=$save_CXXFLAGS + +AC_LANG_POP() + +# Checks for libraries. + +# Additional libraries required for tests. +TESTLDADD= + +# Additional libraries required for programs under src directory. +APPLDFLAGS= + +case "$host_os" in + *android*) + android_build=yes + # android does not need -pthread, but needs following 2 libs for C++ + APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic" + ;; + *) + PTHREAD_LDFLAGS="-pthread" + APPLDFLAGS="$APPLDFLAGS $PTHREAD_LDFLAGS" + ;; +esac + +case "$host_os" in + *solaris*) + APPLDFLAGS="$APPLDFLAGS -lsocket -lnsl" + ;; +esac + +case "${build}" in + *-apple-darwin*) + EXTRA_DEFS="-D__APPLE_USE_RFC_3542" + AC_SUBST([EXTRA_DEFS]) + ;; +esac + +# zlib +have_zlib=no +if test "x${request_zlib}" != "xno"; then + PKG_CHECK_MODULES([ZLIB], [zlib >= 1.2.3], [have_zlib=yes], [have_zlib=no]) + + if test "x${have_zlib}" = "xno"; then + AC_MSG_NOTICE($ZLIB_PKG_ERRORS) + fi +fi + +if test "x${request_zlib}" = "xyes" && + test "x${have_zlib}" != "xyes"; then + AC_MSG_ERROR([zlib was requested (--with-zlib) but not found]) +fi + +# dl: openssl requires libdl when it is statically linked. +case "${host_os}" in + *bsd*) + # dlopen is in libc on *BSD + ;; + *) + save_LIBS=$LIBS + AC_SEARCH_LIBS([dlopen], [dl], [APPLDFLAGS="-ldl $APPLDFLAGS"], [], []) + LIBS=$save_LIBS + ;; +esac + +# cunit +have_cunit=no +if test "x${request_cunit}" != "xno"; then + PKG_CHECK_MODULES([CUNIT], [cunit >= 2.1], [have_cunit=yes], [have_cunit=no]) + # If pkg-config does not find cunit, check it using AC_CHECK_LIB. We + # do this because Debian (Ubuntu) lacks pkg-config file for cunit. + if test "x${have_cunit}" = "xno"; then + AC_MSG_WARN([${CUNIT_PKG_ERRORS}]) + AC_CHECK_LIB([cunit], [CU_initialize_registry], + [have_cunit=yes], [have_cunit=no]) + if test "x${have_cunit}" = "xyes"; then + CUNIT_LIBS="-lcunit" + CUNIT_CFLAGS="" + AC_SUBST([CUNIT_LIBS]) + AC_SUBST([CUNIT_CFLAGS]) + fi + fi + if test "x${have_cunit}" = "xyes"; then + # cunit in Mac OS X requires ncurses. Note that in Mac OS X, test + # program can be built without -lncurses, but it emits runtime + # error. + case "${build}" in + *-apple-darwin*) + CUNIT_LIBS="$CUNIT_LIBS -lncurses" + AC_SUBST([CUNIT_LIBS]) + ;; + esac + fi +fi + +if test "x${request_cunit}" = "xyes" && + test "x${have_cunit}" != "xyes"; then + AC_MSG_ERROR([cunit was requested (--with-cunit) but not found]) +fi + +AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ]) + +# libev (for src) +have_libev=no +if test "x${request_libev}" != "xno"; then + if test "x${LIBEV_LIBS}" = "x" && test "x${LIBEV_CFLAGS}" = "x"; then + # libev does not have pkg-config file. Check it in an old way. + save_LIBS=$LIBS + # android requires -lm for floor + AC_CHECK_LIB([ev], [ev_time], [have_libev=yes], [have_libev=no], [-lm]) + if test "x${have_libev}" = "xyes"; then + AC_CHECK_HEADER([ev.h], [have_libev=yes], [have_libev=no]) + if test "x${have_libev}" = "xyes"; then + LIBEV_LIBS=-lev + LIBEV_CFLAGS= + fi + fi + LIBS=$save_LIBS + else + have_libev=yes + fi + + if test "x${have_libev}" = "xyes"; then + AC_DEFINE([HAVE_LIBEV], [1], [Define to 1 if you have `libev` library.]) + fi +fi + +if test "x${request_libev}" = "xyes" && + test "x${have_libev}" != "xyes"; then + AC_MSG_ERROR([libev was requested (--with-libev) but not found]) +fi + +# openssl (for src) +have_openssl=no +if test "x${request_openssl}" != "xno"; then + PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1], + [have_openssl=yes], [have_openssl=no]) + if test "x${have_openssl}" = "xno"; then + AC_MSG_NOTICE($OPENSSL_PKG_ERRORS) + else + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + CFLAGS="$OPENSSL_CFLAGS $CFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + + # quictls/openssl has SSL_provide_quic_data. boringssl also has + # it. We will deal with it later. + have_ssl_provide_quic_data=no + AC_MSG_CHECKING([for SSL_provide_quic_data]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include + ]], [[ + SSL_provide_quic_data(NULL, 0, NULL, 0); + ]])], + [AC_MSG_RESULT([yes]); have_ssl_provide_quic_data=yes], + [AC_MSG_RESULT([no]); have_ssl_provide_quic_data=no]) + + # boringssl has SSL_set_quic_early_data_context. + AC_MSG_CHECKING([for SSL_set_quic_early_data_context]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include + ]], [[ + SSL *ssl = NULL; + SSL_set_quic_early_data_context(ssl, NULL, 0); + ]])], + [AC_MSG_RESULT([yes]); have_boringssl_quic=yes], + [AC_MSG_RESULT([no]); have_boringssl_quic=no]) + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" + fi +fi + +if test "x${request_openssl}" = "xyes" && + test "x${have_openssl}" != "xyes"; then + AC_MSG_ERROR([openssl was requested (--with-openssl) but not found]) +fi + +# c-ares (for src) +have_libcares=no +if test "x${request_libcares}" != "xno"; then + PKG_CHECK_MODULES([LIBCARES], [libcares >= 1.7.5], [have_libcares=yes], + [have_libcares=no]) + if test "x${have_libcares}" = "xno"; then + AC_MSG_NOTICE($LIBCARES_PKG_ERRORS) + fi +fi + +if test "x${request_libcares}" = "xyes" && + test "x${have_libcares}" != "xyes"; then + AC_MSG_ERROR([libcares was requested (--with-libcares) but not found]) +fi + +# ngtcp2 (for src) +have_libngtcp2=no +if test "x${request_libngtcp2}" != "xno"; then + PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 1.0.0], [have_libngtcp2=yes], + [have_libngtcp2=no]) + if test "x${have_libngtcp2}" = "xno"; then + AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS) + fi +fi + +if test "x${request_libngtcp2}" = "xyes" && + test "x${have_libngtcp2}" != "xyes"; then + AC_MSG_ERROR([libngtcp2 was requested (--with-libngtcp2) but not found]) +fi + +# ngtcp2_crypto_quictls (for src) +have_libngtcp2_crypto_quictls=no +if test "x${have_ssl_provide_quic_data}" = "xyes" && + test "x${have_boringssl_quic}" != "xyes" && + test "x${request_libngtcp2}" != "xno"; then + PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_QUICTLS], + [libngtcp2_crypto_quictls >= 1.0.0], + [have_libngtcp2_crypto_quictls=yes], + [have_libngtcp2_crypto_quictls=no]) + if test "x${have_libngtcp2_crypto_quictls}" = "xno"; then + AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_QUICTLS_PKG_ERRORS) + else + AC_DEFINE([HAVE_LIBNGTCP2_CRYPTO_QUICTLS], [1], + [Define to 1 if you have `libngtcp2_crypto_quictls` library.]) + fi +fi + +if test "x${have_ssl_provide_quic_data}" = "xyes" && + test "x${have_boringssl_quic}" != "xyes" && + test "x${request_libngtcp2}" = "xyes" && + test "x${have_libngtcp2_crypto_quictls}" != "xyes"; then + AC_MSG_ERROR([libngtcp2_crypto_quictls was requested (--with-libngtcp2) but not found]) +fi + +# ngtcp2_crypto_boringssl (for src) +have_libngtcp2_crypto_boringssl=no +if test "x${have_boringssl_quic}" = "xyes" && + test "x${request_libngtcp2}" != "xno"; then + PKG_CHECK_MODULES([LIBNGTCP2_CRYPTO_BORINGSSL], + [libngtcp2_crypto_boringssl >= 0.0.0], + [have_libngtcp2_crypto_boringssl=yes], + [have_libngtcp2_crypto_boringssl=no]) + if test "x${have_libngtcp2_crypto_boringssl}" = "xno"; then + AC_MSG_NOTICE($LIBNGTCP2_CRYPTO_BORINGSSL_PKG_ERRORS) + else + AC_DEFINE([HAVE_LIBNGTCP2_CRYPTO_BORINGSSL], [1], + [Define to 1 if you have `libngtcp2_crypto_boringssl` library.]) + fi +fi + +if test "x${have_boringssl_quic}" = "xyes" && + test "x${request_libngtcp2}" = "xyes" && + test "x${have_libngtcp2_crypto_boringssl}" != "xyes"; then + AC_MSG_ERROR([libngtcp2_crypto_boringssl was requested (--with-libngtcp2) but not found]) +fi + +# nghttp3 (for src) +have_libnghttp3=no +if test "x${request_libnghttp3}" != "xno"; then + PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 1.1.0], [have_libnghttp3=yes], + [have_libnghttp3=no]) + if test "x${have_libnghttp3}" = "xno"; then + AC_MSG_NOTICE($LIBNGHTTP3_PKG_ERRORS) + fi +fi + +if test "x${request_libnghttp3}" = "xyes" && + test "x${have_libnghttp3}" != "xyes"; then + AC_MSG_ERROR([libnghttp3 was requested (--with-libnghttp3) but not found]) +fi + +# libbpf (for src) +have_libbpf=no +if test "x${request_libbpf}" != "xno"; then + PKG_CHECK_MODULES([LIBBPF], [libbpf >= 0.7.0], [have_libbpf=yes], + [have_libbpf=no]) + if test "x${have_libbpf}" = "xyes"; then + AC_DEFINE([HAVE_LIBBPF], [1], [Define to 1 if you have `libbpf` library.]) + if test "x${BPFCFLAGS}" = "x"; then + BPFCFLAGS="-Wall -O2 -g" + fi + # Add the include path for Debian + EXTRABPFCFLAGS="-I/usr/include/$host_cpu-$host_os" + AC_SUBST([EXTRABPFCFLAGS]) + + AC_MSG_CHECKING([whether enum bpf_stats_type is defined in linux/bpf.h]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM( + [[ + #include + ]], + [[ + enum bpf_stats_type foo; + (void)foo; + ]])], + [have_bpf_stats_type=yes], + [have_bpf_stats_type=no]) + + if test "x${have_bpf_stats_type}" = "xyes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_BPF_STATS_TYPE], [1], + [Define to 1 if you have enum bpf_stats_type in linux/bpf.h.]) + else + AC_MSG_RESULT([no]) + fi + else + AC_MSG_NOTICE($LIBBPF_PKG_ERRORS) + fi +fi + +if test "x${request_libbpf}" = "xyes" && + test "x${have_libbpf}" != "xyes"; then + AC_MSG_ERROR([libbpf was requested (--with-libbpf) but not found]) +fi + +AM_CONDITIONAL([HAVE_LIBBPF], [ test "x${have_libbpf}" = "xyes" ]) + +# libevent_openssl (for examples) +# 2.0.8 is required because we use evconnlistener_set_error_cb() +have_libevent_openssl=no +if test "x${request_libevent_openssl}" != "xno"; then + PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8], + [have_libevent_openssl=yes], [have_libevent_openssl=no]) + if test "x${have_libevent_openssl}" = "xno"; then + AC_MSG_NOTICE($LIBEVENT_OPENSSL_PKG_ERRORS) + fi +fi + +if test "x${request_libevent_openssl}" = "xyes" && + test "x${have_libevent_openssl}" != "xyes"; then + AC_MSG_ERROR([libevent_openssl was requested (--with-libevent) but not found]) +fi + +# jansson (for src/nghttp, src/deflatehd and src/inflatehd) +have_jansson=no +if test "x${request_jansson}" != "xno"; then + PKG_CHECK_MODULES([JANSSON], [jansson >= 2.5], + [have_jansson=yes], [have_jansson=no]) + if test "x${have_jansson}" = "xyes"; then + AC_DEFINE([HAVE_JANSSON], [1], + [Define to 1 if you have `libjansson` library.]) + else + AC_MSG_NOTICE($JANSSON_PKG_ERRORS) + fi +fi + +if test "x${request_jansson}" = "xyes" && + test "x${have_jansson}" != "xyes"; then + AC_MSG_ERROR([jansson was requested (--with-jansson) but not found]) +fi + +# libsystemd (for src/nghttpx) +have_libsystemd=no +if test "x${request_systemd}" != "xno"; then + PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 209], [have_libsystemd=yes], + [have_libsystemd=no]) + if test "x${have_libsystemd}" = "xyes"; then + AC_DEFINE([HAVE_LIBSYSTEMD], [1], + [Define to 1 if you have `libsystemd` library.]) + else + AC_MSG_NOTICE($SYSTEMD_PKG_ERRORS) + fi +fi + +if test "x${request_systemd}" = "xyes" && + test "x${have_libsystemd}" != "xyes"; then + AC_MSG_ERROR([systemd was requested (--with-systemd) but not found]) +fi + +# libxml2 (for src/nghttp) +have_libxml2=no +if test "x${request_libxml2}" != "xno"; then + PKG_CHECK_MODULES([LIBXML2], [libxml-2.0 >= 2.6.26], + [have_libxml2=yes], [have_libxml2=no]) + if test "x${have_libxml2}" = "xyes"; then + AC_DEFINE([HAVE_LIBXML2], [1], [Define to 1 if you have `libxml2` library.]) + else + AC_MSG_NOTICE($LIBXML2_PKG_ERRORS) + fi +fi + +if test "x${request_libxml2}" = "xyes" && + test "x${have_libxml2}" != "xyes"; then + AC_MSG_ERROR([libxml2 was requested (--with-libxml2) but not found]) +fi + +AM_CONDITIONAL([HAVE_LIBXML2], [ test "x${have_libxml2}" = "xyes" ]) + +# jemalloc +have_jemalloc=no +if test "x${request_jemalloc}" != "xno"; then + if test "x${JEMALLOC_LIBS}" = "x" && test "x${JEMALLOC_CFLAGS}" = "x"; then + save_LIBS=$LIBS + AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes], [], + [$PTHREAD_LDFLAGS]) + + if test "x${have_jemalloc}" = "xyes"; then + jemalloc_libs=${ac_cv_search_malloc_stats_print} + else + # On Darwin, malloc_stats_print is je_malloc_stats_print + AC_SEARCH_LIBS([je_malloc_stats_print], [jemalloc], [have_jemalloc=yes], [], + [$PTHREAD_LDFLAGS]) + + if test "x${have_jemalloc}" = "xyes"; then + jemalloc_libs=${ac_cv_search_je_malloc_stats_print} + fi + fi + + LIBS=$save_LIBS + + if test "x${have_jemalloc}" = "xyes" && + test "x${jemalloc_libs}" != "xnone required"; then + JEMALLOC_LIBS=${jemalloc_libs} + fi + else + have_jemalloc=yes + fi +fi + +if test "x${request_jemalloc}" = "xyes" && + test "x${have_jemalloc}" != "xyes"; then + AC_MSG_ERROR([jemalloc was requested (--with-jemalloc) but not found]) +fi + +# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL, +# libev, and libc-ares. +enable_app=no +if test "x${request_app}" != "xno" && + test "x${have_zlib}" = "xyes" && + test "x${have_openssl}" = "xyes" && + test "x${have_libev}" = "xyes" && + test "x${have_libcares}" = "xyes"; then + enable_app=yes +fi + +if test "x${request_app}" = "xyes" && + test "x${enable_app}" != "xyes"; then + AC_MSG_ERROR([applications were requested (--enable-app) but dependencies are not met.]) +fi + +AM_CONDITIONAL([ENABLE_APP], [ test "x${enable_app}" = "xyes" ]) + +# Check HTTP/3 support +enable_http3=no +if test "x${request_http3}" != "xno" && + test "x${have_libngtcp2}" = "xyes" && + (test "x${have_libngtcp2_crypto_quictls}" = "xyes" || + test "x${have_libngtcp2_crypto_boringssl}" = "xyes") && + test "x${have_libnghttp3}" = "xyes"; then + enable_http3=yes + AC_DEFINE([ENABLE_HTTP3], [1], [Define to 1 if HTTP/3 is enabled.]) +fi + +if test "x${request_http3}" = "xyes" && + test "x${enable_http3}" != "xyes"; then + AC_MSG_ERROR([HTTP/3 was requested (--enable-http3) but dependencies are not met.]) +fi + +AM_CONDITIONAL([ENABLE_HTTP3], [ test "x${enable_http3}" = "xyes" ]) + +enable_hpack_tools=no +# HPACK tools requires jansson +if test "x${request_hpack_tools}" != "xno" && + test "x${have_jansson}" = "xyes"; then + enable_hpack_tools=yes +fi + +if test "x${request_hpack_tools}" = "xyes" && + test "x${enable_hpack_tools}" != "xyes"; then + AC_MSG_ERROR([HPACK tools were requested (--enable-hpack-tools) but dependencies are not met.]) +fi + +AM_CONDITIONAL([ENABLE_HPACK_TOOLS], [ test "x${enable_hpack_tools}" = "xyes" ]) + +# The example programs depend on OpenSSL and libevent_openssl +enable_examples=no +if test "x${request_examples}" != "xno" && + test "x${have_openssl}" = "xyes" && + test "x${have_libevent_openssl}" = "xyes"; then + enable_examples=yes +fi + +if test "x${request_examples}" = "xyes" && + test "x${enable_examples}" != "xyes"; then + AC_MSG_ERROR([examples were requested (--enable-examples) but dependencies are not met.]) +fi + +AM_CONDITIONAL([ENABLE_EXAMPLES], [ test "x${enable_examples}" = "xyes" ]) + +# third-party only be built when needed + +enable_third_party=no +have_mruby=no +have_neverbleed=no +if test "x${enable_examples}" = "xyes" || + test "x${enable_app}" = "xyes" || + test "x${enable_hpack_tools}" = "xyes"; then + enable_third_party=yes + + # mruby (for src/nghttpx) + if test "x${request_mruby}" = "xyes"; then + # We are going to build mruby + have_mruby=yes + AC_DEFINE([HAVE_MRUBY], [1], [Define to 1 if you have `mruby` library.]) + LIBMRUBY_LIBS="-lmruby -lm" + LIBMRUBY_CFLAGS= + AC_SUBST([LIBMRUBY_LIBS]) + AC_SUBST([LIBMRUBY_CFLAGS]) + fi + + # neverbleed (for src/nghttpx) + if test "x${request_neverbleed}" = "xyes"; then + have_neverbleed=yes + AC_DEFINE([HAVE_NEVERBLEED], [1], [Define to 1 if you have `neverbleed` library.]) + fi +fi + +AM_CONDITIONAL([ENABLE_THIRD_PARTY], [ test "x${enable_third_party}" = "xyes" ]) +AM_CONDITIONAL([HAVE_MRUBY], [test "x${have_mruby}" = "xyes"]) +AM_CONDITIONAL([HAVE_NEVERBLEED], [test "x${have_neverbleed}" = "xyes"]) + +# failmalloc tests +enable_failmalloc=no +if test "x${request_failmalloc}" = "xyes"; then + enable_failmalloc=yes +fi + +AM_CONDITIONAL([ENABLE_FAILMALLOC], [ test "x${enable_failmalloc}" = "xyes" ]) + +# Checks for header files. +AC_HEADER_ASSERT +AC_CHECK_HEADERS([ \ + arpa/inet.h \ + fcntl.h \ + inttypes.h \ + limits.h \ + netdb.h \ + netinet/in.h \ + netinet/ip.h \ + pwd.h \ + stddef.h \ + stdint.h \ + stdlib.h \ + string.h \ + sys/socket.h \ + sys/time.h \ + syslog.h \ + time.h \ + unistd.h \ + windows.h \ +]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_UINT8_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT64_T +AC_TYPE_INT8_T +AC_TYPE_INT16_T +AC_TYPE_INT32_T +AC_TYPE_INT64_T +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_UID_T +AC_CHECK_TYPES([ptrdiff_t]) +AC_C_BIGENDIAN +AC_C_INLINE +AC_SYS_LARGEFILE + +AC_CHECK_MEMBER([struct tm.tm_gmtoff], [have_struct_tm_tm_gmtoff=yes], + [have_struct_tm_tm_gmtoff=no], [[#include ]]) + +AC_CHECK_MEMBER([struct sockaddr_in.sin_len], + [AC_DEFINE([HAVE_SOCKADDR_IN_SIN_LEN],[1], + [Define to 1 if struct sockaddr_in has sin_len member.])], + [], + [[ +#include +#include +#include +]]) + +AC_CHECK_MEMBER([struct sockaddr_in6.sin6_len], + [AC_DEFINE([HAVE_SOCKADDR_IN6_SIN6_LEN],[1], + [Define to 1 if struct sockaddr_in6 has sin6_len member.])], + [], + [[ +#include +#include +#include +]]) + +if test "x$have_struct_tm_tm_gmtoff" = "xyes"; then + AC_DEFINE([HAVE_STRUCT_TM_TM_GMTOFF], [1], + [Define to 1 if you have `struct tm.tm_gmtoff` member.]) +fi + +# Check size of pointer to decide we need 8 bytes alignment +# adjustment. +AC_CHECK_SIZEOF([int *]) + +AC_CHECK_SIZEOF([time_t]) + +# Checks for library functions. + +# Don't check malloc, since it does not play nicely with C++ stdlib +# AC_FUNC_MALLOC + +AC_FUNC_CHOWN +AC_FUNC_ERROR_AT_LINE +AC_FUNC_FORK +# Don't check realloc, since LeakSanitizer detects memory leak during check +# AC_FUNC_REALLOC +AC_FUNC_STRERROR_R +AC_FUNC_STRNLEN + +AC_CHECK_FUNCS([ \ + _Exit \ + accept4 \ + clock_gettime \ + dup2 \ + getcwd \ + getpwnam \ + localtime_r \ + memchr \ + memmove \ + memset \ + mkostemp \ + socket \ + sqrt \ + strchr \ + strdup \ + strerror \ + strndup \ + strstr \ + strtol \ + strtoul \ + timegm \ +]) + +# timerfd_create was added in linux kernel 2.6.25 + +AC_CHECK_FUNC([timerfd_create], + [have_timerfd_create=yes], [have_timerfd_create=no]) + +AC_MSG_CHECKING([checking for GetTickCount64]) +AC_LINK_IFELSE([AC_LANG_PROGRAM( +[[ +#include +]], +[[ +GetTickCount64(); +]])], +[have_gettickcount64=yes], +[have_gettickcount64=no]) + +if test "x${have_gettickcount64}" = "xyes"; then + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_GETTICKCOUNT64], [1], + [Define to 1 if you have `GetTickCount64` function.]) +else + AC_MSG_RESULT([no]) +fi + +# For cygwin: we can link initgroups, so AC_CHECK_FUNCS succeeds, but +# cygwin disables initgroups due to feature test macro magic with our +# configuration. FreeBSD declares initgroups() in unistd.h. +AC_CHECK_DECLS([initgroups], [], [], [[ + #ifdef HAVE_UNISTD_H + # include + #endif + #include +]]) + +AC_CHECK_DECLS([CLOCK_MONOTONIC], [], [], [[ +#include +]]) + +save_CFLAGS=$CFLAGS +save_CXXFLAGS=$CXXFLAGS + +CFLAGS= +CXXFLAGS= + +if test "x$werror" != "xno"; then + # For C compiler + AX_CHECK_COMPILE_FLAG([-Wall], [CFLAGS="$CFLAGS -Wall"]) + AX_CHECK_COMPILE_FLAG([-Wextra], [CFLAGS="$CFLAGS -Wextra"]) + AX_CHECK_COMPILE_FLAG([-Werror], [CFLAGS="$CFLAGS -Werror"]) + AX_CHECK_COMPILE_FLAG([-Wmissing-prototypes], [CFLAGS="$CFLAGS -Wmissing-prototypes"]) + AX_CHECK_COMPILE_FLAG([-Wstrict-prototypes], [CFLAGS="$CFLAGS -Wstrict-prototypes"]) + AX_CHECK_COMPILE_FLAG([-Wmissing-declarations], [CFLAGS="$CFLAGS -Wmissing-declarations"]) + AX_CHECK_COMPILE_FLAG([-Wpointer-arith], [CFLAGS="$CFLAGS -Wpointer-arith"]) + AX_CHECK_COMPILE_FLAG([-Wdeclaration-after-statement], [CFLAGS="$CFLAGS -Wdeclaration-after-statement"]) + AX_CHECK_COMPILE_FLAG([-Wformat-security], [CFLAGS="$CFLAGS -Wformat-security"]) + AX_CHECK_COMPILE_FLAG([-Wwrite-strings], [CFLAGS="$CFLAGS -Wwrite-strings"]) + AX_CHECK_COMPILE_FLAG([-Wshadow], [CFLAGS="$CFLAGS -Wshadow"]) + AX_CHECK_COMPILE_FLAG([-Winline], [CFLAGS="$CFLAGS -Winline"]) + AX_CHECK_COMPILE_FLAG([-Wnested-externs], [CFLAGS="$CFLAGS -Wnested-externs"]) + AX_CHECK_COMPILE_FLAG([-Wfloat-equal], [CFLAGS="$CFLAGS -Wfloat-equal"]) + AX_CHECK_COMPILE_FLAG([-Wundef], [CFLAGS="$CFLAGS -Wundef"]) + AX_CHECK_COMPILE_FLAG([-Wendif-labels], [CFLAGS="$CFLAGS -Wendif-labels"]) + AX_CHECK_COMPILE_FLAG([-Wempty-body], [CFLAGS="$CFLAGS -Wempty-body"]) + AX_CHECK_COMPILE_FLAG([-Wcast-align], [CFLAGS="$CFLAGS -Wcast-align"]) + AX_CHECK_COMPILE_FLAG([-Wclobbered], [CFLAGS="$CFLAGS -Wclobbered"]) + AX_CHECK_COMPILE_FLAG([-Wvla], [CFLAGS="$CFLAGS -Wvla"]) + AX_CHECK_COMPILE_FLAG([-Wpragmas], [CFLAGS="$CFLAGS -Wpragmas"]) + AX_CHECK_COMPILE_FLAG([-Wunreachable-code], [CFLAGS="$CFLAGS -Wunreachable-code"]) + AX_CHECK_COMPILE_FLAG([-Waddress], [CFLAGS="$CFLAGS -Waddress"]) + AX_CHECK_COMPILE_FLAG([-Wattributes], [CFLAGS="$CFLAGS -Wattributes"]) + AX_CHECK_COMPILE_FLAG([-Wdiv-by-zero], [CFLAGS="$CFLAGS -Wdiv-by-zero"]) + AX_CHECK_COMPILE_FLAG([-Wshorten-64-to-32], [CFLAGS="$CFLAGS -Wshorten-64-to-32"]) + + AX_CHECK_COMPILE_FLAG([-Wconversion], [CFLAGS="$CFLAGS -Wconversion"]) + AX_CHECK_COMPILE_FLAG([-Wextended-offsetof], [CFLAGS="$CFLAGS -Wextended-offsetof"]) + AX_CHECK_COMPILE_FLAG([-Wformat-nonliteral], [CFLAGS="$CFLAGS -Wformat-nonliteral"]) + AX_CHECK_COMPILE_FLAG([-Wlanguage-extension-token], [CFLAGS="$CFLAGS -Wlanguage-extension-token"]) + AX_CHECK_COMPILE_FLAG([-Wmissing-field-initializers], [CFLAGS="$CFLAGS -Wmissing-field-initializers"]) + AX_CHECK_COMPILE_FLAG([-Wmissing-noreturn], [CFLAGS="$CFLAGS -Wmissing-noreturn"]) + AX_CHECK_COMPILE_FLAG([-Wmissing-variable-declarations], [CFLAGS="$CFLAGS -Wmissing-variable-declarations"]) + # Not used because we cannot change public structs + # AX_CHECK_COMPILE_FLAG([-Wpadded], [CFLAGS="$CFLAGS -Wpadded"]) + AX_CHECK_COMPILE_FLAG([-Wsign-conversion], [CFLAGS="$CFLAGS -Wsign-conversion"]) + # Not used because this basically disallows default case + # AX_CHECK_COMPILE_FLAG([-Wswitch-enum], [CFLAGS="$CFLAGS -Wswitch-enum"]) + AX_CHECK_COMPILE_FLAG([-Wunreachable-code-break], [CFLAGS="$CFLAGS -Wunreachable-code-break"]) + AX_CHECK_COMPILE_FLAG([-Wunused-macros], [CFLAGS="$CFLAGS -Wunused-macros"]) + AX_CHECK_COMPILE_FLAG([-Wunused-parameter], [CFLAGS="$CFLAGS -Wunused-parameter"]) + AX_CHECK_COMPILE_FLAG([-Wredundant-decls], [CFLAGS="$CFLAGS -Wredundant-decls"]) + # Only work with Clang for the moment + AX_CHECK_COMPILE_FLAG([-Wheader-guard], [CFLAGS="$CFLAGS -Wheader-guard"]) + AX_CHECK_COMPILE_FLAG([-Wsometimes-uninitialized], [CFLAGS="$CFLAGS -Wsometimes-uninitialized"]) + + # This is required because we pass format string as "const char*. + AX_CHECK_COMPILE_FLAG([-Wno-format-nonliteral], [CFLAGS="$CFLAGS -Wno-format-nonliteral"]) + + # For C++ compiler + AC_LANG_PUSH(C++) + AX_CHECK_COMPILE_FLAG([-Wall], [CXXFLAGS="$CXXFLAGS -Wall"]) + AX_CHECK_COMPILE_FLAG([-Werror], [CXXFLAGS="$CXXFLAGS -Werror"]) + AX_CHECK_COMPILE_FLAG([-Wformat-security], [CXXFLAGS="$CXXFLAGS -Wformat-security"]) + AX_CHECK_COMPILE_FLAG([-Wsometimes-uninitialized], [CXXFLAGS="$CXXFLAGS -Wsometimes-uninitialized"]) + # Disable noexcept-type warning of g++-7. This is not harmful as + # long as all source files are compiled with the same compiler. + AX_CHECK_COMPILE_FLAG([-Wno-noexcept-type], [CXXFLAGS="$CXXFLAGS -Wno-noexcept-type"]) + AC_LANG_POP() +fi + +WARNCFLAGS=$CFLAGS +WARNCXXFLAGS=$CXXFLAGS + +CFLAGS=$save_CFLAGS +CXXFLAGS=$save_CXXFLAGS + +AC_SUBST([WARNCFLAGS]) +AC_SUBST([WARNCXXFLAGS]) + +EXTRACFLAG= +AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [EXTRACFLAG="-fvisibility=hidden"]) + +AC_SUBST([EXTRACFLAG]) + +if test "x$debug" != "xno"; then + AC_DEFINE([DEBUGBUILD], [1], [Define to 1 to enable debug output.]) +fi + +enable_threads=yes +# Some platform does not have working std::future. We disable +# threading for those platforms. +if test "x$threads" != "xyes" || + test "x$have_std_future" != "xyes"; then + enable_threads=no + AC_DEFINE([NOTHREADS], [1], [Define to 1 if you want to disable threads.]) +fi + +# propagate $enable_static to tests/Makefile.am +AM_CONDITIONAL([ENABLE_STATIC], [test "x$enable_static" = "xyes"]) + +AC_SUBST([TESTLDADD]) +AC_SUBST([APPLDFLAGS]) + +AC_CONFIG_FILES([ + Makefile + lib/Makefile + lib/libnghttp2.pc + lib/includes/Makefile + lib/includes/nghttp2/nghttp2ver.h + tests/Makefile + tests/testdata/Makefile + third-party/Makefile + src/Makefile + src/testdata/Makefile + bpf/Makefile + examples/Makefile + integration-tests/Makefile + integration-tests/config.go + integration-tests/setenv + doc/Makefile + doc/conf.py + doc/index.rst + doc/package_README.rst + doc/tutorial-client.rst + doc/tutorial-server.rst + doc/tutorial-hpack.rst + doc/nghttpx-howto.rst + doc/h2load-howto.rst + doc/building-android-binary.rst + doc/nghttp2.h.rst + doc/nghttp2ver.h.rst + doc/contribute.rst + contrib/Makefile + script/Makefile +]) +AC_OUTPUT + +AC_MSG_NOTICE([summary of build options: + + Package version: ${VERSION} + Library version: $LT_CURRENT:$LT_REVISION:$LT_AGE + Install prefix: ${prefix} + System types: + Build: ${build} + Host: ${host} + Target: ${target} + Compiler: + C compiler: ${CC} + CFLAGS: ${CFLAGS} + LDFLAGS: ${LDFLAGS} + C++ compiler: ${CXX} + CXXFLAGS: ${CXXFLAGS} + CXXCPP: ${CXXCPP} + C preprocessor: ${CPP} + CPPFLAGS: ${CPPFLAGS} + WARNCFLAGS: ${WARNCFLAGS} + WARNCXXFLAGS: ${WARNCXXFLAGS} + CXX1XCXXFLAGS: ${CXX1XCXXFLAGS} + EXTRACFLAG: ${EXTRACFLAG} + BPFCFLAGS: ${BPFCFLAGS} + EXTRABPFCFLAGS: ${EXTRABPFCFLAGS} + LIBS: ${LIBS} + DEFS: ${DEFS} + EXTRA_DEFS: ${EXTRA_DEFS} + Library: + Shared: ${enable_shared} + Static: ${enable_static} + Libtool: + LIBTOOL_LDFLAGS: ${LIBTOOL_LDFLAGS} + Python: + Python: ${PYTHON} + PYTHON_VERSION: ${PYTHON_VERSION} + Test: + CUnit: ${have_cunit} (CFLAGS='${CUNIT_CFLAGS}' LIBS='${CUNIT_LIBS}') + Failmalloc: ${enable_failmalloc} + Libs: + OpenSSL: ${have_openssl} (CFLAGS='${OPENSSL_CFLAGS}' LIBS='${OPENSSL_LIBS}') + Libxml2: ${have_libxml2} (CFLAGS='${LIBXML2_CFLAGS}' LIBS='${LIBXML2_LIBS}') + Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}') + Libc-ares: ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}') + libngtcp2: ${have_libngtcp2} (CFLAGS='${LIBNGTCP2_CFLAGS}' LIBS='${LIBNGTCP2_LIBS}') + libngtcp2_crypto_quictls: ${have_libngtcp2_crypto_quictls} (CFLAGS='${LIBNGTCP2_CRYPTO_QUICTLS_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_QUICTLS_LIBS}') + libngtcp2_crypto_boringssl: ${have_libngtcp2_crypto_boringssl} (CFLAGS='${LIBNGTCP2_CRYPTO_BORINGSSL_CFLAGS}' LIBS='${LIBNGTCP2_CRYPTO_BORINGSSL_LIBS}') + libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_LIBS}') + libbpf: ${have_libbpf} (CFLAGS='${LIBBPF_CFLAGS}' LIBS='${LIBBPF_LIBS}') + Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}') + Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}') + Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}') + Zlib: ${have_zlib} (CFLAGS='${ZLIB_CFLAGS}' LIBS='${ZLIB_LIBS}') + Systemd: ${have_libsystemd} (CFLAGS='${SYSTEMD_CFLAGS}' LIBS='${SYSTEMD_LIBS}') + Third-party: + http-parser: ${enable_third_party} + MRuby: ${have_mruby} (CFLAGS='${LIBMRUBY_CFLAGS}' LIBS='${LIBMRUBY_LIBS}') + Neverbleed: ${have_neverbleed} + Features: + Applications: ${enable_app} + HPACK tools: ${enable_hpack_tools} + Examples: ${enable_examples} + Threading: ${enable_threads} + HTTP/3 (EXPERIMENTAL): ${enable_http3} +]) diff --git a/lib/nghttp2/contrib/.gitignore b/lib/nghttp2/contrib/.gitignore new file mode 100644 index 00000000000..85acde385e8 --- /dev/null +++ b/lib/nghttp2/contrib/.gitignore @@ -0,0 +1,3 @@ +nghttpx-init +nghttpx.service +nghttpx-upstart.conf diff --git a/lib/nghttp2/contrib/CMakeLists.txt b/lib/nghttp2/contrib/CMakeLists.txt new file mode 100644 index 00000000000..f598f7ba72b --- /dev/null +++ b/lib/nghttp2/contrib/CMakeLists.txt @@ -0,0 +1,12 @@ +set(CONFIGFILES + nghttpx-init + nghttpx.service + nghttpx-upstart.conf +) + +# Note that the execute permissions of nghttpx-init is preserved +foreach(name IN LISTS CONFIGFILES) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +# set(EXTRA_DIST ${CONFIGFILES} nghttpx-logrotate tlsticketupdate.go) diff --git a/lib/nghttp2/contrib/Makefile.am b/lib/nghttp2/contrib/Makefile.am new file mode 100644 index 00000000000..5a02e2f0e3f --- /dev/null +++ b/lib/nghttp2/contrib/Makefile.am @@ -0,0 +1,51 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2014 Tatsuhiro Tsujikawa + +# 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. + +configfiles = nghttpx-init nghttpx.service nghttpx-upstart.conf + +EXTRA_DIST = \ + CMakeLists.txt \ + $(configfiles:%=%.in) \ + nghttpx-logrotate \ + tlsticketupdate.go + +edit = sed -e 's|@bindir[@]|$(bindir)|g' + +nghttpx-init: $(srcdir)/nghttpx-init.in + rm -f $@ $@.tmp + $(edit) $< > $@.tmp + chmod +x $@.tmp + mv $@.tmp $@ + +nghttpx.service: $(srcdir)/nghttpx.service.in + $(edit) $< > $@ + +nghttpx-upstart.conf: $(srcdir)/nghttpx-upstart.conf.in + $(edit) $< > $@ + +$(configfiles): Makefile + +all-local: $(configfiles) + +clean-local: + -rm -f nghttpx-init.tmp $(configfiles) diff --git a/lib/nghttp2/contrib/nghttpx-init.in b/lib/nghttp2/contrib/nghttpx-init.in new file mode 100755 index 00000000000..2b3c76e5b75 --- /dev/null +++ b/lib/nghttp2/contrib/nghttpx-init.in @@ -0,0 +1,164 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: nghttpx +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: nghttpx initscript +# Description: nghttpx initscript +### END INIT INFO + +# Author: Tatsuhiro Tsujikawa +# +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin +DESC="HTTP/2 reverse proxy" +NAME=nghttpx +# Depending on the configuration, binary may be located under @sbindir@ +DAEMON=@bindir@/$NAME +PIDFILE=/var/run/$NAME.pid +DAEMON_ARGS="--conf /etc/nghttpx/nghttpx.conf --pid-file=$PIDFILE --daemon" +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + #start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + #[ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + upgrade) + log_daemon_msg "Upgrading $DESC" "$NAME" + oldpid=`pidofproc -p $PIDFILE $NAME` + case "$?" in + 0) + log_progress_msg "Sending SIGUSR2 to $oldpid..." + kill -USR2 $oldpid + log_progress_msg "Waiting for new binary..." + for i in 1 2 3 4 5 ; do + sleep 1 + newpid=`pidofproc -p $PIDFILE $NAME` + if [ "$newpid" != "$oldpid" ] ; then + break + fi + done + if [ "$newpid" != "$oldpid" ] ; then + log_progress_msg "Sending SIGQUIT to $oldpid..." + kill -QUIT $oldpid + log_end_msg 0 + else + log_progress_msg "New binary failed to start" + log_end_msg 1 + fi + ;; + *) + log_progress_msg "pidofproc() failed" + log_end_msg 1 + ;; + esac + ;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload|upgrade}" >&2 + exit 3 + ;; +esac + +: diff --git a/lib/nghttp2/contrib/nghttpx-logrotate b/lib/nghttp2/contrib/nghttpx-logrotate new file mode 100644 index 00000000000..c715cede2c1 --- /dev/null +++ b/lib/nghttp2/contrib/nghttpx-logrotate @@ -0,0 +1,11 @@ +/var/log/nghttpx/*.log { + weekly + rotate 52 + missingok + compress + delaycompress + notifempty + postrotate + [ -s /var/run/nghttpx.pid ] && kill -USR1 `cat /var/run/nghttpx.pid` 2> /dev/null || true + endscript +} diff --git a/lib/nghttp2/contrib/nghttpx-upstart.conf.in b/lib/nghttp2/contrib/nghttpx-upstart.conf.in new file mode 100644 index 00000000000..0b7991601b7 --- /dev/null +++ b/lib/nghttp2/contrib/nghttpx-upstart.conf.in @@ -0,0 +1,8 @@ +# vim: ft=upstart: + +description "HTTP/2 reverse proxy" + +start on runlevel [2] +stop on runlevel [016] + +exec @bindir@/nghttpx diff --git a/lib/nghttp2/contrib/nghttpx.service.in b/lib/nghttp2/contrib/nghttpx.service.in new file mode 100644 index 00000000000..06fb736c8f1 --- /dev/null +++ b/lib/nghttp2/contrib/nghttpx.service.in @@ -0,0 +1,17 @@ +[Unit] +Description=HTTP/2 proxy +Documentation=man:nghttpx +After=network.target + +[Service] +Type=notify +ExecStart=@bindir@/nghttpx --conf=/etc/nghttpx/nghttpx.conf +ExecReload=/bin/kill --signal HUP $MAINPID +KillSignal=SIGQUIT +PrivateTmp=yes +ProtectHome=yes +ProtectSystem=full +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/lib/nghttp2/contrib/tlsticketupdate.go b/lib/nghttp2/contrib/tlsticketupdate.go new file mode 100644 index 00000000000..ff3d7596aff --- /dev/null +++ b/lib/nghttp2/contrib/tlsticketupdate.go @@ -0,0 +1,112 @@ +// +// nghttp2 - HTTP/2 C Library +// +// Copyright (c) 2015 Tatsuhiro Tsujikawa +// +// 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. +// +package main + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "flag" + "fmt" + "log" + "time" + + "github.com/bradfitz/gomemcache/memcache" +) + +func makeKey(len int) []byte { + b := make([]byte, len) + if _, err := rand.Read(b); err != nil { + log.Fatalf("rand.Read: %v", err) + } + return b +} + +func main() { + var host = flag.String("host", "127.0.0.1", "memcached host") + var port = flag.Int("port", 11211, "memcached port") + var cipher = flag.String("cipher", "aes-128-cbc", "cipher for TLS ticket encryption") + var interval = flag.Int("interval", 3600, "interval to update TLS ticket keys") + + flag.Parse() + + var keylen int + switch *cipher { + case "aes-128-cbc": + keylen = 48 + case "aes-256-cbc": + keylen = 80 + default: + log.Fatalf("cipher: unknown cipher %v", cipher) + } + + mc := memcache.New(fmt.Sprintf("%v:%v", *host, *port)) + + keys := [][]byte{ + makeKey(keylen), // current encryption key + makeKey(keylen), // next encryption key; now decryption only + } + + for { + buf := new(bytes.Buffer) + if err := binary.Write(buf, binary.BigEndian, uint32(1)); err != nil { + log.Fatalf("failed to write version: %v", err) + } + + for _, key := range keys { + if err := binary.Write(buf, binary.BigEndian, uint16(keylen)); err != nil { + log.Fatalf("failed to write length: %v", err) + } + if _, err := buf.Write(key); err != nil { + log.Fatalf("buf.Write: %v", err) + } + } + + mc.Set(&memcache.Item{ + Key: "nghttpx:tls-ticket-key", + Value: buf.Bytes(), + Expiration: int32((*interval) + 300), + }) + + <-time.After(time.Duration(*interval) * time.Second) + + // rotate keys. the last key is now encryption key. + // generate new key and append it to the last, so that + // we can at least decrypt TLS ticket encrypted by new + // key on the host which does not get new key yet. + // keep at most past 11 keys as decryption only key + n := len(keys) + 1 + if n > 13 { + n = 13 + } + newKeys := make([][]byte, n) + newKeys[0] = keys[len(keys)-1] + copy(newKeys[1:], keys[0:n-2]) + newKeys[n-1] = makeKey(keylen) + + keys = newKeys + } + +} diff --git a/lib/nghttp2/contrib/usr.sbin.nghttpx b/lib/nghttp2/contrib/usr.sbin.nghttpx new file mode 100644 index 00000000000..891ff52cad9 --- /dev/null +++ b/lib/nghttp2/contrib/usr.sbin.nghttpx @@ -0,0 +1,16 @@ +#include + +/usr/sbin/nghttpx { + #include + #include + #include + + capability setgid, + capability setuid, + + /usr/sbin/nghttpx rmix, # allow to run itself + /etc/nghttpx/nghttpx.conf r, # allow to read the config file + /etc/ssl/** r, # give access to ssl keys + + /{,var/}run/nghttpx.pid lw, # allow to store a pid file +} diff --git a/lib/nghttp2/doc/.gitignore b/lib/nghttp2/doc/.gitignore new file mode 100644 index 00000000000..ed9e63493a7 --- /dev/null +++ b/lib/nghttp2/doc/.gitignore @@ -0,0 +1,19 @@ +# generated files +apiref.rst +building-android-binary.rst +conf.py +contribute.rst +enums.rst +h2load-howto.rst +index.rst +macros.rst +manual/ +nghttp2.h.rst +nghttp2_*.rst +nghttp2ver.h.rst +nghttpx-howto.rst +package_README.rst +tutorial-client.rst +tutorial-hpack.rst +tutorial-server.rst +types.rst diff --git a/lib/nghttp2/doc/CMakeLists.txt b/lib/nghttp2/doc/CMakeLists.txt new file mode 100644 index 00000000000..531791fc56c --- /dev/null +++ b/lib/nghttp2/doc/CMakeLists.txt @@ -0,0 +1,352 @@ +# Generated documents +set(APIDOCS + macros.rst + enums.rst + types.rst + nghttp2_check_header_name.rst + nghttp2_check_header_value.rst + nghttp2_hd_deflate_bound.rst + nghttp2_hd_deflate_change_table_size.rst + nghttp2_hd_deflate_del.rst + nghttp2_hd_deflate_get_dynamic_table_size.rst + nghttp2_hd_deflate_get_max_dynamic_table_size.rst + nghttp2_hd_deflate_get_num_table_entries.rst + nghttp2_hd_deflate_get_table_entry.rst + nghttp2_hd_deflate_hd.rst + nghttp2_hd_deflate_hd_vec.rst + nghttp2_hd_deflate_new.rst + nghttp2_hd_deflate_new2.rst + nghttp2_hd_inflate_change_table_size.rst + nghttp2_hd_inflate_del.rst + nghttp2_hd_inflate_end_headers.rst + nghttp2_hd_inflate_get_dynamic_table_size.rst + nghttp2_hd_inflate_get_max_dynamic_table_size.rst + nghttp2_hd_inflate_get_num_table_entries.rst + nghttp2_hd_inflate_get_table_entry.rst + nghttp2_hd_inflate_hd.rst + nghttp2_hd_inflate_hd2.rst + nghttp2_hd_inflate_new.rst + nghttp2_hd_inflate_new2.rst + nghttp2_http2_strerror.rst + nghttp2_is_fatal.rst + nghttp2_nv_compare_name.rst + nghttp2_option_del.rst + nghttp2_option_new.rst + nghttp2_option_set_builtin_recv_extension_type.rst + nghttp2_option_set_max_deflate_dynamic_table_size.rst + nghttp2_option_set_max_reserved_remote_streams.rst + nghttp2_option_set_max_send_header_block_length.rst + nghttp2_option_set_no_auto_ping_ack.rst + nghttp2_option_set_no_auto_window_update.rst + nghttp2_option_set_no_http_messaging.rst + nghttp2_option_set_no_recv_client_magic.rst + nghttp2_option_set_peer_max_concurrent_streams.rst + nghttp2_option_set_user_recv_extension_type.rst + nghttp2_option_set_max_settings.rst + nghttp2_pack_settings_payload.rst + nghttp2_priority_spec_check_default.rst + nghttp2_priority_spec_default_init.rst + nghttp2_priority_spec_init.rst + nghttp2_rcbuf_decref.rst + nghttp2_rcbuf_get_buf.rst + nghttp2_rcbuf_incref.rst + nghttp2_rcbuf_is_static.rst + nghttp2_select_next_protocol.rst + nghttp2_session_callbacks_del.rst + nghttp2_session_callbacks_new.rst + nghttp2_session_callbacks_set_before_frame_send_callback.rst + nghttp2_session_callbacks_set_data_source_read_length_callback.rst + nghttp2_session_callbacks_set_error_callback.rst + nghttp2_session_callbacks_set_on_begin_frame_callback.rst + nghttp2_session_callbacks_set_on_begin_headers_callback.rst + nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst + nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst + nghttp2_session_callbacks_set_on_frame_not_send_callback.rst + nghttp2_session_callbacks_set_on_frame_recv_callback.rst + nghttp2_session_callbacks_set_on_frame_send_callback.rst + nghttp2_session_callbacks_set_on_header_callback.rst + nghttp2_session_callbacks_set_on_header_callback2.rst + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst + nghttp2_session_callbacks_set_on_invalid_header_callback.rst + nghttp2_session_callbacks_set_on_invalid_header_callback2.rst + nghttp2_session_callbacks_set_on_stream_close_callback.rst + nghttp2_session_callbacks_set_pack_extension_callback.rst + nghttp2_session_callbacks_set_recv_callback.rst + nghttp2_session_callbacks_set_select_padding_callback.rst + nghttp2_session_callbacks_set_send_callback.rst + nghttp2_session_callbacks_set_send_data_callback.rst + nghttp2_session_callbacks_set_unpack_extension_callback.rst + nghttp2_session_change_stream_priority.rst + nghttp2_session_check_request_allowed.rst + nghttp2_session_check_server_session.rst + nghttp2_session_client_new.rst + nghttp2_session_client_new2.rst + nghttp2_session_client_new3.rst + nghttp2_session_consume.rst + nghttp2_session_consume_connection.rst + nghttp2_session_consume_stream.rst + nghttp2_session_create_idle_stream.rst + nghttp2_session_del.rst + nghttp2_session_find_stream.rst + nghttp2_session_get_effective_local_window_size.rst + nghttp2_session_get_effective_recv_data_length.rst + nghttp2_session_get_hd_deflate_dynamic_table_size.rst + nghttp2_session_get_hd_inflate_dynamic_table_size.rst + nghttp2_session_get_last_proc_stream_id.rst + nghttp2_session_get_local_settings.rst + nghttp2_session_get_local_window_size.rst + nghttp2_session_get_next_stream_id.rst + nghttp2_session_get_outbound_queue_size.rst + nghttp2_session_get_remote_settings.rst + nghttp2_session_get_remote_window_size.rst + nghttp2_session_get_root_stream.rst + nghttp2_session_get_stream_effective_local_window_size.rst + nghttp2_session_get_stream_effective_recv_data_length.rst + nghttp2_session_get_stream_local_close.rst + nghttp2_session_get_stream_local_window_size.rst + nghttp2_session_get_stream_remote_close.rst + nghttp2_session_get_stream_remote_window_size.rst + nghttp2_session_get_stream_user_data.rst + nghttp2_session_mem_recv.rst + nghttp2_session_mem_send.rst + nghttp2_session_recv.rst + nghttp2_session_resume_data.rst + nghttp2_session_send.rst + nghttp2_session_server_new.rst + nghttp2_session_server_new2.rst + nghttp2_session_server_new3.rst + nghttp2_session_set_local_window_size.rst + nghttp2_session_set_next_stream_id.rst + nghttp2_session_set_stream_user_data.rst + nghttp2_session_terminate_session.rst + nghttp2_session_terminate_session2.rst + nghttp2_session_upgrade.rst + nghttp2_session_upgrade2.rst + nghttp2_session_want_read.rst + nghttp2_session_want_write.rst + nghttp2_set_debug_vprintf_callback.rst + nghttp2_stream_get_first_child.rst + nghttp2_stream_get_next_sibling.rst + nghttp2_stream_get_parent.rst + nghttp2_stream_get_previous_sibling.rst + nghttp2_stream_get_state.rst + nghttp2_stream_get_sum_dependency_weight.rst + nghttp2_stream_get_weight.rst + nghttp2_strerror.rst + nghttp2_submit_altsvc.rst + nghttp2_submit_data.rst + nghttp2_submit_extension.rst + nghttp2_submit_goaway.rst + nghttp2_submit_headers.rst + nghttp2_submit_ping.rst + nghttp2_submit_priority.rst + nghttp2_submit_push_promise.rst + nghttp2_submit_request.rst + nghttp2_submit_response.rst + nghttp2_submit_rst_stream.rst + nghttp2_submit_settings.rst + nghttp2_submit_shutdown_notice.rst + nghttp2_submit_trailer.rst + nghttp2_submit_window_update.rst + nghttp2_version.rst +) + +set(MAN_PAGES + nghttp.1 + nghttpd.1 + nghttpx.1 + h2load.1 +) + +# Other .rst files from the source tree that need to be copied +# XXX move them to sources/ and create .in files? +set(RST_FILES + README.rst + programmers-guide.rst + nghttp.1.rst + nghttpd.1.rst + nghttpx.1.rst + h2load.1.rst +) + +# XXX unused for now +set(EXTRA_DIST + mkapiref.py + ${RST_FILES} + ${APIDOCS} + sources/index.rst + sources/tutorial-client.rst + sources/tutorial-server.rst + sources/tutorial-hpack.rst + sources/nghttpx-howto.rst + sources/h2load-howto.rst + sources/building-android-binary.rst + sources/contribute.rst + _exts/rubydomain/LICENSE.rubydomain + _exts/rubydomain/__init__.py + _exts/rubydomain/rubydomain.py + _themes/sphinx_rtd_theme/__init__.py + _themes/sphinx_rtd_theme/breadcrumbs.html + _themes/sphinx_rtd_theme/footer.html + _themes/sphinx_rtd_theme/layout.html + _themes/sphinx_rtd_theme/layout_old.html + _themes/sphinx_rtd_theme/search.html + _themes/sphinx_rtd_theme/searchbox.html + _themes/sphinx_rtd_theme/static/css/badge_only.css + _themes/sphinx_rtd_theme/static/css/theme.css + _themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf + _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot + _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg + _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf + _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff + _themes/sphinx_rtd_theme/static/js/theme.js + _themes/sphinx_rtd_theme/theme.conf + _themes/sphinx_rtd_theme/versions.html + ${MAN_PAGES} + bash_completion/nghttp + bash_completion/nghttpd + bash_completion/nghttpx + bash_completion/h2load +) + +# Based on Makefile for Sphinx documentation + +# You can set these variables from the command line. +set(SPHINXOPTS) +set(SPHINXBUILD sphinx-build) +set(PAPER) +set(BUILDDIR manual) + +# Internal variables. +set(PAPEROPT_a4 -D latex_paper_size=a4) +set(PAPEROPT_letter -D latex_paper_size=letter) +set(ALLSPHINXOPTS -d ${BUILDDIR}/doctrees ${PAPEROPT_${PAPER}} ${SPHINXOPTS} .) + +# "Please use `make ' where is one of" +# " html to make standalone HTML files" +# " dirhtml to make HTML files named index.html in directories" +# " singlehtml to make a single large HTML file" +# " pickle to make pickle files" +# " json to make JSON files" +# " htmlhelp to make HTML files and a HTML help project" +# " qthelp to make HTML files and a qthelp project" +# " devhelp to make HTML files and a Devhelp project" +# " epub to make an epub" +# " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" +# " latexpdf to make LaTeX files and run them through pdflatex" +# " text to make text files" +# " man to make manual pages" +# " changes to make an overview of all changed/added/deprecated items" +# " linkcheck to check all external links for integrity" +# " doctest to run all doctests embedded in the documentation (if enabled)" + + +# Copy files for out-of-tree builds +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + set(RST_BUILD_FILES) + foreach(rstfile IN LISTS RST_FILES) + set(outfile "${CMAKE_CURRENT_BINARY_DIR}/${rstfile}") + add_custom_command(OUTPUT "${outfile}" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/${rstfile}" "${outfile}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${rstfile}" + ) + list(APPEND RST_BUILD_FILES "${outfile}") + endforeach() +else() + set(RST_BUILD_FILES "${RST_FILES}") +endif() + +set(apiref_SOURCES + ${CMAKE_BINARY_DIR}/lib/includes/nghttp2/nghttp2ver.h + ${CMAKE_SOURCE_DIR}/lib/includes/nghttp2/nghttp2.h +) +# Generates apiref.rst and other files +add_custom_command( + OUTPUT + apiref.rst + ${APIDOCS} + COMMAND + "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/mkapiref.py" + apiref.rst macros.rst enums.rst types.rst . + ${apiref_SOURCES} + DEPENDS + ${RST_BUILD_FILES} + ${apiref_SOURCES} +) + + + +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${BUILDDIR}") + +# Invokes sphinx-build and prints the given messages when completed +function(sphinxbuild builder) + set(echo_commands) + foreach(message IN LISTS ARGN) + list(APPEND echo_commands COMMAND ${CMAKE_COMMAND} -E echo "${message}") + endforeach() + add_custom_target(${builder} + COMMAND "${SPHINXBUILD}" -b ${builder} ${ALLSPHINXOPTS} "${BUILDDIR}/${builder}" + COMMAND ${CMAKE_COMMAND} -E echo + ${echo_commands} + VERBATIM + DEPENDS apiref.rst + ) +endfunction() + +foreach(builder html dirhtml singlehtml) + sphinxbuild(${builder} + "Build finished. The HTML pages are in ${BUILDDIR}/${builder}.") +endforeach() +sphinxbuild(pickle "Build finished; now you can process the pickle files.") +sphinxbuild(json "Build finished; now you can process the JSON files.") +sphinxbuild(htmlhelp + "Build finished; now you can run HTML Help Workshop with the" + ".hhp project file in ${BUILDDIR}/htmlhelp." +) +sphinxbuild(qthelp + "Build finished; now you can run \"qcollectiongenerator\" with the" + ".qhcp project file in ${BUILDDIR}/qthelp, like this:" + "# qcollectiongenerator ${BUILDDIR}/qthelp/nghttp2.qhcp" + "To view the help file:" + "# assistant -collectionFile ${BUILDDIR}/qthelp/nghttp2.qhc" +) +sphinxbuild(devhelp + "Build finished." + "To view the help file:" + "# mkdir -p ~/.local/share/devhelp/nghttp2" + "# ln -s ${BUILDDIR}/devhelp ~/.local/share/devhelp/nghttp2" + "# devhelp" +) +sphinxbuild(epub "Build finished. The epub file is in ${BUILDDIR}/epub.") +sphinxbuild(latex + "Build finished; the LaTeX files are in ${BUILDDIR}/latex." + "Run `make' in that directory to run these through (pdf)latex" + "(use `make latexpdf' here to do that automatically)." +) + +# Invoke the Makefile generated by sphinx +add_custom_target(latexpdf + COMMAND ${CMAKE_COMMAND} -E echo "Running LaTeX files through pdflatex..." + COMMAND make -C "${BUILDDIR}/latex" all-pdf + COMMAND ${CMAKE_COMMAND} -E echo "pdflatex finished; the PDF files are in ${BUILDDIR}/latex." + DEPENDS latex +) + +sphinxbuild(text "Build finished. The text files are in ${BUILDDIR}/text.") +sphinxbuild(man "Build finished. The manual pages are in ${BUILDDIR}/man.") +sphinxbuild(changes "The overview file is in ${BUILDDIR}/changes.") +sphinxbuild(linkcheck + "Link check complete; look for any errors in the above output" + "or in ${BUILDDIR}/linkcheck/output.txt." +) +sphinxbuild(doctest + "Testing of doctests in the sources finished, look at the" + "results in ${BUILDDIR}/doctest/output.txt." +) + +foreach(_man_page IN LISTS MAN_PAGES) + install(FILES ${_man_page} + DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" + ) +endforeach() diff --git a/lib/nghttp2/doc/Makefile.am b/lib/nghttp2/doc/Makefile.am new file mode 100644 index 00000000000..4e6f2779e25 --- /dev/null +++ b/lib/nghttp2/doc/Makefile.am @@ -0,0 +1,365 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# 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. + +man_MANS = nghttp.1 nghttpd.1 nghttpx.1 h2load.1 + +APIDOCS= \ + macros.rst \ + enums.rst \ + types.rst \ + nghttp2_check_authority.rst \ + nghttp2_check_header_name.rst \ + nghttp2_check_header_value.rst \ + nghttp2_check_header_value_rfc9113.rst \ + nghttp2_check_method.rst \ + nghttp2_check_path.rst \ + nghttp2_extpri_parse_priority.rst \ + nghttp2_hd_deflate_bound.rst \ + nghttp2_hd_deflate_change_table_size.rst \ + nghttp2_hd_deflate_del.rst \ + nghttp2_hd_deflate_get_dynamic_table_size.rst \ + nghttp2_hd_deflate_get_max_dynamic_table_size.rst \ + nghttp2_hd_deflate_get_num_table_entries.rst \ + nghttp2_hd_deflate_get_table_entry.rst \ + nghttp2_hd_deflate_hd.rst \ + nghttp2_hd_deflate_hd_vec.rst \ + nghttp2_hd_deflate_new.rst \ + nghttp2_hd_deflate_new2.rst \ + nghttp2_hd_inflate_change_table_size.rst \ + nghttp2_hd_inflate_del.rst \ + nghttp2_hd_inflate_end_headers.rst \ + nghttp2_hd_inflate_get_dynamic_table_size.rst \ + nghttp2_hd_inflate_get_max_dynamic_table_size.rst \ + nghttp2_hd_inflate_get_num_table_entries.rst \ + nghttp2_hd_inflate_get_table_entry.rst \ + nghttp2_hd_inflate_hd.rst \ + nghttp2_hd_inflate_hd2.rst \ + nghttp2_hd_inflate_new.rst \ + nghttp2_hd_inflate_new2.rst \ + nghttp2_http2_strerror.rst \ + nghttp2_is_fatal.rst \ + nghttp2_nv_compare_name.rst \ + nghttp2_option_del.rst \ + nghttp2_option_new.rst \ + nghttp2_option_set_builtin_recv_extension_type.rst \ + nghttp2_option_set_max_deflate_dynamic_table_size.rst \ + nghttp2_option_set_max_reserved_remote_streams.rst \ + nghttp2_option_set_max_send_header_block_length.rst \ + nghttp2_option_set_no_auto_ping_ack.rst \ + nghttp2_option_set_no_auto_window_update.rst \ + nghttp2_option_set_no_closed_streams.rst \ + nghttp2_option_set_no_http_messaging.rst \ + nghttp2_option_set_no_recv_client_magic.rst \ + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation.rst \ + nghttp2_option_set_peer_max_concurrent_streams.rst \ + nghttp2_option_set_server_fallback_rfc7540_priorities.rst \ + nghttp2_option_set_user_recv_extension_type.rst \ + nghttp2_option_set_max_outbound_ack.rst \ + nghttp2_option_set_max_settings.rst \ + nghttp2_option_set_stream_reset_rate_limit.rst \ + nghttp2_pack_settings_payload.rst \ + nghttp2_priority_spec_check_default.rst \ + nghttp2_priority_spec_default_init.rst \ + nghttp2_priority_spec_init.rst \ + nghttp2_rcbuf_decref.rst \ + nghttp2_rcbuf_get_buf.rst \ + nghttp2_rcbuf_incref.rst \ + nghttp2_rcbuf_is_static.rst \ + nghttp2_select_next_protocol.rst \ + nghttp2_session_callbacks_del.rst \ + nghttp2_session_callbacks_new.rst \ + nghttp2_session_callbacks_set_before_frame_send_callback.rst \ + nghttp2_session_callbacks_set_data_source_read_length_callback.rst \ + nghttp2_session_callbacks_set_error_callback.rst \ + nghttp2_session_callbacks_set_error_callback2.rst \ + nghttp2_session_callbacks_set_on_begin_frame_callback.rst \ + nghttp2_session_callbacks_set_on_begin_headers_callback.rst \ + nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst \ + nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst \ + nghttp2_session_callbacks_set_on_frame_not_send_callback.rst \ + nghttp2_session_callbacks_set_on_frame_recv_callback.rst \ + nghttp2_session_callbacks_set_on_frame_send_callback.rst \ + nghttp2_session_callbacks_set_on_header_callback.rst \ + nghttp2_session_callbacks_set_on_header_callback2.rst \ + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \ + nghttp2_session_callbacks_set_on_invalid_header_callback.rst \ + nghttp2_session_callbacks_set_on_invalid_header_callback2.rst \ + nghttp2_session_callbacks_set_on_stream_close_callback.rst \ + nghttp2_session_callbacks_set_pack_extension_callback.rst \ + nghttp2_session_callbacks_set_recv_callback.rst \ + nghttp2_session_callbacks_set_select_padding_callback.rst \ + nghttp2_session_callbacks_set_send_callback.rst \ + nghttp2_session_callbacks_set_send_data_callback.rst \ + nghttp2_session_callbacks_set_unpack_extension_callback.rst \ + nghttp2_session_change_extpri_stream_priority.rst \ + nghttp2_session_change_stream_priority.rst \ + nghttp2_session_check_request_allowed.rst \ + nghttp2_session_check_server_session.rst \ + nghttp2_session_client_new.rst \ + nghttp2_session_client_new2.rst \ + nghttp2_session_client_new3.rst \ + nghttp2_session_consume.rst \ + nghttp2_session_consume_connection.rst \ + nghttp2_session_consume_stream.rst \ + nghttp2_session_create_idle_stream.rst \ + nghttp2_session_del.rst \ + nghttp2_session_find_stream.rst \ + nghttp2_session_get_effective_local_window_size.rst \ + nghttp2_session_get_effective_recv_data_length.rst \ + nghttp2_session_get_extpri_stream_priority.rst \ + nghttp2_session_get_hd_deflate_dynamic_table_size.rst \ + nghttp2_session_get_hd_inflate_dynamic_table_size.rst \ + nghttp2_session_get_last_proc_stream_id.rst \ + nghttp2_session_get_local_settings.rst \ + nghttp2_session_get_local_window_size.rst \ + nghttp2_session_get_next_stream_id.rst \ + nghttp2_session_get_outbound_queue_size.rst \ + nghttp2_session_get_remote_settings.rst \ + nghttp2_session_get_remote_window_size.rst \ + nghttp2_session_get_root_stream.rst \ + nghttp2_session_get_stream_effective_local_window_size.rst \ + nghttp2_session_get_stream_effective_recv_data_length.rst \ + nghttp2_session_get_stream_local_close.rst \ + nghttp2_session_get_stream_local_window_size.rst \ + nghttp2_session_get_stream_remote_close.rst \ + nghttp2_session_get_stream_remote_window_size.rst \ + nghttp2_session_get_stream_user_data.rst \ + nghttp2_session_mem_recv.rst \ + nghttp2_session_mem_send.rst \ + nghttp2_session_recv.rst \ + nghttp2_session_resume_data.rst \ + nghttp2_session_send.rst \ + nghttp2_session_server_new.rst \ + nghttp2_session_server_new2.rst \ + nghttp2_session_server_new3.rst \ + nghttp2_session_set_local_window_size.rst \ + nghttp2_session_set_next_stream_id.rst \ + nghttp2_session_set_stream_user_data.rst \ + nghttp2_session_set_user_data.rst \ + nghttp2_session_terminate_session.rst \ + nghttp2_session_terminate_session2.rst \ + nghttp2_session_upgrade.rst \ + nghttp2_session_upgrade2.rst \ + nghttp2_session_want_read.rst \ + nghttp2_session_want_write.rst \ + nghttp2_set_debug_vprintf_callback.rst \ + nghttp2_stream_get_first_child.rst \ + nghttp2_stream_get_next_sibling.rst \ + nghttp2_stream_get_parent.rst \ + nghttp2_stream_get_previous_sibling.rst \ + nghttp2_stream_get_state.rst \ + nghttp2_stream_get_sum_dependency_weight.rst \ + nghttp2_stream_get_weight.rst \ + nghttp2_strerror.rst \ + nghttp2_submit_altsvc.rst \ + nghttp2_submit_data.rst \ + nghttp2_submit_extension.rst \ + nghttp2_submit_goaway.rst \ + nghttp2_submit_headers.rst \ + nghttp2_submit_origin.rst \ + nghttp2_submit_ping.rst \ + nghttp2_submit_priority.rst \ + nghttp2_submit_priority_update.rst \ + nghttp2_submit_push_promise.rst \ + nghttp2_submit_request.rst \ + nghttp2_submit_response.rst \ + nghttp2_submit_rst_stream.rst \ + nghttp2_submit_settings.rst \ + nghttp2_submit_shutdown_notice.rst \ + nghttp2_submit_trailer.rst \ + nghttp2_submit_window_update.rst \ + nghttp2_version.rst + +RST_FILES = \ + README.rst \ + programmers-guide.rst \ + nghttp.1.rst \ + nghttpd.1.rst \ + nghttpx.1.rst \ + h2load.1.rst + +EXTRA_DIST = \ + CMakeLists.txt \ + mkapiref.py \ + $(RST_FILES) \ + $(APIDOCS) \ + sources/index.rst \ + sources/tutorial-client.rst \ + sources/tutorial-server.rst \ + sources/tutorial-hpack.rst \ + sources/nghttpx-howto.rst \ + sources/h2load-howto.rst \ + sources/building-android-binary.rst \ + sources/contribute.rst \ + sources/security.rst \ + _exts/rubydomain/LICENSE.rubydomain \ + _exts/rubydomain/__init__.py \ + _exts/rubydomain/rubydomain.py \ + $(man_MANS) \ + bash_completion/nghttp \ + bash_completion/nghttpd \ + bash_completion/nghttpx \ + bash_completion/h2load + +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD ?= sphinx-build +PAPER = +BUILDDIR = manual + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +apiref.rst: \ + $(top_builddir)/lib/includes/nghttp2/nghttp2ver.h \ + $(top_srcdir)/lib/includes/nghttp2/nghttp2.h + for i in $(RST_FILES); do [ -e $(builddir)/$$i ] || cp $(srcdir)/$$i $(builddir); done + $(PYTHON) $(top_srcdir)/doc/mkapiref.py \ + apiref.rst macros.rst enums.rst types.rst . $^ + +$(APIDOCS): apiref.rst + +clean-local: + if [ $(srcdir) != $(builddir) ]; then for i in $(RST_FILES); do rm -f $(builddir)/$$i; done fi + -rm -f apiref.rst + -rm -f $(APIDOCS) + -rm -rf $(BUILDDIR)/* + +html-local: apiref.rst + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/nghttp2.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/nghttp2.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/nghttp2" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/nghttp2" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: apiref.rst + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/lib/nghttp2/doc/README.rst b/lib/nghttp2/doc/README.rst new file mode 100644 index 00000000000..549e5506ac4 --- /dev/null +++ b/lib/nghttp2/doc/README.rst @@ -0,0 +1,160 @@ +nghttp2 Documentation +===================== + +The documentation of nghttp2 is generated using Sphinx. This +directory contains the source files to be processed by Sphinx. The +source file for API reference is generated using a script called +``mkapiref.py`` from the nghttp2 C source code. + +Generating API reference +------------------------ + +As described earlier, we use ``mkapiref.py`` to generate rst formatted +text of API reference from C source code. The ``mkapiref.py`` is not +so flexible and it requires that C source code is formatted in rather +strict rules. + +To generate API reference, just run ``make html``. It runs +``mkapiref.py`` and then run Sphinx to build the entire document. + +The ``mkapiref.py`` reads C source code and searches the comment block +starts with ``/**``. In other words, it only processes the comment +block starting ``/**``. The comment block must end with ``*/``. The +``mkapiref.py`` requires that which type of the object this comment +block refers to. To specify the type of the object, the next line +must contain the so-called action keyword. Currently, the following +action keywords are supported: ``@function``, ``@functypedef``, +``@enum``, ``@struct`` and ``@union``. The following sections +describes each action keyword. + +@function +######### + +``@function`` is used to refer to the function. The comment block is +used for the document for the function. After the script sees the end +of the comment block, it consumes the lines as the function +declaration until the line which ends with ``;`` is encountered. + +In Sphinx doc, usually the function argument is formatted like +``*this*``. But in C, ``*`` is used for dereferencing a pointer and +we must escape ``*`` with a back slash. To avoid this, we format the +argument like ``|this|``. The ``mkapiref.py`` translates it with +``*this*``, as escaping ``*`` inside ``|`` and ``|`` as necessary. +Note that this shadows the substitution feature of Sphinx. + +The example follows:: + + /** + * @function + * + * Submits PING frame to the |session|. + */ + int nghttp2_submit_ping(nghttp2_session *session); + + +@functypedef +############ + +``@functypedef`` is used to refer to the typedef of the function +pointer. The formatting rule is pretty much the same with +``@function``, but this outputs ``type`` domain, rather than +``function`` domain. + +The example follows:: + + /** + * @functypedef + * + * Callback function invoked when |session| wants to send data to + * remote peer. + */ + typedef ssize_t (*nghttp2_send_callback) + (nghttp2_session *session, + const uint8_t *data, size_t length, int flags, void *user_data); + +@enum +##### + +``@enum`` is used to refer to the enum. Currently, only enum typedefs +are supported. The comment block is used for the document for the +enum type itself. To document each values, put comment block starting +with the line ``/**`` and ending with the ``*/`` just before the enum +value. When the line starts with ``}`` is encountered, the +``mkapiref.py`` extracts strings next to ``}`` as the name of enum. + +At the time of this writing, Sphinx does not support enum type. So we +use ``type`` domain for enum it self and ``macro`` domain for each +value. To refer to the enum value, use ``:enum:`` pseudo role. The +``mkapiref.py`` replaces it with ``:macro:``. By doing this, when +Sphinx will support enum officially, we can replace ``:enum:`` with +the official role easily. + +The example follows:: + + /** + * @enum + * Error codes used in the nghttp2 library. + */ + typedef enum { + /** + * Invalid argument passed. + */ + NGHTTP2_ERR_INVALID_ARGUMENT = -501, + /** + * Zlib error. + */ + NGHTTP2_ERR_ZLIB = -502, + } nghttp2_error; + +@struct +####### + +``@struct`` is used to refer to the struct. Currently, only struct +typedefs are supported. The comment block is used for the document for +the struct type itself.To document each member, put comment block +starting with the line ``/**`` and ending with the ``*/`` just before +the member. When the line starts with ``}`` is encountered, the +``mkapiref.py`` extracts strings next to ``}`` as the name of struct. +The block-less typedef is also supported. In this case, typedef +declaration must be all in one line and the ``mkapiref.py`` uses last +word as the name of struct. + +Some examples follow:: + + /** + * @struct + * The control frame header. + */ + typedef struct { + /** + * SPDY protocol version. + */ + uint16_t version; + /** + * The type of this control frame. + */ + uint16_t type; + /** + * The control frame flags. + */ + uint8_t flags; + /** + * The length field of this control frame. + */ + int32_t length; + } nghttp2_ctrl_hd; + + /** + * @struct + * + * The primary structure to hold the resources needed for a SPDY + * session. The details of this structure is hidden from the public + * API. + */ + typedef struct nghttp2_session nghttp2_session; + +@union +###### + +``@union`` is used to refer to the union. Currently, ``@union`` is an +alias of ``@struct``. diff --git a/lib/nghttp2/doc/_exts/rubydomain/LICENSE.rubydomain b/lib/nghttp2/doc/_exts/rubydomain/LICENSE.rubydomain new file mode 100644 index 00000000000..a560d7597d3 --- /dev/null +++ b/lib/nghttp2/doc/_exts/rubydomain/LICENSE.rubydomain @@ -0,0 +1,28 @@ +If not otherwise noted, the extensions in this package are licensed +under the following license. + +Copyright (c) 2010 by the contributors (see AUTHORS file). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/nghttp2/doc/_exts/rubydomain/__init__.py b/lib/nghttp2/doc/_exts/rubydomain/__init__.py new file mode 100644 index 00000000000..b5a7dc29ef0 --- /dev/null +++ b/lib/nghttp2/doc/_exts/rubydomain/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" + sphinxcontrib + ~~~~~~~~~~~~~ + + This package is a namespace package that contains all extensions + distributed in the ``sphinx-contrib`` distribution. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +__import__('pkg_resources').declare_namespace(__name__) + diff --git a/lib/nghttp2/doc/_exts/rubydomain/rubydomain.py b/lib/nghttp2/doc/_exts/rubydomain/rubydomain.py new file mode 100644 index 00000000000..db352334752 --- /dev/null +++ b/lib/nghttp2/doc/_exts/rubydomain/rubydomain.py @@ -0,0 +1,703 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains.ruby + ~~~~~~~~~~~~~~~~~~~ + + The Ruby domain. + + :copyright: Copyright 2010 by SHIBUKAWA Yoshiki + :license: BSD, see LICENSE for details. +""" + +import re + +from docutils import nodes +from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive + +from sphinx import addnodes +from sphinx import version_info +from sphinx.roles import XRefRole +from sphinx.locale import _ +from sphinx.domains import Domain, ObjType, Index +from sphinx.directives import ObjectDescription +from sphinx.util.nodes import make_refnode +from sphinx.util.docfields import Field, GroupedField, TypedField + +# REs for Ruby signatures +rb_sig_re = re.compile( + r'''^ ([\w.]*\.)? # class name(s) + (\$?\w+\??!?) \s* # thing name + (?: \((.*)\) # optional: arguments + (?:\s* -> \s* (.*))? # return annotation + )? $ # and nothing more + ''', re.VERBOSE) + +rb_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' + +separators = { + 'method':'#', 'attr_reader':'#', 'attr_writer':'#', 'attr_accessor':'#', + 'function':'.', 'classmethod':'.', 'class':'::', 'module':'::', + 'global':'', 'const':'::'} + +rb_separator = re.compile(r"(?:\w+)?(?:::)?(?:\.)?(?:#)?") + + +def _iteritems(d): + + for k in d: + yield k, d[k] + + +def ruby_rsplit(fullname): + items = [item for item in rb_separator.findall(fullname)] + return ''.join(items[:-2]), items[-1] + + +class RubyObject(ObjectDescription): + """ + Description of a general Ruby object. + """ + option_spec = { + 'noindex': directives.flag, + 'module': directives.unchanged, + } + + doc_field_types = [ + TypedField('parameter', label=_('Parameters'), + names=('param', 'parameter', 'arg', 'argument'), + typerolename='obj', typenames=('paramtype', 'type')), + TypedField('variable', label=_('Variables'), rolename='obj', + names=('var', 'ivar', 'cvar'), + typerolename='obj', typenames=('vartype',)), + GroupedField('exceptions', label=_('Raises'), rolename='exc', + names=('raises', 'raise', 'exception', 'except'), + can_collapse=True), + Field('returnvalue', label=_('Returns'), has_arg=False, + names=('returns', 'return')), + Field('returntype', label=_('Return type'), has_arg=False, + names=('rtype',)), + ] + + def get_signature_prefix(self, sig): + """ + May return a prefix to put before the object name in the signature. + """ + return '' + + def needs_arglist(self): + """ + May return true if an empty argument list is to be generated even if + the document contains none. + """ + return False + + def handle_signature(self, sig, signode): + """ + Transform a Ruby signature into RST nodes. + Returns (fully qualified name of the thing, classname if any). + + If inside a class, the current class name is handled intelligently: + * it is stripped from the displayed name if present + * it is added to the full name (return value) if not present + """ + m = rb_sig_re.match(sig) + if m is None: + raise ValueError + name_prefix, name, arglist, retann = m.groups() + if not name_prefix: + name_prefix = "" + # determine module and class name (if applicable), as well as full name + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + classname = self.env.temp_data.get('rb:class') + if self.objtype == 'global': + add_module = False + modname = None + classname = None + fullname = name + elif classname: + add_module = False + if name_prefix and name_prefix.startswith(classname): + fullname = name_prefix + name + # class name is given again in the signature + name_prefix = name_prefix[len(classname):].lstrip('.') + else: + separator = separators[self.objtype] + fullname = classname + separator + name_prefix + name + else: + add_module = True + if name_prefix: + classname = name_prefix.rstrip('.') + fullname = name_prefix + name + else: + classname = '' + fullname = name + + signode['module'] = modname + signode['class'] = self.class_name = classname + signode['fullname'] = fullname + + sig_prefix = self.get_signature_prefix(sig) + if sig_prefix: + signode += addnodes.desc_annotation(sig_prefix, sig_prefix) + + if name_prefix: + signode += addnodes.desc_addname(name_prefix, name_prefix) + # exceptions are a special case, since they are documented in the + # 'exceptions' module. + elif add_module and self.env.config.add_module_names: + if self.objtype == 'global': + nodetext = '' + signode += addnodes.desc_addname(nodetext, nodetext) + else: + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + if modname and modname != 'exceptions': + nodetext = modname + separators[self.objtype] + signode += addnodes.desc_addname(nodetext, nodetext) + + signode += addnodes.desc_name(name, name) + if not arglist: + if self.needs_arglist(): + # for callables, add an empty parameter list + signode += addnodes.desc_parameterlist() + if retann: + signode += addnodes.desc_returns(retann, retann) + return fullname, name_prefix + signode += addnodes.desc_parameterlist() + + stack = [signode[-1]] + for token in rb_paramlist_re.split(arglist): + if token == '[': + opt = addnodes.desc_optional() + stack[-1] += opt + stack.append(opt) + elif token == ']': + try: + stack.pop() + except IndexError: + raise ValueError + elif not token or token == ',' or token.isspace(): + pass + else: + token = token.strip() + stack[-1] += addnodes.desc_parameter(token, token) + if len(stack) != 1: + raise ValueError + if retann: + signode += addnodes.desc_returns(retann, retann) + return fullname, name_prefix + + def get_index_text(self, modname, name): + """ + Return the text for the index entry of the object. + """ + raise NotImplementedError('must be implemented in subclasses') + + def _is_class_member(self): + return self.objtype.endswith('method') or self.objtype.startswith('attr') + + def add_target_and_index(self, name_cls, sig, signode): + if self.objtype == 'global': + modname = '' + else: + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + separator = separators[self.objtype] + if self._is_class_member(): + if signode['class']: + prefix = modname and modname + '::' or '' + else: + prefix = modname and modname + separator or '' + else: + prefix = modname and modname + separator or '' + fullname = prefix + name_cls[0] + # note target + if fullname not in self.state.document.ids: + signode['names'].append(fullname) + signode['ids'].append(fullname) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + objects = self.env.domaindata['rb']['objects'] + if fullname in objects: + self.env.warn( + self.env.docname, + 'duplicate object description of %s, ' % fullname + + 'other instance in ' + + self.env.doc2path(objects[fullname][0]), + self.lineno) + objects[fullname] = (self.env.docname, self.objtype) + + indextext = self.get_index_text(modname, name_cls) + if indextext: + self.indexnode['entries'].append( + _make_index('single', indextext, fullname, fullname)) + + def before_content(self): + # needed for automatic qualification of members (reset in subclasses) + self.clsname_set = False + + def after_content(self): + if self.clsname_set: + self.env.temp_data['rb:class'] = None + + +class RubyModulelevel(RubyObject): + """ + Description of an object on module level (functions, data). + """ + + def needs_arglist(self): + return self.objtype == 'function' + + def get_index_text(self, modname, name_cls): + if self.objtype == 'function': + if not modname: + return _('%s() (global function)') % name_cls[0] + return _('%s() (module function in %s)') % (name_cls[0], modname) + else: + return '' + + +class RubyGloballevel(RubyObject): + """ + Description of an object on module level (functions, data). + """ + + def get_index_text(self, modname, name_cls): + if self.objtype == 'global': + return _('%s (global variable)') % name_cls[0] + else: + return '' + + +class RubyEverywhere(RubyObject): + """ + Description of a class member (methods, attributes). + """ + + def needs_arglist(self): + return self.objtype == 'method' + + def get_index_text(self, modname, name_cls): + name, cls = name_cls + add_modules = self.env.config.add_module_names + if self.objtype == 'method': + try: + clsname, methname = ruby_rsplit(name) + except ValueError: + if modname: + return _('%s() (in module %s)') % (name, modname) + else: + return '%s()' % name + if modname and add_modules: + return _('%s() (%s::%s method)') % (methname, modname, + clsname) + else: + return _('%s() (%s method)') % (methname, clsname) + else: + return '' + + +class RubyClasslike(RubyObject): + """ + Description of a class-like object (classes, exceptions). + """ + + def get_signature_prefix(self, sig): + return self.objtype + ' ' + + def get_index_text(self, modname, name_cls): + if self.objtype == 'class': + if not modname: + return _('%s (class)') % name_cls[0] + return _('%s (class in %s)') % (name_cls[0], modname) + elif self.objtype == 'exception': + return name_cls[0] + else: + return '' + + def before_content(self): + RubyObject.before_content(self) + if self.names: + self.env.temp_data['rb:class'] = self.names[0][0] + self.clsname_set = True + + +class RubyClassmember(RubyObject): + """ + Description of a class member (methods, attributes). + """ + + def needs_arglist(self): + return self.objtype.endswith('method') + + def get_signature_prefix(self, sig): + if self.objtype == 'classmethod': + return "classmethod %s." % self.class_name + elif self.objtype == 'attr_reader': + return "attribute [R] " + elif self.objtype == 'attr_writer': + return "attribute [W] " + elif self.objtype == 'attr_accessor': + return "attribute [R/W] " + return '' + + def get_index_text(self, modname, name_cls): + name, cls = name_cls + add_modules = self.env.config.add_module_names + if self.objtype == 'classmethod': + try: + clsname, methname = ruby_rsplit(name) + except ValueError: + return '%s()' % name + if modname: + return _('%s() (%s.%s class method)') % (methname, modname, + clsname) + else: + return _('%s() (%s class method)') % (methname, clsname) + elif self.objtype.startswith('attr'): + try: + clsname, attrname = ruby_rsplit(name) + except ValueError: + return name + if modname and add_modules: + return _('%s (%s.%s attribute)') % (attrname, modname, clsname) + else: + return _('%s (%s attribute)') % (attrname, clsname) + else: + return '' + + def before_content(self): + RubyObject.before_content(self) + lastname = self.names and self.names[-1][1] + if lastname and not self.env.temp_data.get('rb:class'): + self.env.temp_data['rb:class'] = lastname.strip('.') + self.clsname_set = True + + +class RubyModule(Directive): + """ + Directive to mark description of a new module. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + 'platform': lambda x: x, + 'synopsis': lambda x: x, + 'noindex': directives.flag, + 'deprecated': directives.flag, + } + + def run(self): + env = self.state.document.settings.env + modname = self.arguments[0].strip() + noindex = 'noindex' in self.options + env.temp_data['rb:module'] = modname + env.domaindata['rb']['modules'][modname] = \ + (env.docname, self.options.get('synopsis', ''), + self.options.get('platform', ''), 'deprecated' in self.options) + targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True) + self.state.document.note_explicit_target(targetnode) + ret = [targetnode] + # XXX this behavior of the module directive is a mess... + if 'platform' in self.options: + platform = self.options['platform'] + node = nodes.paragraph() + node += nodes.emphasis('', _('Platforms: ')) + node += nodes.Text(platform, platform) + ret.append(node) + # the synopsis isn't printed; in fact, it is only used in the + # modindex currently + if not noindex: + indextext = _('%s (module)') % modname + inode = addnodes.index(entries=[_make_index( + 'single', indextext, 'module-' + modname, modname)]) + ret.append(inode) + return ret + +def _make_index(entrytype, entryname, target, ignored, key=None): + # Sphinx 1.4 introduced backward incompatible changes, it now + # requires 5 tuples. Last one is categorization key. See + # http://www.sphinx-doc.org/en/stable/extdev/nodes.html#sphinx.addnodes.index + if version_info >= (1, 4, 0, '', 0): + return (entrytype, entryname, target, ignored, key) + else: + return (entrytype, entryname, target, ignored) + +class RubyCurrentModule(Directive): + """ + This directive is just to tell Sphinx that we're documenting + stuff in module foo, but links to module foo won't lead here. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + env = self.state.document.settings.env + modname = self.arguments[0].strip() + if modname == 'None': + env.temp_data['rb:module'] = None + else: + env.temp_data['rb:module'] = modname + return [] + + +class RubyXRefRole(XRefRole): + def process_link(self, env, refnode, has_explicit_title, title, target): + if not has_explicit_title: + title = title.lstrip('.') # only has a meaning for the target + title = title.lstrip('#') + if title.startswith("::"): + title = title[2:] + target = target.lstrip('~') # only has a meaning for the title + # if the first character is a tilde, don't display the module/class + # parts of the contents + if title[0:1] == '~': + m = re.search(r"(?:\.)?(?:#)?(?:::)?(.*)\Z", title) + if m: + title = m.group(1) + if not title.startswith("$"): + refnode['rb:module'] = env.temp_data.get('rb:module') + refnode['rb:class'] = env.temp_data.get('rb:class') + # if the first character is a dot, search more specific namespaces first + # else search builtins first + if target[0:1] == '.': + target = target[1:] + refnode['refspecific'] = True + return title, target + + +class RubyModuleIndex(Index): + """ + Index subclass to provide the Ruby module index. + """ + + name = 'modindex' + localname = _('Ruby Module Index') + shortname = _('modules') + + def generate(self, docnames=None): + content = {} + # list of prefixes to ignore + ignores = self.domain.env.config['modindex_common_prefix'] + ignores = sorted(ignores, key=len, reverse=True) + # list of all modules, sorted by module name + modules = sorted(_iteritems(self.domain.data['modules']), + key=lambda x: x[0].lower()) + # sort out collapsible modules + prev_modname = '' + num_toplevels = 0 + for modname, (docname, synopsis, platforms, deprecated) in modules: + if docnames and docname not in docnames: + continue + + for ignore in ignores: + if modname.startswith(ignore): + modname = modname[len(ignore):] + stripped = ignore + break + else: + stripped = '' + + # we stripped the whole module name? + if not modname: + modname, stripped = stripped, '' + + entries = content.setdefault(modname[0].lower(), []) + + package = modname.split('::')[0] + if package != modname: + # it's a submodule + if prev_modname == package: + # first submodule - make parent a group head + entries[-1][1] = 1 + elif not prev_modname.startswith(package): + # submodule without parent in list, add dummy entry + entries.append([stripped + package, 1, '', '', '', '', '']) + subtype = 2 + else: + num_toplevels += 1 + subtype = 0 + + qualifier = deprecated and _('Deprecated') or '' + entries.append([stripped + modname, subtype, docname, + 'module-' + stripped + modname, platforms, + qualifier, synopsis]) + prev_modname = modname + + # apply heuristics when to collapse modindex at page load: + # only collapse if number of toplevel modules is larger than + # number of submodules + collapse = len(modules) - num_toplevels < num_toplevels + + # sort by first letter + content = sorted(_iteritems(content)) + + return content, collapse + + +class RubyDomain(Domain): + """Ruby language domain.""" + name = 'rb' + label = 'Ruby' + object_types = { + 'function': ObjType(_('function'), 'func', 'obj'), + 'global': ObjType(_('global variable'), 'global', 'obj'), + 'method': ObjType(_('method'), 'meth', 'obj'), + 'class': ObjType(_('class'), 'class', 'obj'), + 'exception': ObjType(_('exception'), 'exc', 'obj'), + 'classmethod': ObjType(_('class method'), 'meth', 'obj'), + 'attr_reader': ObjType(_('attribute'), 'attr', 'obj'), + 'attr_writer': ObjType(_('attribute'), 'attr', 'obj'), + 'attr_accessor': ObjType(_('attribute'), 'attr', 'obj'), + 'const': ObjType(_('const'), 'const', 'obj'), + 'module': ObjType(_('module'), 'mod', 'obj'), + } + + directives = { + 'function': RubyModulelevel, + 'global': RubyGloballevel, + 'method': RubyEverywhere, + 'const': RubyEverywhere, + 'class': RubyClasslike, + 'exception': RubyClasslike, + 'classmethod': RubyClassmember, + 'attr_reader': RubyClassmember, + 'attr_writer': RubyClassmember, + 'attr_accessor': RubyClassmember, + 'module': RubyModule, + 'currentmodule': RubyCurrentModule, + } + + roles = { + 'func': RubyXRefRole(fix_parens=False), + 'global':RubyXRefRole(), + 'class': RubyXRefRole(), + 'exc': RubyXRefRole(), + 'meth': RubyXRefRole(fix_parens=False), + 'attr': RubyXRefRole(), + 'const': RubyXRefRole(), + 'mod': RubyXRefRole(), + 'obj': RubyXRefRole(), + } + initial_data = { + 'objects': {}, # fullname -> docname, objtype + 'modules': {}, # modname -> docname, synopsis, platform, deprecated + } + indices = [ + RubyModuleIndex, + ] + + def clear_doc(self, docname): + for fullname, (fn, _) in list(self.data['objects'].items()): + if fn == docname: + del self.data['objects'][fullname] + for modname, (fn, _, _, _) in list(self.data['modules'].items()): + if fn == docname: + del self.data['modules'][modname] + + def find_obj(self, env, modname, classname, name, type, searchorder=0): + """ + Find a Ruby object for "name", perhaps using the given module and/or + classname. + """ + # skip parens + if name[-2:] == '()': + name = name[:-2] + + if not name: + return None, None + + objects = self.data['objects'] + + newname = None + if searchorder == 1: + if modname and classname and \ + modname + '::' + classname + '#' + name in objects: + newname = modname + '::' + classname + '#' + name + elif modname and classname and \ + modname + '::' + classname + '.' + name in objects: + newname = modname + '::' + classname + '.' + name + elif modname and modname + '::' + name in objects: + newname = modname + '::' + name + elif modname and modname + '#' + name in objects: + newname = modname + '#' + name + elif modname and modname + '.' + name in objects: + newname = modname + '.' + name + elif classname and classname + '.' + name in objects: + newname = classname + '.' + name + elif classname and classname + '#' + name in objects: + newname = classname + '#' + name + elif name in objects: + newname = name + else: + if name in objects: + newname = name + elif classname and classname + '.' + name in objects: + newname = classname + '.' + name + elif classname and classname + '#' + name in objects: + newname = classname + '#' + name + elif modname and modname + '::' + name in objects: + newname = modname + '::' + name + elif modname and modname + '#' + name in objects: + newname = modname + '#' + name + elif modname and modname + '.' + name in objects: + newname = modname + '.' + name + elif modname and classname and \ + modname + '::' + classname + '#' + name in objects: + newname = modname + '::' + classname + '#' + name + elif modname and classname and \ + modname + '::' + classname + '.' + name in objects: + newname = modname + '::' + classname + '.' + name + # special case: object methods + elif type in ('func', 'meth') and '.' not in name and \ + 'object.' + name in objects: + newname = 'object.' + name + if newname is None: + return None, None + return newname, objects[newname] + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + if (typ == 'mod' or + typ == 'obj' and target in self.data['modules']): + docname, synopsis, platform, deprecated = \ + self.data['modules'].get(target, ('','','', '')) + if not docname: + return None + else: + title = '%s%s%s' % ((platform and '(%s) ' % platform), + synopsis, + (deprecated and ' (deprecated)' or '')) + return make_refnode(builder, fromdocname, docname, + 'module-' + target, contnode, title) + else: + modname = node.get('rb:module') + clsname = node.get('rb:class') + searchorder = node.hasattr('refspecific') and 1 or 0 + name, obj = self.find_obj(env, modname, clsname, + target, typ, searchorder) + if not obj: + return None + else: + return make_refnode(builder, fromdocname, obj[0], name, + contnode, name) + + def get_objects(self): + for modname, info in _iteritems(self.data['modules']): + yield (modname, modname, 'module', info[0], 'module-' + modname, 0) + for refname, (docname, type) in _iteritems(self.data['objects']): + yield (refname, refname, type, docname, refname, 1) + + +def setup(app): + app.add_domain(RubyDomain) diff --git a/lib/nghttp2/doc/bash_completion/h2load b/lib/nghttp2/doc/bash_completion/h2load new file mode 100644 index 00000000000..80afe391882 --- /dev/null +++ b/lib/nghttp2/doc/bash_completion/h2load @@ -0,0 +1,19 @@ +_h2load() +{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${COMP_WORDBREAKS//=} + + cmd=${COMP_WORDS[0]} + _get_comp_words_by_ref cur prev + case $cur in + -*) + COMPREPLY=( $( compgen -W '--requests --clients --threads --input-file --max-concurrent-streams --max-frame-size --window-bits --connection-window-bits --header --ciphers --tls13-ciphers --no-tls-proto --data --rate --rate-period --duration --warm-up-time --connection-active-timeout --connection-inactivity-timeout --timing-script-file --base-uri --npn-list --h1 --header-table-size --encoder-header-table-size --log-file --qlog-file-base --connect-to --rps --groups --no-udp-gso --max-udp-payload-size --ktls --verbose --version --help ' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + esac + return 0 +} +complete -F _h2load h2load diff --git a/lib/nghttp2/doc/bash_completion/make_bash_completion.py b/lib/nghttp2/doc/bash_completion/make_bash_completion.py new file mode 100755 index 00000000000..e3a535ba90c --- /dev/null +++ b/lib/nghttp2/doc/bash_completion/make_bash_completion.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import subprocess +import io +import re +import sys +import os.path + +class Option: + def __init__(self, long_opt, short_opt): + self.long_opt = long_opt + self.short_opt = short_opt + +def get_all_options(cmd): + opt_pattern = re.compile(r' (?:(-.), )?(--[^\s\[=]+)(\[)?') + proc = subprocess.Popen([cmd, "--help"], stdout=subprocess.PIPE) + stdoutdata, _ = proc.communicate() + cur_option = None + opts = {} + for line in io.StringIO(stdoutdata.decode('utf-8')): + match = opt_pattern.match(line) + if not match: + continue + long_opt = match.group(2) + short_opt = match.group(1) + opts[long_opt] = Option(long_opt, short_opt) + + return opts + +def output_case(out, name, opts): + out.write('''\ +_{name}() +{{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${{COMP_WORDBREAKS//=}} + + cmd=${{COMP_WORDS[0]}} + _get_comp_words_by_ref cur prev +'''.format(name=name)) + + # Complete option name. + out.write('''\ + case $cur in + -*) + COMPREPLY=( $( compgen -W '\ +''') + for opt in opts.values(): + out.write(opt.long_opt) + out.write(' ') + + out.write('''\ +' -- "$cur" ) ) + ;; +''') + # If no option found for completion then complete with files. + out.write('''\ + *) + _filedir + return 0 + esac + return 0 +}} +complete -F _{name} {name} +'''.format(name=name)) + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Generates bash_completion using `/path/to/cmd --help'") + print("Usage: make_bash_completion.py /path/to/cmd") + exit(1) + name = os.path.basename(sys.argv[1]) + opts = get_all_options(sys.argv[1]) + output_case(sys.stdout, name, opts) diff --git a/lib/nghttp2/doc/bash_completion/nghttp b/lib/nghttp2/doc/bash_completion/nghttp new file mode 100644 index 00000000000..dd48403972f --- /dev/null +++ b/lib/nghttp2/doc/bash_completion/nghttp @@ -0,0 +1,19 @@ +_nghttp() +{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${COMP_WORDBREAKS//=} + + cmd=${COMP_WORDS[0]} + _get_comp_words_by_ref cur prev + case $cur in + -*) + COMPREPLY=( $( compgen -W '--verbose --null-out --remote-name --timeout --window-bits --connection-window-bits --get-assets --stat --header --trailer --cert --key --data --multiply --upgrade --weight --peer-max-concurrent-streams --header-table-size --encoder-header-table-size --padding --har --color --continuation --no-content-length --no-dep --hexdump --no-push --max-concurrent-streams --expect-continue --no-verify-peer --ktls --no-rfc7540-pri --version --help ' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + esac + return 0 +} +complete -F _nghttp nghttp diff --git a/lib/nghttp2/doc/bash_completion/nghttpd b/lib/nghttp2/doc/bash_completion/nghttpd new file mode 100644 index 00000000000..7dd4d9b7973 --- /dev/null +++ b/lib/nghttp2/doc/bash_completion/nghttpd @@ -0,0 +1,19 @@ +_nghttpd() +{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${COMP_WORDBREAKS//=} + + cmd=${COMP_WORDS[0]} + _get_comp_words_by_ref cur prev + case $cur in + -*) + COMPREPLY=( $( compgen -W '--address --daemon --verify-client --htdocs --verbose --no-tls --header-table-size --encoder-header-table-size --color --push --padding --max-concurrent-streams --workers --error-gzip --window-bits --connection-window-bits --dh-param-file --early-response --trailer --hexdump --echo-upload --mime-types-file --no-content-length --ktls --no-rfc7540-pri --version --help ' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + esac + return 0 +} +complete -F _nghttpd nghttpd diff --git a/lib/nghttp2/doc/bash_completion/nghttpx b/lib/nghttp2/doc/bash_completion/nghttpx new file mode 100644 index 00000000000..72dd590b318 --- /dev/null +++ b/lib/nghttp2/doc/bash_completion/nghttpx @@ -0,0 +1,19 @@ +_nghttpx() +{ + local cur prev split=false + COMPREPLY=() + COMP_WORDBREAKS=${COMP_WORDBREAKS//=} + + cmd=${COMP_WORDS[0]} + _get_comp_words_by_ref cur prev + case $cur in + -*) + COMPREPLY=( $( compgen -W '--backend --frontend --backlog --backend-address-family --backend-http-proxy-uri --workers --single-thread --read-rate --read-burst --write-rate --write-burst --worker-read-rate --worker-read-burst --worker-write-rate --worker-write-burst --worker-frontend-connections --backend-connections-per-host --backend-connections-per-frontend --rlimit-nofile --rlimit-memlock --backend-request-buffer --backend-response-buffer --fastopen --no-kqueue --frontend-http2-read-timeout --frontend-http3-read-timeout --frontend-read-timeout --frontend-write-timeout --frontend-keep-alive-timeout --stream-read-timeout --stream-write-timeout --backend-read-timeout --backend-write-timeout --backend-connect-timeout --backend-keep-alive-timeout --listener-disable-timeout --frontend-http2-setting-timeout --backend-http2-settings-timeout --backend-max-backoff --ciphers --tls13-ciphers --client-ciphers --tls13-client-ciphers --ecdh-curves --insecure --cacert --private-key-passwd-file --subcert --dh-param-file --npn-list --verify-client --verify-client-cacert --verify-client-tolerate-expired --client-private-key-file --client-cert-file --tls-min-proto-version --tls-max-proto-version --tls-ticket-key-file --tls-ticket-key-memcached --tls-ticket-key-memcached-address-family --tls-ticket-key-memcached-interval --tls-ticket-key-memcached-max-retry --tls-ticket-key-memcached-max-fail --tls-ticket-key-cipher --tls-ticket-key-memcached-cert-file --tls-ticket-key-memcached-private-key-file --fetch-ocsp-response-file --ocsp-update-interval --ocsp-startup --no-verify-ocsp --no-ocsp --tls-session-cache-memcached --tls-session-cache-memcached-address-family --tls-session-cache-memcached-cert-file --tls-session-cache-memcached-private-key-file --tls-dyn-rec-warmup-threshold --tls-dyn-rec-idle-timeout --no-http2-cipher-block-list --client-no-http2-cipher-block-list --tls-sct-dir --psk-secrets --client-psk-secrets --tls-no-postpone-early-data --tls-max-early-data --tls-ktls --frontend-http2-max-concurrent-streams --backend-http2-max-concurrent-streams --frontend-http2-window-size --frontend-http2-connection-window-size --backend-http2-window-size --backend-http2-connection-window-size --http2-no-cookie-crumbling --padding --no-server-push --frontend-http2-optimize-write-buffer-size --frontend-http2-optimize-window-size --frontend-http2-encoder-dynamic-table-size --frontend-http2-decoder-dynamic-table-size --backend-http2-encoder-dynamic-table-size --backend-http2-decoder-dynamic-table-size --http2-proxy --log-level --accesslog-file --accesslog-syslog --accesslog-format --accesslog-write-early --errorlog-file --errorlog-syslog --syslog-facility --add-x-forwarded-for --strip-incoming-x-forwarded-for --no-add-x-forwarded-proto --no-strip-incoming-x-forwarded-proto --add-forwarded --strip-incoming-forwarded --forwarded-by --forwarded-for --no-via --no-strip-incoming-early-data --no-location-rewrite --host-rewrite --altsvc --http2-altsvc --add-request-header --add-response-header --request-header-field-buffer --max-request-header-fields --response-header-field-buffer --max-response-header-fields --error-page --server-name --no-server-rewrite --redirect-https-port --require-http-scheme --api-max-request-body --dns-cache-timeout --dns-lookup-timeout --dns-max-try --frontend-max-requests --frontend-http2-dump-request-header --frontend-http2-dump-response-header --frontend-frame-debug --daemon --pid-file --user --single-process --max-worker-processes --worker-process-grace-shutdown-period --mruby-file --ignore-per-pattern-mruby-error --frontend-quic-idle-timeout --frontend-quic-debug-log --quic-bpf-program-file --frontend-quic-early-data --frontend-quic-qlog-dir --frontend-quic-require-token --frontend-quic-congestion-controller --frontend-quic-secret-file --quic-server-id --frontend-quic-initial-rtt --no-quic-bpf --frontend-http3-window-size --frontend-http3-connection-window-size --frontend-http3-max-window-size --frontend-http3-max-connection-window-size --frontend-http3-max-concurrent-streams --conf --include --version --help ' -- "$cur" ) ) + ;; + *) + _filedir + return 0 + esac + return 0 +} +complete -F _nghttpx nghttpx diff --git a/lib/nghttp2/doc/building-android-binary.rst.in b/lib/nghttp2/doc/building-android-binary.rst.in new file mode 100644 index 00000000000..2c77c98537b --- /dev/null +++ b/lib/nghttp2/doc/building-android-binary.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/building-android-binary.rst diff --git a/lib/nghttp2/doc/conf.py.in b/lib/nghttp2/doc/conf.py.in new file mode 100644 index 00000000000..228bdb195f6 --- /dev/null +++ b/lib/nghttp2/doc/conf.py.in @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# 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. + +# +# nghttp2 documentation build configuration file, created by +# sphinx-quickstart on Sun Mar 11 22:57:49 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +sys.path.insert(0, os.path.abspath('@top_srcdir@/doc/_exts')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['rubydomain.rubydomain'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['@top_srcdir@/_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'nghttp2' +copyright = u'2012, 2015, 2016, Tatsuhiro Tsujikawa' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '@PACKAGE_VERSION@' +# The full version, including alpha/beta/rc tags. +release = '@PACKAGE_VERSION@' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['manual', 'README.rst', '*-header.rst', 'sources'] + +# The reST default role (used for this markup: `text`) to use for all documents. +default_role = 'c:func' +primary_domain = 'c' + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The default language to highlight source code in. The default is 'python'. +highlight_language = 'c' + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = ['@top_srcdir@/doc/_themes'] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = False + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + '**': ['menu.html', 'localtoc.html', 'relations.html', 'sourcelink.html', + 'searchbox.html'] + } + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False +html_copy_source = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'nghttp2doc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'nghttp2.tex', u'nghttp2 Documentation', + u'Tatsuhiro Tsujikawa', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('nghttp.1', 'nghttp', u'HTTP/2 client', [u'Tatsuhiro Tsujikawa'], 1), + ('nghttpd.1', 'nghttpd', u'HTTP/2 server', [u'Tatsuhiro Tsujikawa'], 1), + ('nghttpx.1', 'nghttpx', u'HTTP/2 proxy', [u'Tatsuhiro Tsujikawa'], 1), + ('h2load.1', 'h2load', u'HTTP/2 benchmarking tool', + [u'Tatsuhiro Tsujikawa'], 1) +] diff --git a/lib/nghttp2/doc/contribute.rst.in b/lib/nghttp2/doc/contribute.rst.in new file mode 100644 index 00000000000..6a229d42805 --- /dev/null +++ b/lib/nghttp2/doc/contribute.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/contribute.rst diff --git a/lib/nghttp2/doc/docutils.conf b/lib/nghttp2/doc/docutils.conf new file mode 100644 index 00000000000..f5208a871bf --- /dev/null +++ b/lib/nghttp2/doc/docutils.conf @@ -0,0 +1,2 @@ +[parsers] +smart_quotes=no diff --git a/lib/nghttp2/doc/h2load-howto.rst.in b/lib/nghttp2/doc/h2load-howto.rst.in new file mode 100644 index 00000000000..252069d838f --- /dev/null +++ b/lib/nghttp2/doc/h2load-howto.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/h2load-howto.rst diff --git a/lib/nghttp2/doc/h2load.1 b/lib/nghttp2/doc/h2load.1 new file mode 100644 index 00000000000..9f4cec51e22 --- /dev/null +++ b/lib/nghttp2/doc/h2load.1 @@ -0,0 +1,531 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "H2LOAD" "1" "Oct 27, 2023" "1.58.0" "nghttp2" +.SH NAME +h2load \- HTTP/2 benchmarking tool +.SH SYNOPSIS +.sp +\fBh2load\fP [OPTIONS]... [URI]... +.SH DESCRIPTION +.sp +benchmarking tool for HTTP/2 server +.INDENT 0.0 +.TP +.B +Specify URI to access. Multiple URIs can be specified. +URIs are used in this order for each client. All URIs +are used, then first URI is used and then 2nd URI, and +so on. The scheme, host and port in the subsequent +URIs, if present, are ignored. Those in the first URI +are used solely. Definition of a base URI overrides all +scheme, host or port values. +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.TP +.B \-n, \-\-requests= +Number of requests across all clients. If it is used +with \fI\%\-\-timing\-script\-file\fP option, this option specifies +the number of requests each client performs rather than +the number of requests across all clients. This option +is ignored if timing\-based benchmarking is enabled (see +\fI\%\-\-duration\fP option). +.sp +Default: \fB1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-c, \-\-clients= +Number of concurrent clients. With \fI\%\-r\fP option, this +specifies the maximum number of connections to be made. +.sp +Default: \fB1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-t, \-\-threads= +Number of native threads. +.sp +Default: \fB1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-i, \-\-input\-file= +Path of a file with multiple URIs are separated by EOLs. +This option will disable URIs getting from command\-line. +If \(aq\-\(aq is given as , URIs will be read from stdin. +URIs are used in this order for each client. All URIs +are used, then first URI is used and then 2nd URI, and +so on. The scheme, host and port in the subsequent +URIs, if present, are ignored. Those in the first URI +are used solely. Definition of a base URI overrides all +scheme, host or port values. +.UNINDENT +.INDENT 0.0 +.TP +.B \-m, \-\-max\-concurrent\-streams= +Max concurrent streams to issue per session. When +http/1.1 is used, this specifies the number of HTTP +pipelining requests in\-flight. +.sp +Default: \fB1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-f, \-\-max\-frame\-size= +Maximum frame size that the local endpoint is willing to +receive. +.sp +Default: \fB16K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-w, \-\-window\-bits= +Sets the stream level initial window size to (2**)\-1. +For QUIC, is capped to 26 (roughly 64MiB). +.sp +Default: \fB30\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-W, \-\-connection\-window\-bits= +Sets the connection level initial window size to +(2**)\-1. +.sp +Default: \fB30\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-H, \-\-header=
+Add/Override a header to the requests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ciphers= +Set allowed cipher list for TLSv1.2 or earlier. The +format of the string is described in OpenSSL ciphers(1). +.sp +Default: \fBECDHE\-ECDSA\-AES128\-GCM\-SHA256:ECDHE\-RSA\-AES128\-GCM\-SHA256:ECDHE\-ECDSA\-AES256\-GCM\-SHA384:ECDHE\-RSA\-AES256\-GCM\-SHA384:ECDHE\-ECDSA\-CHACHA20\-POLY1305:ECDHE\-RSA\-CHACHA20\-POLY1305:DHE\-RSA\-AES128\-GCM\-SHA256:DHE\-RSA\-AES256\-GCM\-SHA384\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls13\-ciphers= +Set allowed cipher list for TLSv1.3. The format of the +string is described in OpenSSL ciphers(1). +.sp +Default: \fBTLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-p, \-\-no\-tls\-proto= +Specify ALPN identifier of the protocol to be used when +accessing http URI without SSL/TLS. +Available protocols: h2c and http/1.1 +.sp +Default: \fBh2c\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-d, \-\-data= +Post FILE to server. The request method is changed to +POST. For http/1.1 connection, if \fI\%\-d\fP is used, the +maximum number of in\-flight pipelined requests is set to +1. +.UNINDENT +.INDENT 0.0 +.TP +.B \-r, \-\-rate= +Specifies the fixed rate at which connections are +created. The rate must be a positive integer, +representing the number of connections to be made per +rate period. The maximum number of connections to be +made is given in \fI\%\-c\fP option. This rate will be +distributed among threads as evenly as possible. For +example, with \fI\%\-t\fP2 and \fI\%\-r\fP4, each thread gets 2 +connections per period. When the rate is 0, the program +will run as it normally does, creating connections at +whatever variable rate it wants. The default value for +this option is 0. \fI\%\-r\fP and \fI\%\-D\fP are mutually exclusive. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-rate\-period= +Specifies the time period between creating connections. +The period must be a positive number, representing the +length of the period in time. This option is ignored if +the rate option is not used. The default value for this +option is 1s. +.UNINDENT +.INDENT 0.0 +.TP +.B \-D, \-\-duration= +Specifies the main duration for the measurements in case +of timing\-based benchmarking. \fI\%\-D\fP and \fI\%\-r\fP are mutually +exclusive. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-warm\-up\-time= +Specifies the time period before starting the actual +measurements, in case of timing\-based benchmarking. +Needs to provided along with \fI\%\-D\fP option. +.UNINDENT +.INDENT 0.0 +.TP +.B \-T, \-\-connection\-active\-timeout= +Specifies the maximum time that h2load is willing to +keep a connection open, regardless of the activity on +said connection. must be a positive integer, +specifying the amount of time to wait. When no timeout +value is set (either active or inactive), h2load will +keep a connection open indefinitely, waiting for a +response. +.UNINDENT +.INDENT 0.0 +.TP +.B \-N, \-\-connection\-inactivity\-timeout= +Specifies the amount of time that h2load is willing to +wait to see activity on a given connection. +must be a positive integer, specifying the amount of +time to wait. When no timeout value is set (either +active or inactive), h2load will keep a connection open +indefinitely, waiting for a response. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-timing\-script\-file= +Path of a file containing one or more lines separated by +EOLs. Each script line is composed of two tab\-separated +fields. The first field represents the time offset from +the start of execution, expressed as a positive value of +milliseconds with microsecond resolution. The second +field represents the URI. This option will disable URIs +getting from command\-line. If \(aq\-\(aq is given as , +script lines will be read from stdin. Script lines are +used in order for each client. If \fI\%\-n\fP is given, it must +be less than or equal to the number of script lines, +larger values are clamped to the number of script lines. +If \fI\%\-n\fP is not given, the number of requests will default +to the number of script lines. The scheme, host and +port defined in the first URI are used solely. Values +contained in other URIs, if present, are ignored. +Definition of a base URI overrides all scheme, host or +port values. \fI\%\-\-timing\-script\-file\fP and \fI\%\-\-rps\fP are +mutually exclusive. +.UNINDENT +.INDENT 0.0 +.TP +.B \-B, \-\-base\-uri=(|unix:) +Specify URI from which the scheme, host and port will be +used for all requests. The base URI overrides all +values defined either at the command line or inside +input files. If argument starts with \(dqunix:\(dq, then the +rest of the argument will be treated as UNIX domain +socket path. The connection is made through that path +instead of TCP. In this case, scheme is inferred from +the first URI appeared in the command line or inside +input files as usual. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-npn\-list= +Comma delimited list of ALPN protocol identifier sorted +in the order of preference. That means most desirable +protocol comes first. This is used in both ALPN and +NPN. The parameter must be delimited by a single comma +only and any white spaces are treated as a part of +protocol string. +.sp +Default: \fBh2,h2\-16,h2\-14,http/1.1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-h1 +Short hand for \fI\%\-\-npn\-list\fP=http/1.1 +\fI\%\-\-no\-tls\-proto\fP=http/1.1, which effectively force +http/1.1 for both http and https URI. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-header\-table\-size= +Specify decoder header table size. +.sp +Default: \fB4K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-encoder\-header\-table\-size= +Specify encoder header table size. The decoder (server) +specifies the maximum dynamic table size it accepts. +Then the negotiated dynamic table size is the minimum of +this option value and the value which server specified. +.sp +Default: \fB4K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-log\-file= +Write per\-request information to a file as tab\-separated +columns: start time as microseconds since epoch; HTTP +status code; microseconds until end of response. More +columns may be added later. Rows are ordered by end\-of\- +response time when using one worker thread, but may +appear slightly out of order with multiple threads due +to buffering. Status code is \-1 for failed streams. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-qlog\-file\-base= +Enable qlog output and specify base file name for qlogs. +Qlog is emitted for each connection. For a given base +name \(dqbase\(dq, each output file name becomes +\(dqbase.M.N.sqlog\(dq where M is worker ID and N is client ID +(e.g. \(dqbase.0.3.sqlog\(dq). Only effective in QUIC runs. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-connect\-to=[:] +Host and port to connect instead of using the authority +in . +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-rps= +Specify request per second for each client. \fI\%\-\-rps\fP and +\fI\%\-\-timing\-script\-file\fP are mutually exclusive. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-groups= +Specify the supported groups. +.sp +Default: \fBX25519:P\-256:P\-384:P\-521\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-udp\-gso +Disable UDP GSO. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-max\-udp\-payload\-size= +Specify the maximum outgoing UDP datagram payload size. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ktls +Enable ktls. +.UNINDENT +.INDENT 0.0 +.TP +.B \-v, \-\-verbose +Output debug information. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-version +Display version information and exit. +.UNINDENT +.INDENT 0.0 +.TP +.B \-h, \-\-help +Display this help and exit. +.UNINDENT +.sp +The argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). +.sp +The argument is an integer and an optional unit (e.g., 1s +is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms +(hours, minutes, seconds and milliseconds, respectively). If a unit +is omitted, a second is used as unit. +.SH OUTPUT +.INDENT 0.0 +.TP +.B requests +.INDENT 7.0 +.TP +.B total +The number of requests h2load was instructed to make. +.TP +.B started +The number of requests h2load has started. +.TP +.B done +The number of requests completed. +.TP +.B succeeded +The number of requests completed successfully. Only HTTP status +code 2xx or3xx are considered as success. +.TP +.B failed +The number of requests failed, including HTTP level failures +(non\-successful HTTP status code). +.TP +.B errored +The number of requests failed, except for HTTP level failures. +This is the subset of the number reported in \fBfailed\fP and most +likely the network level failures or stream was reset by +RST_STREAM. +.TP +.B timeout +The number of requests whose connection timed out before they were +completed. This is the subset of the number reported in +\fBerrored\fP\&. +.UNINDENT +.TP +.B status codes +The number of status code h2load received. +.TP +.B traffic +.INDENT 7.0 +.TP +.B total +The number of bytes received from the server \(dqon the wire\(dq. If +requests were made via TLS, this value is the number of decrypted +bytes. +.TP +.B headers +The number of response header bytes from the server without +decompression. The \fBspace savings\fP shows efficiency of header +compression. Let \fBdecompressed(headers)\fP to the number of bytes +used for header fields after decompression. The \fBspace savings\fP +is calculated by (1 \- \fBheaders\fP / \fBdecompressed(headers)\fP) * +100. For HTTP/1.1, this is usually 0.00%, since it does not have +header compression. For HTTP/2, it shows some insightful numbers. +.TP +.B data +The number of response body bytes received from the server. +.UNINDENT +.TP +.B time for request +.INDENT 7.0 +.TP +.B min +The minimum time taken for request and response. +.TP +.B max +The maximum time taken for request and response. +.TP +.B mean +The mean time taken for request and response. +.TP +.B sd +The standard deviation of the time taken for request and response. +.TP +.B +/\- sd +The fraction of the number of requests within standard deviation +range (mean +/\- sd) against total number of successful requests. +.UNINDENT +.TP +.B time for connect +.INDENT 7.0 +.TP +.B min +The minimum time taken to connect to a server including TLS +handshake. +.TP +.B max +The maximum time taken to connect to a server including TLS +handshake. +.TP +.B mean +The mean time taken to connect to a server including TLS +handshake. +.TP +.B sd +The standard deviation of the time taken to connect to a server. +.TP +.B +/\- sd +The fraction of the number of connections within standard +deviation range (mean +/\- sd) against total number of successful +connections. +.UNINDENT +.TP +.B time for 1st byte (of (decrypted in case of TLS) application data) +.INDENT 7.0 +.TP +.B min +The minimum time taken to get 1st byte from a server. +.TP +.B max +The maximum time taken to get 1st byte from a server. +.TP +.B mean +The mean time taken to get 1st byte from a server. +.TP +.B sd +The standard deviation of the time taken to get 1st byte from a +server. +.TP +.B +/\- sd +The fraction of the number of connections within standard +deviation range (mean +/\- sd) against total number of successful +connections. +.UNINDENT +.TP +.B req/s +.INDENT 7.0 +.TP +.B min +The minimum request per second among all clients. +.TP +.B max +The maximum request per second among all clients. +.TP +.B mean +The mean request per second among all clients. +.TP +.B sd +The standard deviation of request per second among all clients. +server. +.TP +.B +/\- sd +The fraction of the number of connections within standard +deviation range (mean +/\- sd) against total number of successful +connections. +.UNINDENT +.UNINDENT +.SH FLOW CONTROL +.sp +h2load sets large flow control window by default, and effectively +disables flow control to avoid under utilization of server +performance. To set smaller flow control window, use \fI\%\-w\fP and +\fI\%\-W\fP options. For example, use \fB\-w16 \-W16\fP to set default +window size described in HTTP/2 protocol specification. +.SH SEE ALSO +.sp +\fBnghttp(1)\fP, \fBnghttpd(1)\fP, \fBnghttpx(1)\fP +.SH AUTHOR +Tatsuhiro Tsujikawa +.SH COPYRIGHT +2012, 2015, 2016, Tatsuhiro Tsujikawa +.\" Generated by docutils manpage writer. +. diff --git a/lib/nghttp2/doc/h2load.1.rst b/lib/nghttp2/doc/h2load.1.rst new file mode 100644 index 00000000000..0f65849d683 --- /dev/null +++ b/lib/nghttp2/doc/h2load.1.rst @@ -0,0 +1,435 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: h2load + +h2load(1) +========= + +SYNOPSIS +-------- + +**h2load** [OPTIONS]... [URI]... + +DESCRIPTION +----------- + +benchmarking tool for HTTP/2 server + +.. describe:: + + Specify URI to access. Multiple URIs can be specified. + URIs are used in this order for each client. All URIs + are used, then first URI is used and then 2nd URI, and + so on. The scheme, host and port in the subsequent + URIs, if present, are ignored. Those in the first URI + are used solely. Definition of a base URI overrides all + scheme, host or port values. + +OPTIONS +------- + +.. option:: -n, --requests= + + Number of requests across all clients. If it is used + with :option:`--timing-script-file` option, this option specifies + the number of requests each client performs rather than + the number of requests across all clients. This option + is ignored if timing-based benchmarking is enabled (see + :option:`--duration` option). + + Default: ``1`` + +.. option:: -c, --clients= + + Number of concurrent clients. With :option:`-r` option, this + specifies the maximum number of connections to be made. + + Default: ``1`` + +.. option:: -t, --threads= + + Number of native threads. + + Default: ``1`` + +.. option:: -i, --input-file= + + Path of a file with multiple URIs are separated by EOLs. + This option will disable URIs getting from command-line. + If '-' is given as , URIs will be read from stdin. + URIs are used in this order for each client. All URIs + are used, then first URI is used and then 2nd URI, and + so on. The scheme, host and port in the subsequent + URIs, if present, are ignored. Those in the first URI + are used solely. Definition of a base URI overrides all + scheme, host or port values. + +.. option:: -m, --max-concurrent-streams= + + Max concurrent streams to issue per session. When + http/1.1 is used, this specifies the number of HTTP + pipelining requests in-flight. + + Default: ``1`` + +.. option:: -f, --max-frame-size= + + Maximum frame size that the local endpoint is willing to + receive. + + Default: ``16K`` + +.. option:: -w, --window-bits= + + Sets the stream level initial window size to (2\*\*)-1. + For QUIC, is capped to 26 (roughly 64MiB). + + Default: ``30`` + +.. option:: -W, --connection-window-bits= + + Sets the connection level initial window size to + (2\*\*)-1. + + Default: ``30`` + +.. option:: -H, --header=
+ + Add/Override a header to the requests. + +.. option:: --ciphers= + + Set allowed cipher list for TLSv1.2 or earlier. The + format of the string is described in OpenSSL ciphers(1). + + Default: ``ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384`` + +.. option:: --tls13-ciphers= + + Set allowed cipher list for TLSv1.3. The format of the + string is described in OpenSSL ciphers(1). + + Default: ``TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256`` + +.. option:: -p, --no-tls-proto= + + Specify ALPN identifier of the protocol to be used when + accessing http URI without SSL/TLS. + Available protocols: h2c and http/1.1 + + Default: ``h2c`` + +.. option:: -d, --data= + + Post FILE to server. The request method is changed to + POST. For http/1.1 connection, if :option:`-d` is used, the + maximum number of in-flight pipelined requests is set to + 1. + +.. option:: -r, --rate= + + Specifies the fixed rate at which connections are + created. The rate must be a positive integer, + representing the number of connections to be made per + rate period. The maximum number of connections to be + made is given in :option:`-c` option. This rate will be + distributed among threads as evenly as possible. For + example, with :option:`-t`\2 and :option:`-r`\4, each thread gets 2 + connections per period. When the rate is 0, the program + will run as it normally does, creating connections at + whatever variable rate it wants. The default value for + this option is 0. :option:`-r` and :option:`\-D` are mutually exclusive. + +.. option:: --rate-period= + + Specifies the time period between creating connections. + The period must be a positive number, representing the + length of the period in time. This option is ignored if + the rate option is not used. The default value for this + option is 1s. + +.. option:: -D, --duration= + + Specifies the main duration for the measurements in case + of timing-based benchmarking. :option:`-D` and :option:`\-r` are mutually + exclusive. + +.. option:: --warm-up-time= + + Specifies the time period before starting the actual + measurements, in case of timing-based benchmarking. + Needs to provided along with :option:`-D` option. + +.. option:: -T, --connection-active-timeout= + + Specifies the maximum time that h2load is willing to + keep a connection open, regardless of the activity on + said connection. must be a positive integer, + specifying the amount of time to wait. When no timeout + value is set (either active or inactive), h2load will + keep a connection open indefinitely, waiting for a + response. + +.. option:: -N, --connection-inactivity-timeout= + + Specifies the amount of time that h2load is willing to + wait to see activity on a given connection. + must be a positive integer, specifying the amount of + time to wait. When no timeout value is set (either + active or inactive), h2load will keep a connection open + indefinitely, waiting for a response. + +.. option:: --timing-script-file= + + Path of a file containing one or more lines separated by + EOLs. Each script line is composed of two tab-separated + fields. The first field represents the time offset from + the start of execution, expressed as a positive value of + milliseconds with microsecond resolution. The second + field represents the URI. This option will disable URIs + getting from command-line. If '-' is given as , + script lines will be read from stdin. Script lines are + used in order for each client. If :option:`-n` is given, it must + be less than or equal to the number of script lines, + larger values are clamped to the number of script lines. + If :option:`-n` is not given, the number of requests will default + to the number of script lines. The scheme, host and + port defined in the first URI are used solely. Values + contained in other URIs, if present, are ignored. + Definition of a base URI overrides all scheme, host or + port values. :option:`--timing-script-file` and :option:`\--rps` are + mutually exclusive. + +.. option:: -B, --base-uri=(|unix:) + + Specify URI from which the scheme, host and port will be + used for all requests. The base URI overrides all + values defined either at the command line or inside + input files. If argument starts with "unix:", then the + rest of the argument will be treated as UNIX domain + socket path. The connection is made through that path + instead of TCP. In this case, scheme is inferred from + the first URI appeared in the command line or inside + input files as usual. + +.. option:: --npn-list= + + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. This is used in both ALPN and + NPN. The parameter must be delimited by a single comma + only and any white spaces are treated as a part of + protocol string. + + Default: ``h2,h2-16,h2-14,http/1.1`` + +.. option:: --h1 + + Short hand for :option:`--npn-list`\=http/1.1 + :option:`--no-tls-proto`\=http/1.1, which effectively force + http/1.1 for both http and https URI. + +.. option:: --header-table-size= + + Specify decoder header table size. + + Default: ``4K`` + +.. option:: --encoder-header-table-size= + + Specify encoder header table size. The decoder (server) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which server specified. + + Default: ``4K`` + +.. option:: --log-file= + + Write per-request information to a file as tab-separated + columns: start time as microseconds since epoch; HTTP + status code; microseconds until end of response. More + columns may be added later. Rows are ordered by end-of- + response time when using one worker thread, but may + appear slightly out of order with multiple threads due + to buffering. Status code is -1 for failed streams. + +.. option:: --qlog-file-base= + + Enable qlog output and specify base file name for qlogs. + Qlog is emitted for each connection. For a given base + name "base", each output file name becomes + "base.M.N.sqlog" where M is worker ID and N is client ID + (e.g. "base.0.3.sqlog"). Only effective in QUIC runs. + +.. option:: --connect-to=[:] + + Host and port to connect instead of using the authority + in . + +.. option:: --rps= + + Specify request per second for each client. :option:`--rps` and + :option:`--timing-script-file` are mutually exclusive. + +.. option:: --groups= + + Specify the supported groups. + + Default: ``X25519:P-256:P-384:P-521`` + +.. option:: --no-udp-gso + + Disable UDP GSO. + +.. option:: --max-udp-payload-size= + + Specify the maximum outgoing UDP datagram payload size. + +.. option:: --ktls + + Enable ktls. + +.. option:: -v, --verbose + + Output debug information. + +.. option:: --version + + Display version information and exit. + +.. option:: -h, --help + + Display this help and exit. + + + +The argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). + +The argument is an integer and an optional unit (e.g., 1s +is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms +(hours, minutes, seconds and milliseconds, respectively). If a unit +is omitted, a second is used as unit. + +.. _h2load-1-output: + +OUTPUT +------ + +requests + total + The number of requests h2load was instructed to make. + started + The number of requests h2load has started. + done + The number of requests completed. + succeeded + The number of requests completed successfully. Only HTTP status + code 2xx or3xx are considered as success. + failed + The number of requests failed, including HTTP level failures + (non-successful HTTP status code). + errored + The number of requests failed, except for HTTP level failures. + This is the subset of the number reported in ``failed`` and most + likely the network level failures or stream was reset by + RST_STREAM. + timeout + The number of requests whose connection timed out before they were + completed. This is the subset of the number reported in + ``errored``. + +status codes + The number of status code h2load received. + +traffic + total + The number of bytes received from the server "on the wire". If + requests were made via TLS, this value is the number of decrypted + bytes. + headers + The number of response header bytes from the server without + decompression. The ``space savings`` shows efficiency of header + compression. Let ``decompressed(headers)`` to the number of bytes + used for header fields after decompression. The ``space savings`` + is calculated by (1 - ``headers`` / ``decompressed(headers)``) * + 100. For HTTP/1.1, this is usually 0.00%, since it does not have + header compression. For HTTP/2, it shows some insightful numbers. + data + The number of response body bytes received from the server. + +time for request + min + The minimum time taken for request and response. + max + The maximum time taken for request and response. + mean + The mean time taken for request and response. + sd + The standard deviation of the time taken for request and response. + +/- sd + The fraction of the number of requests within standard deviation + range (mean +/- sd) against total number of successful requests. + +time for connect + min + The minimum time taken to connect to a server including TLS + handshake. + max + The maximum time taken to connect to a server including TLS + handshake. + mean + The mean time taken to connect to a server including TLS + handshake. + sd + The standard deviation of the time taken to connect to a server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +time for 1st byte (of (decrypted in case of TLS) application data) + min + The minimum time taken to get 1st byte from a server. + max + The maximum time taken to get 1st byte from a server. + mean + The mean time taken to get 1st byte from a server. + sd + The standard deviation of the time taken to get 1st byte from a + server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +req/s + min + The minimum request per second among all clients. + max + The maximum request per second among all clients. + mean + The mean request per second among all clients. + sd + The standard deviation of request per second among all clients. + server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +FLOW CONTROL +------------ + +h2load sets large flow control window by default, and effectively +disables flow control to avoid under utilization of server +performance. To set smaller flow control window, use :option:`-w` and +:option:`-W` options. For example, use ``-w16 -W16`` to set default +window size described in HTTP/2 protocol specification. + +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`nghttpx(1)` diff --git a/lib/nghttp2/doc/h2load.h2r b/lib/nghttp2/doc/h2load.h2r new file mode 100644 index 00000000000..50d9977de34 --- /dev/null +++ b/lib/nghttp2/doc/h2load.h2r @@ -0,0 +1,120 @@ +.. _h2load-1-output: + +OUTPUT +------ + +requests + total + The number of requests h2load was instructed to make. + started + The number of requests h2load has started. + done + The number of requests completed. + succeeded + The number of requests completed successfully. Only HTTP status + code 2xx or3xx are considered as success. + failed + The number of requests failed, including HTTP level failures + (non-successful HTTP status code). + errored + The number of requests failed, except for HTTP level failures. + This is the subset of the number reported in ``failed`` and most + likely the network level failures or stream was reset by + RST_STREAM. + timeout + The number of requests whose connection timed out before they were + completed. This is the subset of the number reported in + ``errored``. + +status codes + The number of status code h2load received. + +traffic + total + The number of bytes received from the server "on the wire". If + requests were made via TLS, this value is the number of decrypted + bytes. + headers + The number of response header bytes from the server without + decompression. The ``space savings`` shows efficiency of header + compression. Let ``decompressed(headers)`` to the number of bytes + used for header fields after decompression. The ``space savings`` + is calculated by (1 - ``headers`` / ``decompressed(headers)``) * + 100. For HTTP/1.1, this is usually 0.00%, since it does not have + header compression. For HTTP/2, it shows some insightful numbers. + data + The number of response body bytes received from the server. + +time for request + min + The minimum time taken for request and response. + max + The maximum time taken for request and response. + mean + The mean time taken for request and response. + sd + The standard deviation of the time taken for request and response. + +/- sd + The fraction of the number of requests within standard deviation + range (mean +/- sd) against total number of successful requests. + +time for connect + min + The minimum time taken to connect to a server including TLS + handshake. + max + The maximum time taken to connect to a server including TLS + handshake. + mean + The mean time taken to connect to a server including TLS + handshake. + sd + The standard deviation of the time taken to connect to a server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +time for 1st byte (of (decrypted in case of TLS) application data) + min + The minimum time taken to get 1st byte from a server. + max + The maximum time taken to get 1st byte from a server. + mean + The mean time taken to get 1st byte from a server. + sd + The standard deviation of the time taken to get 1st byte from a + server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +req/s + min + The minimum request per second among all clients. + max + The maximum request per second among all clients. + mean + The mean request per second among all clients. + sd + The standard deviation of request per second among all clients. + server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +FLOW CONTROL +------------ + +h2load sets large flow control window by default, and effectively +disables flow control to avoid under utilization of server +performance. To set smaller flow control window, use :option:`-w` and +:option:`-W` options. For example, use ``-w16 -W16`` to set default +window size described in HTTP/2 protocol specification. + +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`nghttpx(1)` diff --git a/lib/nghttp2/doc/index.rst.in b/lib/nghttp2/doc/index.rst.in new file mode 100644 index 00000000000..2a49369c0db --- /dev/null +++ b/lib/nghttp2/doc/index.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/index.rst diff --git a/lib/nghttp2/doc/make.bat b/lib/nghttp2/doc/make.bat new file mode 100644 index 00000000000..2e0500de72f --- /dev/null +++ b/lib/nghttp2/doc/make.bat @@ -0,0 +1,170 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\nghttp2.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\nghttp2.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/lib/nghttp2/doc/mkapiref.py b/lib/nghttp2/doc/mkapiref.py new file mode 100755 index 00000000000..df59fbc7275 --- /dev/null +++ b/lib/nghttp2/doc/mkapiref.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2020 nghttp2 contributors +# Copyright (c) 2020 ngtcp2 contributors +# Copyright (c) 2012 Tatsuhiro Tsujikawa +# +# 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. + +# Generates API reference from C source code. + +import re, sys, argparse, os.path + +class FunctionDoc: + def __init__(self, name, content, domain, filename): + self.name = name + self.content = content + self.domain = domain + if self.domain == 'function': + self.funcname = re.search(r'(nghttp2_[^ )]+)\(', self.name).group(1) + self.filename = filename + + def write(self, out): + out.write('.. {}:: {}\n'.format(self.domain, self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +class StructDoc: + def __init__(self, name, content, members, member_domain): + self.name = name + self.content = content + self.members = members + self.member_domain = member_domain + + def write(self, out): + if self.name: + out.write('.. type:: {}\n'.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + out.write('\n') + for name, content in self.members: + out.write(' .. {}:: {}\n'.format(self.member_domain, name)) + out.write('\n') + for line in content: + out.write(' {}\n'.format(line)) + out.write('\n') + +class EnumDoc: + def __init__(self, name, content, members): + self.name = name + self.content = content + self.members = members + + def write(self, out): + if self.name: + out.write('.. type:: {}\n'.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + out.write('\n') + for name, content in self.members: + out.write(' .. enum:: {}\n'.format(name)) + out.write('\n') + for line in content: + out.write(' {}\n'.format(line)) + out.write('\n') + +class MacroDoc: + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, out): + out.write('''.. macro:: {}\n'''.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +class MacroSectionDoc: + def __init__(self, content): + self.content = content + + def write(self, out): + out.write('\n') + c = ' '.join(self.content).strip() + out.write(c) + out.write('\n') + out.write('-' * len(c)) + out.write('\n\n') + +class TypedefDoc: + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, out): + out.write('''.. type:: {}\n'''.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +def make_api_ref(infile): + macros = [] + enums = [] + types = [] + functions = [] + while True: + line = infile.readline() + if not line: + break + elif line == '/**\n': + line = infile.readline() + doctype = line.split()[1] + if doctype == '@function': + functions.append(process_function('function', infile)) + elif doctype == '@functypedef': + types.append(process_function('type', infile)) + elif doctype == '@struct' or doctype == '@union': + types.append(process_struct(infile)) + elif doctype == '@enum': + enums.append(process_enum(infile)) + elif doctype == '@macro': + macros.append(process_macro(infile)) + elif doctype == '@macrosection': + macros.append(process_macrosection(infile)) + elif doctype == '@typedef': + types.append(process_typedef(infile)) + return macros, enums, types, functions + +def output( + title, indexfile, macrosfile, enumsfile, typesfile, funcsdir, + macros, enums, types, functions): + indexfile.write(''' +{title} +{titledecoration} + +.. toctree:: + :maxdepth: 1 + + {macros} + {enums} + {types} +'''.format( + title=title, titledecoration='='*len(title), + macros=os.path.splitext(os.path.basename(macrosfile.name))[0], + enums=os.path.splitext(os.path.basename(enumsfile.name))[0], + types=os.path.splitext(os.path.basename(typesfile.name))[0], +)) + + for doc in functions: + indexfile.write(' {}\n'.format(doc.funcname)) + + macrosfile.write(''' +Macros +====== +''') + for doc in macros: + doc.write(macrosfile) + + enumsfile.write(''' +Enums +===== +''') + for doc in enums: + doc.write(enumsfile) + + typesfile.write(''' +Types (structs, unions and typedefs) +==================================== +''') + for doc in types: + doc.write(typesfile) + + for doc in functions: + with open(os.path.join(funcsdir, doc.funcname + '.rst'), 'w') as f: + f.write(''' +{funcname} +{secul} + +Synopsis +-------- + +*#include * + +'''.format(funcname=doc.funcname, secul='='*len(doc.funcname), + filename=doc.filename)) + doc.write(f) + +def process_macro(infile): + content = read_content(infile) + line = infile.readline() + macro_name = line.split()[1] + return MacroDoc(macro_name, content) + +def process_macrosection(infile): + content = read_content(infile) + return MacroSectionDoc(content) + +def process_typedef(infile): + content = read_content(infile) + typedef = infile.readline() + typedef = re.sub(r';\n$', '', typedef) + typedef = re.sub(r'typedef ', '', typedef) + return TypedefDoc(typedef, content) + +def process_enum(infile): + members = [] + enum_name = None + content = read_content(infile) + while True: + line = infile.readline() + if not line: + break + elif re.match(r'\s*/\*\*\n', line): + member_content = read_content(infile) + line = infile.readline() + items = line.split() + member_name = items[0].rstrip(',') + if len(items) >= 3: + member_content.insert(0, '(``{}``) '\ + .format(' '.join(items[2:]).rstrip(','))) + members.append((member_name, member_content)) + elif line.startswith('}'): + enum_name = line.rstrip().split()[1] + enum_name = re.sub(r';$', '', enum_name) + break + return EnumDoc(enum_name, content, members) + +def process_struct(infile): + members = [] + struct_name = None + content = read_content(infile) + while True: + line = infile.readline() + if not line: + break + elif re.match(r'\s*/\*\*\n', line): + member_content = read_content(infile) + line = infile.readline() + member_name = line.rstrip().rstrip(';') + members.append((member_name, member_content)) + elif line.startswith('}') or\ + (line.startswith('typedef ') and line.endswith(';\n')): + if line.startswith('}'): + index = 1 + else: + index = 3 + struct_name = line.rstrip().split()[index] + struct_name = re.sub(r';$', '', struct_name) + break + return StructDoc(struct_name, content, members, 'member') + +def process_function(domain, infile): + content = read_content(infile) + func_proto = [] + while True: + line = infile.readline() + if not line: + break + elif line == '\n': + break + else: + func_proto.append(line) + func_proto = ''.join(func_proto) + func_proto = re.sub(r';\n$', '', func_proto) + func_proto = re.sub(r'\s+', ' ', func_proto) + func_proto = re.sub(r'NGHTTP2_EXTERN ', '', func_proto) + func_proto = re.sub(r'typedef ', '', func_proto) + filename = os.path.basename(infile.name) + return FunctionDoc(func_proto, content, domain, filename) + +def read_content(infile): + content = [] + while True: + line = infile.readline() + if not line: + break + if re.match(r'\s*\*/\n', line): + break + else: + content.append(transform_content(line.rstrip())) + return content + +def arg_repl(matchobj): + return '*{}*'.format(matchobj.group(1).replace('*', '\\*')) + +def transform_content(content): + content = re.sub(r'^\s+\* ?', '', content) + content = re.sub(r'\|([^\s|]+)\|', arg_repl, content) + return content + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Generate API reference") + parser.add_argument('--title', default='API Reference', + help='title of index page') + parser.add_argument('index', type=argparse.FileType('w'), + help='index output file') + parser.add_argument('macros', type=argparse.FileType('w'), + help='macros section output file. The filename should be macros.rst') + parser.add_argument('enums', type=argparse.FileType('w'), + help='enums section output file. The filename should be enums.rst') + parser.add_argument('types', type=argparse.FileType('w'), + help='types section output file. The filename should be types.rst') + parser.add_argument('funcsdir', + help='functions doc output dir') + parser.add_argument('files', nargs='+', type=argparse.FileType('r'), + help='source file') + args = parser.parse_args() + macros = [] + enums = [] + types = [] + funcs = [] + for infile in args.files: + m, e, t, f = make_api_ref(infile) + macros.extend(m) + enums.extend(e) + types.extend(t) + funcs.extend(f) + funcs.sort(key=lambda x: x.funcname) + output( + args.title, + args.index, args.macros, args.enums, args.types, args.funcsdir, + macros, enums, types, funcs) diff --git a/lib/nghttp2/doc/nghttp.1 b/lib/nghttp2/doc/nghttp.1 new file mode 100644 index 00000000000..4bf274fb870 --- /dev/null +++ b/lib/nghttp2/doc/nghttp.1 @@ -0,0 +1,336 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "NGHTTP" "1" "Oct 27, 2023" "1.58.0" "nghttp2" +.SH NAME +nghttp \- HTTP/2 client +.SH SYNOPSIS +.sp +\fBnghttp\fP [OPTIONS]... ... +.SH DESCRIPTION +.sp +HTTP/2 client +.INDENT 0.0 +.TP +.B +Specify URI to access. +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.TP +.B \-v, \-\-verbose +Print debug information such as reception and +transmission of frames and name/value pairs. Specifying +this option multiple times increases verbosity. +.UNINDENT +.INDENT 0.0 +.TP +.B \-n, \-\-null\-out +Discard downloaded data. +.UNINDENT +.INDENT 0.0 +.TP +.B \-O, \-\-remote\-name +Save download data in the current directory. The +filename is derived from URI. If URI ends with \(aq\fI/\fP\(aq, +\(aqindex.html\(aq is used as a filename. Not implemented +yet. +.UNINDENT +.INDENT 0.0 +.TP +.B \-t, \-\-timeout= +Timeout each request after . Set 0 to disable +timeout. +.UNINDENT +.INDENT 0.0 +.TP +.B \-w, \-\-window\-bits= +Sets the stream level initial window size to 2**\-1. +.UNINDENT +.INDENT 0.0 +.TP +.B \-W, \-\-connection\-window\-bits= +Sets the connection level initial window size to +2**\-1. +.UNINDENT +.INDENT 0.0 +.TP +.B \-a, \-\-get\-assets +Download assets such as stylesheets, images and script +files linked from the downloaded resource. Only links +whose origins are the same with the linking resource +will be downloaded. nghttp prioritizes resources using +HTTP/2 dependency based priority. The priority order, +from highest to lowest, is html itself, css, javascript +and images. +.UNINDENT +.INDENT 0.0 +.TP +.B \-s, \-\-stat +Print statistics. +.UNINDENT +.INDENT 0.0 +.TP +.B \-H, \-\-header=
+Add a header to the requests. Example: \fI\%\-H\fP\(aq:method: PUT\(aq +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-trailer=
+Add a trailer header to the requests.
must not +include pseudo header field (header field name starting +with \(aq:\(aq). To send trailer, one must use \fI\%\-d\fP option to +send request body. Example: \fI\%\-\-trailer\fP \(aqfoo: bar\(aq. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cert= +Use the specified client certificate file. The file +must be in PEM format. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-key= +Use the client private key file. The file must be in +PEM format. +.UNINDENT +.INDENT 0.0 +.TP +.B \-d, \-\-data= +Post FILE to server. If \(aq\-\(aq is given, data will be read +from stdin. +.UNINDENT +.INDENT 0.0 +.TP +.B \-m, \-\-multiply= +Request each URI times. By default, same URI is not +requested twice. This option disables it too. +.UNINDENT +.INDENT 0.0 +.TP +.B \-u, \-\-upgrade +Perform HTTP Upgrade for HTTP/2. This option is ignored +if the request URI has https scheme. If \fI\%\-d\fP is used, the +HTTP upgrade request is performed with OPTIONS method. +.UNINDENT +.INDENT 0.0 +.TP +.B \-p, \-\-weight= +Sets weight of given URI. This option can be used +multiple times, and N\-th \fI\%\-p\fP option sets weight of N\-th +URI in the command line. If the number of \fI\%\-p\fP option is +less than the number of URI, the last \fI\%\-p\fP option value is +repeated. If there is no \fI\%\-p\fP option, default weight, 16, +is assumed. The valid value range is +[1, 256], inclusive. +.UNINDENT +.INDENT 0.0 +.TP +.B \-M, \-\-peer\-max\-concurrent\-streams= +Use as SETTINGS_MAX_CONCURRENT_STREAMS value of +remote endpoint as if it is received in SETTINGS frame. +.sp +Default: \fB100\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-c, \-\-header\-table\-size= +Specify decoder header table size. If this option is +used multiple times, and the minimum value among the +given values except for last one is strictly less than +the last value, that minimum value is set in SETTINGS +frame payload before the last value, to simulate +multiple header table size change. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-encoder\-header\-table\-size= +Specify encoder header table size. The decoder (server) +specifies the maximum dynamic table size it accepts. +Then the negotiated dynamic table size is the minimum of +this option value and the value which server specified. +.UNINDENT +.INDENT 0.0 +.TP +.B \-b, \-\-padding= +Add at most bytes to a frame payload as padding. +Specify 0 to disable padding. +.UNINDENT +.INDENT 0.0 +.TP +.B \-r, \-\-har= +Output HTTP transactions in HAR format. If \(aq\-\(aq +is given, data is written to stdout. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-color +Force colored log output. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-continuation +Send large header to test CONTINUATION. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-content\-length +Don\(aqt send content\-length header field. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-dep +Don\(aqt send dependency based priority hint to server. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-hexdump +Display the incoming traffic in hexadecimal (Canonical +hex+ASCII display). If SSL/TLS is used, decrypted data +are used. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-push +Disable server push. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-max\-concurrent\-streams= +The number of concurrent pushed streams this client +accepts. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-expect\-continue +Perform an Expect/Continue handshake: wait to send DATA +(up to a short timeout) until the server sends a 100 +Continue interim response. This option is ignored unless +combined with the \fI\%\-d\fP option. +.UNINDENT +.INDENT 0.0 +.TP +.B \-y, \-\-no\-verify\-peer +Suppress warning on server certificate verification +failure. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ktls +Enable ktls. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-rfc7540\-pri +Disable RFC7540 priorities. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-version +Display version information and exit. +.UNINDENT +.INDENT 0.0 +.TP +.B \-h, \-\-help +Display this help and exit. +.UNINDENT +.sp +The argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). +.sp +The argument is an integer and an optional unit (e.g., 1s +is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms +(hours, minutes, seconds and milliseconds, respectively). If a unit +is omitted, a second is used as unit. +.SH DEPENDENCY BASED PRIORITY +.sp +nghttp sends priority hints to server by default unless +\fI\%\-\-no\-dep\fP is used. nghttp mimics the way Firefox employs to +manages dependency using idle streams. We follows the behaviour of +Firefox Nightly as of April, 2015, and nghttp\(aqs behaviour is very +static and could be different from Firefox in detail. But reproducing +the same behaviour of Firefox is not our goal. The goal is provide +the easy way to test out the dependency priority in server +implementation. +.sp +When connection is established, nghttp sends 5 PRIORITY frames to idle +streams 3, 5, 7, 9 and 11 to create \(dqanchor\(dq nodes in dependency +tree: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C + +\-\-\-\-\-+ + |id=0 | + +\-\-\-\-\-+ + ^ ^ ^ + w=201 / | \e w=1 + / | \e + / w=101| \e + +\-\-\-\-\-+ +\-\-\-\-\-+ +\-\-\-\-\-+ + |id=3 | |id=5 | |id=7 | + +\-\-\-\-\-+ +\-\-\-\-\-+ +\-\-\-\-\-+ + ^ ^ +w=1 | w=1 | + | | + +\-\-\-\-\-+ +\-\-\-\-\-+ + |id=11| |id=9 | + +\-\-\-\-\-+ +\-\-\-\-\-+ +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +In the above figure, \fBid\fP means stream ID, and \fBw\fP means weight. +The stream 0 is non\-existence stream, and forms the root of the tree. +The stream 7 and 9 are not used for now. +.sp +The URIs given in the command\-line depend on stream 11 with the weight +given in \fI\%\-p\fP option, which defaults to 16. +.sp +If \fI\%\-a\fP option is used, nghttp parses the resource pointed by +URI given in command\-line as html, and extracts resource links from +it. When requesting those resources, nghttp uses dependency according +to its resource type. +.sp +For CSS, and Javascript files inside \(dqhead\(dq element, they depend on +stream 3 with the weight 2. The Javascript files outside \(dqhead\(dq +element depend on stream 5 with the weight 2. The mages depend on +stream 11 with the weight 12. The other resources (e.g., icon) depend +on stream 11 with the weight 2. +.SH SEE ALSO +.sp +\fBnghttpd(1)\fP, \fBnghttpx(1)\fP, \fBh2load(1)\fP +.SH AUTHOR +Tatsuhiro Tsujikawa +.SH COPYRIGHT +2012, 2015, 2016, Tatsuhiro Tsujikawa +.\" Generated by docutils manpage writer. +. diff --git a/lib/nghttp2/doc/nghttp.1.rst b/lib/nghttp2/doc/nghttp.1.rst new file mode 100644 index 00000000000..e10f3ee8a71 --- /dev/null +++ b/lib/nghttp2/doc/nghttp.1.rst @@ -0,0 +1,276 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttp + +nghttp(1) +========= + +SYNOPSIS +-------- + +**nghttp** [OPTIONS]... ... + +DESCRIPTION +----------- + +HTTP/2 client + +.. describe:: + + Specify URI to access. + +OPTIONS +------- + +.. option:: -v, --verbose + + Print debug information such as reception and + transmission of frames and name/value pairs. Specifying + this option multiple times increases verbosity. + +.. option:: -n, --null-out + + Discard downloaded data. + +.. option:: -O, --remote-name + + Save download data in the current directory. The + filename is derived from URI. If URI ends with '*/*', + 'index.html' is used as a filename. Not implemented + yet. + +.. option:: -t, --timeout= + + Timeout each request after . Set 0 to disable + timeout. + +.. option:: -w, --window-bits= + + Sets the stream level initial window size to 2\*\*-1. + +.. option:: -W, --connection-window-bits= + + Sets the connection level initial window size to + 2\*\*-1. + +.. option:: -a, --get-assets + + Download assets such as stylesheets, images and script + files linked from the downloaded resource. Only links + whose origins are the same with the linking resource + will be downloaded. nghttp prioritizes resources using + HTTP/2 dependency based priority. The priority order, + from highest to lowest, is html itself, css, javascript + and images. + +.. option:: -s, --stat + + Print statistics. + +.. option:: -H, --header=
+ + Add a header to the requests. Example: :option:`-H`\':method: PUT' + +.. option:: --trailer=
+ + Add a trailer header to the requests.
must not + include pseudo header field (header field name starting + with ':'). To send trailer, one must use :option:`-d` option to + send request body. Example: :option:`--trailer` 'foo: bar'. + +.. option:: --cert= + + Use the specified client certificate file. The file + must be in PEM format. + +.. option:: --key= + + Use the client private key file. The file must be in + PEM format. + +.. option:: -d, --data= + + Post FILE to server. If '-' is given, data will be read + from stdin. + +.. option:: -m, --multiply= + + Request each URI times. By default, same URI is not + requested twice. This option disables it too. + +.. option:: -u, --upgrade + + Perform HTTP Upgrade for HTTP/2. This option is ignored + if the request URI has https scheme. If :option:`-d` is used, the + HTTP upgrade request is performed with OPTIONS method. + +.. option:: -p, --weight= + + Sets weight of given URI. This option can be used + multiple times, and N-th :option:`-p` option sets weight of N-th + URI in the command line. If the number of :option:`-p` option is + less than the number of URI, the last :option:`-p` option value is + repeated. If there is no :option:`-p` option, default weight, 16, + is assumed. The valid value range is + [1, 256], inclusive. + +.. option:: -M, --peer-max-concurrent-streams= + + Use as SETTINGS_MAX_CONCURRENT_STREAMS value of + remote endpoint as if it is received in SETTINGS frame. + + Default: ``100`` + +.. option:: -c, --header-table-size= + + Specify decoder header table size. If this option is + used multiple times, and the minimum value among the + given values except for last one is strictly less than + the last value, that minimum value is set in SETTINGS + frame payload before the last value, to simulate + multiple header table size change. + +.. option:: --encoder-header-table-size= + + Specify encoder header table size. The decoder (server) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which server specified. + +.. option:: -b, --padding= + + Add at most bytes to a frame payload as padding. + Specify 0 to disable padding. + +.. option:: -r, --har= + + Output HTTP transactions in HAR format. If '-' + is given, data is written to stdout. + +.. option:: --color + + Force colored log output. + +.. option:: --continuation + + Send large header to test CONTINUATION. + +.. option:: --no-content-length + + Don't send content-length header field. + +.. option:: --no-dep + + Don't send dependency based priority hint to server. + +.. option:: --hexdump + + Display the incoming traffic in hexadecimal (Canonical + hex+ASCII display). If SSL/TLS is used, decrypted data + are used. + +.. option:: --no-push + + Disable server push. + +.. option:: --max-concurrent-streams= + + The number of concurrent pushed streams this client + accepts. + +.. option:: --expect-continue + + Perform an Expect/Continue handshake: wait to send DATA + (up to a short timeout) until the server sends a 100 + Continue interim response. This option is ignored unless + combined with the :option:`-d` option. + +.. option:: -y, --no-verify-peer + + Suppress warning on server certificate verification + failure. + +.. option:: --ktls + + Enable ktls. + +.. option:: --no-rfc7540-pri + + Disable RFC7540 priorities. + +.. option:: --version + + Display version information and exit. + +.. option:: -h, --help + + Display this help and exit. + + + +The argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). + +The argument is an integer and an optional unit (e.g., 1s +is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms +(hours, minutes, seconds and milliseconds, respectively). If a unit +is omitted, a second is used as unit. + +DEPENDENCY BASED PRIORITY +------------------------- + +nghttp sends priority hints to server by default unless +:option:`--no-dep` is used. nghttp mimics the way Firefox employs to +manages dependency using idle streams. We follows the behaviour of +Firefox Nightly as of April, 2015, and nghttp's behaviour is very +static and could be different from Firefox in detail. But reproducing +the same behaviour of Firefox is not our goal. The goal is provide +the easy way to test out the dependency priority in server +implementation. + +When connection is established, nghttp sends 5 PRIORITY frames to idle +streams 3, 5, 7, 9 and 11 to create "anchor" nodes in dependency +tree: + +.. code-block:: text + + +-----+ + |id=0 | + +-----+ + ^ ^ ^ + w=201 / | \ w=1 + / | \ + / w=101| \ + +-----+ +-----+ +-----+ + |id=3 | |id=5 | |id=7 | + +-----+ +-----+ +-----+ + ^ ^ + w=1 | w=1 | + | | + +-----+ +-----+ + |id=11| |id=9 | + +-----+ +-----+ + +In the above figure, ``id`` means stream ID, and ``w`` means weight. +The stream 0 is non-existence stream, and forms the root of the tree. +The stream 7 and 9 are not used for now. + +The URIs given in the command-line depend on stream 11 with the weight +given in :option:`-p` option, which defaults to 16. + +If :option:`-a` option is used, nghttp parses the resource pointed by +URI given in command-line as html, and extracts resource links from +it. When requesting those resources, nghttp uses dependency according +to its resource type. + +For CSS, and Javascript files inside "head" element, they depend on +stream 3 with the weight 2. The Javascript files outside "head" +element depend on stream 5 with the weight 2. The mages depend on +stream 11 with the weight 12. The other resources (e.g., icon) depend +on stream 11 with the weight 2. + +SEE ALSO +-------- + +:manpage:`nghttpd(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)` diff --git a/lib/nghttp2/doc/nghttp.h2r b/lib/nghttp2/doc/nghttp.h2r new file mode 100644 index 00000000000..9d2a90eff7a --- /dev/null +++ b/lib/nghttp2/doc/nghttp.h2r @@ -0,0 +1,57 @@ +DEPENDENCY BASED PRIORITY +------------------------- + +nghttp sends priority hints to server by default unless +:option:`--no-dep` is used. nghttp mimics the way Firefox employs to +manages dependency using idle streams. We follows the behaviour of +Firefox Nightly as of April, 2015, and nghttp's behaviour is very +static and could be different from Firefox in detail. But reproducing +the same behaviour of Firefox is not our goal. The goal is provide +the easy way to test out the dependency priority in server +implementation. + +When connection is established, nghttp sends 5 PRIORITY frames to idle +streams 3, 5, 7, 9 and 11 to create "anchor" nodes in dependency +tree: + +.. code-block:: text + + +-----+ + |id=0 | + +-----+ + ^ ^ ^ + w=201 / | \ w=1 + / | \ + / w=101| \ + +-----+ +-----+ +-----+ + |id=3 | |id=5 | |id=7 | + +-----+ +-----+ +-----+ + ^ ^ + w=1 | w=1 | + | | + +-----+ +-----+ + |id=11| |id=9 | + +-----+ +-----+ + +In the above figure, ``id`` means stream ID, and ``w`` means weight. +The stream 0 is non-existence stream, and forms the root of the tree. +The stream 7 and 9 are not used for now. + +The URIs given in the command-line depend on stream 11 with the weight +given in :option:`-p` option, which defaults to 16. + +If :option:`-a` option is used, nghttp parses the resource pointed by +URI given in command-line as html, and extracts resource links from +it. When requesting those resources, nghttp uses dependency according +to its resource type. + +For CSS, and Javascript files inside "head" element, they depend on +stream 3 with the weight 2. The Javascript files outside "head" +element depend on stream 5 with the weight 2. The mages depend on +stream 11 with the weight 12. The other resources (e.g., icon) depend +on stream 11 with the weight 2. + +SEE ALSO +-------- + +:manpage:`nghttpd(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)` diff --git a/lib/nghttp2/doc/nghttp2.h.rst.in b/lib/nghttp2/doc/nghttp2.h.rst.in new file mode 100644 index 00000000000..29e641da5ee --- /dev/null +++ b/lib/nghttp2/doc/nghttp2.h.rst.in @@ -0,0 +1,4 @@ +nghttp2.h +========= + +.. literalinclude:: @top_srcdir@/lib/includes/nghttp2/nghttp2.h diff --git a/lib/nghttp2/doc/nghttp2ver.h.rst.in b/lib/nghttp2/doc/nghttp2ver.h.rst.in new file mode 100644 index 00000000000..c6aa779da0d --- /dev/null +++ b/lib/nghttp2/doc/nghttp2ver.h.rst.in @@ -0,0 +1,4 @@ +nghttp2ver.h +============ + +.. literalinclude:: @top_builddir@/lib/includes/nghttp2/nghttp2ver.h diff --git a/lib/nghttp2/doc/nghttpd.1 b/lib/nghttp2/doc/nghttpd.1 new file mode 100644 index 00000000000..232757da1bb --- /dev/null +++ b/lib/nghttp2/doc/nghttpd.1 @@ -0,0 +1,236 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "NGHTTPD" "1" "Oct 27, 2023" "1.58.0" "nghttp2" +.SH NAME +nghttpd \- HTTP/2 server +.SH SYNOPSIS +.sp +\fBnghttpd\fP [OPTION]... [ ] +.SH DESCRIPTION +.sp +HTTP/2 server +.INDENT 0.0 +.TP +.B +Specify listening port number. +.UNINDENT +.INDENT 0.0 +.TP +.B +Set path to server\(aqs private key. Required unless +\fI\%\-\-no\-tls\fP is specified. +.UNINDENT +.INDENT 0.0 +.TP +.B +Set path to server\(aqs certificate. Required unless +\fI\%\-\-no\-tls\fP is specified. +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.TP +.B \-a, \-\-address= +The address to bind to. If not specified the default IP +address determined by getaddrinfo is used. +.UNINDENT +.INDENT 0.0 +.TP +.B \-D, \-\-daemon +Run in a background. If \fI\%\-D\fP is used, the current working +directory is changed to \(aq\fI/\fP\(aq. Therefore if this option +is used, \fI\%\-d\fP option must be specified. +.UNINDENT +.INDENT 0.0 +.TP +.B \-V, \-\-verify\-client +The server sends a client certificate request. If the +client did not return a certificate, the handshake is +terminated. Currently, this option just requests a +client certificate and does not verify it. +.UNINDENT +.INDENT 0.0 +.TP +.B \-d, \-\-htdocs= +Specify document root. If this option is not specified, +the document root is the current working directory. +.UNINDENT +.INDENT 0.0 +.TP +.B \-v, \-\-verbose +Print debug information such as reception/ transmission +of frames and name/value pairs. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-tls +Disable SSL/TLS. +.UNINDENT +.INDENT 0.0 +.TP +.B \-c, \-\-header\-table\-size= +Specify decoder header table size. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-encoder\-header\-table\-size= +Specify encoder header table size. The decoder (client) +specifies the maximum dynamic table size it accepts. +Then the negotiated dynamic table size is the minimum of +this option value and the value which client specified. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-color +Force colored log output. +.UNINDENT +.INDENT 0.0 +.TP +.B \-p, \-\-push== +Push resources s when is requested. +This option can be used repeatedly to specify multiple +push configurations. and s are +relative to document root. See \fI\%\-\-htdocs\fP option. +Example: \fI\%\-p\fP/=/foo.png \fI\%\-p\fP/doc=/bar.css +.UNINDENT +.INDENT 0.0 +.TP +.B \-b, \-\-padding= +Add at most bytes to a frame payload as padding. +Specify 0 to disable padding. +.UNINDENT +.INDENT 0.0 +.TP +.B \-m, \-\-max\-concurrent\-streams= +Set the maximum number of the concurrent streams in one +HTTP/2 session. +.sp +Default: \fB100\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-n, \-\-workers= +Set the number of worker threads. +.sp +Default: \fB1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-e, \-\-error\-gzip +Make error response gzipped. +.UNINDENT +.INDENT 0.0 +.TP +.B \-w, \-\-window\-bits= +Sets the stream level initial window size to 2**\-1. +.UNINDENT +.INDENT 0.0 +.TP +.B \-W, \-\-connection\-window\-bits= +Sets the connection level initial window size to +2**\-1. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-dh\-param\-file= +Path to file that contains DH parameters in PEM format. +Without this option, DHE cipher suites are not +available. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-early\-response +Start sending response when request HEADERS is received, +rather than complete request is received. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-trailer=
+Add a trailer header to a response.
must not +include pseudo header field (header field name starting +with \(aq:\(aq). The trailer is sent only if a response has +body part. Example: \fI\%\-\-trailer\fP \(aqfoo: bar\(aq. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-hexdump +Display the incoming traffic in hexadecimal (Canonical +hex+ASCII display). If SSL/TLS is used, decrypted data +are used. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-echo\-upload +Send back uploaded content if method is POST or PUT. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-mime\-types\-file= +Path to file that contains MIME media types and the +extensions that represent them. +.sp +Default: \fB/etc/mime.types\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-content\-length +Don\(aqt send content\-length header field. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ktls +Enable ktls. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-rfc7540\-pri +Disable RFC7540 priorities. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-version +Display version information and exit. +.UNINDENT +.INDENT 0.0 +.TP +.B \-h, \-\-help +Display this help and exit. +.UNINDENT +.sp +The argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). +.SH SEE ALSO +.sp +\fBnghttp(1)\fP, \fBnghttpx(1)\fP, \fBh2load(1)\fP +.SH AUTHOR +Tatsuhiro Tsujikawa +.SH COPYRIGHT +2012, 2015, 2016, Tatsuhiro Tsujikawa +.\" Generated by docutils manpage writer. +. diff --git a/lib/nghttp2/doc/nghttpd.1.rst b/lib/nghttp2/doc/nghttpd.1.rst new file mode 100644 index 00000000000..654a0253499 --- /dev/null +++ b/lib/nghttp2/doc/nghttpd.1.rst @@ -0,0 +1,186 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttpd + +nghttpd(1) +========== + +SYNOPSIS +-------- + +**nghttpd** [OPTION]... [ ] + +DESCRIPTION +----------- + +HTTP/2 server + +.. describe:: + + Specify listening port number. + +.. describe:: + + + Set path to server's private key. Required unless + :option:`--no-tls` is specified. + +.. describe:: + + Set path to server's certificate. Required unless + :option:`--no-tls` is specified. + +OPTIONS +------- + +.. option:: -a, --address= + + The address to bind to. If not specified the default IP + address determined by getaddrinfo is used. + +.. option:: -D, --daemon + + Run in a background. If :option:`-D` is used, the current working + directory is changed to '*/*'. Therefore if this option + is used, :option:`-d` option must be specified. + +.. option:: -V, --verify-client + + The server sends a client certificate request. If the + client did not return a certificate, the handshake is + terminated. Currently, this option just requests a + client certificate and does not verify it. + +.. option:: -d, --htdocs= + + Specify document root. If this option is not specified, + the document root is the current working directory. + +.. option:: -v, --verbose + + Print debug information such as reception/ transmission + of frames and name/value pairs. + +.. option:: --no-tls + + Disable SSL/TLS. + +.. option:: -c, --header-table-size= + + Specify decoder header table size. + +.. option:: --encoder-header-table-size= + + Specify encoder header table size. The decoder (client) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which client specified. + +.. option:: --color + + Force colored log output. + +.. option:: -p, --push== + + Push resources s when is requested. + This option can be used repeatedly to specify multiple + push configurations. and s are + relative to document root. See :option:`--htdocs` option. + Example: :option:`-p`\/=/foo.png :option:`-p`\/doc=/bar.css + +.. option:: -b, --padding= + + Add at most bytes to a frame payload as padding. + Specify 0 to disable padding. + +.. option:: -m, --max-concurrent-streams= + + Set the maximum number of the concurrent streams in one + HTTP/2 session. + + Default: ``100`` + +.. option:: -n, --workers= + + Set the number of worker threads. + + Default: ``1`` + +.. option:: -e, --error-gzip + + Make error response gzipped. + +.. option:: -w, --window-bits= + + Sets the stream level initial window size to 2\*\*-1. + +.. option:: -W, --connection-window-bits= + + Sets the connection level initial window size to + 2\*\*-1. + +.. option:: --dh-param-file= + + Path to file that contains DH parameters in PEM format. + Without this option, DHE cipher suites are not + available. + +.. option:: --early-response + + Start sending response when request HEADERS is received, + rather than complete request is received. + +.. option:: --trailer=
+ + Add a trailer header to a response.
must not + include pseudo header field (header field name starting + with ':'). The trailer is sent only if a response has + body part. Example: :option:`--trailer` 'foo: bar'. + +.. option:: --hexdump + + Display the incoming traffic in hexadecimal (Canonical + hex+ASCII display). If SSL/TLS is used, decrypted data + are used. + +.. option:: --echo-upload + + Send back uploaded content if method is POST or PUT. + +.. option:: --mime-types-file= + + Path to file that contains MIME media types and the + extensions that represent them. + + Default: ``/etc/mime.types`` + +.. option:: --no-content-length + + Don't send content-length header field. + +.. option:: --ktls + + Enable ktls. + +.. option:: --no-rfc7540-pri + + Disable RFC7540 priorities. + +.. option:: --version + + Display version information and exit. + +.. option:: -h, --help + + Display this help and exit. + + + +The argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). + +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)` diff --git a/lib/nghttp2/doc/nghttpd.h2r b/lib/nghttp2/doc/nghttpd.h2r new file mode 100644 index 00000000000..e346cd1645d --- /dev/null +++ b/lib/nghttp2/doc/nghttpd.h2r @@ -0,0 +1,4 @@ +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)` diff --git a/lib/nghttp2/doc/nghttpx-howto.rst.in b/lib/nghttp2/doc/nghttpx-howto.rst.in new file mode 100644 index 00000000000..082ce510148 --- /dev/null +++ b/lib/nghttp2/doc/nghttpx-howto.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/doc/sources/nghttpx-howto.rst diff --git a/lib/nghttp2/doc/nghttpx.1 b/lib/nghttp2/doc/nghttpx.1 new file mode 100644 index 00000000000..1aaa47e60dc --- /dev/null +++ b/lib/nghttp2/doc/nghttpx.1 @@ -0,0 +1,2771 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "NGHTTPX" "1" "Oct 27, 2023" "1.58.0" "nghttp2" +.SH NAME +nghttpx \- HTTP/2 proxy +.SH SYNOPSIS +.sp +\fBnghttpx\fP [OPTIONS]... [ ] +.SH DESCRIPTION +.sp +A reverse proxy for HTTP/3, HTTP/2, and HTTP/1. +.INDENT 0.0 +.TP +.B +Set path to server\(aqs private key. Required unless +\(dqno\-tls\(dq parameter is used in \fI\%\-\-frontend\fP option. +.UNINDENT +.INDENT 0.0 +.TP +.B +Set path to server\(aqs certificate. Required unless +\(dqno\-tls\(dq parameter is used in \fI\%\-\-frontend\fP option. To +make OCSP stapling work, this must be an absolute path. +.UNINDENT +.SH OPTIONS +.sp +The options are categorized into several groups. +.SS Connections +.INDENT 0.0 +.TP +.B \-b, \-\-backend=(,|unix:)[;[[:...]][[;]...] +Set backend host and port. The multiple backend +addresses are accepted by repeating this option. UNIX +domain socket can be specified by prefixing path name +with \(dqunix:\(dq (e.g., unix:/var/run/backend.sock). +.sp +Optionally, if s are given, the backend address +is only used if request matches the pattern. The +pattern matching is closely designed to ServeMux in +net/http package of Go programming language. +consists of path, host + path or just host. The path +must start with \(dq\fI/\fP\(dq. If it ends with \(dq\fI/\fP\(dq, it matches +all request path in its subtree. To deal with the +request to the directory without trailing slash, the +path which ends with \(dq\fI/\fP\(dq also matches the request path +which only lacks trailing \(aq\fI/\fP\(aq (e.g., path \(dq\fI/foo/\fP\(dq +matches request path \(dq\fI/foo\fP\(dq). If it does not end with +\(dq\fI/\fP\(dq, it performs exact match against the request path. +If host is given, it performs a match against the +request host. For a request received on the frontend +listener with \(dqsni\-fwd\(dq parameter enabled, SNI host is +used instead of a request host. If host alone is given, +\(dq\fI/\fP\(dq is appended to it, so that it matches all request +paths under the host (e.g., specifying \(dqnghttp2.org\(dq +equals to \(dqnghttp2.org/\(dq). CONNECT method is treated +specially. It does not have path, and we don\(aqt allow +empty path. To workaround this, we assume that CONNECT +method has \(dq\fI/\fP\(dq as path. +.sp +Patterns with host take precedence over patterns with +just path. Then, longer patterns take precedence over +shorter ones. +.sp +Host can include \(dq*\(dq in the left most position to +indicate wildcard match (only suffix match is done). +The \(dq*\(dq must match at least one character. For example, +host pattern \(dq*.nghttp2.org\(dq matches against +\(dqwww.nghttp2.org\(dq and \(dqgit.ngttp2.org\(dq, but does not +match against \(dqnghttp2.org\(dq. The exact hosts match +takes precedence over the wildcard hosts match. +.sp +If path part ends with \(dq*\(dq, it is treated as wildcard +path. The wildcard path behaves differently from the +normal path. For normal path, match is made around the +boundary of path component separator,\(dq\fI/\fP\(dq. On the other +hand, the wildcard path does not take into account the +path component separator. All paths which include the +wildcard path without last \(dq*\(dq as prefix, and are +strictly longer than wildcard path without last \(dq*\(dq are +matched. \(dq*\(dq must match at least one character. For +example, the pattern \(dq\fI/foo*\fP\(dq matches \(dq\fI/foo/\fP\(dq and +\(dq\fI/foobar\fP\(dq. But it does not match \(dq\fI/foo\fP\(dq, or \(dq\fI/fo\fP\(dq. +.sp +If is omitted or empty string, \(dq\fI/\fP\(dq is used as +pattern, which matches all request paths (catch\-all +pattern). The catch\-all backend must be given. +.sp +When doing a match, nghttpx made some normalization to +pattern, request host and path. For host part, they are +converted to lower case. For path part, percent\-encoded +unreserved characters defined in RFC 3986 are decoded, +and any dot\-segments (\(dq..\(dq and \(dq.\(dq) are resolved and +removed. +.sp +For example, \fI\%\-b\fP\(aq127.0.0.1,8080;nghttp2.org/httpbin/\(aq +matches the request host \(dqnghttp2.org\(dq and the request +path \(dq\fI/httpbin/get\fP\(dq, but does not match the request host +\(dqnghttp2.org\(dq and the request path \(dq\fI/index.html\fP\(dq. +.sp +The multiple s can be specified, delimiting +them by \(dq:\(dq. Specifying +\fI\%\-b\fP\(aq127.0.0.1,8080;nghttp2.org:www.nghttp2.org\(aq has the +same effect to specify \fI\%\-b\fP\(aq127.0.0.1,8080;nghttp2.org\(aq +and \fI\%\-b\fP\(aq127.0.0.1,8080;www.nghttp2.org\(aq. +.sp +The backend addresses sharing same are grouped +together forming load balancing group. +.sp +Several parameters are accepted after . +The parameters are delimited by \(dq;\(dq. The available +parameters are: \(dqproto=\(dq, \(dqtls\(dq, +\(dqsni=\(dq, \(dqfall=\(dq, \(dqrise=\(dq, +\(dqaffinity=\(dq, \(dqdns\(dq, \(dqredirect\-if\-not\-tls\(dq, +\(dqupgrade\-scheme\(dq, \(dqmruby=\(dq, +\(dqread\-timeout=\(dq, \(dqwrite\-timeout=\(dq, +\(dqgroup=\(dq, \(dqgroup\-weight=\(dq, \(dqweight=\(dq, and +\(dqdnf\(dq. The parameter consists of keyword, and +optionally followed by \(dq=\(dq and value. For example, the +parameter \(dqproto=h2\(dq consists of the keyword \(dqproto\(dq and +value \(dqh2\(dq. The parameter \(dqtls\(dq consists of the keyword +\(dqtls\(dq without value. Each parameter is described as +follows. +.sp +The backend application protocol can be specified using +optional \(dqproto\(dq parameter, and in the form of +\(dqproto=\(dq. should be one of the following +list without quotes: \(dqh2\(dq, \(dqhttp/1.1\(dq. The default +value of is \(dqhttp/1.1\(dq. Note that usually \(dqh2\(dq +refers to HTTP/2 over TLS. But in this option, it may +mean HTTP/2 over cleartext TCP unless \(dqtls\(dq keyword is +used (see below). +.sp +TLS can be enabled by specifying optional \(dqtls\(dq +parameter. TLS is not enabled by default. +.sp +With \(dqsni=\(dq parameter, it can override the TLS +SNI field value with given . This will +default to the backend name +.sp +The feature to detect whether backend is online or +offline can be enabled using optional \(dqfall\(dq and \(dqrise\(dq +parameters. Using \(dqfall=\(dq parameter, if nghttpx +cannot connect to a this backend times in a row, +this backend is assumed to be offline, and it is +excluded from load balancing. If is 0, this backend +never be excluded from load balancing whatever times +nghttpx cannot connect to it, and this is the default. +There is also \(dqrise=\(dq parameter. After backend was +excluded from load balancing group, nghttpx periodically +attempts to make a connection to the failed backend, and +if the connection is made successfully times in a +row, the backend is assumed to be online, and it is now +eligible for load balancing target. If is 0, a +backend is permanently offline, once it goes in that +state, and this is the default behaviour. +.sp +The session affinity is enabled using +\(dqaffinity=\(dq parameter. If \(dqip\(dq is given in +, client IP based session affinity is enabled. +If \(dqcookie\(dq is given in , cookie based session +affinity is enabled. If \(dqnone\(dq is given in , +session affinity is disabled, and this is the default. +The session affinity is enabled per . If at +least one backend has \(dqaffinity\(dq parameter, and its + is not \(dqnone\(dq, session affinity is enabled for +all backend servers sharing the same . It is +advised to set \(dqaffinity\(dq parameter to all backend +explicitly if session affinity is desired. The session +affinity may break if one of the backend gets +unreachable, or backend settings are reloaded or +replaced by API. +.sp +If \(dqaffinity=cookie\(dq is used, the additional +configuration is required. +\(dqaffinity\-cookie\-name=\(dq must be used to specify a +name of cookie to use. Optionally, +\(dqaffinity\-cookie\-path=\(dq can be used to specify a +path which cookie is applied. The optional +\(dqaffinity\-cookie\-secure=\(dq controls the Secure +attribute of a cookie. The default value is \(dqauto\(dq, and +the Secure attribute is determined by a request scheme. +If a request scheme is \(dqhttps\(dq, then Secure attribute is +set. Otherwise, it is not set. If is \(dqyes\(dq, +the Secure attribute is always set. If is +\(dqno\(dq, the Secure attribute is always omitted. +\(dqaffinity\-cookie\-stickiness=\(dq controls +stickiness of this affinity. If is +\(dqloose\(dq, removing or adding a backend server might break +the affinity and the request might be forwarded to a +different backend server. If is \(dqstrict\(dq, +removing the designated backend server breaks affinity, +but adding new backend server does not cause breakage. +If the designated backend server becomes unavailable, +new backend server is chosen as if the request does not +have an affinity cookie. defaults to +\(dqloose\(dq. +.sp +By default, name resolution of backend host name is done +at start up, or reloading configuration. If \(dqdns\(dq +parameter is given, name resolution takes place +dynamically. This is useful if backend address changes +frequently. If \(dqdns\(dq is given, name resolution of +backend host name at start up, or reloading +configuration is skipped. +.sp +If \(dqredirect\-if\-not\-tls\(dq parameter is used, the matched +backend requires that frontend connection is TLS +encrypted. If it isn\(aqt, nghttpx responds to the request +with 308 status code, and https URI the client should +use instead is included in Location header field. The +port number in redirect URI is 443 by default, and can +be changed using \fI\%\-\-redirect\-https\-port\fP option. If at +least one backend has \(dqredirect\-if\-not\-tls\(dq parameter, +this feature is enabled for all backend servers sharing +the same . It is advised to set +\(dqredirect\-if\-no\-tls\(dq parameter to all backends +explicitly if this feature is desired. +.sp +If \(dqupgrade\-scheme\(dq parameter is used along with \(dqtls\(dq +parameter, HTTP/2 :scheme pseudo header field is changed +to \(dqhttps\(dq from \(dqhttp\(dq when forwarding a request to this +particular backend. This is a workaround for a backend +server which requires \(dqhttps\(dq :scheme pseudo header +field on TLS encrypted connection. +.sp +\(dqmruby=\(dq parameter specifies a path to mruby +script file which is invoked when this pattern is +matched. All backends which share the same pattern must +have the same mruby path. +.sp +\(dqread\-timeout=\(dq and \(dqwrite\-timeout=\(dq +parameters specify the read and write timeout of the +backend connection when this pattern is matched. All +backends which share the same pattern must have the same +timeouts. If these timeouts are entirely omitted for a +pattern, \fI\%\-\-backend\-read\-timeout\fP and +\fI\%\-\-backend\-write\-timeout\fP are used. +.sp +\(dqgroup=\(dq parameter specifies the name of group +this backend address belongs to. By default, it belongs +to the unnamed default group. The name of group is +unique per pattern. \(dqgroup\-weight=\(dq parameter +specifies the weight of the group. The higher weight +gets more frequently selected by the load balancing +algorithm. must be [1, 256] inclusive. The weight +8 has 4 times more weight than 2. must be the same +for all addresses which share the same . If +\(dqgroup\-weight\(dq is omitted in an address, but the other +address which belongs to the same group specifies +\(dqgroup\-weight\(dq, its weight is used. If no +\(dqgroup\-weight\(dq is specified for all addresses, the +weight of a group becomes 1. \(dqgroup\(dq and \(dqgroup\-weight\(dq +are ignored if session affinity is enabled. +.sp +\(dqweight=\(dq parameter specifies the weight of the +backend address inside a group which this address +belongs to. The higher weight gets more frequently +selected by the load balancing algorithm. must be +[1, 256] inclusive. The weight 8 has 4 times more +weight than weight 2. If this parameter is omitted, +weight becomes 1. \(dqweight\(dq is ignored if session +affinity is enabled. +.sp +If \(dqdnf\(dq parameter is specified, an incoming request is +not forwarded to a backend and just consumed along with +the request body (actually a backend server never be +contacted). It is expected that the HTTP response is +generated by mruby script (see \(dqmruby=\(dq parameter +above). \(dqdnf\(dq is an abbreviation of \(dqdo not forward\(dq. +.sp +Since \(dq;\(dq and \(dq:\(dq are used as delimiter, must +not contain these characters. In order to include \(dq:\(dq +in , one has to specify \(dq%3A\(dq (which is +percent\-encoded from of \(dq:\(dq) instead. Since \(dq;\(dq has +special meaning in shell, the option value must be +quoted. +.sp +Default: \fB127.0.0.1,80\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-f, \-\-frontend=(,|unix:)[[;]...] +Set frontend host and port. If is \(aq*\(aq, it +assumes all addresses including both IPv4 and IPv6. +UNIX domain socket can be specified by prefixing path +name with \(dqunix:\(dq (e.g., unix:/var/run/nghttpx.sock). +This option can be used multiple times to listen to +multiple addresses. +.sp +This option can take 0 or more parameters, which are +described below. Note that \(dqapi\(dq and \(dqhealthmon\(dq +parameters are mutually exclusive. +.sp +Optionally, TLS can be disabled by specifying \(dqno\-tls\(dq +parameter. TLS is enabled by default. +.sp +If \(dqsni\-fwd\(dq parameter is used, when performing a match +to select a backend server, SNI host name received from +the client is used instead of the request host. See +\fI\%\-\-backend\fP option about the pattern match. +.sp +To make this frontend as API endpoint, specify \(dqapi\(dq +parameter. This is disabled by default. It is +important to limit the access to the API frontend. +Otherwise, someone may change the backend server, and +break your services, or expose confidential information +to the outside the world. +.sp +To make this frontend as health monitor endpoint, +specify \(dqhealthmon\(dq parameter. This is disabled by +default. Any requests which come through this address +are replied with 200 HTTP status, without no body. +.sp +To accept PROXY protocol version 1 and 2 on frontend +connection, specify \(dqproxyproto\(dq parameter. This is +disabled by default. +.sp +To receive HTTP/3 (QUIC) traffic, specify \(dqquic\(dq +parameter. It makes nghttpx listen on UDP port rather +than TCP port. UNIX domain socket, \(dqapi\(dq, and +\(dqhealthmon\(dq parameters cannot be used with \(dqquic\(dq +parameter. +.sp +Default: \fB*,3000\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backlog= +Set listen backlog size. +.sp +Default: \fB65536\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-address\-family=(auto|IPv4|IPv6) +Specify address family of backend connections. If +\(dqauto\(dq is given, both IPv4 and IPv6 are considered. If +\(dqIPv4\(dq is given, only IPv4 address is considered. If +\(dqIPv6\(dq is given, only IPv6 address is considered. +.sp +Default: \fBauto\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-http\-proxy\-uri= +Specify proxy URI in the form +\fI\%http:/\fP/[:@]:. If a proxy +requires authentication, specify and . +Note that they must be properly percent\-encoded. This +proxy is used when the backend connection is HTTP/2. +First, make a CONNECT request to the proxy and it +connects to the backend on behalf of nghttpx. This +forms tunnel. After that, nghttpx performs SSL/TLS +handshake with the downstream through the tunnel. The +timeouts when connecting and making CONNECT request can +be specified by \fI\%\-\-backend\-read\-timeout\fP and +\fI\%\-\-backend\-write\-timeout\fP options. +.UNINDENT +.SS Performance +.INDENT 0.0 +.TP +.B \-n, \-\-workers= +Set the number of worker threads. +.sp +Default: \fB1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-single\-thread +Run everything in one thread inside the worker process. +This feature is provided for better debugging +experience, or for the platforms which lack thread +support. If threading is disabled, this option is +always enabled. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-read\-rate= +Set maximum average read rate on frontend connection. +Setting 0 to this option means read rate is unlimited. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-read\-burst= +Set maximum read burst size on frontend connection. +Setting 0 to this option means read burst size is +unlimited. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-write\-rate= +Set maximum average write rate on frontend connection. +Setting 0 to this option means write rate is unlimited. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-write\-burst= +Set maximum write burst size on frontend connection. +Setting 0 to this option means write burst size is +unlimited. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-worker\-read\-rate= +Set maximum average read rate on frontend connection per +worker. Setting 0 to this option means read rate is +unlimited. Not implemented yet. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-worker\-read\-burst= +Set maximum read burst size on frontend connection per +worker. Setting 0 to this option means read burst size +is unlimited. Not implemented yet. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-worker\-write\-rate= +Set maximum average write rate on frontend connection +per worker. Setting 0 to this option means write rate +is unlimited. Not implemented yet. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-worker\-write\-burst= +Set maximum write burst size on frontend connection per +worker. Setting 0 to this option means write burst size +is unlimited. Not implemented yet. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-worker\-frontend\-connections= +Set maximum number of simultaneous connections frontend +accepts. Setting 0 means unlimited. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-connections\-per\-host= +Set maximum number of backend concurrent connections +(and/or streams in case of HTTP/2) per origin host. +This option is meaningful when \fI\%\-\-http2\-proxy\fP option is +used. The origin host is determined by authority +portion of request URI (or :authority header field for +HTTP/2). To limit the number of connections per +frontend for default mode, use +\fI\%\-\-backend\-connections\-per\-frontend\fP\&. +.sp +Default: \fB8\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-connections\-per\-frontend= +Set maximum number of backend concurrent connections +(and/or streams in case of HTTP/2) per frontend. This +option is only used for default mode. 0 means +unlimited. To limit the number of connections per host +with \fI\%\-\-http2\-proxy\fP option, use +\fI\%\-\-backend\-connections\-per\-host\fP\&. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-rlimit\-nofile= +Set maximum number of open files (RLIMIT_NOFILE) to . +If 0 is given, nghttpx does not set the limit. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-rlimit\-memlock= +Set maximum number of bytes of memory that may be locked +into RAM. If 0 is given, nghttpx does not set the +limit. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-request\-buffer= +Set buffer size used to store backend request. +.sp +Default: \fB16K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-response\-buffer= +Set buffer size used to store backend response. +.sp +Default: \fB128K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-fastopen= +Enables \(dqTCP Fast Open\(dq for the listening socket and +limits the maximum length for the queue of connections +that have not yet completed the three\-way handshake. If +value is 0 then fast open is disabled. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-kqueue +Don\(aqt use kqueue. This option is only applicable for +the platforms which have kqueue. For other platforms, +this option will be simply ignored. +.UNINDENT +.SS Timeout +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-read\-timeout= +Specify read timeout for HTTP/2 frontend connection. +.sp +Default: \fB3m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-read\-timeout= +Specify read timeout for HTTP/3 frontend connection. +.sp +Default: \fB3m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-read\-timeout= +Specify read timeout for HTTP/1.1 frontend connection. +.sp +Default: \fB1m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-write\-timeout= +Specify write timeout for all frontend connections. +.sp +Default: \fB30s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-keep\-alive\-timeout= +Specify keep\-alive timeout for frontend HTTP/1 +connection. +.sp +Default: \fB1m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-stream\-read\-timeout= +Specify read timeout for HTTP/2 streams. 0 means no +timeout. +.sp +Default: \fB0\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-stream\-write\-timeout= +Specify write timeout for HTTP/2 streams. 0 means no +timeout. +.sp +Default: \fB1m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-read\-timeout= +Specify read timeout for backend connection. +.sp +Default: \fB1m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-write\-timeout= +Specify write timeout for backend connection. +.sp +Default: \fB30s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-connect\-timeout= +Specify timeout before establishing TCP connection to +backend. +.sp +Default: \fB30s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-keep\-alive\-timeout= +Specify keep\-alive timeout for backend HTTP/1 +connection. +.sp +Default: \fB2s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-listener\-disable\-timeout= +After accepting connection failed, connection listener +is disabled for a given amount of time. Specifying 0 +disables this feature. +.sp +Default: \fB30s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-setting\-timeout= +Specify timeout before SETTINGS ACK is received from +client. +.sp +Default: \fB10s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-http2\-settings\-timeout= +Specify timeout before SETTINGS ACK is received from +backend server. +.sp +Default: \fB10s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-max\-backoff= +Specify maximum backoff interval. This is used when +doing health check against offline backend (see \(dqfail\(dq +parameter in \fI\%\-\-backend\fP option). It is also used to +limit the maximum interval to temporarily disable +backend when nghttpx failed to connect to it. These +intervals are calculated using exponential backoff, and +consecutive failed attempts increase the interval. This +option caps its maximum value. +.sp +Default: \fB2m\fP +.UNINDENT +.SS SSL/TLS +.INDENT 0.0 +.TP +.B \-\-ciphers= +Set allowed cipher list for frontend connection. The +format of the string is described in OpenSSL ciphers(1). +This option sets cipher suites for TLSv1.2 or earlier. +Use \fI\%\-\-tls13\-ciphers\fP for TLSv1.3. +.sp +Default: \fBECDHE\-ECDSA\-AES128\-GCM\-SHA256:ECDHE\-RSA\-AES128\-GCM\-SHA256:ECDHE\-ECDSA\-AES256\-GCM\-SHA384:ECDHE\-RSA\-AES256\-GCM\-SHA384:ECDHE\-ECDSA\-CHACHA20\-POLY1305:ECDHE\-RSA\-CHACHA20\-POLY1305:DHE\-RSA\-AES128\-GCM\-SHA256:DHE\-RSA\-AES256\-GCM\-SHA384\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls13\-ciphers= +Set allowed cipher list for frontend connection. The +format of the string is described in OpenSSL ciphers(1). +This option sets cipher suites for TLSv1.3. Use +\fI\%\-\-ciphers\fP for TLSv1.2 or earlier. +.sp +Default: \fBTLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-client\-ciphers= +Set allowed cipher list for backend connection. The +format of the string is described in OpenSSL ciphers(1). +This option sets cipher suites for TLSv1.2 or earlier. +Use \fI\%\-\-tls13\-client\-ciphers\fP for TLSv1.3. +.sp +Default: \fBECDHE\-ECDSA\-AES128\-GCM\-SHA256:ECDHE\-RSA\-AES128\-GCM\-SHA256:ECDHE\-ECDSA\-AES256\-GCM\-SHA384:ECDHE\-RSA\-AES256\-GCM\-SHA384:ECDHE\-ECDSA\-CHACHA20\-POLY1305:ECDHE\-RSA\-CHACHA20\-POLY1305:DHE\-RSA\-AES128\-GCM\-SHA256:DHE\-RSA\-AES256\-GCM\-SHA384\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls13\-client\-ciphers= +Set allowed cipher list for backend connection. The +format of the string is described in OpenSSL ciphers(1). +This option sets cipher suites for TLSv1.3. Use +\fI\%\-\-tls13\-client\-ciphers\fP for TLSv1.2 or earlier. +.sp +Default: \fBTLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ecdh\-curves= +Set supported curve list for frontend connections. + is a colon separated list of curve NID or names +in the preference order. The supported curves depend on +the linked OpenSSL library. This function requires +OpenSSL >= 1.0.2. +.sp +Default: \fBX25519:P\-256:P\-384:P\-521\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-k, \-\-insecure +Don\(aqt verify backend server\(aqs certificate if TLS is +enabled for backend connections. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-cacert= +Set path to trusted CA certificate file. It is used in +backend TLS connections to verify peer\(aqs certificate. +It is also used to verify OCSP response from the script +set by \fI\%\-\-fetch\-ocsp\-response\-file\fP\&. The file must be in +PEM format. It can contain multiple certificates. If +the linked OpenSSL is configured to load system wide +certificates, they are loaded at startup regardless of +this option. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-private\-key\-passwd\-file= +Path to file that contains password for the server\(aqs +private key. If none is given and the private key is +password protected it\(aqll be requested interactively. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-subcert=:[[;]...] +Specify additional certificate and private key file. +nghttpx will choose certificates based on the hostname +indicated by client using TLS SNI extension. If nghttpx +is built with OpenSSL >= 1.0.2, the shared elliptic +curves (e.g., P\-256) between client and server are also +taken into consideration. This allows nghttpx to send +ECDSA certificate to modern clients, while sending RSA +based certificate to older clients. This option can be +used multiple times. To make OCSP stapling work, + must be absolute path. +.sp +Additional parameter can be specified in . The +available is \(dqsct\-dir=\(dq. +.sp +\(dqsct\-dir=\(dq specifies the path to directory which +contains *.sct files for TLS +signed_certificate_timestamp extension (RFC 6962). This +feature requires OpenSSL >= 1.0.2. See also +\fI\%\-\-tls\-sct\-dir\fP option. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-dh\-param\-file= +Path to file that contains DH parameters in PEM format. +Without this option, DHE cipher suites are not +available. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-npn\-list= +Comma delimited list of ALPN protocol identifier sorted +in the order of preference. That means most desirable +protocol comes first. This is used in both ALPN and +NPN. The parameter must be delimited by a single comma +only and any white spaces are treated as a part of +protocol string. +.sp +Default: \fBh2,h2\-16,h2\-14,http/1.1\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-verify\-client +Require and verify client certificate. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-verify\-client\-cacert= +Path to file that contains CA certificates to verify +client certificate. The file must be in PEM format. It +can contain multiple certificates. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-verify\-client\-tolerate\-expired +Accept expired client certificate. Operator should +handle the expired client certificate by some means +(e.g., mruby script). Otherwise, this option might +cause a security risk. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-client\-private\-key\-file= +Path to file that contains client private key used in +backend client authentication. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-client\-cert\-file= +Path to file that contains client certificate used in +backend client authentication. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-min\-proto\-version= +Specify minimum SSL/TLS protocol. The name matching is +done in case\-insensitive manner. The versions between +\fI\%\-\-tls\-min\-proto\-version\fP and \fI\%\-\-tls\-max\-proto\-version\fP are +enabled. If the protocol list advertised by client does +not overlap this range, you will receive the error +message \(dqunknown protocol\(dq. If a protocol version lower +than TLSv1.2 is specified, make sure that the compatible +ciphers are included in \fI\%\-\-ciphers\fP option. The default +cipher list only includes ciphers compatible with +TLSv1.2 or above. The available versions are: +TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.0 +.sp +Default: \fBTLSv1.2\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-max\-proto\-version= +Specify maximum SSL/TLS protocol. The name matching is +done in case\-insensitive manner. The versions between +\fI\%\-\-tls\-min\-proto\-version\fP and \fI\%\-\-tls\-max\-proto\-version\fP are +enabled. If the protocol list advertised by client does +not overlap this range, you will receive the error +message \(dqunknown protocol\(dq. The available versions are: +TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.0 +.sp +Default: \fBTLSv1.3\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-file= +Path to file that contains random data to construct TLS +session ticket parameters. If aes\-128\-cbc is given in +\fI\%\-\-tls\-ticket\-key\-cipher\fP, the file must contain exactly +48 bytes. If aes\-256\-cbc is given in +\fI\%\-\-tls\-ticket\-key\-cipher\fP, the file must contain exactly +80 bytes. This options can be used repeatedly to +specify multiple ticket parameters. If several files +are given, only the first key is used to encrypt TLS +session tickets. Other keys are accepted but server +will issue new session ticket with first key. This +allows session key rotation. Please note that key +rotation does not occur automatically. User should +rearrange files or change options values and restart +nghttpx gracefully. If opening or reading given file +fails, all loaded keys are discarded and it is treated +as if none of this option is given. If this option is +not given or an error occurred while opening or reading +a file, key is generated every 1 hour internally and +they are valid for 12 hours. This is recommended if +ticket key sharing between nghttpx instances is not +required. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-memcached=,[;tls] +Specify address of memcached server to get TLS ticket +keys for session resumption. This enables shared TLS +ticket key between multiple nghttpx instances. nghttpx +does not set TLS ticket key to memcached. The external +ticket key generator is required. nghttpx just gets TLS +ticket keys from memcached, and use them, possibly +replacing current set of keys. It is up to extern TLS +ticket key generator to rotate keys frequently. See +\(dqTLS SESSION TICKET RESUMPTION\(dq section in manual page +to know the data format in memcached entry. Optionally, +memcached connection can be encrypted with TLS by +specifying \(dqtls\(dq parameter. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-memcached\-address\-family=(auto|IPv4|IPv6) +Specify address family of memcached connections to get +TLS ticket keys. If \(dqauto\(dq is given, both IPv4 and IPv6 +are considered. If \(dqIPv4\(dq is given, only IPv4 address +is considered. If \(dqIPv6\(dq is given, only IPv6 address is +considered. +.sp +Default: \fBauto\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-memcached\-interval= +Set interval to get TLS ticket keys from memcached. +.sp +Default: \fB10m\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-memcached\-max\-retry= +Set maximum number of consecutive retries before +abandoning TLS ticket key retrieval. If this number is +reached, the attempt is considered as failure, and +\(dqfailure\(dq count is incremented by 1, which contributed +to the value controlled +\fI\%\-\-tls\-ticket\-key\-memcached\-max\-fail\fP option. +.sp +Default: \fB3\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-memcached\-max\-fail= +Set maximum number of consecutive failure before +disabling TLS ticket until next scheduled key retrieval. +.sp +Default: \fB2\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-cipher= +Specify cipher to encrypt TLS session ticket. Specify +either aes\-128\-cbc or aes\-256\-cbc. By default, +aes\-128\-cbc is used. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-memcached\-cert\-file= +Path to client certificate for memcached connections to +get TLS ticket keys. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ticket\-key\-memcached\-private\-key\-file= +Path to client private key for memcached connections to +get TLS ticket keys. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-fetch\-ocsp\-response\-file= +Path to fetch\-ocsp\-response script file. It should be +absolute path. +.sp +Default: \fB/usr/local/share/nghttp2/fetch\-ocsp\-response\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ocsp\-update\-interval= +Set interval to update OCSP response cache. +.sp +Default: \fB4h\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ocsp\-startup +Start accepting connections after initial attempts to +get OCSP responses finish. It does not matter some of +the attempts fail. This feature is useful if OCSP +responses must be available before accepting +connections. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-verify\-ocsp +nghttpx does not verify OCSP response. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-ocsp +Disable OCSP stapling. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-session\-cache\-memcached=,[;tls] +Specify address of memcached server to store session +cache. This enables shared session cache between +multiple nghttpx instances. Optionally, memcached +connection can be encrypted with TLS by specifying \(dqtls\(dq +parameter. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-session\-cache\-memcached\-address\-family=(auto|IPv4|IPv6) +Specify address family of memcached connections to store +session cache. If \(dqauto\(dq is given, both IPv4 and IPv6 +are considered. If \(dqIPv4\(dq is given, only IPv4 address +is considered. If \(dqIPv6\(dq is given, only IPv6 address is +considered. +.sp +Default: \fBauto\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-session\-cache\-memcached\-cert\-file= +Path to client certificate for memcached connections to +store session cache. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-session\-cache\-memcached\-private\-key\-file= +Path to client private key for memcached connections to +store session cache. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-dyn\-rec\-warmup\-threshold= +Specify the threshold size for TLS dynamic record size +behaviour. During a TLS session, after the threshold +number of bytes have been written, the TLS record size +will be increased to the maximum allowed (16K). The max +record size will continue to be used on the active TLS +session. After \fI\%\-\-tls\-dyn\-rec\-idle\-timeout\fP has elapsed, +the record size is reduced to 1300 bytes. Specify 0 to +always use the maximum record size, regardless of idle +period. This behaviour applies to all TLS based +frontends, and TLS HTTP/2 backends. +.sp +Default: \fB1M\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-dyn\-rec\-idle\-timeout= +Specify TLS dynamic record size behaviour timeout. See +\fI\%\-\-tls\-dyn\-rec\-warmup\-threshold\fP for more information. +This behaviour applies to all TLS based frontends, and +TLS HTTP/2 backends. +.sp +Default: \fB1s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-http2\-cipher\-block\-list +Allow block listed cipher suite on frontend HTTP/2 +connection. See +\fI\%https://tools.ietf.org/html/rfc7540#appendix\-A\fP for the +complete HTTP/2 cipher suites block list. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-client\-no\-http2\-cipher\-block\-list +Allow block listed cipher suite on backend HTTP/2 +connection. See +\fI\%https://tools.ietf.org/html/rfc7540#appendix\-A\fP for the +complete HTTP/2 cipher suites block list. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-sct\-dir= +Specifies the directory where *.sct files exist. All +*.sct files in are read, and sent as +extension_data of TLS signed_certificate_timestamp (RFC +6962) to client. These *.sct files are for the +certificate specified in positional command\-line +argument , or certificate option in configuration +file. For additional certificates, use \fI\%\-\-subcert\fP +option. This option requires OpenSSL >= 1.0.2. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-psk\-secrets= +Read list of PSK identity and secrets from . This +is used for frontend connection. The each line of input +file is formatted as :, where + is PSK identity, and is secret +in hex. An empty line, and line which starts with \(aq#\(aq +are skipped. The default enabled cipher list might not +contain any PSK cipher suite. In that case, desired PSK +cipher suites must be enabled using \fI\%\-\-ciphers\fP option. +The desired PSK cipher suite may be block listed by +HTTP/2. To use those cipher suites with HTTP/2, +consider to use \fI\%\-\-no\-http2\-cipher\-block\-list\fP option. +But be aware its implications. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-client\-psk\-secrets= +Read PSK identity and secrets from . This is used +for backend connection. The each line of input file is +formatted as :, where +is PSK identity, and is secret in hex. An +empty line, and line which starts with \(aq#\(aq are skipped. +The first identity and secret pair encountered is used. +The default enabled cipher list might not contain any +PSK cipher suite. In that case, desired PSK cipher +suites must be enabled using \fI\%\-\-client\-ciphers\fP option. +The desired PSK cipher suite may be block listed by +HTTP/2. To use those cipher suites with HTTP/2, +consider to use \fI\%\-\-client\-no\-http2\-cipher\-block\-list\fP +option. But be aware its implications. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-no\-postpone\-early\-data +By default, except for QUIC connections, nghttpx +postpones forwarding HTTP requests sent in early data, +including those sent in partially in it, until TLS +handshake finishes. If all backend server recognizes +\(dqEarly\-Data\(dq header field, using this option makes +nghttpx not postpone forwarding request and get full +potential of 0\-RTT data. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-max\-early\-data= +Sets the maximum amount of 0\-RTT data that server +accepts. +.sp +Default: \fB16K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-tls\-ktls +Enable ktls. For server, ktls is enable if +\fI\%\-\-tls\-session\-cache\-memcached\fP is not configured. +.UNINDENT +.SS HTTP/2 +.INDENT 0.0 +.TP +.B \-c, \-\-frontend\-http2\-max\-concurrent\-streams= +Set the maximum number of the concurrent streams in one +frontend HTTP/2 session. +.sp +Default: \fB100\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-http2\-max\-concurrent\-streams= +Set the maximum number of the concurrent streams in one +backend HTTP/2 session. This sets maximum number of +concurrent opened pushed streams. The maximum number of +concurrent requests are set by a remote server. +.sp +Default: \fB100\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-window\-size= +Sets the per\-stream initial window size of HTTP/2 +frontend connection. +.sp +Default: \fB65535\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-connection\-window\-size= +Sets the per\-connection window size of HTTP/2 frontend +connection. +.sp +Default: \fB65535\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-http2\-window\-size= +Sets the initial window size of HTTP/2 backend +connection. +.sp +Default: \fB65535\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-http2\-connection\-window\-size= +Sets the per\-connection window size of HTTP/2 backend +connection. +.sp +Default: \fB2147483647\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-http2\-no\-cookie\-crumbling +Don\(aqt crumble cookie header field. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-padding= +Add at most bytes to a HTTP/2 frame payload as +padding. Specify 0 to disable padding. This option is +meant for debugging purpose and not intended to enhance +protocol security. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-server\-push +Disable HTTP/2 server push. Server push is supported by +default mode and HTTP/2 frontend via Link header field. +It is also supported if both frontend and backend are +HTTP/2 in default mode. In this case, server push from +backend session is relayed to frontend, and server push +via Link header field is also supported. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-optimize\-write\-buffer\-size +(Experimental) Enable write buffer size optimization in +frontend HTTP/2 TLS connection. This optimization aims +to reduce write buffer size so that it only contains +bytes which can send immediately. This makes server +more responsive to prioritized HTTP/2 stream because the +buffering of lower priority stream is reduced. This +option is only effective on recent Linux platform. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-optimize\-window\-size +(Experimental) Automatically tune connection level +window size of frontend HTTP/2 TLS connection. If this +feature is enabled, connection window size starts with +the default window size, 65535 bytes. nghttpx +automatically adjusts connection window size based on +TCP receiving window size. The maximum window size is +capped by the value specified by +\fI\%\-\-frontend\-http2\-connection\-window\-size\fP\&. Since the +stream is subject to stream level window size, it should +be adjusted using \fI\%\-\-frontend\-http2\-window\-size\fP option as +well. This option is only effective on recent Linux +platform. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-encoder\-dynamic\-table\-size= +Specify the maximum dynamic table size of HPACK encoder +in the frontend HTTP/2 connection. The decoder (client) +specifies the maximum dynamic table size it accepts. +Then the negotiated dynamic table size is the minimum of +this option value and the value which client specified. +.sp +Default: \fB4K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-decoder\-dynamic\-table\-size= +Specify the maximum dynamic table size of HPACK decoder +in the frontend HTTP/2 connection. +.sp +Default: \fB4K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-http2\-encoder\-dynamic\-table\-size= +Specify the maximum dynamic table size of HPACK encoder +in the backend HTTP/2 connection. The decoder (backend) +specifies the maximum dynamic table size it accepts. +Then the negotiated dynamic table size is the minimum of +this option value and the value which backend specified. +.sp +Default: \fB4K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-backend\-http2\-decoder\-dynamic\-table\-size= +Specify the maximum dynamic table size of HPACK decoder +in the backend HTTP/2 connection. +.sp +Default: \fB4K\fP +.UNINDENT +.SS Mode +.INDENT 0.0 +.TP +.B (default mode) +Accept HTTP/2, and HTTP/1.1 over SSL/TLS. \(dqno\-tls\(dq +parameter is used in \fI\%\-\-frontend\fP option, accept HTTP/2 +and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1 +connection can be upgraded to HTTP/2 through HTTP +Upgrade. +.UNINDENT +.INDENT 0.0 +.TP +.B \-s, \-\-http2\-proxy +Like default mode, but enable forward proxy. This is so +called HTTP/2 proxy mode. +.UNINDENT +.SS Logging +.INDENT 0.0 +.TP +.B \-L, \-\-log\-level= +Set the severity level of log output. must be +one of INFO, NOTICE, WARN, ERROR and FATAL. +.sp +Default: \fBNOTICE\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-accesslog\-file= +Set path to write access log. To reopen file, send USR1 +signal to nghttpx. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-accesslog\-syslog +Send access log to syslog. If this option is used, +\fI\%\-\-accesslog\-file\fP option is ignored. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-accesslog\-format= +Specify format string for access log. The default +format is combined format. The following variables are +available: +.INDENT 7.0 +.IP \(bu 2 +$remote_addr: client IP address. +.IP \(bu 2 +$time_local: local time in Common Log format. +.IP \(bu 2 +$time_iso8601: local time in ISO 8601 format. +.IP \(bu 2 +$request: HTTP request line. +.IP \(bu 2 +$status: HTTP response status code. +.IP \(bu 2 +$body_bytes_sent: the number of bytes sent to client +as response body. +.IP \(bu 2 +$http_: value of HTTP request header where +\(aq_\(aq in is replaced with \(aq\-\(aq. +.IP \(bu 2 +$remote_port: client port. +.IP \(bu 2 +$server_port: server port. +.IP \(bu 2 +$request_time: request processing time in seconds with +milliseconds resolution. +.IP \(bu 2 +$pid: PID of the running process. +.IP \(bu 2 +$alpn: ALPN identifier of the protocol which generates +the response. For HTTP/1, ALPN is always http/1.1, +regardless of minor version. +.IP \(bu 2 +$tls_cipher: cipher used for SSL/TLS connection. +.IP \(bu 2 +$tls_client_fingerprint_sha256: SHA\-256 fingerprint of +client certificate. +.IP \(bu 2 +$tls_client_fingerprint_sha1: SHA\-1 fingerprint of +client certificate. +.IP \(bu 2 +$tls_client_subject_name: subject name in client +certificate. +.IP \(bu 2 +$tls_client_issuer_name: issuer name in client +certificate. +.IP \(bu 2 +$tls_client_serial: serial number in client +certificate. +.IP \(bu 2 +$tls_protocol: protocol for SSL/TLS connection. +.IP \(bu 2 +$tls_session_id: session ID for SSL/TLS connection. +.IP \(bu 2 +$tls_session_reused: \(dqr\(dq if SSL/TLS session was +reused. Otherwise, \(dq.\(dq +.IP \(bu 2 +$tls_sni: SNI server name for SSL/TLS connection. +.IP \(bu 2 +$backend_host: backend host used to fulfill the +request. \(dq\-\(dq if backend host is not available. +.IP \(bu 2 +$backend_port: backend port used to fulfill the +request. \(dq\-\(dq if backend host is not available. +.IP \(bu 2 +$method: HTTP method +.IP \(bu 2 +$path: Request path including query. For CONNECT +request, authority is recorded. +.IP \(bu 2 +$path_without_query: $path up to the first \(aq?\(aq +character. For CONNECT request, authority is +recorded. +.IP \(bu 2 +$protocol_version: HTTP version (e.g., HTTP/1.1, +HTTP/2) +.UNINDENT +.sp +The variable can be enclosed by \(dq{\(dq and \(dq}\(dq for +disambiguation (e.g., ${remote_addr}). +.sp +Default: \fB$remote_addr \- \- [$time_local] \(dq$request\(dq $status $body_bytes_sent \(dq$http_referer\(dq \(dq$http_user_agent\(dq\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-accesslog\-write\-early +Write access log when response header fields are +received from backend rather than when request +transaction finishes. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-errorlog\-file= +Set path to write error log. To reopen file, send USR1 +signal to nghttpx. stderr will be redirected to the +error log file unless \fI\%\-\-errorlog\-syslog\fP is used. +.sp +Default: \fB/dev/stderr\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-errorlog\-syslog +Send error log to syslog. If this option is used, +\fI\%\-\-errorlog\-file\fP option is ignored. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-syslog\-facility= +Set syslog facility to . +.sp +Default: \fBdaemon\fP +.UNINDENT +.SS HTTP +.INDENT 0.0 +.TP +.B \-\-add\-x\-forwarded\-for +Append X\-Forwarded\-For header field to the downstream +request. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-strip\-incoming\-x\-forwarded\-for +Strip X\-Forwarded\-For header field from inbound client +requests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-add\-x\-forwarded\-proto +Don\(aqt append additional X\-Forwarded\-Proto header field +to the backend request. If inbound client sets +X\-Forwarded\-Proto, and +\fI\%\-\-no\-strip\-incoming\-x\-forwarded\-proto\fP option is used, +they are passed to the backend. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-strip\-incoming\-x\-forwarded\-proto +Don\(aqt strip X\-Forwarded\-Proto header field from inbound +client requests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-add\-forwarded= +Append RFC 7239 Forwarded header field with parameters +specified in comma delimited list . The supported +parameters are \(dqby\(dq, \(dqfor\(dq, \(dqhost\(dq, and \(dqproto\(dq. By +default, the value of \(dqby\(dq and \(dqfor\(dq parameters are +obfuscated string. See \fI\%\-\-forwarded\-by\fP and +\fI\%\-\-forwarded\-for\fP options respectively. Note that nghttpx +does not translate non\-standard X\-Forwarded\-* header +fields into Forwarded header field, and vice versa. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-strip\-incoming\-forwarded +Strip Forwarded header field from inbound client +requests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-forwarded\-by=(obfuscated|ip|) +Specify the parameter value sent out with \(dqby\(dq parameter +of Forwarded header field. If \(dqobfuscated\(dq is given, +the string is randomly generated at startup. If \(dqip\(dq is +given, the interface address of the connection, +including port number, is sent with \(dqby\(dq parameter. In +case of UNIX domain socket, \(dqlocalhost\(dq is used instead +of address and port. User can also specify the static +obfuscated string. The limitation is that it must start +with \(dq_\(dq, and only consists of character set +[A\-Za\-z0\-9._\-], as described in RFC 7239. +.sp +Default: \fBobfuscated\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-forwarded\-for=(obfuscated|ip) +Specify the parameter value sent out with \(dqfor\(dq +parameter of Forwarded header field. If \(dqobfuscated\(dq is +given, the string is randomly generated for each client +connection. If \(dqip\(dq is given, the remote client address +of the connection, without port number, is sent with +\(dqfor\(dq parameter. In case of UNIX domain socket, +\(dqlocalhost\(dq is used instead of address. +.sp +Default: \fBobfuscated\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-via +Don\(aqt append to Via header field. If Via header field +is received, it is left unaltered. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-strip\-incoming\-early\-data +Don\(aqt strip Early\-Data header field from inbound client +requests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-location\-rewrite +Don\(aqt rewrite location header field in default mode. +When \fI\%\-\-http2\-proxy\fP is used, location header field will +not be altered regardless of this option. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-host\-rewrite +Rewrite host and :authority header fields in default +mode. When \fI\%\-\-http2\-proxy\fP is used, these headers will +not be altered regardless of this option. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-altsvc= +Specify protocol ID, port, host and origin of +alternative service. , and are +optional. Empty and are allowed and +they are treated as nothing is specified. They are +advertised in alt\-svc header field only in HTTP/1.1 +frontend. This option can be used multiple times to +specify multiple alternative services. +Example: \fI\%\-\-altsvc\fP=\(dqh2,443,,,ma=3600; persist=1\(dq +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-http2\-altsvc= +Just like \fI\%\-\-altsvc\fP option, but this altsvc is only sent +in HTTP/2 frontend. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-add\-request\-header=
+Specify additional header field to add to request header +set. The field name must be lowercase. This option +just appends header field and won\(aqt replace anything +already set. This option can be used several times to +specify multiple header fields. +Example: \fI\%\-\-add\-request\-header\fP=\(dqfoo: bar\(dq +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-add\-response\-header=
+Specify additional header field to add to response +header set. The field name must be lowercase. This +option just appends header field and won\(aqt replace +anything already set. This option can be used several +times to specify multiple header fields. +Example: \fI\%\-\-add\-response\-header\fP=\(dqfoo: bar\(dq +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-request\-header\-field\-buffer= +Set maximum buffer size for incoming HTTP request header +field list. This is the sum of header name and value in +bytes. If trailer fields exist, they are counted +towards this number. +.sp +Default: \fB64K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-max\-request\-header\-fields= +Set maximum number of incoming HTTP request header +fields. If trailer fields exist, they are counted +towards this number. +.sp +Default: \fB100\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-response\-header\-field\-buffer= +Set maximum buffer size for incoming HTTP response +header field list. This is the sum of header name and +value in bytes. If trailer fields exist, they are +counted towards this number. +.sp +Default: \fB64K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-max\-response\-header\-fields= +Set maximum number of incoming HTTP response header +fields. If trailer fields exist, they are counted +towards this number. +.sp +Default: \fB500\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-error\-page=(|*)= +Set file path to custom error page served when nghttpx +originally generates HTTP error status code . + must be greater than or equal to 400, and at most +599. If \(dq*\(dq is used instead of , it matches all +HTTP status code. If error status code comes from +backend server, the custom error pages are not used. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-server\-name= +Change server response header field value to . +.sp +Default: \fBnghttpx\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-server\-rewrite +Don\(aqt rewrite server header field in default mode. When +\fI\%\-\-http2\-proxy\fP is used, these headers will not be altered +regardless of this option. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-redirect\-https\-port= +Specify the port number which appears in Location header +field when redirect to HTTPS URI is made due to +\(dqredirect\-if\-not\-tls\(dq parameter in \fI\%\-\-backend\fP option. +.sp +Default: \fB443\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-require\-http\-scheme +Always require http or https scheme in HTTP request. It +also requires that https scheme must be used for an +encrypted connection. Otherwise, http scheme must be +used. This option is recommended for a server +deployment which directly faces clients and the services +it provides only require http or https scheme. +.UNINDENT +.SS API +.INDENT 0.0 +.TP +.B \-\-api\-max\-request\-body= +Set the maximum size of request body for API request. +.sp +Default: \fB32M\fP +.UNINDENT +.SS DNS +.INDENT 0.0 +.TP +.B \-\-dns\-cache\-timeout= +Set duration that cached DNS results remain valid. Note +that nghttpx caches the unsuccessful results as well. +.sp +Default: \fB10s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-dns\-lookup\-timeout= +Set timeout that DNS server is given to respond to the +initial DNS query. For the 2nd and later queries, +server is given time based on this timeout, and it is +scaled linearly. +.sp +Default: \fB5s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-dns\-max\-try= +Set the number of DNS query before nghttpx gives up name +lookup. +.sp +Default: \fB2\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-max\-requests= +The number of requests that single frontend connection +can process. For HTTP/2, this is the number of streams +in one HTTP/2 connection. For HTTP/1, this is the +number of keep alive requests. This is hint to nghttpx, +and it may allow additional few requests. The default +value is unlimited. +.UNINDENT +.SS Debug +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-dump\-request\-header= +Dumps request headers received by HTTP/2 frontend to the +file denoted in . The output is done in HTTP/1 +header field format and each header block is followed by +an empty line. This option is not thread safe and MUST +NOT be used with option \fI\%\-n\fP, where >= 2. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http2\-dump\-response\-header= +Dumps response headers sent from HTTP/2 frontend to the +file denoted in . The output is done in HTTP/1 +header field format and each header block is followed by +an empty line. This option is not thread safe and MUST +NOT be used with option \fI\%\-n\fP, where >= 2. +.UNINDENT +.INDENT 0.0 +.TP +.B \-o, \-\-frontend\-frame\-debug +Print HTTP/2 frames in frontend to stderr. This option +is not thread safe and MUST NOT be used with option +\fI\%\-n\fP=N, where N >= 2. +.UNINDENT +.SS Process +.INDENT 0.0 +.TP +.B \-D, \-\-daemon +Run in a background. If \fI\%\-D\fP is used, the current working +directory is changed to \(aq\fI/\fP\(aq. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-pid\-file= +Set path to save PID of this program. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-user= +Run this program as . This option is intended to +be used to drop root privileges. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-single\-process +Run this program in a single process mode for debugging +purpose. Without this option, nghttpx creates at least +2 processes: main and worker processes. If this option +is used, main and worker are unified into a single +process. nghttpx still spawns additional process if +neverbleed is used. In the single process mode, the +signal handling feature is disabled. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-max\-worker\-processes= +The maximum number of worker processes. nghttpx spawns +new worker process when it reloads its configuration. +The previous worker process enters graceful termination +period and will terminate when it finishes handling the +existing connections. However, if reloading +configurations happen very frequently, the worker +processes might be piled up if they take a bit long time +to finish the existing connections. With this option, +if the number of worker processes exceeds the given +value, the oldest worker process is terminated +immediately. Specifying 0 means no limit and it is the +default behaviour. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-worker\-process\-grace\-shutdown\-period= +Maximum period for a worker process to terminate +gracefully. When a worker process enters in graceful +shutdown period (e.g., when nghttpx reloads its +configuration) and it does not finish handling the +existing connections in the given period of time, it is +immediately terminated. Specifying 0 means no limit and +it is the default behaviour. +.UNINDENT +.SS Scripting +.INDENT 0.0 +.TP +.B \-\-mruby\-file= +Set mruby script file +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-ignore\-per\-pattern\-mruby\-error +Ignore mruby compile error for per\-pattern mruby script +file. If error occurred, it is treated as if no mruby +file were specified for the pattern. +.UNINDENT +.SS HTTP/3 and QUIC +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-idle\-timeout= +Specify an idle timeout for QUIC connection. +.sp +Default: \fB30s\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-debug\-log +Output QUIC debug log to \fI/dev/stderr.\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-quic\-bpf\-program\-file= +Specify a path to eBPF program file reuseport_kern.o to +direct an incoming QUIC UDP datagram to a correct +socket. +.sp +Default: \fB/usr/local/lib/nghttp2/reuseport_kern.o\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-early\-data +Enable early data on frontend QUIC connections. nghttpx +sends \(dqEarly\-Data\(dq header field to a backend server if a +request is received in early data and handshake has not +finished. All backend servers should deal with possibly +replayed requests. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-qlog\-dir= +Specify a directory where a qlog file is written for +frontend QUIC connections. A qlog file is created per +each QUIC connection. The file name is ISO8601 basic +format, followed by \(dq\-\(dq, server Source Connection ID and +\(dq.sqlog\(dq. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-require\-token +Require an address validation token for a frontend QUIC +connection. Server sends a token in Retry packet or +NEW_TOKEN frame in the previous connection. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-congestion\-controller= +Specify a congestion controller algorithm for a frontend +QUIC connection. should be either \(dqcubic\(dq or +\(dqbbr\(dq. +.sp +Default: \fBcubic\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-secret\-file= +Path to file that contains secure random data to be used +as QUIC keying materials. It is used to derive keys for +encrypting tokens and Connection IDs. It is not used to +encrypt QUIC packets. Each line of this file must +contain exactly 136 bytes hex\-encoded string (when +decoded the byte string is 68 bytes long). The first 2 +bits of decoded byte string are used to identify the +keying material. An empty line or a line which starts +\(aq#\(aq is ignored. The file can contain more than one +keying materials. Because the identifier is 2 bits, at +most 4 keying materials are read and the remaining data +is discarded. The first keying material in the file is +primarily used for encryption and decryption for new +connection. The other ones are used to decrypt data for +the existing connections. Specifying multiple keying +materials enables key rotation. Please note that key +rotation does not occur automatically. User should +update files or change options values and restart +nghttpx gracefully. If opening or reading given file +fails, all loaded keying materials are discarded and it +is treated as if none of this option is given. If this +option is not given or an error occurred while opening +or reading a file, a keying material is generated +internally on startup and reload. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-quic\-server\-id= +Specify server ID encoded in Connection ID to identify +this particular server instance. Connection ID is +encrypted and this part is not visible in public. It +must be 4 bytes long and must be encoded in hex string +(which is 8 bytes long). If this option is omitted, a +random server ID is generated on startup and +configuration reload. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-quic\-initial\-rtt= +Specify the initial RTT of the frontend QUIC connection. +.sp +Default: \fB333ms\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-no\-quic\-bpf +Disable eBPF. +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-window\-size= +Sets the per\-stream initial window size of HTTP/3 +frontend connection. +.sp +Default: \fB256K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-connection\-window\-size= +Sets the per\-connection window size of HTTP/3 frontend +connection. +.sp +Default: \fB1M\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-max\-window\-size= +Sets the maximum per\-stream window size of HTTP/3 +frontend connection. The window size is adjusted based +on the receiving rate of stream data. The initial value +is the value specified by \fI\%\-\-frontend\-http3\-window\-size\fP +and the window size grows up to bytes. +.sp +Default: \fB6M\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-max\-connection\-window\-size= +Sets the maximum per\-connection window size of HTTP/3 +frontend connection. The window size is adjusted based +on the receiving rate of stream data. The initial value +is the value specified by +\fI\%\-\-frontend\-http3\-connection\-window\-size\fP and the window +size grows up to bytes. +.sp +Default: \fB8M\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-frontend\-http3\-max\-concurrent\-streams= +Set the maximum number of the concurrent streams in one +frontend HTTP/3 connection. +.sp +Default: \fB100\fP +.UNINDENT +.SS Misc +.INDENT 0.0 +.TP +.B \-\-conf= +Load configuration from . Please note that +nghttpx always tries to read the default configuration +file if \fI\%\-\-conf\fP is not given. +.sp +Default: \fB/etc/nghttpx/nghttpx.conf\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-include= +Load additional configurations from . File +is read when configuration parser encountered this +option. This option can be used multiple times, or even +recursively. +.UNINDENT +.INDENT 0.0 +.TP +.B \-v, \-\-version +Print version and exit. +.UNINDENT +.INDENT 0.0 +.TP +.B \-h, \-\-help +Print this help and exit. +.UNINDENT +.sp +The argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). +.sp +The argument is an integer and an optional unit (e.g., 1s +is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms +(hours, minutes, seconds and milliseconds, respectively). If a unit +is omitted, a second is used as unit. +.SH FILES +.INDENT 0.0 +.TP +.B \fI/etc/nghttpx/nghttpx.conf\fP +The default configuration file path nghttpx searches at startup. +The configuration file path can be changed using \fI\%\-\-conf\fP +option. +.sp +Those lines which are staring \fB#\fP are treated as comment. +.sp +The option name in the configuration file is the long command\-line +option name with leading \fB\-\-\fP stripped (e.g., \fBfrontend\fP). Put +\fB=\fP between option name and value. Don\(aqt put extra leading or +trailing spaces. +.sp +When specifying arguments including characters which have special +meaning to a shell, we usually use quotes so that shell does not +interpret them. When writing this configuration file, quotes for +this purpose must not be used. For example, specify additional +request header field, do this: +.INDENT 7.0 +.INDENT 3.5 +.sp +.nf +.ft C +add\-request\-header=foo: bar +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +instead of: +.INDENT 7.0 +.INDENT 3.5 +.sp +.nf +.ft C +add\-request\-header=\(dqfoo: bar\(dq +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +The options which do not take argument in the command\-line \fItake\fP +argument in the configuration file. Specify \fByes\fP as an argument +(e.g., \fBhttp2\-proxy=yes\fP). If other string is given, it is +ignored. +.sp +To specify private key and certificate file which are given as +positional arguments in command\-line, use \fBprivate\-key\-file\fP and +\fBcertificate\-file\fP\&. +.sp +\fI\%\-\-conf\fP option cannot be used in the configuration file and +will be ignored if specified. +.TP +.B Error log +Error log is written to stderr by default. It can be configured +using \fI\%\-\-errorlog\-file\fP\&. The format of log message is as +follows: +.sp + (:) +.INDENT 7.0 +.TP +.B +It is a combination of date and time when the log is written. It +is in ISO 8601 format. +.TP +.B +It is a main process ID. +.TP +.B +It is a process ID which writes this log. +.TP +.B +It is a thread ID which writes this log. It would be unique +within . +.TP +.B and +They are source file name, and line number which produce this log. +.TP +.B +It is a log message body. +.UNINDENT +.UNINDENT +.SH SIGNALS +.INDENT 0.0 +.TP +.B SIGQUIT +Shutdown gracefully. First accept pending connections and stop +accepting connection. After all connections are handled, nghttpx +exits. +.TP +.B SIGHUP +Reload configuration file given in \fI\%\-\-conf\fP\&. +.TP +.B SIGUSR1 +Reopen log files. +.UNINDENT +.sp +SIGUSR2 +.INDENT 0.0 +.INDENT 3.5 +Fork and execute nghttpx. It will execute the binary in the same +path with same command\-line arguments and environment variables. As +of nghttpx version 1.20.0, the new main process sends SIGQUIT to the +original main process when it is ready to serve requests. For the +earlier versions of nghttpx, user has to send SIGQUIT to the +original main process. +.sp +The difference between SIGUSR2 (+ SIGQUIT) and SIGHUP is that former +is usually used to execute new binary, and the main process is newly +spawned. On the other hand, the latter just reloads configuration +file, and the same main process continues to exist. +.UNINDENT +.UNINDENT +.sp +\fBNOTE:\fP +.INDENT 0.0 +.INDENT 3.5 +nghttpx consists of multiple processes: one process for processing +these signals, and another one for processing requests. The former +spawns the latter. The former is called main process, and the +latter is called worker process. If neverbleed is enabled, the +worker process spawns neverbleed daemon process which does RSA key +processing. The above signal must be sent to the main process. If +the other processes received one of them, it is ignored. This +behaviour of these processes may change in the future release. In +other words, in the future release, the processes other than main +process may terminate upon the reception of these signals. +Therefore these signals should not be sent to the processes other +than main process. +.UNINDENT +.UNINDENT +.SH SERVER PUSH +.sp +nghttpx supports HTTP/2 server push in default mode with Link header +field. nghttpx looks for Link header field (\fI\%RFC 5988\fP) in response headers from +backend server and extracts URI\-reference with parameter +\fBrel=preload\fP (see \fI\%preload\fP) +and pushes those URIs to the frontend client. Here is a sample Link +header field to initiate server push: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +Link: ; rel=preload +Link: ; rel=preload +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +Currently, the following restriction is applied for server push: +.INDENT 0.0 +.IP 1. 3 +The associated stream must have method \(dqGET\(dq or \(dqPOST\(dq. The +associated stream\(aqs status code must be 200. +.UNINDENT +.sp +This limitation may be loosened in the future release. +.sp +nghttpx also supports server push if both frontend and backend are +HTTP/2 in default mode. In this case, in addition to server push via +Link header field, server push from backend is forwarded to frontend +HTTP/2 session. +.sp +HTTP/2 server push will be disabled if \fI\%\-\-http2\-proxy\fP is +used. +.SH UNIX DOMAIN SOCKET +.sp +nghttpx supports UNIX domain socket with a filename for both frontend +and backend connections. +.sp +Please note that current nghttpx implementation does not delete a +socket with a filename. And on start up, if nghttpx detects that the +specified socket already exists in the file system, nghttpx first +deletes it. However, if SIGUSR2 is used to execute new binary and +both old and new configurations use same filename, new binary does not +delete the socket and continues to use it. +.SH OCSP STAPLING +.sp +OCSP query is done using external Python script +\fBfetch\-ocsp\-response\fP, which has been originally developed in Perl +as part of h2o project (\fI\%https://github.com/h2o/h2o\fP), and was +translated into Python. +.sp +The script file is usually installed under +\fB$(prefix)/share/nghttp2/\fP directory. The actual path to script can +be customized using \fI\%\-\-fetch\-ocsp\-response\-file\fP option. +.sp +If OCSP query is failed, previous OCSP response, if any, is continued +to be used. +.sp +\fI\%\-\-fetch\-ocsp\-response\-file\fP option provides wide range of +possibility to manage OCSP response. It can take an arbitrary script +or executable. The requirement is that it supports the command\-line +interface of \fBfetch\-ocsp\-response\fP script, and it must return a +valid DER encoded OCSP response on success. It must return exit code +0 on success, and 75 for temporary error, and the other error code for +generic failure. For large cluster of servers, it is not efficient +for each server to perform OCSP query using \fBfetch\-ocsp\-response\fP\&. +Instead, you can retrieve OCSP response in some way, and store it in a +disk or a shared database. Then specify a program in +\fI\%\-\-fetch\-ocsp\-response\-file\fP to fetch it from those stores. +This could provide a way to share the OCSP response between fleet of +servers, and also any OCSP query strategy can be applied which may be +beyond the ability of nghttpx itself or \fBfetch\-ocsp\-response\fP +script. +.SH TLS SESSION RESUMPTION +.sp +nghttpx supports TLS session resumption through both session ID and +session ticket. +.SS SESSION ID RESUMPTION +.sp +By default, session ID is shared by all worker threads. +.sp +If \fI\%\-\-tls\-session\-cache\-memcached\fP is given, nghttpx will +insert serialized session data to memcached with +\fBnghttpx:tls\-session\-cache:\fP + lowercase hex string of session ID +as a memcached entry key, with expiry time 12 hours. Session timeout +is set to 12 hours. +.sp +By default, connections to memcached server are not encrypted. To +enable encryption, use \fBtls\fP keyword in +\fI\%\-\-tls\-session\-cache\-memcached\fP option. +.SS TLS SESSION TICKET RESUMPTION +.sp +By default, session ticket is shared by all worker threads. The +automatic key rotation is also enabled by default. Every an hour, new +encryption key is generated, and previous encryption key becomes +decryption only key. We set session timeout to 12 hours, and thus we +keep at most 12 keys. +.sp +If \fI\%\-\-tls\-ticket\-key\-memcached\fP is given, encryption keys are +retrieved from memcached. nghttpx just reads keys from memcached; one +has to deploy key generator program to update keys frequently (e.g., +every 1 hour). The example key generator tlsticketupdate.go is +available under contrib directory in nghttp2 archive. The memcached +entry key is \fBnghttpx:tls\-ticket\-key\fP\&. The data format stored in +memcached is the binary format described below: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C ++\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ +| VERSION (4) |LEN (2)|KEY(48 or 80) ... ++\-\-\-\-\-\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ + ^ | + | | + +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ + (LEN, KEY) pair can be repeated +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +All numbers in the above figure is bytes. All integer fields are +network byte order. +.sp +First 4 bytes integer VERSION field, which must be 1. The 2 bytes +integer LEN field gives the length of following KEY field, which +contains key. If \fI\%\-\-tls\-ticket\-key\-cipher\fP=aes\-128\-cbc is +used, LEN must be 48. If +\fI\%\-\-tls\-ticket\-key\-cipher\fP=aes\-256\-cbc is used, LEN must be +80. LEN and KEY pair can be repeated multiple times to store multiple +keys. The key appeared first is used as encryption key. All the +remaining keys are used as decryption only. +.sp +By default, connections to memcached server are not encrypted. To +enable encryption, use \fBtls\fP keyword in +\fI\%\-\-tls\-ticket\-key\-memcached\fP option. +.sp +If \fI\%\-\-tls\-ticket\-key\-file\fP is given, encryption key is read +from the given file. In this case, nghttpx does not rotate key +automatically. To rotate key, one has to restart nghttpx (see +SIGNALS). +.SH CERTIFICATE TRANSPARENCY +.sp +nghttpx supports TLS \fBsigned_certificate_timestamp\fP extension (\fI\%RFC +6962\fP). The relevant options +are \fI\%\-\-tls\-sct\-dir\fP and \fBsct\-dir\fP parameter in +\fI\%\-\-subcert\fP\&. They takes a directory, and nghttpx reads all +files whose extension is \fB\&.sct\fP under the directory. The \fB*.sct\fP +files are encoded as \fBSignedCertificateTimestamp\fP struct described +in \fI\%section 3.2 of RFC 69662\fP\&. This format is +the same one used by \fI\%nginx\-ct\fP and \fI\%mod_ssl_ct\fP\&. +\fI\%ct\-submit\fP can be +used to submit certificates to log servers, and obtain the +\fBSignedCertificateTimestamp\fP struct which can be used with nghttpx. +.SH MRUBY SCRIPTING +.sp +\fBWARNING:\fP +.INDENT 0.0 +.INDENT 3.5 +The current mruby extension API is experimental and not frozen. The +API is subject to change in the future release. +.UNINDENT +.UNINDENT +.sp +\fBWARNING:\fP +.INDENT 0.0 +.INDENT 3.5 +Almost all string value returned from method, or attribute is a +fresh new mruby string, which involves memory allocation, and +copies. Therefore, it is strongly recommended to store a return +value in a local variable, and use it, instead of calling method or +accessing attribute repeatedly. +.UNINDENT +.UNINDENT +.sp +nghttpx allows users to extend its capability using mruby scripts. +nghttpx has 2 hook points to execute mruby script: request phase and +response phase. The request phase hook is invoked after all request +header fields are received from client. The response phase hook is +invoked after all response header fields are received from backend +server. These hooks allows users to modify header fields, or common +HTTP variables, like authority or request path, and even return custom +response without forwarding request to backend servers. +.sp +There are 2 levels of mruby script invocations: global and +per\-pattern. The global mruby script is set by \fI\%\-\-mruby\-file\fP +option and is called for all requests. The per\-pattern mruby script +is set by \(dqmruby\(dq parameter in \fI\%\-b\fP option. It is invoked for +a request which matches the particular pattern. The order of hook +invocation is: global request phase hook, per\-pattern request phase +hook, per\-pattern response phase hook, and finally global response +phase hook. If a hook returns a response, any later hooks are not +invoked. The global request hook is invoked before the pattern +matching is made and changing request path may affect the pattern +matching. +.sp +Please note that request and response hooks of per\-pattern mruby +script for a single request might not come from the same script. This +might happen after a request hook is executed, backend failed for some +reason, and at the same time, backend configuration is replaced by API +request, and then the request uses new configuration on retry. The +response hook from new configuration, if it is specified, will be +invoked. +.sp +The all mruby script will be evaluated once per thread on startup, and +it must instantiate object and evaluate it as the return value (e.g., +\fBApp.new\fP). This object is called app object. If app object +defines \fBon_req\fP method, it is called with \fI\%Nghttpx::Env\fP +object on request hook. Similarly, if app object defines \fBon_resp\fP +method, it is called with \fI\%Nghttpx::Env\fP object on response +hook. For each method invocation, user can can access +\fI\%Nghttpx::Request\fP and \fI\%Nghttpx::Response\fP objects +via \fI\%Nghttpx::Env#req\fP and \fI\%Nghttpx::Env#resp\fP +respectively. +.INDENT 0.0 +.TP +.B Nghttpx::REQUEST_PHASE +Constant to represent request phase. +.UNINDENT +.INDENT 0.0 +.TP +.B Nghttpx::RESPONSE_PHASE +Constant to represent response phase. +.UNINDENT +.INDENT 0.0 +.TP +.B class Nghttpx::Env +Object to represent current request specific context. +.INDENT 7.0 +.TP +.B attribute [R] req +Return \fI\%Request\fP object. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] resp +Return \fI\%Response\fP object. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] ctx +Return Ruby hash object. It persists until request finishes. +So values set in request phase hook can be retrieved in +response phase hook. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] phase +Return the current phase. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] remote_addr +Return IP address of a remote client. If connection is made +via UNIX domain socket, this returns the string \(dqlocalhost\(dq. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] server_addr +Return address of server that accepted the connection. This +is a string which specified in \fI\%\-\-frontend\fP option, +excluding port number, and not a resolved IP address. For +UNIX domain socket, this is a path to UNIX domain socket. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] server_port +Return port number of the server frontend which accepted the +connection from client. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_used +Return true if TLS is used on the connection. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_sni +Return the TLS SNI value which client sent in this connection. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_client_fingerprint_sha256 +Return the SHA\-256 fingerprint of a client certificate. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_client_fingerprint_sha1 +Return the SHA\-1 fingerprint of a client certificate. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_client_issuer_name +Return the issuer name of a client certificate. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_client_subject_name +Return the subject name of a client certificate. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_client_serial +Return the serial number of a client certificate. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_client_not_before +Return the start date of a client certificate in seconds since +the epoch. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_client_not_after +Return the end date of a client certificate in seconds since +the epoch. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_cipher +Return a TLS cipher negotiated in this connection. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_protocol +Return a TLS protocol version negotiated in this connection. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_session_id +Return a session ID for this connection in hex string. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_session_reused +Return true if, and only if a SSL/TLS session is reused. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] alpn +Return ALPN identifier negotiated in this connection. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] tls_handshake_finished +Return true if SSL/TLS handshake has finished. If it returns +false in the request phase hook, the request is received in +TLSv1.3 early data (0\-RTT) and might be vulnerable to the +replay attack. nghttpx will send Early\-Data header field to +backend servers to indicate this. +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B class Nghttpx::Request +Object to represent request from client. The modification to +Request object is allowed only in request phase hook. +.INDENT 7.0 +.TP +.B attribute [R] http_version_major +Return HTTP major version. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] http_version_minor +Return HTTP minor version. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R/W] method +HTTP method. On assignment, copy of given value is assigned. +We don\(aqt accept arbitrary method name. We will document them +later, but well known methods, like GET, PUT and POST, are all +supported. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R/W] authority +Authority (i.e., example.org), including optional port +component . On assignment, copy of given value is assigned. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R/W] scheme +Scheme (i.e., http, https). On assignment, copy of given +value is assigned. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R/W] path +Request path, including query component (i.e., /index.html). +On assignment, copy of given value is assigned. The path does +not include authority component of URI. This may include +query component. nghttpx makes certain normalization for +path. It decodes percent\-encoding for unreserved characters +(see \fI\%https://tools.ietf.org/html/rfc3986#section\-2.3\fP), and +resolves \(dq..\(dq and \(dq.\(dq. But it may leave characters which +should be percent\-encoded as is. So be careful when comparing +path against desired string. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] headers +Return Ruby hash containing copy of request header fields. +Changing values in returned hash does not change request +header fields actually used in request processing. Use +\fI\%Nghttpx::Request#add_header\fP or +\fI\%Nghttpx::Request#set_header\fP to change request +header fields. +.UNINDENT +.INDENT 7.0 +.TP +.B add_header(key, value) +Add header entry associated with key. The value can be single +string or array of string. It does not replace any existing +values associated with key. +.UNINDENT +.INDENT 7.0 +.TP +.B set_header(key, value) +Set header entry associated with key. The value can be single +string or array of string. It replaces any existing values +associated with key. +.UNINDENT +.INDENT 7.0 +.TP +.B clear_headers() +Clear all existing request header fields. +.UNINDENT +.INDENT 7.0 +.TP +.B push(uri) +Initiate to push resource identified by \fIuri\fP\&. Only HTTP/2 +protocol supports this feature. For the other protocols, this +method is noop. \fIuri\fP can be absolute URI, absolute path or +relative path to the current request. For absolute or +relative path, scheme and authority are inherited from the +current request. Currently, method is always GET. nghttpx +will issue request to backend servers to fulfill this request. +The request and response phase hooks will be called for pushed +resource as well. +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B class Nghttpx::Response +Object to represent response from backend server. +.INDENT 7.0 +.TP +.B attribute [R] http_version_major +Return HTTP major version. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] http_version_minor +Return HTTP minor version. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R/W] status +HTTP status code. It must be in the range [200, 999], +inclusive. The non\-final status code is not supported in +mruby scripting at the moment. +.UNINDENT +.INDENT 7.0 +.TP +.B attribute [R] headers +Return Ruby hash containing copy of response header fields. +Changing values in returned hash does not change response +header fields actually used in response processing. Use +\fI\%Nghttpx::Response#add_header\fP or +\fI\%Nghttpx::Response#set_header\fP to change response +header fields. +.UNINDENT +.INDENT 7.0 +.TP +.B add_header(key, value) +Add header entry associated with key. The value can be single +string or array of string. It does not replace any existing +values associated with key. +.UNINDENT +.INDENT 7.0 +.TP +.B set_header(key, value) +Set header entry associated with key. The value can be single +string or array of string. It replaces any existing values +associated with key. +.UNINDENT +.INDENT 7.0 +.TP +.B clear_headers() +Clear all existing response header fields. +.UNINDENT +.INDENT 7.0 +.TP +.B return(body) +Return custom response \fIbody\fP to a client. When this method +is called in request phase hook, the request is not forwarded +to the backend, and response phase hook for this request will +not be invoked. When this method is called in response phase +hook, response from backend server is canceled and discarded. +The status code and response header fields should be set +before using this method. To set status code, use +\fI\%Nghttpx::Response#status\fP\&. If status code is not +set, 200 is used. To set response header fields, +\fI\%Nghttpx::Response#add_header\fP and +\fI\%Nghttpx::Response#set_header\fP\&. When this method is +invoked in response phase hook, the response headers are +filled with the ones received from backend server. To send +completely custom header fields, first call +\fI\%Nghttpx::Response#clear_headers\fP to erase all +existing header fields, and then add required header fields. +It is an error to call this method twice for a given request. +.UNINDENT +.INDENT 7.0 +.TP +.B send_info(status, headers) +Send non\-final (informational) response to a client. \fIstatus\fP +must be in the range [100, 199], inclusive. \fIheaders\fP is a +hash containing response header fields. Its key must be a +string, and the associated value must be either string or +array of strings. Since this is not a final response, even if +this method is invoked, request is still forwarded to a +backend unless \fI\%Nghttpx::Response#return\fP is called. +This method can be called multiple times. It cannot be called +after \fI\%Nghttpx::Response#return\fP is called. +.UNINDENT +.UNINDENT +.SS MRUBY EXAMPLES +.sp +Modify request path: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +class App + def on_req(env) + env.req.path = \(dq/apps#{env.req.path}\(dq + end +end + +App.new +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +Don\(aqt forget to instantiate and evaluate object at the last line. +.sp +Restrict permission of viewing a content to a specific client +addresses: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +class App + def on_req(env) + allowed_clients = [\(dq127.0.0.1\(dq, \(dq::1\(dq] + + if env.req.path.start_with?(\(dq/log/\(dq) && + !allowed_clients.include?(env.remote_addr) then + env.resp.status = 404 + env.resp.return \(dqpermission denied\(dq + end + end +end + +App.new +.ft P +.fi +.UNINDENT +.UNINDENT +.SH API ENDPOINTS +.sp +nghttpx exposes API endpoints to manipulate it via HTTP based API. By +default, API endpoint is disabled. To enable it, add a dedicated +frontend for API using \fI\%\-\-frontend\fP option with \(dqapi\(dq +parameter. All requests which come from this frontend address, will +be treated as API request. +.sp +The response is normally JSON dictionary, and at least includes the +following keys: +.INDENT 0.0 +.TP +.B status +The status of the request processing. The following values are +defined: +.INDENT 7.0 +.TP +.B Success +The request was successful. +.TP +.B Failure +The request was failed. No change has been made. +.UNINDENT +.TP +.B code +HTTP status code +.UNINDENT +.sp +Additionally, depending on the API endpoint, \fBdata\fP key may be +present, and its value contains the API endpoint specific data. +.sp +We wrote \(dqnormally\(dq, since nghttpx may return ordinal HTML response in +some cases where the error has occurred before reaching API endpoint +(e.g., header field is too large). +.sp +The following section describes available API endpoints. +.SS POST /api/v1beta1/backendconfig +.sp +This API replaces the current backend server settings with the +requested ones. The request method should be POST, but PUT is also +acceptable. The request body must be nghttpx configuration file +format. For configuration file format, see \fI\%FILES\fP section. The +line separator inside the request body must be single LF (0x0A). +Currently, only \fI\%backend\fP option is parsed, the +others are simply ignored. The semantics of this API is replace the +current backend with the backend options in request body. Describe +the desired set of backend severs, and nghttpx makes it happen. If +there is no \fI\%backend\fP option is found in request +body, the current set of backend is replaced with the \fI\%backend\fP option\(aqs default value, which is \fB127.0.0.1,80\fP\&. +.sp +The replacement is done instantly without breaking existing +connections or requests. It also avoids any process creation as is +the case with hot swapping with signals. +.sp +The one limitation is that only numeric IP address is allowed in +\fI\%backend\fP in request body unless \(dqdns\(dq parameter +is used while non numeric hostname is allowed in command\-line or +configuration file is read using \fI\%\-\-conf\fP\&. +.SS GET /api/v1beta1/configrevision +.sp +This API returns configuration revision of the current nghttpx. The +configuration revision is opaque string, and it changes after each +reloading by SIGHUP. With this API, an external application knows +that whether nghttpx has finished reloading its configuration by +comparing the configuration revisions between before and after +reloading. It is recommended to disable persistent (keep\-alive) +connection for this purpose in order to avoid to send a request using +the reused connection which may bound to an old process. +.sp +This API returns response including \fBdata\fP key. Its value is JSON +object, and it contains at least the following key: +.INDENT 0.0 +.TP +.B configRevision +The configuration revision of the current nghttpx +.UNINDENT +.SH SEE ALSO +.sp +\fBnghttp(1)\fP, \fBnghttpd(1)\fP, \fBh2load(1)\fP +.SH AUTHOR +Tatsuhiro Tsujikawa +.SH COPYRIGHT +2012, 2015, 2016, Tatsuhiro Tsujikawa +.\" Generated by docutils manpage writer. +. diff --git a/lib/nghttp2/doc/nghttpx.1.rst b/lib/nghttp2/doc/nghttpx.1.rst new file mode 100644 index 00000000000..b70b163233e --- /dev/null +++ b/lib/nghttp2/doc/nghttpx.1.rst @@ -0,0 +1,2527 @@ + +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: nghttpx + +nghttpx(1) +========== + +SYNOPSIS +-------- + +**nghttpx** [OPTIONS]... [ ] + +DESCRIPTION +----------- + +A reverse proxy for HTTP/3, HTTP/2, and HTTP/1. + +.. describe:: + + + Set path to server's private key. Required unless + "no-tls" parameter is used in :option:`--frontend` option. + +.. describe:: + + Set path to server's certificate. Required unless + "no-tls" parameter is used in :option:`--frontend` option. To + make OCSP stapling work, this must be an absolute path. + + +OPTIONS +------- + +The options are categorized into several groups. + +Connections +~~~~~~~~~~~ + +.. option:: -b, --backend=(,|unix:)[;[[:...]][[;]...] + + + Set backend host and port. The multiple backend + addresses are accepted by repeating this option. UNIX + domain socket can be specified by prefixing path name + with "unix:" (e.g., unix:/var/run/backend.sock). + + Optionally, if s are given, the backend address + is only used if request matches the pattern. The + pattern matching is closely designed to ServeMux in + net/http package of Go programming language. + consists of path, host + path or just host. The path + must start with "*/*". If it ends with "*/*", it matches + all request path in its subtree. To deal with the + request to the directory without trailing slash, the + path which ends with "*/*" also matches the request path + which only lacks trailing '*/*' (e.g., path "*/foo/*" + matches request path "*/foo*"). If it does not end with + "*/*", it performs exact match against the request path. + If host is given, it performs a match against the + request host. For a request received on the frontend + listener with "sni-fwd" parameter enabled, SNI host is + used instead of a request host. If host alone is given, + "*/*" is appended to it, so that it matches all request + paths under the host (e.g., specifying "nghttp2.org" + equals to "nghttp2.org/"). CONNECT method is treated + specially. It does not have path, and we don't allow + empty path. To workaround this, we assume that CONNECT + method has "*/*" as path. + + Patterns with host take precedence over patterns with + just path. Then, longer patterns take precedence over + shorter ones. + + Host can include "\*" in the left most position to + indicate wildcard match (only suffix match is done). + The "\*" must match at least one character. For example, + host pattern "\*.nghttp2.org" matches against + "www.nghttp2.org" and "git.ngttp2.org", but does not + match against "nghttp2.org". The exact hosts match + takes precedence over the wildcard hosts match. + + If path part ends with "\*", it is treated as wildcard + path. The wildcard path behaves differently from the + normal path. For normal path, match is made around the + boundary of path component separator,"*/*". On the other + hand, the wildcard path does not take into account the + path component separator. All paths which include the + wildcard path without last "\*" as prefix, and are + strictly longer than wildcard path without last "\*" are + matched. "\*" must match at least one character. For + example, the pattern "*/foo\**" matches "*/foo/*" and + "*/foobar*". But it does not match "*/foo*", or "*/fo*". + + If is omitted or empty string, "*/*" is used as + pattern, which matches all request paths (catch-all + pattern). The catch-all backend must be given. + + When doing a match, nghttpx made some normalization to + pattern, request host and path. For host part, they are + converted to lower case. For path part, percent-encoded + unreserved characters defined in RFC 3986 are decoded, + and any dot-segments (".." and ".") are resolved and + removed. + + For example, :option:`-b`\'127.0.0.1,8080;nghttp2.org/httpbin/' + matches the request host "nghttp2.org" and the request + path "*/httpbin/get*", but does not match the request host + "nghttp2.org" and the request path "*/index.html*". + + The multiple s can be specified, delimiting + them by ":". Specifying + :option:`-b`\'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the + same effect to specify :option:`-b`\'127.0.0.1,8080;nghttp2.org' + and :option:`-b`\'127.0.0.1,8080;www.nghttp2.org'. + + The backend addresses sharing same are grouped + together forming load balancing group. + + Several parameters are accepted after . + The parameters are delimited by ";". The available + parameters are: "proto=", "tls", + "sni=", "fall=", "rise=", + "affinity=", "dns", "redirect-if-not-tls", + "upgrade-scheme", "mruby=", + "read-timeout=", "write-timeout=", + "group=", "group-weight=", "weight=", and + "dnf". The parameter consists of keyword, and + optionally followed by "=" and value. For example, the + parameter "proto=h2" consists of the keyword "proto" and + value "h2". The parameter "tls" consists of the keyword + "tls" without value. Each parameter is described as + follows. + + The backend application protocol can be specified using + optional "proto" parameter, and in the form of + "proto=". should be one of the following + list without quotes: "h2", "http/1.1". The default + value of is "http/1.1". Note that usually "h2" + refers to HTTP/2 over TLS. But in this option, it may + mean HTTP/2 over cleartext TCP unless "tls" keyword is + used (see below). + + TLS can be enabled by specifying optional "tls" + parameter. TLS is not enabled by default. + + With "sni=" parameter, it can override the TLS + SNI field value with given . This will + default to the backend name + + The feature to detect whether backend is online or + offline can be enabled using optional "fall" and "rise" + parameters. Using "fall=" parameter, if nghttpx + cannot connect to a this backend times in a row, + this backend is assumed to be offline, and it is + excluded from load balancing. If is 0, this backend + never be excluded from load balancing whatever times + nghttpx cannot connect to it, and this is the default. + There is also "rise=" parameter. After backend was + excluded from load balancing group, nghttpx periodically + attempts to make a connection to the failed backend, and + if the connection is made successfully times in a + row, the backend is assumed to be online, and it is now + eligible for load balancing target. If is 0, a + backend is permanently offline, once it goes in that + state, and this is the default behaviour. + + The session affinity is enabled using + "affinity=" parameter. If "ip" is given in + , client IP based session affinity is enabled. + If "cookie" is given in , cookie based session + affinity is enabled. If "none" is given in , + session affinity is disabled, and this is the default. + The session affinity is enabled per . If at + least one backend has "affinity" parameter, and its + is not "none", session affinity is enabled for + all backend servers sharing the same . It is + advised to set "affinity" parameter to all backend + explicitly if session affinity is desired. The session + affinity may break if one of the backend gets + unreachable, or backend settings are reloaded or + replaced by API. + + If "affinity=cookie" is used, the additional + configuration is required. + "affinity-cookie-name=" must be used to specify a + name of cookie to use. Optionally, + "affinity-cookie-path=" can be used to specify a + path which cookie is applied. The optional + "affinity-cookie-secure=" controls the Secure + attribute of a cookie. The default value is "auto", and + the Secure attribute is determined by a request scheme. + If a request scheme is "https", then Secure attribute is + set. Otherwise, it is not set. If is "yes", + the Secure attribute is always set. If is + "no", the Secure attribute is always omitted. + "affinity-cookie-stickiness=" controls + stickiness of this affinity. If is + "loose", removing or adding a backend server might break + the affinity and the request might be forwarded to a + different backend server. If is "strict", + removing the designated backend server breaks affinity, + but adding new backend server does not cause breakage. + If the designated backend server becomes unavailable, + new backend server is chosen as if the request does not + have an affinity cookie. defaults to + "loose". + + By default, name resolution of backend host name is done + at start up, or reloading configuration. If "dns" + parameter is given, name resolution takes place + dynamically. This is useful if backend address changes + frequently. If "dns" is given, name resolution of + backend host name at start up, or reloading + configuration is skipped. + + If "redirect-if-not-tls" parameter is used, the matched + backend requires that frontend connection is TLS + encrypted. If it isn't, nghttpx responds to the request + with 308 status code, and https URI the client should + use instead is included in Location header field. The + port number in redirect URI is 443 by default, and can + be changed using :option:`--redirect-https-port` option. If at + least one backend has "redirect-if-not-tls" parameter, + this feature is enabled for all backend servers sharing + the same . It is advised to set + "redirect-if-no-tls" parameter to all backends + explicitly if this feature is desired. + + If "upgrade-scheme" parameter is used along with "tls" + parameter, HTTP/2 :scheme pseudo header field is changed + to "https" from "http" when forwarding a request to this + particular backend. This is a workaround for a backend + server which requires "https" :scheme pseudo header + field on TLS encrypted connection. + + "mruby=" parameter specifies a path to mruby + script file which is invoked when this pattern is + matched. All backends which share the same pattern must + have the same mruby path. + + "read-timeout=" and "write-timeout=" + parameters specify the read and write timeout of the + backend connection when this pattern is matched. All + backends which share the same pattern must have the same + timeouts. If these timeouts are entirely omitted for a + pattern, :option:`--backend-read-timeout` and + :option:`--backend-write-timeout` are used. + + "group=" parameter specifies the name of group + this backend address belongs to. By default, it belongs + to the unnamed default group. The name of group is + unique per pattern. "group-weight=" parameter + specifies the weight of the group. The higher weight + gets more frequently selected by the load balancing + algorithm. must be [1, 256] inclusive. The weight + 8 has 4 times more weight than 2. must be the same + for all addresses which share the same . If + "group-weight" is omitted in an address, but the other + address which belongs to the same group specifies + "group-weight", its weight is used. If no + "group-weight" is specified for all addresses, the + weight of a group becomes 1. "group" and "group-weight" + are ignored if session affinity is enabled. + + "weight=" parameter specifies the weight of the + backend address inside a group which this address + belongs to. The higher weight gets more frequently + selected by the load balancing algorithm. must be + [1, 256] inclusive. The weight 8 has 4 times more + weight than weight 2. If this parameter is omitted, + weight becomes 1. "weight" is ignored if session + affinity is enabled. + + If "dnf" parameter is specified, an incoming request is + not forwarded to a backend and just consumed along with + the request body (actually a backend server never be + contacted). It is expected that the HTTP response is + generated by mruby script (see "mruby=" parameter + above). "dnf" is an abbreviation of "do not forward". + + Since ";" and ":" are used as delimiter, must + not contain these characters. In order to include ":" + in , one has to specify "%3A" (which is + percent-encoded from of ":") instead. Since ";" has + special meaning in shell, the option value must be + quoted. + + + Default: ``127.0.0.1,80`` + +.. option:: -f, --frontend=(,|unix:)[[;]...] + + Set frontend host and port. If is '\*', it + assumes all addresses including both IPv4 and IPv6. + UNIX domain socket can be specified by prefixing path + name with "unix:" (e.g., unix:/var/run/nghttpx.sock). + This option can be used multiple times to listen to + multiple addresses. + + This option can take 0 or more parameters, which are + described below. Note that "api" and "healthmon" + parameters are mutually exclusive. + + Optionally, TLS can be disabled by specifying "no-tls" + parameter. TLS is enabled by default. + + If "sni-fwd" parameter is used, when performing a match + to select a backend server, SNI host name received from + the client is used instead of the request host. See + :option:`--backend` option about the pattern match. + + To make this frontend as API endpoint, specify "api" + parameter. This is disabled by default. It is + important to limit the access to the API frontend. + Otherwise, someone may change the backend server, and + break your services, or expose confidential information + to the outside the world. + + To make this frontend as health monitor endpoint, + specify "healthmon" parameter. This is disabled by + default. Any requests which come through this address + are replied with 200 HTTP status, without no body. + + To accept PROXY protocol version 1 and 2 on frontend + connection, specify "proxyproto" parameter. This is + disabled by default. + + To receive HTTP/3 (QUIC) traffic, specify "quic" + parameter. It makes nghttpx listen on UDP port rather + than TCP port. UNIX domain socket, "api", and + "healthmon" parameters cannot be used with "quic" + parameter. + + + Default: ``*,3000`` + +.. option:: --backlog= + + Set listen backlog size. + + Default: ``65536`` + +.. option:: --backend-address-family=(auto|IPv4|IPv6) + + Specify address family of backend connections. If + "auto" is given, both IPv4 and IPv6 are considered. If + "IPv4" is given, only IPv4 address is considered. If + "IPv6" is given, only IPv6 address is considered. + + Default: ``auto`` + +.. option:: --backend-http-proxy-uri= + + Specify proxy URI in the form + http://[:@]:. If a proxy + requires authentication, specify and . + Note that they must be properly percent-encoded. This + proxy is used when the backend connection is HTTP/2. + First, make a CONNECT request to the proxy and it + connects to the backend on behalf of nghttpx. This + forms tunnel. After that, nghttpx performs SSL/TLS + handshake with the downstream through the tunnel. The + timeouts when connecting and making CONNECT request can + be specified by :option:`--backend-read-timeout` and + :option:`--backend-write-timeout` options. + + +Performance +~~~~~~~~~~~ + +.. option:: -n, --workers= + + Set the number of worker threads. + + Default: ``1`` + +.. option:: --single-thread + + Run everything in one thread inside the worker process. + This feature is provided for better debugging + experience, or for the platforms which lack thread + support. If threading is disabled, this option is + always enabled. + +.. option:: --read-rate= + + Set maximum average read rate on frontend connection. + Setting 0 to this option means read rate is unlimited. + + Default: ``0`` + +.. option:: --read-burst= + + Set maximum read burst size on frontend connection. + Setting 0 to this option means read burst size is + unlimited. + + Default: ``0`` + +.. option:: --write-rate= + + Set maximum average write rate on frontend connection. + Setting 0 to this option means write rate is unlimited. + + Default: ``0`` + +.. option:: --write-burst= + + Set maximum write burst size on frontend connection. + Setting 0 to this option means write burst size is + unlimited. + + Default: ``0`` + +.. option:: --worker-read-rate= + + Set maximum average read rate on frontend connection per + worker. Setting 0 to this option means read rate is + unlimited. Not implemented yet. + + Default: ``0`` + +.. option:: --worker-read-burst= + + Set maximum read burst size on frontend connection per + worker. Setting 0 to this option means read burst size + is unlimited. Not implemented yet. + + Default: ``0`` + +.. option:: --worker-write-rate= + + Set maximum average write rate on frontend connection + per worker. Setting 0 to this option means write rate + is unlimited. Not implemented yet. + + Default: ``0`` + +.. option:: --worker-write-burst= + + Set maximum write burst size on frontend connection per + worker. Setting 0 to this option means write burst size + is unlimited. Not implemented yet. + + Default: ``0`` + +.. option:: --worker-frontend-connections= + + Set maximum number of simultaneous connections frontend + accepts. Setting 0 means unlimited. + + Default: ``0`` + +.. option:: --backend-connections-per-host= + + Set maximum number of backend concurrent connections + (and/or streams in case of HTTP/2) per origin host. + This option is meaningful when :option:`--http2-proxy` option is + used. The origin host is determined by authority + portion of request URI (or :authority header field for + HTTP/2). To limit the number of connections per + frontend for default mode, use + :option:`--backend-connections-per-frontend`\. + + Default: ``8`` + +.. option:: --backend-connections-per-frontend= + + Set maximum number of backend concurrent connections + (and/or streams in case of HTTP/2) per frontend. This + option is only used for default mode. 0 means + unlimited. To limit the number of connections per host + with :option:`--http2-proxy` option, use + :option:`--backend-connections-per-host`\. + + Default: ``0`` + +.. option:: --rlimit-nofile= + + Set maximum number of open files (RLIMIT_NOFILE) to . + If 0 is given, nghttpx does not set the limit. + + Default: ``0`` + +.. option:: --rlimit-memlock= + + Set maximum number of bytes of memory that may be locked + into RAM. If 0 is given, nghttpx does not set the + limit. + + Default: ``0`` + +.. option:: --backend-request-buffer= + + Set buffer size used to store backend request. + + Default: ``16K`` + +.. option:: --backend-response-buffer= + + Set buffer size used to store backend response. + + Default: ``128K`` + +.. option:: --fastopen= + + Enables "TCP Fast Open" for the listening socket and + limits the maximum length for the queue of connections + that have not yet completed the three-way handshake. If + value is 0 then fast open is disabled. + + Default: ``0`` + +.. option:: --no-kqueue + + Don't use kqueue. This option is only applicable for + the platforms which have kqueue. For other platforms, + this option will be simply ignored. + + +Timeout +~~~~~~~ + +.. option:: --frontend-http2-read-timeout= + + Specify read timeout for HTTP/2 frontend connection. + + Default: ``3m`` + +.. option:: --frontend-http3-read-timeout= + + Specify read timeout for HTTP/3 frontend connection. + + Default: ``3m`` + +.. option:: --frontend-read-timeout= + + Specify read timeout for HTTP/1.1 frontend connection. + + Default: ``1m`` + +.. option:: --frontend-write-timeout= + + Specify write timeout for all frontend connections. + + Default: ``30s`` + +.. option:: --frontend-keep-alive-timeout= + + Specify keep-alive timeout for frontend HTTP/1 + connection. + + Default: ``1m`` + +.. option:: --stream-read-timeout= + + Specify read timeout for HTTP/2 streams. 0 means no + timeout. + + Default: ``0`` + +.. option:: --stream-write-timeout= + + Specify write timeout for HTTP/2 streams. 0 means no + timeout. + + Default: ``1m`` + +.. option:: --backend-read-timeout= + + Specify read timeout for backend connection. + + Default: ``1m`` + +.. option:: --backend-write-timeout= + + Specify write timeout for backend connection. + + Default: ``30s`` + +.. option:: --backend-connect-timeout= + + Specify timeout before establishing TCP connection to + backend. + + Default: ``30s`` + +.. option:: --backend-keep-alive-timeout= + + Specify keep-alive timeout for backend HTTP/1 + connection. + + Default: ``2s`` + +.. option:: --listener-disable-timeout= + + After accepting connection failed, connection listener + is disabled for a given amount of time. Specifying 0 + disables this feature. + + Default: ``30s`` + +.. option:: --frontend-http2-setting-timeout= + + Specify timeout before SETTINGS ACK is received from + client. + + Default: ``10s`` + +.. option:: --backend-http2-settings-timeout= + + Specify timeout before SETTINGS ACK is received from + backend server. + + Default: ``10s`` + +.. option:: --backend-max-backoff= + + Specify maximum backoff interval. This is used when + doing health check against offline backend (see "fail" + parameter in :option:`--backend` option). It is also used to + limit the maximum interval to temporarily disable + backend when nghttpx failed to connect to it. These + intervals are calculated using exponential backoff, and + consecutive failed attempts increase the interval. This + option caps its maximum value. + + Default: ``2m`` + + +SSL/TLS +~~~~~~~ + +.. option:: --ciphers= + + Set allowed cipher list for frontend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.2 or earlier. + Use :option:`--tls13-ciphers` for TLSv1.3. + + Default: ``ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384`` + +.. option:: --tls13-ciphers= + + Set allowed cipher list for frontend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.3. Use + :option:`--ciphers` for TLSv1.2 or earlier. + + Default: ``TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256`` + +.. option:: --client-ciphers= + + Set allowed cipher list for backend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.2 or earlier. + Use :option:`--tls13-client-ciphers` for TLSv1.3. + + Default: ``ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384`` + +.. option:: --tls13-client-ciphers= + + Set allowed cipher list for backend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.3. Use + :option:`--tls13-client-ciphers` for TLSv1.2 or earlier. + + Default: ``TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256`` + +.. option:: --ecdh-curves= + + Set supported curve list for frontend connections. + is a colon separated list of curve NID or names + in the preference order. The supported curves depend on + the linked OpenSSL library. This function requires + OpenSSL >= 1.0.2. + + Default: ``X25519:P-256:P-384:P-521`` + +.. option:: -k, --insecure + + Don't verify backend server's certificate if TLS is + enabled for backend connections. + +.. option:: --cacert= + + Set path to trusted CA certificate file. It is used in + backend TLS connections to verify peer's certificate. + It is also used to verify OCSP response from the script + set by :option:`--fetch-ocsp-response-file`\. The file must be in + PEM format. It can contain multiple certificates. If + the linked OpenSSL is configured to load system wide + certificates, they are loaded at startup regardless of + this option. + +.. option:: --private-key-passwd-file= + + Path to file that contains password for the server's + private key. If none is given and the private key is + password protected it'll be requested interactively. + +.. option:: --subcert=:[[;]...] + + Specify additional certificate and private key file. + nghttpx will choose certificates based on the hostname + indicated by client using TLS SNI extension. If nghttpx + is built with OpenSSL >= 1.0.2, the shared elliptic + curves (e.g., P-256) between client and server are also + taken into consideration. This allows nghttpx to send + ECDSA certificate to modern clients, while sending RSA + based certificate to older clients. This option can be + used multiple times. To make OCSP stapling work, + must be absolute path. + + Additional parameter can be specified in . The + available is "sct-dir=". + + "sct-dir=" specifies the path to directory which + contains \*.sct files for TLS + signed_certificate_timestamp extension (RFC 6962). This + feature requires OpenSSL >= 1.0.2. See also + :option:`--tls-sct-dir` option. + +.. option:: --dh-param-file= + + Path to file that contains DH parameters in PEM format. + Without this option, DHE cipher suites are not + available. + +.. option:: --npn-list= + + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. This is used in both ALPN and + NPN. The parameter must be delimited by a single comma + only and any white spaces are treated as a part of + protocol string. + + Default: ``h2,h2-16,h2-14,http/1.1`` + +.. option:: --verify-client + + Require and verify client certificate. + +.. option:: --verify-client-cacert= + + Path to file that contains CA certificates to verify + client certificate. The file must be in PEM format. It + can contain multiple certificates. + +.. option:: --verify-client-tolerate-expired + + Accept expired client certificate. Operator should + handle the expired client certificate by some means + (e.g., mruby script). Otherwise, this option might + cause a security risk. + +.. option:: --client-private-key-file= + + Path to file that contains client private key used in + backend client authentication. + +.. option:: --client-cert-file= + + Path to file that contains client certificate used in + backend client authentication. + +.. option:: --tls-min-proto-version= + + Specify minimum SSL/TLS protocol. The name matching is + done in case-insensitive manner. The versions between + :option:`--tls-min-proto-version` and :option:`\--tls-max-proto-version` are + enabled. If the protocol list advertised by client does + not overlap this range, you will receive the error + message "unknown protocol". If a protocol version lower + than TLSv1.2 is specified, make sure that the compatible + ciphers are included in :option:`--ciphers` option. The default + cipher list only includes ciphers compatible with + TLSv1.2 or above. The available versions are: + TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.0 + + Default: ``TLSv1.2`` + +.. option:: --tls-max-proto-version= + + Specify maximum SSL/TLS protocol. The name matching is + done in case-insensitive manner. The versions between + :option:`--tls-min-proto-version` and :option:`\--tls-max-proto-version` are + enabled. If the protocol list advertised by client does + not overlap this range, you will receive the error + message "unknown protocol". The available versions are: + TLSv1.3, TLSv1.2, TLSv1.1, and TLSv1.0 + + Default: ``TLSv1.3`` + +.. option:: --tls-ticket-key-file= + + Path to file that contains random data to construct TLS + session ticket parameters. If aes-128-cbc is given in + :option:`--tls-ticket-key-cipher`\, the file must contain exactly + 48 bytes. If aes-256-cbc is given in + :option:`--tls-ticket-key-cipher`\, the file must contain exactly + 80 bytes. This options can be used repeatedly to + specify multiple ticket parameters. If several files + are given, only the first key is used to encrypt TLS + session tickets. Other keys are accepted but server + will issue new session ticket with first key. This + allows session key rotation. Please note that key + rotation does not occur automatically. User should + rearrange files or change options values and restart + nghttpx gracefully. If opening or reading given file + fails, all loaded keys are discarded and it is treated + as if none of this option is given. If this option is + not given or an error occurred while opening or reading + a file, key is generated every 1 hour internally and + they are valid for 12 hours. This is recommended if + ticket key sharing between nghttpx instances is not + required. + +.. option:: --tls-ticket-key-memcached=,[;tls] + + Specify address of memcached server to get TLS ticket + keys for session resumption. This enables shared TLS + ticket key between multiple nghttpx instances. nghttpx + does not set TLS ticket key to memcached. The external + ticket key generator is required. nghttpx just gets TLS + ticket keys from memcached, and use them, possibly + replacing current set of keys. It is up to extern TLS + ticket key generator to rotate keys frequently. See + "TLS SESSION TICKET RESUMPTION" section in manual page + to know the data format in memcached entry. Optionally, + memcached connection can be encrypted with TLS by + specifying "tls" parameter. + +.. option:: --tls-ticket-key-memcached-address-family=(auto|IPv4|IPv6) + + Specify address family of memcached connections to get + TLS ticket keys. If "auto" is given, both IPv4 and IPv6 + are considered. If "IPv4" is given, only IPv4 address + is considered. If "IPv6" is given, only IPv6 address is + considered. + + Default: ``auto`` + +.. option:: --tls-ticket-key-memcached-interval= + + Set interval to get TLS ticket keys from memcached. + + Default: ``10m`` + +.. option:: --tls-ticket-key-memcached-max-retry= + + Set maximum number of consecutive retries before + abandoning TLS ticket key retrieval. If this number is + reached, the attempt is considered as failure, and + "failure" count is incremented by 1, which contributed + to the value controlled + :option:`--tls-ticket-key-memcached-max-fail` option. + + Default: ``3`` + +.. option:: --tls-ticket-key-memcached-max-fail= + + Set maximum number of consecutive failure before + disabling TLS ticket until next scheduled key retrieval. + + Default: ``2`` + +.. option:: --tls-ticket-key-cipher= + + Specify cipher to encrypt TLS session ticket. Specify + either aes-128-cbc or aes-256-cbc. By default, + aes-128-cbc is used. + +.. option:: --tls-ticket-key-memcached-cert-file= + + Path to client certificate for memcached connections to + get TLS ticket keys. + +.. option:: --tls-ticket-key-memcached-private-key-file= + + Path to client private key for memcached connections to + get TLS ticket keys. + +.. option:: --fetch-ocsp-response-file= + + Path to fetch-ocsp-response script file. It should be + absolute path. + + Default: ``/usr/local/share/nghttp2/fetch-ocsp-response`` + +.. option:: --ocsp-update-interval= + + Set interval to update OCSP response cache. + + Default: ``4h`` + +.. option:: --ocsp-startup + + Start accepting connections after initial attempts to + get OCSP responses finish. It does not matter some of + the attempts fail. This feature is useful if OCSP + responses must be available before accepting + connections. + +.. option:: --no-verify-ocsp + + nghttpx does not verify OCSP response. + +.. option:: --no-ocsp + + Disable OCSP stapling. + +.. option:: --tls-session-cache-memcached=,[;tls] + + Specify address of memcached server to store session + cache. This enables shared session cache between + multiple nghttpx instances. Optionally, memcached + connection can be encrypted with TLS by specifying "tls" + parameter. + +.. option:: --tls-session-cache-memcached-address-family=(auto|IPv4|IPv6) + + Specify address family of memcached connections to store + session cache. If "auto" is given, both IPv4 and IPv6 + are considered. If "IPv4" is given, only IPv4 address + is considered. If "IPv6" is given, only IPv6 address is + considered. + + Default: ``auto`` + +.. option:: --tls-session-cache-memcached-cert-file= + + Path to client certificate for memcached connections to + store session cache. + +.. option:: --tls-session-cache-memcached-private-key-file= + + Path to client private key for memcached connections to + store session cache. + +.. option:: --tls-dyn-rec-warmup-threshold= + + Specify the threshold size for TLS dynamic record size + behaviour. During a TLS session, after the threshold + number of bytes have been written, the TLS record size + will be increased to the maximum allowed (16K). The max + record size will continue to be used on the active TLS + session. After :option:`--tls-dyn-rec-idle-timeout` has elapsed, + the record size is reduced to 1300 bytes. Specify 0 to + always use the maximum record size, regardless of idle + period. This behaviour applies to all TLS based + frontends, and TLS HTTP/2 backends. + + Default: ``1M`` + +.. option:: --tls-dyn-rec-idle-timeout= + + Specify TLS dynamic record size behaviour timeout. See + :option:`--tls-dyn-rec-warmup-threshold` for more information. + This behaviour applies to all TLS based frontends, and + TLS HTTP/2 backends. + + Default: ``1s`` + +.. option:: --no-http2-cipher-block-list + + Allow block listed cipher suite on frontend HTTP/2 + connection. See + https://tools.ietf.org/html/rfc7540#appendix-A for the + complete HTTP/2 cipher suites block list. + +.. option:: --client-no-http2-cipher-block-list + + Allow block listed cipher suite on backend HTTP/2 + connection. See + https://tools.ietf.org/html/rfc7540#appendix-A for the + complete HTTP/2 cipher suites block list. + +.. option:: --tls-sct-dir= + + Specifies the directory where \*.sct files exist. All + \*.sct files in are read, and sent as + extension_data of TLS signed_certificate_timestamp (RFC + 6962) to client. These \*.sct files are for the + certificate specified in positional command-line + argument , or certificate option in configuration + file. For additional certificates, use :option:`--subcert` + option. This option requires OpenSSL >= 1.0.2. + +.. option:: --psk-secrets= + + Read list of PSK identity and secrets from . This + is used for frontend connection. The each line of input + file is formatted as :, where + is PSK identity, and is secret + in hex. An empty line, and line which starts with '#' + are skipped. The default enabled cipher list might not + contain any PSK cipher suite. In that case, desired PSK + cipher suites must be enabled using :option:`--ciphers` option. + The desired PSK cipher suite may be block listed by + HTTP/2. To use those cipher suites with HTTP/2, + consider to use :option:`--no-http2-cipher-block-list` option. + But be aware its implications. + +.. option:: --client-psk-secrets= + + Read PSK identity and secrets from . This is used + for backend connection. The each line of input file is + formatted as :, where + is PSK identity, and is secret in hex. An + empty line, and line which starts with '#' are skipped. + The first identity and secret pair encountered is used. + The default enabled cipher list might not contain any + PSK cipher suite. In that case, desired PSK cipher + suites must be enabled using :option:`--client-ciphers` option. + The desired PSK cipher suite may be block listed by + HTTP/2. To use those cipher suites with HTTP/2, + consider to use :option:`--client-no-http2-cipher-block-list` + option. But be aware its implications. + +.. option:: --tls-no-postpone-early-data + + By default, except for QUIC connections, nghttpx + postpones forwarding HTTP requests sent in early data, + including those sent in partially in it, until TLS + handshake finishes. If all backend server recognizes + "Early-Data" header field, using this option makes + nghttpx not postpone forwarding request and get full + potential of 0-RTT data. + +.. option:: --tls-max-early-data= + + Sets the maximum amount of 0-RTT data that server + accepts. + + Default: ``16K`` + +.. option:: --tls-ktls + + Enable ktls. For server, ktls is enable if + :option:`--tls-session-cache-memcached` is not configured. + + +HTTP/2 +~~~~~~ + +.. option:: -c, --frontend-http2-max-concurrent-streams= + + Set the maximum number of the concurrent streams in one + frontend HTTP/2 session. + + Default: ``100`` + +.. option:: --backend-http2-max-concurrent-streams= + + Set the maximum number of the concurrent streams in one + backend HTTP/2 session. This sets maximum number of + concurrent opened pushed streams. The maximum number of + concurrent requests are set by a remote server. + + Default: ``100`` + +.. option:: --frontend-http2-window-size= + + Sets the per-stream initial window size of HTTP/2 + frontend connection. + + Default: ``65535`` + +.. option:: --frontend-http2-connection-window-size= + + Sets the per-connection window size of HTTP/2 frontend + connection. + + Default: ``65535`` + +.. option:: --backend-http2-window-size= + + Sets the initial window size of HTTP/2 backend + connection. + + Default: ``65535`` + +.. option:: --backend-http2-connection-window-size= + + Sets the per-connection window size of HTTP/2 backend + connection. + + Default: ``2147483647`` + +.. option:: --http2-no-cookie-crumbling + + Don't crumble cookie header field. + +.. option:: --padding= + + Add at most bytes to a HTTP/2 frame payload as + padding. Specify 0 to disable padding. This option is + meant for debugging purpose and not intended to enhance + protocol security. + +.. option:: --no-server-push + + Disable HTTP/2 server push. Server push is supported by + default mode and HTTP/2 frontend via Link header field. + It is also supported if both frontend and backend are + HTTP/2 in default mode. In this case, server push from + backend session is relayed to frontend, and server push + via Link header field is also supported. + +.. option:: --frontend-http2-optimize-write-buffer-size + + (Experimental) Enable write buffer size optimization in + frontend HTTP/2 TLS connection. This optimization aims + to reduce write buffer size so that it only contains + bytes which can send immediately. This makes server + more responsive to prioritized HTTP/2 stream because the + buffering of lower priority stream is reduced. This + option is only effective on recent Linux platform. + +.. option:: --frontend-http2-optimize-window-size + + (Experimental) Automatically tune connection level + window size of frontend HTTP/2 TLS connection. If this + feature is enabled, connection window size starts with + the default window size, 65535 bytes. nghttpx + automatically adjusts connection window size based on + TCP receiving window size. The maximum window size is + capped by the value specified by + :option:`--frontend-http2-connection-window-size`\. Since the + stream is subject to stream level window size, it should + be adjusted using :option:`--frontend-http2-window-size` option as + well. This option is only effective on recent Linux + platform. + +.. option:: --frontend-http2-encoder-dynamic-table-size= + + Specify the maximum dynamic table size of HPACK encoder + in the frontend HTTP/2 connection. The decoder (client) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which client specified. + + Default: ``4K`` + +.. option:: --frontend-http2-decoder-dynamic-table-size= + + Specify the maximum dynamic table size of HPACK decoder + in the frontend HTTP/2 connection. + + Default: ``4K`` + +.. option:: --backend-http2-encoder-dynamic-table-size= + + Specify the maximum dynamic table size of HPACK encoder + in the backend HTTP/2 connection. The decoder (backend) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which backend specified. + + Default: ``4K`` + +.. option:: --backend-http2-decoder-dynamic-table-size= + + Specify the maximum dynamic table size of HPACK decoder + in the backend HTTP/2 connection. + + Default: ``4K`` + + +Mode +~~~~ + +.. describe:: (default mode) + + + Accept HTTP/2, and HTTP/1.1 over SSL/TLS. "no-tls" + parameter is used in :option:`--frontend` option, accept HTTP/2 + and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1 + connection can be upgraded to HTTP/2 through HTTP + Upgrade. + +.. option:: -s, --http2-proxy + + Like default mode, but enable forward proxy. This is so + called HTTP/2 proxy mode. + + +Logging +~~~~~~~ + +.. option:: -L, --log-level= + + Set the severity level of log output. must be + one of INFO, NOTICE, WARN, ERROR and FATAL. + + Default: ``NOTICE`` + +.. option:: --accesslog-file= + + Set path to write access log. To reopen file, send USR1 + signal to nghttpx. + +.. option:: --accesslog-syslog + + Send access log to syslog. If this option is used, + :option:`--accesslog-file` option is ignored. + +.. option:: --accesslog-format= + + Specify format string for access log. The default + format is combined format. The following variables are + available: + + * $remote_addr: client IP address. + * $time_local: local time in Common Log format. + * $time_iso8601: local time in ISO 8601 format. + * $request: HTTP request line. + * $status: HTTP response status code. + * $body_bytes_sent: the number of bytes sent to client + as response body. + * $http_: value of HTTP request header where + '_' in is replaced with '-'. + * $remote_port: client port. + * $server_port: server port. + * $request_time: request processing time in seconds with + milliseconds resolution. + * $pid: PID of the running process. + * $alpn: ALPN identifier of the protocol which generates + the response. For HTTP/1, ALPN is always http/1.1, + regardless of minor version. + * $tls_cipher: cipher used for SSL/TLS connection. + * $tls_client_fingerprint_sha256: SHA-256 fingerprint of + client certificate. + * $tls_client_fingerprint_sha1: SHA-1 fingerprint of + client certificate. + * $tls_client_subject_name: subject name in client + certificate. + * $tls_client_issuer_name: issuer name in client + certificate. + * $tls_client_serial: serial number in client + certificate. + * $tls_protocol: protocol for SSL/TLS connection. + * $tls_session_id: session ID for SSL/TLS connection. + * $tls_session_reused: "r" if SSL/TLS session was + reused. Otherwise, "." + * $tls_sni: SNI server name for SSL/TLS connection. + * $backend_host: backend host used to fulfill the + request. "-" if backend host is not available. + * $backend_port: backend port used to fulfill the + request. "-" if backend host is not available. + * $method: HTTP method + * $path: Request path including query. For CONNECT + request, authority is recorded. + * $path_without_query: $path up to the first '?' + character. For CONNECT request, authority is + recorded. + * $protocol_version: HTTP version (e.g., HTTP/1.1, + HTTP/2) + + The variable can be enclosed by "{" and "}" for + disambiguation (e.g., ${remote_addr}). + + + Default: ``$remote_addr - - [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"`` + +.. option:: --accesslog-write-early + + Write access log when response header fields are + received from backend rather than when request + transaction finishes. + +.. option:: --errorlog-file= + + Set path to write error log. To reopen file, send USR1 + signal to nghttpx. stderr will be redirected to the + error log file unless :option:`--errorlog-syslog` is used. + + Default: ``/dev/stderr`` + +.. option:: --errorlog-syslog + + Send error log to syslog. If this option is used, + :option:`--errorlog-file` option is ignored. + +.. option:: --syslog-facility= + + Set syslog facility to . + + Default: ``daemon`` + + +HTTP +~~~~ + +.. option:: --add-x-forwarded-for + + Append X-Forwarded-For header field to the downstream + request. + +.. option:: --strip-incoming-x-forwarded-for + + Strip X-Forwarded-For header field from inbound client + requests. + +.. option:: --no-add-x-forwarded-proto + + Don't append additional X-Forwarded-Proto header field + to the backend request. If inbound client sets + X-Forwarded-Proto, and + :option:`--no-strip-incoming-x-forwarded-proto` option is used, + they are passed to the backend. + +.. option:: --no-strip-incoming-x-forwarded-proto + + Don't strip X-Forwarded-Proto header field from inbound + client requests. + +.. option:: --add-forwarded= + + Append RFC 7239 Forwarded header field with parameters + specified in comma delimited list . The supported + parameters are "by", "for", "host", and "proto". By + default, the value of "by" and "for" parameters are + obfuscated string. See :option:`--forwarded-by` and + :option:`--forwarded-for` options respectively. Note that nghttpx + does not translate non-standard X-Forwarded-\* header + fields into Forwarded header field, and vice versa. + +.. option:: --strip-incoming-forwarded + + Strip Forwarded header field from inbound client + requests. + +.. option:: --forwarded-by=(obfuscated|ip|) + + Specify the parameter value sent out with "by" parameter + of Forwarded header field. If "obfuscated" is given, + the string is randomly generated at startup. If "ip" is + given, the interface address of the connection, + including port number, is sent with "by" parameter. In + case of UNIX domain socket, "localhost" is used instead + of address and port. User can also specify the static + obfuscated string. The limitation is that it must start + with "_", and only consists of character set + [A-Za-z0-9._-], as described in RFC 7239. + + Default: ``obfuscated`` + +.. option:: --forwarded-for=(obfuscated|ip) + + Specify the parameter value sent out with "for" + parameter of Forwarded header field. If "obfuscated" is + given, the string is randomly generated for each client + connection. If "ip" is given, the remote client address + of the connection, without port number, is sent with + "for" parameter. In case of UNIX domain socket, + "localhost" is used instead of address. + + Default: ``obfuscated`` + +.. option:: --no-via + + Don't append to Via header field. If Via header field + is received, it is left unaltered. + +.. option:: --no-strip-incoming-early-data + + Don't strip Early-Data header field from inbound client + requests. + +.. option:: --no-location-rewrite + + Don't rewrite location header field in default mode. + When :option:`--http2-proxy` is used, location header field will + not be altered regardless of this option. + +.. option:: --host-rewrite + + Rewrite host and :authority header fields in default + mode. When :option:`--http2-proxy` is used, these headers will + not be altered regardless of this option. + +.. option:: --altsvc= + + Specify protocol ID, port, host and origin of + alternative service. , and are + optional. Empty and are allowed and + they are treated as nothing is specified. They are + advertised in alt-svc header field only in HTTP/1.1 + frontend. This option can be used multiple times to + specify multiple alternative services. + Example: :option:`--altsvc`\="h2,443,,,ma=3600; persist=1" + +.. option:: --http2-altsvc= + + Just like :option:`--altsvc` option, but this altsvc is only sent + in HTTP/2 frontend. + +.. option:: --add-request-header=
+ + Specify additional header field to add to request header + set. The field name must be lowercase. This option + just appends header field and won't replace anything + already set. This option can be used several times to + specify multiple header fields. + Example: :option:`--add-request-header`\="foo: bar" + +.. option:: --add-response-header=
+ + Specify additional header field to add to response + header set. The field name must be lowercase. This + option just appends header field and won't replace + anything already set. This option can be used several + times to specify multiple header fields. + Example: :option:`--add-response-header`\="foo: bar" + +.. option:: --request-header-field-buffer= + + Set maximum buffer size for incoming HTTP request header + field list. This is the sum of header name and value in + bytes. If trailer fields exist, they are counted + towards this number. + + Default: ``64K`` + +.. option:: --max-request-header-fields= + + Set maximum number of incoming HTTP request header + fields. If trailer fields exist, they are counted + towards this number. + + Default: ``100`` + +.. option:: --response-header-field-buffer= + + Set maximum buffer size for incoming HTTP response + header field list. This is the sum of header name and + value in bytes. If trailer fields exist, they are + counted towards this number. + + Default: ``64K`` + +.. option:: --max-response-header-fields= + + Set maximum number of incoming HTTP response header + fields. If trailer fields exist, they are counted + towards this number. + + Default: ``500`` + +.. option:: --error-page=(|*)= + + Set file path to custom error page served when nghttpx + originally generates HTTP error status code . + must be greater than or equal to 400, and at most + 599. If "\*" is used instead of , it matches all + HTTP status code. If error status code comes from + backend server, the custom error pages are not used. + +.. option:: --server-name= + + Change server response header field value to . + + Default: ``nghttpx`` + +.. option:: --no-server-rewrite + + Don't rewrite server header field in default mode. When + :option:`--http2-proxy` is used, these headers will not be altered + regardless of this option. + +.. option:: --redirect-https-port= + + Specify the port number which appears in Location header + field when redirect to HTTPS URI is made due to + "redirect-if-not-tls" parameter in :option:`--backend` option. + + Default: ``443`` + +.. option:: --require-http-scheme + + Always require http or https scheme in HTTP request. It + also requires that https scheme must be used for an + encrypted connection. Otherwise, http scheme must be + used. This option is recommended for a server + deployment which directly faces clients and the services + it provides only require http or https scheme. + + +API +~~~ + +.. option:: --api-max-request-body= + + Set the maximum size of request body for API request. + + Default: ``32M`` + + +DNS +~~~ + +.. option:: --dns-cache-timeout= + + Set duration that cached DNS results remain valid. Note + that nghttpx caches the unsuccessful results as well. + + Default: ``10s`` + +.. option:: --dns-lookup-timeout= + + Set timeout that DNS server is given to respond to the + initial DNS query. For the 2nd and later queries, + server is given time based on this timeout, and it is + scaled linearly. + + Default: ``5s`` + +.. option:: --dns-max-try= + + Set the number of DNS query before nghttpx gives up name + lookup. + + Default: ``2`` + +.. option:: --frontend-max-requests= + + The number of requests that single frontend connection + can process. For HTTP/2, this is the number of streams + in one HTTP/2 connection. For HTTP/1, this is the + number of keep alive requests. This is hint to nghttpx, + and it may allow additional few requests. The default + value is unlimited. + + +Debug +~~~~~ + +.. option:: --frontend-http2-dump-request-header= + + Dumps request headers received by HTTP/2 frontend to the + file denoted in . The output is done in HTTP/1 + header field format and each header block is followed by + an empty line. This option is not thread safe and MUST + NOT be used with option :option:`-n`\, where >= 2. + +.. option:: --frontend-http2-dump-response-header= + + Dumps response headers sent from HTTP/2 frontend to the + file denoted in . The output is done in HTTP/1 + header field format and each header block is followed by + an empty line. This option is not thread safe and MUST + NOT be used with option :option:`-n`\, where >= 2. + +.. option:: -o, --frontend-frame-debug + + Print HTTP/2 frames in frontend to stderr. This option + is not thread safe and MUST NOT be used with option + :option:`-n`\=N, where N >= 2. + + +Process +~~~~~~~ + +.. option:: -D, --daemon + + Run in a background. If :option:`-D` is used, the current working + directory is changed to '*/*'. + +.. option:: --pid-file= + + Set path to save PID of this program. + +.. option:: --user= + + Run this program as . This option is intended to + be used to drop root privileges. + +.. option:: --single-process + + Run this program in a single process mode for debugging + purpose. Without this option, nghttpx creates at least + 2 processes: main and worker processes. If this option + is used, main and worker are unified into a single + process. nghttpx still spawns additional process if + neverbleed is used. In the single process mode, the + signal handling feature is disabled. + +.. option:: --max-worker-processes= + + The maximum number of worker processes. nghttpx spawns + new worker process when it reloads its configuration. + The previous worker process enters graceful termination + period and will terminate when it finishes handling the + existing connections. However, if reloading + configurations happen very frequently, the worker + processes might be piled up if they take a bit long time + to finish the existing connections. With this option, + if the number of worker processes exceeds the given + value, the oldest worker process is terminated + immediately. Specifying 0 means no limit and it is the + default behaviour. + +.. option:: --worker-process-grace-shutdown-period= + + Maximum period for a worker process to terminate + gracefully. When a worker process enters in graceful + shutdown period (e.g., when nghttpx reloads its + configuration) and it does not finish handling the + existing connections in the given period of time, it is + immediately terminated. Specifying 0 means no limit and + it is the default behaviour. + + +Scripting +~~~~~~~~~ + +.. option:: --mruby-file= + + Set mruby script file + +.. option:: --ignore-per-pattern-mruby-error + + Ignore mruby compile error for per-pattern mruby script + file. If error occurred, it is treated as if no mruby + file were specified for the pattern. + + +HTTP/3 and QUIC +~~~~~~~~~~~~~~~ + +.. option:: --frontend-quic-idle-timeout= + + Specify an idle timeout for QUIC connection. + + Default: ``30s`` + +.. option:: --frontend-quic-debug-log + + Output QUIC debug log to */dev/stderr.* + +.. option:: --quic-bpf-program-file= + + Specify a path to eBPF program file reuseport_kern.o to + direct an incoming QUIC UDP datagram to a correct + socket. + + Default: ``/usr/local/lib/nghttp2/reuseport_kern.o`` + +.. option:: --frontend-quic-early-data + + Enable early data on frontend QUIC connections. nghttpx + sends "Early-Data" header field to a backend server if a + request is received in early data and handshake has not + finished. All backend servers should deal with possibly + replayed requests. + +.. option:: --frontend-quic-qlog-dir= + + Specify a directory where a qlog file is written for + frontend QUIC connections. A qlog file is created per + each QUIC connection. The file name is ISO8601 basic + format, followed by "-", server Source Connection ID and + ".sqlog". + +.. option:: --frontend-quic-require-token + + Require an address validation token for a frontend QUIC + connection. Server sends a token in Retry packet or + NEW_TOKEN frame in the previous connection. + +.. option:: --frontend-quic-congestion-controller= + + Specify a congestion controller algorithm for a frontend + QUIC connection. should be either "cubic" or + "bbr". + + Default: ``cubic`` + +.. option:: --frontend-quic-secret-file= + + Path to file that contains secure random data to be used + as QUIC keying materials. It is used to derive keys for + encrypting tokens and Connection IDs. It is not used to + encrypt QUIC packets. Each line of this file must + contain exactly 136 bytes hex-encoded string (when + decoded the byte string is 68 bytes long). The first 2 + bits of decoded byte string are used to identify the + keying material. An empty line or a line which starts + '#' is ignored. The file can contain more than one + keying materials. Because the identifier is 2 bits, at + most 4 keying materials are read and the remaining data + is discarded. The first keying material in the file is + primarily used for encryption and decryption for new + connection. The other ones are used to decrypt data for + the existing connections. Specifying multiple keying + materials enables key rotation. Please note that key + rotation does not occur automatically. User should + update files or change options values and restart + nghttpx gracefully. If opening or reading given file + fails, all loaded keying materials are discarded and it + is treated as if none of this option is given. If this + option is not given or an error occurred while opening + or reading a file, a keying material is generated + internally on startup and reload. + +.. option:: --quic-server-id= + + Specify server ID encoded in Connection ID to identify + this particular server instance. Connection ID is + encrypted and this part is not visible in public. It + must be 4 bytes long and must be encoded in hex string + (which is 8 bytes long). If this option is omitted, a + random server ID is generated on startup and + configuration reload. + +.. option:: --frontend-quic-initial-rtt= + + Specify the initial RTT of the frontend QUIC connection. + + Default: ``333ms`` + +.. option:: --no-quic-bpf + + Disable eBPF. + +.. option:: --frontend-http3-window-size= + + Sets the per-stream initial window size of HTTP/3 + frontend connection. + + Default: ``256K`` + +.. option:: --frontend-http3-connection-window-size= + + Sets the per-connection window size of HTTP/3 frontend + connection. + + Default: ``1M`` + +.. option:: --frontend-http3-max-window-size= + + Sets the maximum per-stream window size of HTTP/3 + frontend connection. The window size is adjusted based + on the receiving rate of stream data. The initial value + is the value specified by :option:`--frontend-http3-window-size` + and the window size grows up to bytes. + + Default: ``6M`` + +.. option:: --frontend-http3-max-connection-window-size= + + Sets the maximum per-connection window size of HTTP/3 + frontend connection. The window size is adjusted based + on the receiving rate of stream data. The initial value + is the value specified by + :option:`--frontend-http3-connection-window-size` and the window + size grows up to bytes. + + Default: ``8M`` + +.. option:: --frontend-http3-max-concurrent-streams= + + Set the maximum number of the concurrent streams in one + frontend HTTP/3 connection. + + Default: ``100`` + + +Misc +~~~~ + +.. option:: --conf= + + Load configuration from . Please note that + nghttpx always tries to read the default configuration + file if :option:`--conf` is not given. + + Default: ``/etc/nghttpx/nghttpx.conf`` + +.. option:: --include= + + Load additional configurations from . File + is read when configuration parser encountered this + option. This option can be used multiple times, or even + recursively. + +.. option:: -v, --version + + Print version and exit. + +.. option:: -h, --help + + Print this help and exit. + + + +The argument is an integer and an optional unit (e.g., 10K is +10 * 1024). Units are K, M and G (powers of 1024). + +The argument is an integer and an optional unit (e.g., 1s +is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms +(hours, minutes, seconds and milliseconds, respectively). If a unit +is omitted, a second is used as unit. + +FILES +----- + +*/etc/nghttpx/nghttpx.conf* + The default configuration file path nghttpx searches at startup. + The configuration file path can be changed using :option:`--conf` + option. + + Those lines which are staring ``#`` are treated as comment. + + The option name in the configuration file is the long command-line + option name with leading ``--`` stripped (e.g., ``frontend``). Put + ``=`` between option name and value. Don't put extra leading or + trailing spaces. + + When specifying arguments including characters which have special + meaning to a shell, we usually use quotes so that shell does not + interpret them. When writing this configuration file, quotes for + this purpose must not be used. For example, specify additional + request header field, do this: + + .. code-block:: text + + add-request-header=foo: bar + + instead of: + + .. code-block:: text + + add-request-header="foo: bar" + + The options which do not take argument in the command-line *take* + argument in the configuration file. Specify ``yes`` as an argument + (e.g., ``http2-proxy=yes``). If other string is given, it is + ignored. + + To specify private key and certificate file which are given as + positional arguments in command-line, use ``private-key-file`` and + ``certificate-file``. + + :option:`--conf` option cannot be used in the configuration file and + will be ignored if specified. + +Error log + Error log is written to stderr by default. It can be configured + using :option:`--errorlog-file`. The format of log message is as + follows: + + (:) + + + It is a combination of date and time when the log is written. It + is in ISO 8601 format. + + + It is a main process ID. + + + It is a process ID which writes this log. + + + It is a thread ID which writes this log. It would be unique + within . + + and + They are source file name, and line number which produce this log. + + + It is a log message body. + +SIGNALS +------- + +SIGQUIT + Shutdown gracefully. First accept pending connections and stop + accepting connection. After all connections are handled, nghttpx + exits. + +SIGHUP + Reload configuration file given in :option:`--conf`. + +SIGUSR1 + Reopen log files. + +SIGUSR2 + + Fork and execute nghttpx. It will execute the binary in the same + path with same command-line arguments and environment variables. As + of nghttpx version 1.20.0, the new main process sends SIGQUIT to the + original main process when it is ready to serve requests. For the + earlier versions of nghttpx, user has to send SIGQUIT to the + original main process. + + The difference between SIGUSR2 (+ SIGQUIT) and SIGHUP is that former + is usually used to execute new binary, and the main process is newly + spawned. On the other hand, the latter just reloads configuration + file, and the same main process continues to exist. + +.. note:: + + nghttpx consists of multiple processes: one process for processing + these signals, and another one for processing requests. The former + spawns the latter. The former is called main process, and the + latter is called worker process. If neverbleed is enabled, the + worker process spawns neverbleed daemon process which does RSA key + processing. The above signal must be sent to the main process. If + the other processes received one of them, it is ignored. This + behaviour of these processes may change in the future release. In + other words, in the future release, the processes other than main + process may terminate upon the reception of these signals. + Therefore these signals should not be sent to the processes other + than main process. + +SERVER PUSH +----------- + +nghttpx supports HTTP/2 server push in default mode with Link header +field. nghttpx looks for Link header field (`RFC 5988 +`_) in response headers from +backend server and extracts URI-reference with parameter +``rel=preload`` (see `preload +`_) +and pushes those URIs to the frontend client. Here is a sample Link +header field to initiate server push: + +.. code-block:: text + + Link: ; rel=preload + Link: ; rel=preload + +Currently, the following restriction is applied for server push: + +1. The associated stream must have method "GET" or "POST". The + associated stream's status code must be 200. + +This limitation may be loosened in the future release. + +nghttpx also supports server push if both frontend and backend are +HTTP/2 in default mode. In this case, in addition to server push via +Link header field, server push from backend is forwarded to frontend +HTTP/2 session. + +HTTP/2 server push will be disabled if :option:`--http2-proxy` is +used. + +UNIX DOMAIN SOCKET +------------------ + +nghttpx supports UNIX domain socket with a filename for both frontend +and backend connections. + +Please note that current nghttpx implementation does not delete a +socket with a filename. And on start up, if nghttpx detects that the +specified socket already exists in the file system, nghttpx first +deletes it. However, if SIGUSR2 is used to execute new binary and +both old and new configurations use same filename, new binary does not +delete the socket and continues to use it. + +OCSP STAPLING +------------- + +OCSP query is done using external Python script +``fetch-ocsp-response``, which has been originally developed in Perl +as part of h2o project (https://github.com/h2o/h2o), and was +translated into Python. + +The script file is usually installed under +``$(prefix)/share/nghttp2/`` directory. The actual path to script can +be customized using :option:`--fetch-ocsp-response-file` option. + +If OCSP query is failed, previous OCSP response, if any, is continued +to be used. + +:option:`--fetch-ocsp-response-file` option provides wide range of +possibility to manage OCSP response. It can take an arbitrary script +or executable. The requirement is that it supports the command-line +interface of ``fetch-ocsp-response`` script, and it must return a +valid DER encoded OCSP response on success. It must return exit code +0 on success, and 75 for temporary error, and the other error code for +generic failure. For large cluster of servers, it is not efficient +for each server to perform OCSP query using ``fetch-ocsp-response``. +Instead, you can retrieve OCSP response in some way, and store it in a +disk or a shared database. Then specify a program in +:option:`--fetch-ocsp-response-file` to fetch it from those stores. +This could provide a way to share the OCSP response between fleet of +servers, and also any OCSP query strategy can be applied which may be +beyond the ability of nghttpx itself or ``fetch-ocsp-response`` +script. + +TLS SESSION RESUMPTION +---------------------- + +nghttpx supports TLS session resumption through both session ID and +session ticket. + +SESSION ID RESUMPTION +~~~~~~~~~~~~~~~~~~~~~ + +By default, session ID is shared by all worker threads. + +If :option:`--tls-session-cache-memcached` is given, nghttpx will +insert serialized session data to memcached with +``nghttpx:tls-session-cache:`` + lowercase hex string of session ID +as a memcached entry key, with expiry time 12 hours. Session timeout +is set to 12 hours. + +By default, connections to memcached server are not encrypted. To +enable encryption, use ``tls`` keyword in +:option:`--tls-session-cache-memcached` option. + +TLS SESSION TICKET RESUMPTION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, session ticket is shared by all worker threads. The +automatic key rotation is also enabled by default. Every an hour, new +encryption key is generated, and previous encryption key becomes +decryption only key. We set session timeout to 12 hours, and thus we +keep at most 12 keys. + +If :option:`--tls-ticket-key-memcached` is given, encryption keys are +retrieved from memcached. nghttpx just reads keys from memcached; one +has to deploy key generator program to update keys frequently (e.g., +every 1 hour). The example key generator tlsticketupdate.go is +available under contrib directory in nghttp2 archive. The memcached +entry key is ``nghttpx:tls-ticket-key``. The data format stored in +memcached is the binary format described below: + +.. code-block:: text + + +--------------+-------+----------------+ + | VERSION (4) |LEN (2)|KEY(48 or 80) ... + +--------------+-------+----------------+ + ^ | + | | + +------------------------+ + (LEN, KEY) pair can be repeated + +All numbers in the above figure is bytes. All integer fields are +network byte order. + +First 4 bytes integer VERSION field, which must be 1. The 2 bytes +integer LEN field gives the length of following KEY field, which +contains key. If :option:`--tls-ticket-key-cipher`\=aes-128-cbc is +used, LEN must be 48. If +:option:`--tls-ticket-key-cipher`\=aes-256-cbc is used, LEN must be +80. LEN and KEY pair can be repeated multiple times to store multiple +keys. The key appeared first is used as encryption key. All the +remaining keys are used as decryption only. + +By default, connections to memcached server are not encrypted. To +enable encryption, use ``tls`` keyword in +:option:`--tls-ticket-key-memcached` option. + +If :option:`--tls-ticket-key-file` is given, encryption key is read +from the given file. In this case, nghttpx does not rotate key +automatically. To rotate key, one has to restart nghttpx (see +SIGNALS). + +CERTIFICATE TRANSPARENCY +------------------------ + +nghttpx supports TLS ``signed_certificate_timestamp`` extension (`RFC +6962 `_). The relevant options +are :option:`--tls-sct-dir` and ``sct-dir`` parameter in +:option:`--subcert`. They takes a directory, and nghttpx reads all +files whose extension is ``.sct`` under the directory. The ``*.sct`` +files are encoded as ``SignedCertificateTimestamp`` struct described +in `section 3.2 of RFC 69662 +`_. This format is +the same one used by `nginx-ct +`_ and `mod_ssl_ct +`_. +`ct-submit `_ can be +used to submit certificates to log servers, and obtain the +``SignedCertificateTimestamp`` struct which can be used with nghttpx. + +MRUBY SCRIPTING +--------------- + +.. warning:: + + The current mruby extension API is experimental and not frozen. The + API is subject to change in the future release. + +.. warning:: + + Almost all string value returned from method, or attribute is a + fresh new mruby string, which involves memory allocation, and + copies. Therefore, it is strongly recommended to store a return + value in a local variable, and use it, instead of calling method or + accessing attribute repeatedly. + +nghttpx allows users to extend its capability using mruby scripts. +nghttpx has 2 hook points to execute mruby script: request phase and +response phase. The request phase hook is invoked after all request +header fields are received from client. The response phase hook is +invoked after all response header fields are received from backend +server. These hooks allows users to modify header fields, or common +HTTP variables, like authority or request path, and even return custom +response without forwarding request to backend servers. + +There are 2 levels of mruby script invocations: global and +per-pattern. The global mruby script is set by :option:`--mruby-file` +option and is called for all requests. The per-pattern mruby script +is set by "mruby" parameter in :option:`-b` option. It is invoked for +a request which matches the particular pattern. The order of hook +invocation is: global request phase hook, per-pattern request phase +hook, per-pattern response phase hook, and finally global response +phase hook. If a hook returns a response, any later hooks are not +invoked. The global request hook is invoked before the pattern +matching is made and changing request path may affect the pattern +matching. + +Please note that request and response hooks of per-pattern mruby +script for a single request might not come from the same script. This +might happen after a request hook is executed, backend failed for some +reason, and at the same time, backend configuration is replaced by API +request, and then the request uses new configuration on retry. The +response hook from new configuration, if it is specified, will be +invoked. + +The all mruby script will be evaluated once per thread on startup, and +it must instantiate object and evaluate it as the return value (e.g., +``App.new``). This object is called app object. If app object +defines ``on_req`` method, it is called with :rb:class:`Nghttpx::Env` +object on request hook. Similarly, if app object defines ``on_resp`` +method, it is called with :rb:class:`Nghttpx::Env` object on response +hook. For each method invocation, user can can access +:rb:class:`Nghttpx::Request` and :rb:class:`Nghttpx::Response` objects +via :rb:attr:`Nghttpx::Env#req` and :rb:attr:`Nghttpx::Env#resp` +respectively. + +.. rb:module:: Nghttpx + +.. rb:const:: REQUEST_PHASE + + Constant to represent request phase. + +.. rb:const:: RESPONSE_PHASE + + Constant to represent response phase. + +.. rb:class:: Env + + Object to represent current request specific context. + + .. rb:attr_reader:: req + + Return :rb:class:`Request` object. + + .. rb:attr_reader:: resp + + Return :rb:class:`Response` object. + + .. rb:attr_reader:: ctx + + Return Ruby hash object. It persists until request finishes. + So values set in request phase hook can be retrieved in + response phase hook. + + .. rb:attr_reader:: phase + + Return the current phase. + + .. rb:attr_reader:: remote_addr + + Return IP address of a remote client. If connection is made + via UNIX domain socket, this returns the string "localhost". + + .. rb:attr_reader:: server_addr + + Return address of server that accepted the connection. This + is a string which specified in :option:`--frontend` option, + excluding port number, and not a resolved IP address. For + UNIX domain socket, this is a path to UNIX domain socket. + + .. rb:attr_reader:: server_port + + Return port number of the server frontend which accepted the + connection from client. + + .. rb:attr_reader:: tls_used + + Return true if TLS is used on the connection. + + .. rb:attr_reader:: tls_sni + + Return the TLS SNI value which client sent in this connection. + + .. rb:attr_reader:: tls_client_fingerprint_sha256 + + Return the SHA-256 fingerprint of a client certificate. + + .. rb:attr_reader:: tls_client_fingerprint_sha1 + + Return the SHA-1 fingerprint of a client certificate. + + .. rb:attr_reader:: tls_client_issuer_name + + Return the issuer name of a client certificate. + + .. rb:attr_reader:: tls_client_subject_name + + Return the subject name of a client certificate. + + .. rb:attr_reader:: tls_client_serial + + Return the serial number of a client certificate. + + .. rb:attr_reader:: tls_client_not_before + + Return the start date of a client certificate in seconds since + the epoch. + + .. rb:attr_reader:: tls_client_not_after + + Return the end date of a client certificate in seconds since + the epoch. + + .. rb:attr_reader:: tls_cipher + + Return a TLS cipher negotiated in this connection. + + .. rb:attr_reader:: tls_protocol + + Return a TLS protocol version negotiated in this connection. + + .. rb:attr_reader:: tls_session_id + + Return a session ID for this connection in hex string. + + .. rb:attr_reader:: tls_session_reused + + Return true if, and only if a SSL/TLS session is reused. + + .. rb:attr_reader:: alpn + + Return ALPN identifier negotiated in this connection. + + .. rb:attr_reader:: tls_handshake_finished + + Return true if SSL/TLS handshake has finished. If it returns + false in the request phase hook, the request is received in + TLSv1.3 early data (0-RTT) and might be vulnerable to the + replay attack. nghttpx will send Early-Data header field to + backend servers to indicate this. + +.. rb:class:: Request + + Object to represent request from client. The modification to + Request object is allowed only in request phase hook. + + .. rb:attr_reader:: http_version_major + + Return HTTP major version. + + .. rb:attr_reader:: http_version_minor + + Return HTTP minor version. + + .. rb:attr_accessor:: method + + HTTP method. On assignment, copy of given value is assigned. + We don't accept arbitrary method name. We will document them + later, but well known methods, like GET, PUT and POST, are all + supported. + + .. rb:attr_accessor:: authority + + Authority (i.e., example.org), including optional port + component . On assignment, copy of given value is assigned. + + .. rb:attr_accessor:: scheme + + Scheme (i.e., http, https). On assignment, copy of given + value is assigned. + + .. rb:attr_accessor:: path + + Request path, including query component (i.e., /index.html). + On assignment, copy of given value is assigned. The path does + not include authority component of URI. This may include + query component. nghttpx makes certain normalization for + path. It decodes percent-encoding for unreserved characters + (see https://tools.ietf.org/html/rfc3986#section-2.3), and + resolves ".." and ".". But it may leave characters which + should be percent-encoded as is. So be careful when comparing + path against desired string. + + .. rb:attr_reader:: headers + + Return Ruby hash containing copy of request header fields. + Changing values in returned hash does not change request + header fields actually used in request processing. Use + :rb:meth:`Nghttpx::Request#add_header` or + :rb:meth:`Nghttpx::Request#set_header` to change request + header fields. + + .. rb:method:: add_header(key, value) + + Add header entry associated with key. The value can be single + string or array of string. It does not replace any existing + values associated with key. + + .. rb:method:: set_header(key, value) + + Set header entry associated with key. The value can be single + string or array of string. It replaces any existing values + associated with key. + + .. rb:method:: clear_headers + + Clear all existing request header fields. + + .. rb:method:: push(uri) + + Initiate to push resource identified by *uri*. Only HTTP/2 + protocol supports this feature. For the other protocols, this + method is noop. *uri* can be absolute URI, absolute path or + relative path to the current request. For absolute or + relative path, scheme and authority are inherited from the + current request. Currently, method is always GET. nghttpx + will issue request to backend servers to fulfill this request. + The request and response phase hooks will be called for pushed + resource as well. + +.. rb:class:: Response + + Object to represent response from backend server. + + .. rb:attr_reader:: http_version_major + + Return HTTP major version. + + .. rb:attr_reader:: http_version_minor + + Return HTTP minor version. + + .. rb:attr_accessor:: status + + HTTP status code. It must be in the range [200, 999], + inclusive. The non-final status code is not supported in + mruby scripting at the moment. + + .. rb:attr_reader:: headers + + Return Ruby hash containing copy of response header fields. + Changing values in returned hash does not change response + header fields actually used in response processing. Use + :rb:meth:`Nghttpx::Response#add_header` or + :rb:meth:`Nghttpx::Response#set_header` to change response + header fields. + + .. rb:method:: add_header(key, value) + + Add header entry associated with key. The value can be single + string or array of string. It does not replace any existing + values associated with key. + + .. rb:method:: set_header(key, value) + + Set header entry associated with key. The value can be single + string or array of string. It replaces any existing values + associated with key. + + .. rb:method:: clear_headers + + Clear all existing response header fields. + + .. rb:method:: return(body) + + Return custom response *body* to a client. When this method + is called in request phase hook, the request is not forwarded + to the backend, and response phase hook for this request will + not be invoked. When this method is called in response phase + hook, response from backend server is canceled and discarded. + The status code and response header fields should be set + before using this method. To set status code, use + :rb:attr:`Nghttpx::Response#status`. If status code is not + set, 200 is used. To set response header fields, + :rb:meth:`Nghttpx::Response#add_header` and + :rb:meth:`Nghttpx::Response#set_header`. When this method is + invoked in response phase hook, the response headers are + filled with the ones received from backend server. To send + completely custom header fields, first call + :rb:meth:`Nghttpx::Response#clear_headers` to erase all + existing header fields, and then add required header fields. + It is an error to call this method twice for a given request. + + .. rb:method:: send_info(status, headers) + + Send non-final (informational) response to a client. *status* + must be in the range [100, 199], inclusive. *headers* is a + hash containing response header fields. Its key must be a + string, and the associated value must be either string or + array of strings. Since this is not a final response, even if + this method is invoked, request is still forwarded to a + backend unless :rb:meth:`Nghttpx::Response#return` is called. + This method can be called multiple times. It cannot be called + after :rb:meth:`Nghttpx::Response#return` is called. + +MRUBY EXAMPLES +~~~~~~~~~~~~~~ + +Modify request path: + +.. code-block:: ruby + + class App + def on_req(env) + env.req.path = "/apps#{env.req.path}" + end + end + + App.new + +Don't forget to instantiate and evaluate object at the last line. + +Restrict permission of viewing a content to a specific client +addresses: + +.. code-block:: ruby + + class App + def on_req(env) + allowed_clients = ["127.0.0.1", "::1"] + + if env.req.path.start_with?("/log/") && + !allowed_clients.include?(env.remote_addr) then + env.resp.status = 404 + env.resp.return "permission denied" + end + end + end + + App.new + +API ENDPOINTS +------------- + +nghttpx exposes API endpoints to manipulate it via HTTP based API. By +default, API endpoint is disabled. To enable it, add a dedicated +frontend for API using :option:`--frontend` option with "api" +parameter. All requests which come from this frontend address, will +be treated as API request. + +The response is normally JSON dictionary, and at least includes the +following keys: + +status + The status of the request processing. The following values are + defined: + + Success + The request was successful. + + Failure + The request was failed. No change has been made. + +code + HTTP status code + +Additionally, depending on the API endpoint, ``data`` key may be +present, and its value contains the API endpoint specific data. + +We wrote "normally", since nghttpx may return ordinal HTML response in +some cases where the error has occurred before reaching API endpoint +(e.g., header field is too large). + +The following section describes available API endpoints. + +POST /api/v1beta1/backendconfig +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This API replaces the current backend server settings with the +requested ones. The request method should be POST, but PUT is also +acceptable. The request body must be nghttpx configuration file +format. For configuration file format, see `FILES`_ section. The +line separator inside the request body must be single LF (0x0A). +Currently, only :option:`backend <--backend>` option is parsed, the +others are simply ignored. The semantics of this API is replace the +current backend with the backend options in request body. Describe +the desired set of backend severs, and nghttpx makes it happen. If +there is no :option:`backend <--backend>` option is found in request +body, the current set of backend is replaced with the :option:`backend +<--backend>` option's default value, which is ``127.0.0.1,80``. + +The replacement is done instantly without breaking existing +connections or requests. It also avoids any process creation as is +the case with hot swapping with signals. + +The one limitation is that only numeric IP address is allowed in +:option:`backend <--backend>` in request body unless "dns" parameter +is used while non numeric hostname is allowed in command-line or +configuration file is read using :option:`--conf`. + +GET /api/v1beta1/configrevision +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This API returns configuration revision of the current nghttpx. The +configuration revision is opaque string, and it changes after each +reloading by SIGHUP. With this API, an external application knows +that whether nghttpx has finished reloading its configuration by +comparing the configuration revisions between before and after +reloading. It is recommended to disable persistent (keep-alive) +connection for this purpose in order to avoid to send a request using +the reused connection which may bound to an old process. + +This API returns response including ``data`` key. Its value is JSON +object, and it contains at least the following key: + +configRevision + The configuration revision of the current nghttpx + + +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`h2load(1)` diff --git a/lib/nghttp2/doc/nghttpx.h2r b/lib/nghttp2/doc/nghttpx.h2r new file mode 100644 index 00000000000..12ac718e159 --- /dev/null +++ b/lib/nghttp2/doc/nghttpx.h2r @@ -0,0 +1,719 @@ +FILES +----- + +*/etc/nghttpx/nghttpx.conf* + The default configuration file path nghttpx searches at startup. + The configuration file path can be changed using :option:`--conf` + option. + + Those lines which are staring ``#`` are treated as comment. + + The option name in the configuration file is the long command-line + option name with leading ``--`` stripped (e.g., ``frontend``). Put + ``=`` between option name and value. Don't put extra leading or + trailing spaces. + + When specifying arguments including characters which have special + meaning to a shell, we usually use quotes so that shell does not + interpret them. When writing this configuration file, quotes for + this purpose must not be used. For example, specify additional + request header field, do this: + + .. code-block:: text + + add-request-header=foo: bar + + instead of: + + .. code-block:: text + + add-request-header="foo: bar" + + The options which do not take argument in the command-line *take* + argument in the configuration file. Specify ``yes`` as an argument + (e.g., ``http2-proxy=yes``). If other string is given, it is + ignored. + + To specify private key and certificate file which are given as + positional arguments in command-line, use ``private-key-file`` and + ``certificate-file``. + + :option:`--conf` option cannot be used in the configuration file and + will be ignored if specified. + +Error log + Error log is written to stderr by default. It can be configured + using :option:`--errorlog-file`. The format of log message is as + follows: + + (:) + + + It is a combination of date and time when the log is written. It + is in ISO 8601 format. + + + It is a main process ID. + + + It is a process ID which writes this log. + + + It is a thread ID which writes this log. It would be unique + within . + + and + They are source file name, and line number which produce this log. + + + It is a log message body. + +SIGNALS +------- + +SIGQUIT + Shutdown gracefully. First accept pending connections and stop + accepting connection. After all connections are handled, nghttpx + exits. + +SIGHUP + Reload configuration file given in :option:`--conf`. + +SIGUSR1 + Reopen log files. + +SIGUSR2 + + Fork and execute nghttpx. It will execute the binary in the same + path with same command-line arguments and environment variables. As + of nghttpx version 1.20.0, the new main process sends SIGQUIT to the + original main process when it is ready to serve requests. For the + earlier versions of nghttpx, user has to send SIGQUIT to the + original main process. + + The difference between SIGUSR2 (+ SIGQUIT) and SIGHUP is that former + is usually used to execute new binary, and the main process is newly + spawned. On the other hand, the latter just reloads configuration + file, and the same main process continues to exist. + +.. note:: + + nghttpx consists of multiple processes: one process for processing + these signals, and another one for processing requests. The former + spawns the latter. The former is called main process, and the + latter is called worker process. If neverbleed is enabled, the + worker process spawns neverbleed daemon process which does RSA key + processing. The above signal must be sent to the main process. If + the other processes received one of them, it is ignored. This + behaviour of these processes may change in the future release. In + other words, in the future release, the processes other than main + process may terminate upon the reception of these signals. + Therefore these signals should not be sent to the processes other + than main process. + +SERVER PUSH +----------- + +nghttpx supports HTTP/2 server push in default mode with Link header +field. nghttpx looks for Link header field (`RFC 5988 +`_) in response headers from +backend server and extracts URI-reference with parameter +``rel=preload`` (see `preload +`_) +and pushes those URIs to the frontend client. Here is a sample Link +header field to initiate server push: + +.. code-block:: text + + Link: ; rel=preload + Link: ; rel=preload + +Currently, the following restriction is applied for server push: + +1. The associated stream must have method "GET" or "POST". The + associated stream's status code must be 200. + +This limitation may be loosened in the future release. + +nghttpx also supports server push if both frontend and backend are +HTTP/2 in default mode. In this case, in addition to server push via +Link header field, server push from backend is forwarded to frontend +HTTP/2 session. + +HTTP/2 server push will be disabled if :option:`--http2-proxy` is +used. + +UNIX DOMAIN SOCKET +------------------ + +nghttpx supports UNIX domain socket with a filename for both frontend +and backend connections. + +Please note that current nghttpx implementation does not delete a +socket with a filename. And on start up, if nghttpx detects that the +specified socket already exists in the file system, nghttpx first +deletes it. However, if SIGUSR2 is used to execute new binary and +both old and new configurations use same filename, new binary does not +delete the socket and continues to use it. + +OCSP STAPLING +------------- + +OCSP query is done using external Python script +``fetch-ocsp-response``, which has been originally developed in Perl +as part of h2o project (https://github.com/h2o/h2o), and was +translated into Python. + +The script file is usually installed under +``$(prefix)/share/nghttp2/`` directory. The actual path to script can +be customized using :option:`--fetch-ocsp-response-file` option. + +If OCSP query is failed, previous OCSP response, if any, is continued +to be used. + +:option:`--fetch-ocsp-response-file` option provides wide range of +possibility to manage OCSP response. It can take an arbitrary script +or executable. The requirement is that it supports the command-line +interface of ``fetch-ocsp-response`` script, and it must return a +valid DER encoded OCSP response on success. It must return exit code +0 on success, and 75 for temporary error, and the other error code for +generic failure. For large cluster of servers, it is not efficient +for each server to perform OCSP query using ``fetch-ocsp-response``. +Instead, you can retrieve OCSP response in some way, and store it in a +disk or a shared database. Then specify a program in +:option:`--fetch-ocsp-response-file` to fetch it from those stores. +This could provide a way to share the OCSP response between fleet of +servers, and also any OCSP query strategy can be applied which may be +beyond the ability of nghttpx itself or ``fetch-ocsp-response`` +script. + +TLS SESSION RESUMPTION +---------------------- + +nghttpx supports TLS session resumption through both session ID and +session ticket. + +SESSION ID RESUMPTION +~~~~~~~~~~~~~~~~~~~~~ + +By default, session ID is shared by all worker threads. + +If :option:`--tls-session-cache-memcached` is given, nghttpx will +insert serialized session data to memcached with +``nghttpx:tls-session-cache:`` + lowercase hex string of session ID +as a memcached entry key, with expiry time 12 hours. Session timeout +is set to 12 hours. + +By default, connections to memcached server are not encrypted. To +enable encryption, use ``tls`` keyword in +:option:`--tls-session-cache-memcached` option. + +TLS SESSION TICKET RESUMPTION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, session ticket is shared by all worker threads. The +automatic key rotation is also enabled by default. Every an hour, new +encryption key is generated, and previous encryption key becomes +decryption only key. We set session timeout to 12 hours, and thus we +keep at most 12 keys. + +If :option:`--tls-ticket-key-memcached` is given, encryption keys are +retrieved from memcached. nghttpx just reads keys from memcached; one +has to deploy key generator program to update keys frequently (e.g., +every 1 hour). The example key generator tlsticketupdate.go is +available under contrib directory in nghttp2 archive. The memcached +entry key is ``nghttpx:tls-ticket-key``. The data format stored in +memcached is the binary format described below: + +.. code-block:: text + + +--------------+-------+----------------+ + | VERSION (4) |LEN (2)|KEY(48 or 80) ... + +--------------+-------+----------------+ + ^ | + | | + +------------------------+ + (LEN, KEY) pair can be repeated + +All numbers in the above figure is bytes. All integer fields are +network byte order. + +First 4 bytes integer VERSION field, which must be 1. The 2 bytes +integer LEN field gives the length of following KEY field, which +contains key. If :option:`--tls-ticket-key-cipher`\=aes-128-cbc is +used, LEN must be 48. If +:option:`--tls-ticket-key-cipher`\=aes-256-cbc is used, LEN must be +80. LEN and KEY pair can be repeated multiple times to store multiple +keys. The key appeared first is used as encryption key. All the +remaining keys are used as decryption only. + +By default, connections to memcached server are not encrypted. To +enable encryption, use ``tls`` keyword in +:option:`--tls-ticket-key-memcached` option. + +If :option:`--tls-ticket-key-file` is given, encryption key is read +from the given file. In this case, nghttpx does not rotate key +automatically. To rotate key, one has to restart nghttpx (see +SIGNALS). + +CERTIFICATE TRANSPARENCY +------------------------ + +nghttpx supports TLS ``signed_certificate_timestamp`` extension (`RFC +6962 `_). The relevant options +are :option:`--tls-sct-dir` and ``sct-dir`` parameter in +:option:`--subcert`. They takes a directory, and nghttpx reads all +files whose extension is ``.sct`` under the directory. The ``*.sct`` +files are encoded as ``SignedCertificateTimestamp`` struct described +in `section 3.2 of RFC 69662 +`_. This format is +the same one used by `nginx-ct +`_ and `mod_ssl_ct +`_. +`ct-submit `_ can be +used to submit certificates to log servers, and obtain the +``SignedCertificateTimestamp`` struct which can be used with nghttpx. + +MRUBY SCRIPTING +--------------- + +.. warning:: + + The current mruby extension API is experimental and not frozen. The + API is subject to change in the future release. + +.. warning:: + + Almost all string value returned from method, or attribute is a + fresh new mruby string, which involves memory allocation, and + copies. Therefore, it is strongly recommended to store a return + value in a local variable, and use it, instead of calling method or + accessing attribute repeatedly. + +nghttpx allows users to extend its capability using mruby scripts. +nghttpx has 2 hook points to execute mruby script: request phase and +response phase. The request phase hook is invoked after all request +header fields are received from client. The response phase hook is +invoked after all response header fields are received from backend +server. These hooks allows users to modify header fields, or common +HTTP variables, like authority or request path, and even return custom +response without forwarding request to backend servers. + +There are 2 levels of mruby script invocations: global and +per-pattern. The global mruby script is set by :option:`--mruby-file` +option and is called for all requests. The per-pattern mruby script +is set by "mruby" parameter in :option:`-b` option. It is invoked for +a request which matches the particular pattern. The order of hook +invocation is: global request phase hook, per-pattern request phase +hook, per-pattern response phase hook, and finally global response +phase hook. If a hook returns a response, any later hooks are not +invoked. The global request hook is invoked before the pattern +matching is made and changing request path may affect the pattern +matching. + +Please note that request and response hooks of per-pattern mruby +script for a single request might not come from the same script. This +might happen after a request hook is executed, backend failed for some +reason, and at the same time, backend configuration is replaced by API +request, and then the request uses new configuration on retry. The +response hook from new configuration, if it is specified, will be +invoked. + +The all mruby script will be evaluated once per thread on startup, and +it must instantiate object and evaluate it as the return value (e.g., +``App.new``). This object is called app object. If app object +defines ``on_req`` method, it is called with :rb:class:`Nghttpx::Env` +object on request hook. Similarly, if app object defines ``on_resp`` +method, it is called with :rb:class:`Nghttpx::Env` object on response +hook. For each method invocation, user can can access +:rb:class:`Nghttpx::Request` and :rb:class:`Nghttpx::Response` objects +via :rb:attr:`Nghttpx::Env#req` and :rb:attr:`Nghttpx::Env#resp` +respectively. + +.. rb:module:: Nghttpx + +.. rb:const:: REQUEST_PHASE + + Constant to represent request phase. + +.. rb:const:: RESPONSE_PHASE + + Constant to represent response phase. + +.. rb:class:: Env + + Object to represent current request specific context. + + .. rb:attr_reader:: req + + Return :rb:class:`Request` object. + + .. rb:attr_reader:: resp + + Return :rb:class:`Response` object. + + .. rb:attr_reader:: ctx + + Return Ruby hash object. It persists until request finishes. + So values set in request phase hook can be retrieved in + response phase hook. + + .. rb:attr_reader:: phase + + Return the current phase. + + .. rb:attr_reader:: remote_addr + + Return IP address of a remote client. If connection is made + via UNIX domain socket, this returns the string "localhost". + + .. rb:attr_reader:: server_addr + + Return address of server that accepted the connection. This + is a string which specified in :option:`--frontend` option, + excluding port number, and not a resolved IP address. For + UNIX domain socket, this is a path to UNIX domain socket. + + .. rb:attr_reader:: server_port + + Return port number of the server frontend which accepted the + connection from client. + + .. rb:attr_reader:: tls_used + + Return true if TLS is used on the connection. + + .. rb:attr_reader:: tls_sni + + Return the TLS SNI value which client sent in this connection. + + .. rb:attr_reader:: tls_client_fingerprint_sha256 + + Return the SHA-256 fingerprint of a client certificate. + + .. rb:attr_reader:: tls_client_fingerprint_sha1 + + Return the SHA-1 fingerprint of a client certificate. + + .. rb:attr_reader:: tls_client_issuer_name + + Return the issuer name of a client certificate. + + .. rb:attr_reader:: tls_client_subject_name + + Return the subject name of a client certificate. + + .. rb:attr_reader:: tls_client_serial + + Return the serial number of a client certificate. + + .. rb:attr_reader:: tls_client_not_before + + Return the start date of a client certificate in seconds since + the epoch. + + .. rb:attr_reader:: tls_client_not_after + + Return the end date of a client certificate in seconds since + the epoch. + + .. rb:attr_reader:: tls_cipher + + Return a TLS cipher negotiated in this connection. + + .. rb:attr_reader:: tls_protocol + + Return a TLS protocol version negotiated in this connection. + + .. rb:attr_reader:: tls_session_id + + Return a session ID for this connection in hex string. + + .. rb:attr_reader:: tls_session_reused + + Return true if, and only if a SSL/TLS session is reused. + + .. rb:attr_reader:: alpn + + Return ALPN identifier negotiated in this connection. + + .. rb:attr_reader:: tls_handshake_finished + + Return true if SSL/TLS handshake has finished. If it returns + false in the request phase hook, the request is received in + TLSv1.3 early data (0-RTT) and might be vulnerable to the + replay attack. nghttpx will send Early-Data header field to + backend servers to indicate this. + +.. rb:class:: Request + + Object to represent request from client. The modification to + Request object is allowed only in request phase hook. + + .. rb:attr_reader:: http_version_major + + Return HTTP major version. + + .. rb:attr_reader:: http_version_minor + + Return HTTP minor version. + + .. rb:attr_accessor:: method + + HTTP method. On assignment, copy of given value is assigned. + We don't accept arbitrary method name. We will document them + later, but well known methods, like GET, PUT and POST, are all + supported. + + .. rb:attr_accessor:: authority + + Authority (i.e., example.org), including optional port + component . On assignment, copy of given value is assigned. + + .. rb:attr_accessor:: scheme + + Scheme (i.e., http, https). On assignment, copy of given + value is assigned. + + .. rb:attr_accessor:: path + + Request path, including query component (i.e., /index.html). + On assignment, copy of given value is assigned. The path does + not include authority component of URI. This may include + query component. nghttpx makes certain normalization for + path. It decodes percent-encoding for unreserved characters + (see https://tools.ietf.org/html/rfc3986#section-2.3), and + resolves ".." and ".". But it may leave characters which + should be percent-encoded as is. So be careful when comparing + path against desired string. + + .. rb:attr_reader:: headers + + Return Ruby hash containing copy of request header fields. + Changing values in returned hash does not change request + header fields actually used in request processing. Use + :rb:meth:`Nghttpx::Request#add_header` or + :rb:meth:`Nghttpx::Request#set_header` to change request + header fields. + + .. rb:method:: add_header(key, value) + + Add header entry associated with key. The value can be single + string or array of string. It does not replace any existing + values associated with key. + + .. rb:method:: set_header(key, value) + + Set header entry associated with key. The value can be single + string or array of string. It replaces any existing values + associated with key. + + .. rb:method:: clear_headers + + Clear all existing request header fields. + + .. rb:method:: push(uri) + + Initiate to push resource identified by *uri*. Only HTTP/2 + protocol supports this feature. For the other protocols, this + method is noop. *uri* can be absolute URI, absolute path or + relative path to the current request. For absolute or + relative path, scheme and authority are inherited from the + current request. Currently, method is always GET. nghttpx + will issue request to backend servers to fulfill this request. + The request and response phase hooks will be called for pushed + resource as well. + +.. rb:class:: Response + + Object to represent response from backend server. + + .. rb:attr_reader:: http_version_major + + Return HTTP major version. + + .. rb:attr_reader:: http_version_minor + + Return HTTP minor version. + + .. rb:attr_accessor:: status + + HTTP status code. It must be in the range [200, 999], + inclusive. The non-final status code is not supported in + mruby scripting at the moment. + + .. rb:attr_reader:: headers + + Return Ruby hash containing copy of response header fields. + Changing values in returned hash does not change response + header fields actually used in response processing. Use + :rb:meth:`Nghttpx::Response#add_header` or + :rb:meth:`Nghttpx::Response#set_header` to change response + header fields. + + .. rb:method:: add_header(key, value) + + Add header entry associated with key. The value can be single + string or array of string. It does not replace any existing + values associated with key. + + .. rb:method:: set_header(key, value) + + Set header entry associated with key. The value can be single + string or array of string. It replaces any existing values + associated with key. + + .. rb:method:: clear_headers + + Clear all existing response header fields. + + .. rb:method:: return(body) + + Return custom response *body* to a client. When this method + is called in request phase hook, the request is not forwarded + to the backend, and response phase hook for this request will + not be invoked. When this method is called in response phase + hook, response from backend server is canceled and discarded. + The status code and response header fields should be set + before using this method. To set status code, use + :rb:attr:`Nghttpx::Response#status`. If status code is not + set, 200 is used. To set response header fields, + :rb:meth:`Nghttpx::Response#add_header` and + :rb:meth:`Nghttpx::Response#set_header`. When this method is + invoked in response phase hook, the response headers are + filled with the ones received from backend server. To send + completely custom header fields, first call + :rb:meth:`Nghttpx::Response#clear_headers` to erase all + existing header fields, and then add required header fields. + It is an error to call this method twice for a given request. + + .. rb:method:: send_info(status, headers) + + Send non-final (informational) response to a client. *status* + must be in the range [100, 199], inclusive. *headers* is a + hash containing response header fields. Its key must be a + string, and the associated value must be either string or + array of strings. Since this is not a final response, even if + this method is invoked, request is still forwarded to a + backend unless :rb:meth:`Nghttpx::Response#return` is called. + This method can be called multiple times. It cannot be called + after :rb:meth:`Nghttpx::Response#return` is called. + +MRUBY EXAMPLES +~~~~~~~~~~~~~~ + +Modify request path: + +.. code-block:: ruby + + class App + def on_req(env) + env.req.path = "/apps#{env.req.path}" + end + end + + App.new + +Don't forget to instantiate and evaluate object at the last line. + +Restrict permission of viewing a content to a specific client +addresses: + +.. code-block:: ruby + + class App + def on_req(env) + allowed_clients = ["127.0.0.1", "::1"] + + if env.req.path.start_with?("/log/") && + !allowed_clients.include?(env.remote_addr) then + env.resp.status = 404 + env.resp.return "permission denied" + end + end + end + + App.new + +API ENDPOINTS +------------- + +nghttpx exposes API endpoints to manipulate it via HTTP based API. By +default, API endpoint is disabled. To enable it, add a dedicated +frontend for API using :option:`--frontend` option with "api" +parameter. All requests which come from this frontend address, will +be treated as API request. + +The response is normally JSON dictionary, and at least includes the +following keys: + +status + The status of the request processing. The following values are + defined: + + Success + The request was successful. + + Failure + The request was failed. No change has been made. + +code + HTTP status code + +Additionally, depending on the API endpoint, ``data`` key may be +present, and its value contains the API endpoint specific data. + +We wrote "normally", since nghttpx may return ordinal HTML response in +some cases where the error has occurred before reaching API endpoint +(e.g., header field is too large). + +The following section describes available API endpoints. + +POST /api/v1beta1/backendconfig +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This API replaces the current backend server settings with the +requested ones. The request method should be POST, but PUT is also +acceptable. The request body must be nghttpx configuration file +format. For configuration file format, see `FILES`_ section. The +line separator inside the request body must be single LF (0x0A). +Currently, only :option:`backend <--backend>` option is parsed, the +others are simply ignored. The semantics of this API is replace the +current backend with the backend options in request body. Describe +the desired set of backend severs, and nghttpx makes it happen. If +there is no :option:`backend <--backend>` option is found in request +body, the current set of backend is replaced with the :option:`backend +<--backend>` option's default value, which is ``127.0.0.1,80``. + +The replacement is done instantly without breaking existing +connections or requests. It also avoids any process creation as is +the case with hot swapping with signals. + +The one limitation is that only numeric IP address is allowed in +:option:`backend <--backend>` in request body unless "dns" parameter +is used while non numeric hostname is allowed in command-line or +configuration file is read using :option:`--conf`. + +GET /api/v1beta1/configrevision +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This API returns configuration revision of the current nghttpx. The +configuration revision is opaque string, and it changes after each +reloading by SIGHUP. With this API, an external application knows +that whether nghttpx has finished reloading its configuration by +comparing the configuration revisions between before and after +reloading. It is recommended to disable persistent (keep-alive) +connection for this purpose in order to avoid to send a request using +the reused connection which may bound to an old process. + +This API returns response including ``data`` key. Its value is JSON +object, and it contains at least the following key: + +configRevision + The configuration revision of the current nghttpx + + +SEE ALSO +-------- + +:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`h2load(1)` diff --git a/lib/nghttp2/doc/package_README.rst.in b/lib/nghttp2/doc/package_README.rst.in new file mode 100644 index 00000000000..dfa6b2d4152 --- /dev/null +++ b/lib/nghttp2/doc/package_README.rst.in @@ -0,0 +1 @@ +.. include:: @top_srcdir@/README.rst diff --git a/lib/nghttp2/doc/programmers-guide.rst b/lib/nghttp2/doc/programmers-guide.rst new file mode 100644 index 00000000000..820cd204951 --- /dev/null +++ b/lib/nghttp2/doc/programmers-guide.rst @@ -0,0 +1,526 @@ +Programmers' Guide +================== + +Architecture +------------ + +The most notable point in nghttp2 library architecture is it does not +perform any I/O. nghttp2 only performs HTTP/2 protocol stuff based on +input byte strings. It will call callback functions set by +applications while processing input. The output of nghttp2 is just +byte string. An application is responsible to send these output to +the remote peer. The callback functions may be called while producing +output. + +Not doing I/O makes embedding nghttp2 library in the existing code +base very easy. Usually, the existing applications have its own I/O +event loops. It is very hard to use nghttp2 in that situation if +nghttp2 does its own I/O. It also makes light weight language wrapper +for nghttp2 easy with the same reason. The down side is that an +application author has to write more code to write complete +application using nghttp2. This is especially true for simple "toy" +application. For the real applications, however, this is not the +case. This is because you probably want to support HTTP/1 which +nghttp2 does not provide, and to do that, you will need to write your +own HTTP/1 stack or use existing third-party library, and bind them +together with nghttp2 and I/O event loop. In this point, not +performing I/O in nghttp2 has more point than doing it. + +The primary object that an application uses is :type:`nghttp2_session` +object, which is opaque struct and its details are hidden in order to +ensure the upgrading its internal architecture without breaking the +backward compatibility. An application can set callbacks to +:type:`nghttp2_session` object through the dedicated object and +functions, and it also interacts with it via many API function calls. + +An application can create as many :type:`nghttp2_session` object as it +wants. But single :type:`nghttp2_session` object must be used by a +single thread at the same time. This is not so hard to enforce since +most event-based architecture applications use is single thread per +core, and handling one connection I/O is done by single thread. + +To feed input to :type:`nghttp2_session` object, one can use +`nghttp2_session_recv()` or `nghttp2_session_mem_recv()` functions. +They behave similarly, and the difference is that +`nghttp2_session_recv()` will use :type:`nghttp2_read_callback` to get +input. On the other hand, `nghttp2_session_mem_recv()` will take +input as its parameter. If in doubt, use `nghttp2_session_mem_recv()` +since it is simpler, and could be faster since it avoids calling +callback function. + +To get output from :type:`nghttp2_session` object, one can use +`nghttp2_session_send()` or `nghttp2_session_mem_send()`. The +difference between them is that the former uses +:type:`nghttp2_send_callback` to pass output to an application. On +the other hand, the latter returns the output to the caller. If in +doubt, use `nghttp2_session_mem_send()` since it is simpler. But +`nghttp2_session_send()` might be easier to use if the output buffer +an application has is fixed sized. + +In general, an application should call `nghttp2_session_mem_send()` +when it gets input from underlying connection. Since there is great +chance to get something pushed into transmission queue while the call +of `nghttp2_session_mem_send()`, it is recommended to call +`nghttp2_session_mem_recv()` after `nghttp2_session_mem_send()`. + +There is a question when we are safe to close HTTP/2 session without +waiting for the closure of underlying connection. We offer 2 API +calls for this: `nghttp2_session_want_read()` and +`nghttp2_session_want_write()`. If they both return 0, application +can destroy :type:`nghttp2_session`, and then close the underlying +connection. But make sure that the buffered output has been +transmitted to the peer before closing the connection when +`nghttp2_session_mem_send()` is used, since +`nghttp2_session_want_write()` does not take into account the +transmission of the buffered data outside of :type:`nghttp2_session`. + +Includes +-------- + +To use the public APIs, include ``nghttp2/nghttp2.h``:: + + #include + +The header files are also available online: :doc:`nghttp2.h` and +:doc:`nghttp2ver.h`. + +Remarks +------- + +Do not call `nghttp2_session_send()`, `nghttp2_session_mem_send()`, +`nghttp2_session_recv()` or `nghttp2_session_mem_recv()` from the +nghttp2 callback functions directly or indirectly. It will lead to the +crash. You can submit requests or frames in the callbacks then call +these functions outside the callbacks. + +`nghttp2_session_send()` and `nghttp2_session_mem_send()` send first +24 bytes of client magic string (MAGIC) +(:macro:`NGHTTP2_CLIENT_MAGIC`) on client configuration. The +applications are responsible to send SETTINGS frame as part of +connection preface using `nghttp2_submit_settings()`. Similarly, +`nghttp2_session_recv()` and `nghttp2_session_mem_recv()` consume +MAGIC on server configuration unless +`nghttp2_option_set_no_recv_client_magic()` is used with nonzero +option value. + +.. _http-messaging: + +HTTP Messaging +-------------- + +By default, nghttp2 library checks HTTP messaging rules described in +`HTTP/2 specification, section 8 +`_. Everything +described in that section is not validated however. We briefly +describe what the library does in this area. In the following +description, without loss of generality we omit CONTINUATION frame +since they must follow HEADERS frame and are processed atomically. In +other words, they are just one big HEADERS frame. To disable these +validations, use `nghttp2_option_set_no_http_messaging()`. Please +note that disabling this feature does not change the fundamental +client and server model of HTTP. That is, even if the validation is +disabled, only client can send requests. + +For HTTP request, including those carried by PUSH_PROMISE, HTTP +message starts with one HEADERS frame containing request headers. It +is followed by zero or more DATA frames containing request body, which +is followed by zero or one HEADERS containing trailer headers. The +request headers must include ":scheme", ":method" and ":path" pseudo +header fields unless ":method" is not "CONNECT". ":authority" is +optional, but nghttp2 requires either ":authority" or "Host" header +field must be present. If ":method" is "CONNECT", the request headers +must include ":method" and ":authority" and must omit ":scheme" and +":path". + +For HTTP response, HTTP message starts with zero or more HEADERS +frames containing non-final response (status code 1xx). They are +followed by one HEADERS frame containing final response headers +(non-1xx). It is followed by zero or more DATA frames containing +response body, which is followed by zero or one HEADERS containing +trailer headers. The non-final and final response headers must +contain ":status" pseudo header field containing 3 digits only. + +All request and response headers must include exactly one valid value +for each pseudo header field. Additionally nghttp2 requires all +request headers must not include more than one "Host" header field. + +HTTP/2 prohibits connection-specific header fields. The following +header fields must not appear: "Connection", "Keep-Alive", +"Proxy-Connection", "Transfer-Encoding" and "Upgrade". Additionally, +"TE" header field must not include any value other than "trailers". + +Each header field name and value must obey the field-name and +field-value production rules described in `RFC 7230, section +3.2. `_. +Additionally, all field name must be lower cased. The invalid header +fields are treated as stream error, and that stream is reset. If +application wants to treat these headers in their own way, use +`nghttp2_on_invalid_header_callback +`_. + +For "http" or "https" URIs, ":path" pseudo header fields must start +with "/". The only exception is OPTIONS request, in that case, "*" is +allowed in ":path" pseudo header field to represent system-wide +OPTIONS request. + +With the above validations, nghttp2 library guarantees that header +field name passed to `nghttp2_on_header_callback()` is not empty. +Also required pseudo headers are all present and not empty. + +nghttp2 enforces "Content-Length" validation as well. All request or +response headers must not contain more than one "Content-Length" +header field. If "Content-Length" header field is present, it must be +parsed as 64 bit signed integer. The sum of data length in the +following DATA frames must match with the number in "Content-Length" +header field if it is present (this does not include padding bytes). + +RFC 7230 says that server must not send "Content-Length" in any +response with 1xx, and 204 status code. It also says that +"Content-Length" is not allowed in any response with 200 status code +to a CONNECT request. nghttp2 enforces them as well. + +Any deviation results in stream error of type PROTOCOL_ERROR. If +error is found in PUSH_PROMISE frame, stream error is raised against +promised stream. + +The order of transmission of the HTTP/2 frames +---------------------------------------------- + +This section describes the internals of libnghttp2 about the +scheduling of transmission of HTTP/2 frames. This is pretty much +internal stuff, so the details could change in the future versions of +the library. + +libnghttp2 categorizes HTTP/2 frames into 4 categories: urgent, +regular, syn_stream, and data in the order of higher priority. + +The urgent category includes PING and SETTINGS. They are sent with +highest priority. The order inside the category is FIFO. + +The regular category includes frames other than PING, SETTINGS, DATA, +and HEADERS which does not create stream (which counts toward +concurrent stream limit). The order inside the category is FIFO. + +The syn_stream category includes HEADERS frame which creates stream, +that counts toward the concurrent stream limit. + +The data category includes DATA frame, and the scheduling among DATA +frames are determined by HTTP/2 dependency tree. + +If the application wants to send frames in the specific order, and the +default transmission order does not fit, it has to schedule frames by +itself using the callbacks (e.g., +:type:`nghttp2_on_frame_send_callback`). + +RST_STREAM has special side effect when it is submitted by +`nghttp2_submit_rst_stream()`. It cancels all pending HEADERS and +DATA frames whose stream ID matches the one in the RST_STREAM frame. +This may cause unexpected behaviour for the application in some cases. +For example, suppose that application wants to send RST_STREAM after +sending response HEADERS and DATA. Because of the reason we mentioned +above, the following code does not work: + +.. code-block:: c + + nghttp2_submit_response(...) + nghttp2_submit_rst_stream(...) + +RST_STREAM cancels HEADERS (and DATA), and just RST_STREAM is sent. +The correct way is use :type:`nghttp2_on_frame_send_callback`, and +after HEADERS and DATA frames are sent, issue +`nghttp2_submit_rst_stream()`. FYI, +:type:`nghttp2_on_frame_not_send_callback` tells you why frames are +not sent. + +Implement user defined HTTP/2 non-critical extensions +----------------------------------------------------- + +As of nghttp2 v1.8.0, we have added HTTP/2 non-critical extension +framework, which lets application send and receive user defined custom +HTTP/2 non-critical extension frames. nghttp2 also offers built-in +functionality to send and receive official HTTP/2 extension frames +(e.g., ALTSVC frame). For these built-in handler, refer to the next +section. + +To send extension frame, use `nghttp2_submit_extension()`, and +implement :type:`nghttp2_pack_extension_callback`. The callback +implements how to encode data into wire format. The callback must be +set to :type:`nghttp2_session_callbacks` using +`nghttp2_session_callbacks_set_pack_extension_callback()`. + +For example, we will illustrate how to send `ALTSVC +`_ frame. + +.. code-block:: c + + typedef struct { + const char *origin; + const char *field; + } alt_svc; + + ssize_t pack_extension_callback(nghttp2_session *session, uint8_t *buf, + size_t len, const nghttp2_frame *frame, + void *user_data) { + const alt_svc *altsvc = (const alt_svc *)frame->ext.payload; + size_t originlen = strlen(altsvc->origin); + size_t fieldlen = strlen(altsvc->field); + + uint8_t *p; + + if (len < 2 + originlen + fieldlen || originlen > 0xffff) { + return NGHTTP2_ERR_CANCEL; + } + + p = buf; + *p++ = originlen >> 8; + *p++ = originlen & 0xff; + memcpy(p, altsvc->origin, originlen); + p += originlen; + memcpy(p, altsvc->field, fieldlen); + p += fieldlen; + + return p - buf; + } + +This implements :type:`nghttp2_pack_extension_callback`. We have to +set this callback to :type:`nghttp2_session_callbacks`: + +.. code-block:: c + + nghttp2_session_callbacks_set_pack_extension_callback( + callbacks, pack_extension_callback); + +To send ALTSVC frame, call `nghttp2_submit_extension()`: + +.. code-block:: c + + static const alt_svc altsvc = {"example.com", "h2=\":8000\""}; + + nghttp2_submit_extension(session, 0xa, NGHTTP2_FLAG_NONE, 0, + (void *)&altsvc); + +Notice that ALTSVC is use frame type ``0xa``. + +To receive extension frames, implement 2 callbacks: +:type:`nghttp2_unpack_extension_callback` and +:type:`nghttp2_on_extension_chunk_recv_callback`. +:type:`nghttp2_unpack_extension_callback` implements the way how to +decode wire format. :type:`nghttp2_on_extension_chunk_recv_callback` +implements how to buffer the incoming extension payload. These +callbacks must be set using +`nghttp2_session_callbacks_set_unpack_extension_callback()` and +`nghttp2_session_callbacks_set_on_extension_chunk_recv_callback()` +respectively. The application also must tell the library which +extension frame type it is willing to receive using +`nghttp2_option_set_user_recv_extension_type()`. Note that the +application has to create :type:`nghttp2_option` object for that +purpose, and initialize session with it. + +We use ALTSVC again to illustrate how to receive extension frames. We +use different ``alt_svc`` struct than the previous one. + +First implement 2 callbacks. We store incoming ALTSVC payload to +global variable ``altsvc_buffer``. Don't do this in production code +since this is not thread safe: + +.. code-block:: c + + typedef struct { + const uint8_t *origin; + size_t originlen; + const uint8_t *field; + size_t fieldlen; + } alt_svc; + + /* buffers incoming ALTSVC payload */ + uint8_t altsvc_buffer[4096]; + /* The length of byte written to altsvc_buffer */ + size_t altsvc_bufferlen = 0; + + int on_extension_chunk_recv_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + const uint8_t *data, size_t len, + void *user_data) { + if (sizeof(altsvc_buffer) < altsvc_bufferlen + len) { + altsvc_bufferlen = 0; + return NGHTTP2_ERR_CANCEL; + } + + memcpy(altsvc_buffer + altsvc_bufferlen, data, len); + altsvc_bufferlen += len; + + return 0; + } + + int unpack_extension_callback(nghttp2_session *session, void **payload, + const nghttp2_frame_hd *hd, void *user_data) { + uint8_t *origin, *field; + size_t originlen, fieldlen; + uint8_t *p, *end; + alt_svc *altsvc; + + if (altsvc_bufferlen < 2) { + altsvc_bufferlen = 0; + return NGHTTP2_ERR_CANCEL; + } + + p = altsvc_buffer; + end = altsvc_buffer + altsvc_bufferlen; + + originlen = ((*p) << 8) + *(p + 1); + p += 2; + + if (p + originlen > end) { + altsvc_bufferlen = 0; + return NGHTTP2_ERR_CANCEL; + } + + origin = p; + field = p + originlen; + fieldlen = end - field; + + altsvc = (alt_svc *)malloc(sizeof(alt_svc)); + altsvc->origin = origin; + altsvc->originlen = originlen; + altsvc->field = field; + altsvc->fieldlen = fieldlen; + + *payload = altsvc; + + altsvc_bufferlen = 0; + + return 0; + } + +Set these callbacks to :type:`nghttp2_session_callbacks`: + +.. code-block:: c + + nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + callbacks, on_extension_chunk_recv_callback); + + nghttp2_session_callbacks_set_unpack_extension_callback( + callbacks, unpack_extension_callback); + + +In ``unpack_extension_callback`` above, we set unpacked ``alt_svc`` +object to ``*payload``. nghttp2 library then, calls +:type:`nghttp2_on_frame_recv_callback`, and ``*payload`` will be +available as ``frame->ext.payload``: + +.. code-block:: c + + int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + + switch (frame->hd.type) { + ... + case 0xa: { + alt_svc *altsvc = (alt_svc *)frame->ext.payload; + fprintf(stderr, "ALTSVC frame received\n"); + fprintf(stderr, " origin: %.*s\n", (int)altsvc->originlen, altsvc->origin); + fprintf(stderr, " field : %.*s\n", (int)altsvc->fieldlen, altsvc->field); + free(altsvc); + break; + } + } + + return 0; + } + +Finally, application should set the extension frame types it is +willing to receive: + +.. code-block:: c + + nghttp2_option_set_user_recv_extension_type(option, 0xa); + +The :type:`nghttp2_option` must be set to :type:`nghttp2_session` on +its creation: + +.. code-block:: c + + nghttp2_session_client_new2(&session, callbacks, user_data, option); + +How to use built-in HTTP/2 extension frame handlers +--------------------------------------------------- + +In the previous section, we talked about the user defined HTTP/2 +extension frames. In this section, we talk about HTTP/2 extension +frame support built into nghttp2 library. + +As of this writing, nghttp2 supports ALTSVC extension frame. To send +ALTSVC frame, use `nghttp2_submit_altsvc()` function. + +To receive ALTSVC frame through built-in functionality, application +has to use `nghttp2_option_set_builtin_recv_extension_type()` to +indicate the willingness of receiving ALTSVC frame: + +.. code-block:: c + + nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC); + +This is very similar to the case when we used to receive user defined +frames. + +If the same frame type is set using +`nghttp2_option_set_builtin_recv_extension_type()` and +`nghttp2_option_set_user_recv_extension_type()`, the latter takes +precedence. Application can implement its own frame handler rather +than using built-in handler. + +The :type:`nghttp2_option` must be set to :type:`nghttp2_session` on +its creation, like so: + +.. code-block:: c + + nghttp2_session_client_new2(&session, callbacks, user_data, option); + +When ALTSVC is received, :type:`nghttp2_on_frame_recv_callback` will +be called as usual. + +Stream priorities +----------------- + +By default, the stream prioritization scheme described in :rfc:`7540` +is used. This scheme has been formally deprecated by :rfc:`9113`. In +order to disable it, send +:enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` of +value of 1 via `nghttp2_submit_settings()`. This settings ID is +defined by :rfc:`9218`. The sender of this settings value disables +RFC 7540 priorities, and instead it enables RFC 9218 Extensible +Prioritization Scheme. This new prioritization scheme has 2 methods +to convey the stream priorities to a remote endpoint: Priority header +field and PRIORITY_UPDATE frame. nghttp2 supports both methods. In +order to receive and process PRIORITY_UPDATE frame, server has to call +``nghttp2_option_set_builtin_recv_extension_type(option, +NGHTTP2_PRIORITY_UPDATE)`` (see the above section), and pass the +option to `nghttp2_session_server_new2()` or +`nghttp2_session_server_new3()` to create a server session. Client +can send Priority header field via `nghttp2_submit_request()`. It can +also send PRIORITY_UPDATE frame via +`nghttp2_submit_priority_update()`. Server processes Priority header +field in a request header field and updates the stream priority unless +HTTP messaging rule enforcement is disabled (see +`nghttp2_option_set_no_http_messaging()`). + +For the purpose of smooth migration from RFC 7540 priorities, client +is advised to send +:enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` of +value of 1. Until it receives the first server SETTINGS frame, it can +send both RFC 7540 and RFC 9128 priority signals. If client receives +SETTINGS_NO_RFC7540_PRIORITIES of value of 0, or it is omitted , +client stops sending PRIORITY_UPDATE frame. Priority header field +will be sent in anyway since it is an end-to-end signal. If +SETTINGS_NO_RFC7540_PRIORITIES of value of 1 is received, client stops +sending RFC 7540 priority signals. This is the advice described in +:rfc:`9218#section-2.1.1`. + +Server has an optional mechanism to fallback to RFC 7540 priorities. +By default, if server sends SETTINGS_NO_RFC7540_PRIORITIES of value of +1, it completely disables RFC 7540 priorities and no fallback. By +setting nonzero value to +`nghttp2_option_set_server_fallback_rfc7540_priorities()`, server +falls back to RFC 7540 priorities if it sends +SETTINGS_NO_RFC7540_PRIORITIES value of value of 1, and client omits +SETTINGS_NO_RFC7540_PRIORITIES in its SETTINGS frame. diff --git a/lib/nghttp2/doc/security.rst b/lib/nghttp2/doc/security.rst new file mode 100644 index 00000000000..00b0c9cb054 --- /dev/null +++ b/lib/nghttp2/doc/security.rst @@ -0,0 +1 @@ +.. include:: ../doc/sources/security.rst diff --git a/lib/nghttp2/doc/sources/security.rst b/lib/nghttp2/doc/sources/security.rst new file mode 100644 index 00000000000..5a8fcd07966 --- /dev/null +++ b/lib/nghttp2/doc/sources/security.rst @@ -0,0 +1,33 @@ +Security Process +================ + +If you find a vulnerability in our software, please send the email to +"tatsuhiro.t at gmail dot com" about its details instead of submitting +issues on github issue page. It is a standard practice not to +disclose vulnerability information publicly until a fixed version is +released, or mitigation is worked out. In the future, we may setup a +dedicated mail address for this purpose. + +If we identify that the reported issue is really a vulnerability, we +open a new security advisory draft using `GitHub security feature +`_ and discuss the +mitigation and bug fixes there. The fixes are committed to the +private repository. + +We write the security advisory and get CVE number from GitHub +privately. We also discuss the disclosure date to the public. + +We make a new release with the fix at the same time when the +vulnerability is disclosed to public. + +At least 7 days before the public disclosure date, we open a new issue +on `nghttp2 issue tracker +`_ which notifies that the +upcoming release will have a security fix. The ``SECURITY`` label is +attached to this kind of issue. The issue is not opened if a +vulnerability is already disclosed, and it is publicly known that +nghttp2 is affected by that. + +Before few hours of new release, we merge the fixes to the master +branch (and/or a release branch if necessary) and make a new release. +Security advisory is disclosed on GitHub. diff --git a/lib/nghttp2/doc/tutorial-client.rst.in b/lib/nghttp2/doc/tutorial-client.rst.in new file mode 100644 index 00000000000..4f7fcfc9ebc --- /dev/null +++ b/lib/nghttp2/doc/tutorial-client.rst.in @@ -0,0 +1,6 @@ +.. include:: @top_srcdir@/doc/sources/tutorial-client.rst + +libevent-client.c +----------------- + +.. literalinclude:: @top_srcdir@/examples/libevent-client.c diff --git a/lib/nghttp2/doc/tutorial-hpack.rst.in b/lib/nghttp2/doc/tutorial-hpack.rst.in new file mode 100644 index 00000000000..832dedf7d61 --- /dev/null +++ b/lib/nghttp2/doc/tutorial-hpack.rst.in @@ -0,0 +1,6 @@ +.. include:: @top_srcdir@/doc/sources/tutorial-hpack.rst + +deflate.c +--------- + +.. literalinclude:: @top_srcdir@/examples/deflate.c diff --git a/lib/nghttp2/doc/tutorial-server.rst.in b/lib/nghttp2/doc/tutorial-server.rst.in new file mode 100644 index 00000000000..197226610c5 --- /dev/null +++ b/lib/nghttp2/doc/tutorial-server.rst.in @@ -0,0 +1,6 @@ +.. include:: @top_srcdir@/doc/sources/tutorial-server.rst + +libevent-server.c +----------------- + +.. literalinclude:: @top_srcdir@/examples/libevent-server.c diff --git a/lib/nghttp2/docker/Dockerfile b/lib/nghttp2/docker/Dockerfile new file mode 100644 index 00000000000..28839479415 --- /dev/null +++ b/lib/nghttp2/docker/Dockerfile @@ -0,0 +1,78 @@ +FROM debian:12 as build + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git clang make binutils autoconf automake autotools-dev libtool \ + pkg-config \ + zlib1g-dev libev-dev libjemalloc-dev ruby-dev libc-ares-dev bison \ + libelf-dev + +RUN git clone --depth 1 -b OpenSSL_1_1_1w+quic https://github.com/quictls/openssl && \ + cd openssl && \ + ./config --openssldir=/etc/ssl && \ + make -j$(nproc) && \ + make install_sw && \ + cd .. && \ + rm -rf openssl + +RUN git clone --depth 1 -b v1.1.0 https://github.com/ngtcp2/nghttp3 && \ + cd nghttp3 && \ + autoreconf -i && \ + ./configure --enable-lib-only && \ + make -j$(nproc) && \ + make install-strip && \ + cd .. && \ + rm -rf nghttp3 + +RUN git clone --depth 1 -b v1.1.0 https://github.com/ngtcp2/ngtcp2 && \ + cd ngtcp2 && \ + autoreconf -i && \ + ./configure --enable-lib-only \ + LIBTOOL_LDFLAGS="-static-libtool-libs" \ + OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -lpthread" \ + PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \ + make -j$(nproc) && \ + make install-strip && \ + cd .. && \ + rm -rf ngtcp2 + +RUN git clone --depth 1 -b v1.3.0 https://github.com/libbpf/libbpf && \ + cd libbpf && \ + PREFIX=/usr/local make -C src install && \ + cd .. && \ + rm -rf libbpf + +RUN git clone --depth 1 https://github.com/nghttp2/nghttp2.git && \ + cd nghttp2 && \ + git submodule update --init && \ + autoreconf -i && \ + ./configure --disable-examples --disable-hpack-tools \ + --with-mruby --with-neverbleed \ + --enable-http3 --with-libbpf \ + CC=clang CXX=clang++ \ + LIBTOOL_LDFLAGS="-static-libtool-libs" \ + OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -pthread" \ + LIBEV_LIBS="-l:libev.a" \ + JEMALLOC_LIBS="-l:libjemalloc.a" \ + LIBCARES_LIBS="-l:libcares.a" \ + ZLIB_LIBS="-l:libz.a" \ + LIBBPF_LIBS="-L/usr/local/lib64 -l:libbpf.a -l:libelf.a" \ + LDFLAGS="-static-libgcc -static-libstdc++" \ + PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig" && \ + make -j$(nproc) install-strip && \ + cd .. && \ + rm -rf nghttp2 + +FROM gcr.io/distroless/base-debian12 + +COPY --from=build \ + /usr/local/share/nghttp2/ \ + /usr/local/share/nghttp2/ +COPY --from=build \ + /usr/local/bin/h2load \ + /usr/local/bin/nghttpx \ + /usr/local/bin/nghttp \ + /usr/local/bin/nghttpd \ + /usr/local/bin/ +COPY --from=build /usr/local/lib/nghttp2/reuseport_kern.o \ + /usr/local/lib/nghttp2/ diff --git a/lib/nghttp2/docker/README.rst b/lib/nghttp2/docker/README.rst new file mode 100644 index 00000000000..e08af23c5df --- /dev/null +++ b/lib/nghttp2/docker/README.rst @@ -0,0 +1,25 @@ +Dockerfile +========== + +Dockerfile creates the applications bundled with nghttp2. +These applications are: + +- nghttp +- nghttpd +- nghttpx +- h2load + +HTTP/3 and eBPF features are enabled. + +In order to run nghttpx with HTTP/3 endpoint, you need to run the +image with the escalated privilege. Here is the example command-line +to run nghttpx to listen to HTTP/3 on port 443, assuming that the +current directory contains a private key and a certificate in +server.key and server.crt respectively: + +.. code-block:: text + + $ docker run --rm -it -v /path/to/certs:/shared --net=host --privileged \ + nghttp2 nghttpx \ + /shared/server.key /shared/server.crt \ + -f'*,443;quic' diff --git a/lib/nghttp2/examples/.gitignore b/lib/nghttp2/examples/.gitignore new file mode 100644 index 00000000000..16335a5031a --- /dev/null +++ b/lib/nghttp2/examples/.gitignore @@ -0,0 +1,5 @@ +client +libevent-client +libevent-server +deflate +tiny-nghttpd diff --git a/lib/nghttp2/examples/CMakeLists.txt b/lib/nghttp2/examples/CMakeLists.txt new file mode 100644 index 00000000000..7a57bbf4108 --- /dev/null +++ b/lib/nghttp2/examples/CMakeLists.txt @@ -0,0 +1,37 @@ +if(ENABLE_EXAMPLES) + file(GLOB c_sources *.c) + set_source_files_properties(${c_sources} PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}") + file(GLOB cxx_sources *.cc) + set_source_files_properties(${cxx_sources} PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}") + + include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + "${CMAKE_CURRENT_SOURCE_DIR}/../third-party" + "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/llhttp/include" + + ${LIBEVENT_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} + ) + + link_libraries( + nghttp2 + ${LIBEVENT_OPENSSL_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${APP_LIBRARIES} + ) + + add_executable(client client.c $ + $ + ) + add_executable(libevent-client libevent-client.c $ + $ + ) + add_executable(libevent-server libevent-server.c $ + $ + ) + add_executable(deflate deflate.c $ + $ + ) +endif() diff --git a/lib/nghttp2/examples/Makefile.am b/lib/nghttp2/examples/Makefile.am new file mode 100644 index 00000000000..7b682ed4981 --- /dev/null +++ b/lib/nghttp2/examples/Makefile.am @@ -0,0 +1,54 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# 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 + +if ENABLE_EXAMPLES + +AM_CFLAGS = $(WARNCFLAGS) +AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS) +AM_CPPFLAGS = \ + -I$(top_srcdir)/lib/includes \ + -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/third-party \ + @LIBEVENT_OPENSSL_CFLAGS@ \ + @OPENSSL_CFLAGS@ \ + @DEFS@ +AM_LDFLAGS = @LIBTOOL_LDFLAGS@ +LDADD = $(top_builddir)/lib/libnghttp2.la \ + $(top_builddir)/third-party/liburl-parser.la \ + @LIBEVENT_OPENSSL_LIBS@ \ + @OPENSSL_LIBS@ \ + @APPLDFLAGS@ + +noinst_PROGRAMS = client libevent-client libevent-server deflate + +client_SOURCES = client.c + +libevent_client_SOURCES = libevent-client.c + +libevent_server_SOURCES = libevent-server.c + +deflate_SOURCES = deflate.c + +endif # ENABLE_EXAMPLES diff --git a/lib/nghttp2/examples/client.c b/lib/nghttp2/examples/client.c new file mode 100644 index 00000000000..6012d27cfbe --- /dev/null +++ b/lib/nghttp2/examples/client.c @@ -0,0 +1,741 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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. + */ +/* + * This program is written to show how to use nghttp2 API in C and + * intentionally made simple. + */ +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#ifdef HAVE_FCNTL_H +# include +#endif /* HAVE_FCNTL_H */ +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETDB_H +# include +#endif /* HAVE_NETDB_H */ +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +enum { IO_NONE, WANT_READ, WANT_WRITE }; + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +#define MAKE_NV_CS(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, strlen(VALUE), \ + NGHTTP2_NV_FLAG_NONE \ + } + +struct Connection { + SSL *ssl; + nghttp2_session *session; + /* WANT_READ if SSL/TLS connection needs more input; or WANT_WRITE + if it needs more output; or IO_NONE. This is necessary because + SSL/TLS re-negotiation is possible at any time. nghttp2 API + offers similar functions like nghttp2_session_want_read() and + nghttp2_session_want_write() but they do not take into account + SSL/TSL connection. */ + int want_io; +}; + +struct Request { + char *host; + /* In this program, path contains query component as well. */ + char *path; + /* This is the concatenation of host and port with ":" in + between. */ + char *hostport; + /* Stream ID for this request. */ + int32_t stream_id; + uint16_t port; +}; + +struct URI { + const char *host; + /* In this program, path contains query component as well. */ + const char *path; + size_t pathlen; + const char *hostport; + size_t hostlen; + size_t hostportlen; + uint16_t port; +}; + +/* + * Returns copy of string |s| with the length |len|. The returned + * string is NULL-terminated. + */ +static char *strcopy(const char *s, size_t len) { + char *dst; + dst = malloc(len + 1); + memcpy(dst, s, len); + dst[len] = '\0'; + return dst; +} + +/* + * Prints error message |msg| and exit. + */ +NGHTTP2_NORETURN +static void die(const char *msg) { + fprintf(stderr, "FATAL: %s\n", msg); + exit(EXIT_FAILURE); +} + +/* + * Prints error containing the function name |func| and message |msg| + * and exit. + */ +NGHTTP2_NORETURN +static void dief(const char *func, const char *msg) { + fprintf(stderr, "FATAL: %s: %s\n", func, msg); + exit(EXIT_FAILURE); +} + +/* + * Prints error containing the function name |func| and error code + * |error_code| and exit. + */ +NGHTTP2_NORETURN +static void diec(const char *func, int error_code) { + fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code, + nghttp2_strerror(error_code)); + exit(EXIT_FAILURE); +} + +/* + * The implementation of nghttp2_send_callback type. Here we write + * |data| with size |length| to the network and return the number of + * bytes actually written. See the documentation of + * nghttp2_send_callback for the details. + */ +static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, + size_t length, int flags, void *user_data) { + struct Connection *connection; + int rv; + (void)session; + (void)flags; + + connection = (struct Connection *)user_data; + connection->want_io = IO_NONE; + ERR_clear_error(); + rv = SSL_write(connection->ssl, data, (int)length); + if (rv <= 0) { + int err = SSL_get_error(connection->ssl, rv); + if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { + connection->want_io = + (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE); + rv = NGHTTP2_ERR_WOULDBLOCK; + } else { + rv = NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return rv; +} + +/* + * The implementation of nghttp2_recv_callback type. Here we read data + * from the network and write them in |buf|. The capacity of |buf| is + * |length| bytes. Returns the number of bytes stored in |buf|. See + * the documentation of nghttp2_recv_callback for the details. + */ +static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf, + size_t length, int flags, void *user_data) { + struct Connection *connection; + int rv; + (void)session; + (void)flags; + + connection = (struct Connection *)user_data; + connection->want_io = IO_NONE; + ERR_clear_error(); + rv = SSL_read(connection->ssl, buf, (int)length); + if (rv < 0) { + int err = SSL_get_error(connection->ssl, rv); + if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) { + connection->want_io = + (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE); + rv = NGHTTP2_ERR_WOULDBLOCK; + } else { + rv = NGHTTP2_ERR_CALLBACK_FAILURE; + } + } else if (rv == 0) { + rv = NGHTTP2_ERR_EOF; + } + return rv; +} + +static int on_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + size_t i; + (void)user_data; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) { + const nghttp2_nv *nva = frame->headers.nva; + printf("[INFO] C ----------------------------> S (HEADERS)\n"); + for (i = 0; i < frame->headers.nvlen; ++i) { + fwrite(nva[i].name, 1, nva[i].namelen, stdout); + printf(": "); + fwrite(nva[i].value, 1, nva[i].valuelen, stdout); + printf("\n"); + } + } + break; + case NGHTTP2_RST_STREAM: + printf("[INFO] C ----------------------------> S (RST_STREAM)\n"); + break; + case NGHTTP2_GOAWAY: + printf("[INFO] C ----------------------------> S (GOAWAY)\n"); + break; + } + return 0; +} + +static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + size_t i; + (void)user_data; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { + const nghttp2_nv *nva = frame->headers.nva; + struct Request *req; + req = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + if (req) { + printf("[INFO] C <---------------------------- S (HEADERS)\n"); + for (i = 0; i < frame->headers.nvlen; ++i) { + fwrite(nva[i].name, 1, nva[i].namelen, stdout); + printf(": "); + fwrite(nva[i].value, 1, nva[i].valuelen, stdout); + printf("\n"); + } + } + } + break; + case NGHTTP2_RST_STREAM: + printf("[INFO] C <---------------------------- S (RST_STREAM)\n"); + break; + case NGHTTP2_GOAWAY: + printf("[INFO] C <---------------------------- S (GOAWAY)\n"); + break; + } + return 0; +} + +/* + * The implementation of nghttp2_on_stream_close_callback type. We use + * this function to know the response is fully received. Since we just + * fetch 1 resource in this program, after reception of the response, + * we submit GOAWAY and close the session. + */ +static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + struct Request *req; + (void)error_code; + (void)user_data; + + req = nghttp2_session_get_stream_user_data(session, stream_id); + if (req) { + int rv; + rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); + + if (rv != 0) { + diec("nghttp2_session_terminate_session", rv); + } + } + return 0; +} + +/* + * The implementation of nghttp2_on_data_chunk_recv_callback type. We + * use this function to print the received response body. + */ +static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + struct Request *req; + (void)flags; + (void)user_data; + + req = nghttp2_session_get_stream_user_data(session, stream_id); + if (req) { + printf("[INFO] C <---------------------------- S (DATA chunk)\n" + "%lu bytes\n", + (unsigned long int)len); + fwrite(data, 1, len, stdout); + printf("\n"); + } + return 0; +} + +/* + * Setup callback functions. nghttp2 API offers many callback + * functions, but most of them are optional. The send_callback is + * always required. Since we use nghttp2_session_recv(), the + * recv_callback is also required. + */ +static void setup_nghttp2_callbacks(nghttp2_session_callbacks *callbacks) { + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + + nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback); + + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); +} + +#ifndef OPENSSL_NO_NEXTPROTONEG +/* + * Callback function for TLS NPN. Since this program only supports + * HTTP/2 protocol, if server does not offer HTTP/2 the nghttp2 + * library supports, we terminate program. + */ +static int select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + int rv; + (void)ssl; + (void)arg; + + /* nghttp2_select_next_protocol() selects HTTP/2 protocol the + nghttp2 library supports. */ + rv = nghttp2_select_next_protocol(out, outlen, in, inlen); + if (rv <= 0) { + die("Server did not advertise HTTP/2 protocol"); + } + return SSL_TLSEXT_ERR_OK; +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +/* + * Setup SSL/TLS context. + */ +static void init_ssl_ctx(SSL_CTX *ssl_ctx) { + /* Disable SSLv2 and enable all workarounds for buggy servers */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + /* Set NPN callback */ +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ +} + +static void ssl_handshake(SSL *ssl, int fd) { + int rv; + if (SSL_set_fd(ssl, fd) == 0) { + dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL)); + } + ERR_clear_error(); + rv = SSL_connect(ssl); + if (rv <= 0) { + dief("SSL_connect", ERR_error_string(ERR_get_error(), NULL)); + } +} + +/* + * Connects to the host |host| and port |port|. This function returns + * the file descriptor of the client socket. + */ +static int connect_to(const char *host, uint16_t port) { + struct addrinfo hints; + int fd = -1; + int rv; + char service[NI_MAXSERV]; + struct addrinfo *res, *rp; + snprintf(service, sizeof(service), "%u", port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + rv = getaddrinfo(host, service, &hints, &res); + if (rv != 0) { + dief("getaddrinfo", gai_strerror(rv)); + } + for (rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + continue; + } + while ((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 && + errno == EINTR) + ; + if (rv == 0) { + break; + } + close(fd); + fd = -1; + } + freeaddrinfo(res); + return fd; +} + +static void make_non_block(int fd) { + int flags, rv; + while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR) + ; + if (flags == -1) { + dief("fcntl", strerror(errno)); + } + while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR) + ; + if (rv == -1) { + dief("fcntl", strerror(errno)); + } +} + +static void set_tcp_nodelay(int fd) { + int val = 1; + int rv; + rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val)); + if (rv == -1) { + dief("setsockopt", strerror(errno)); + } +} + +/* + * Update |pollfd| based on the state of |connection|. + */ +static void ctl_poll(struct pollfd *pollfd, struct Connection *connection) { + pollfd->events = 0; + if (nghttp2_session_want_read(connection->session) || + connection->want_io == WANT_READ) { + pollfd->events |= POLLIN; + } + if (nghttp2_session_want_write(connection->session) || + connection->want_io == WANT_WRITE) { + pollfd->events |= POLLOUT; + } +} + +/* + * Submits the request |req| to the connection |connection|. This + * function does not send packets; just append the request to the + * internal queue in |connection->session|. + */ +static void submit_request(struct Connection *connection, struct Request *req) { + int32_t stream_id; + /* Make sure that the last item is NULL */ + const nghttp2_nv nva[] = {MAKE_NV(":method", "GET"), + MAKE_NV_CS(":path", req->path), + MAKE_NV(":scheme", "https"), + MAKE_NV_CS(":authority", req->hostport), + MAKE_NV("accept", "*/*"), + MAKE_NV("user-agent", "nghttp2/" NGHTTP2_VERSION)}; + + stream_id = nghttp2_submit_request(connection->session, NULL, nva, + sizeof(nva) / sizeof(nva[0]), NULL, req); + + if (stream_id < 0) { + diec("nghttp2_submit_request", stream_id); + } + + req->stream_id = stream_id; + printf("[INFO] Stream ID = %d\n", stream_id); +} + +/* + * Performs the network I/O. + */ +static void exec_io(struct Connection *connection) { + int rv; + rv = nghttp2_session_recv(connection->session); + if (rv != 0) { + diec("nghttp2_session_recv", rv); + } + rv = nghttp2_session_send(connection->session); + if (rv != 0) { + diec("nghttp2_session_send", rv); + } +} + +static void request_init(struct Request *req, const struct URI *uri) { + req->host = strcopy(uri->host, uri->hostlen); + req->port = uri->port; + req->path = strcopy(uri->path, uri->pathlen); + req->hostport = strcopy(uri->hostport, uri->hostportlen); + req->stream_id = -1; +} + +static void request_free(struct Request *req) { + free(req->host); + free(req->path); + free(req->hostport); +} + +/* + * Fetches the resource denoted by |uri|. + */ +static void fetch_uri(const struct URI *uri) { + nghttp2_session_callbacks *callbacks; + int fd; + SSL_CTX *ssl_ctx; + SSL *ssl; + struct Request req; + struct Connection connection; + int rv; + nfds_t npollfds = 1; + struct pollfd pollfds[1]; + + request_init(&req, uri); + + /* Establish connection and setup SSL */ + fd = connect_to(req.host, req.port); + if (fd == -1) { + die("Could not open file descriptor"); + } + ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (ssl_ctx == NULL) { + dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL)); + } + init_ssl_ctx(ssl_ctx); + ssl = SSL_new(ssl_ctx); + if (ssl == NULL) { + dief("SSL_new", ERR_error_string(ERR_get_error(), NULL)); + } + /* To simplify the program, we perform SSL/TLS handshake in blocking + I/O. */ + ssl_handshake(ssl, fd); + + connection.ssl = ssl; + connection.want_io = IO_NONE; + + /* Here make file descriptor non-block */ + make_non_block(fd); + set_tcp_nodelay(fd); + + printf("[INFO] SSL/TLS handshake completed\n"); + + rv = nghttp2_session_callbacks_new(&callbacks); + + if (rv != 0) { + diec("nghttp2_session_callbacks_new", rv); + } + + setup_nghttp2_callbacks(callbacks); + + rv = nghttp2_session_client_new(&connection.session, callbacks, &connection); + + nghttp2_session_callbacks_del(callbacks); + + if (rv != 0) { + diec("nghttp2_session_client_new", rv); + } + + rv = nghttp2_submit_settings(connection.session, NGHTTP2_FLAG_NONE, NULL, 0); + + if (rv != 0) { + diec("nghttp2_submit_settings", rv); + } + + /* Submit the HTTP request to the outbound queue. */ + submit_request(&connection, &req); + + pollfds[0].fd = fd; + ctl_poll(pollfds, &connection); + + /* Event loop */ + while (nghttp2_session_want_read(connection.session) || + nghttp2_session_want_write(connection.session)) { + int nfds = poll(pollfds, npollfds, -1); + if (nfds == -1) { + dief("poll", strerror(errno)); + } + if (pollfds[0].revents & (POLLIN | POLLOUT)) { + exec_io(&connection); + } + if ((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) { + die("Connection error"); + } + ctl_poll(pollfds, &connection); + } + + /* Resource cleanup */ + nghttp2_session_del(connection.session); + SSL_shutdown(ssl); + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + shutdown(fd, SHUT_WR); + close(fd); + request_free(&req); +} + +static int parse_uri(struct URI *res, const char *uri) { + /* We only interested in https */ + size_t len, i, offset; + int ipv6addr = 0; + memset(res, 0, sizeof(struct URI)); + len = strlen(uri); + if (len < 9 || memcmp("https://", uri, 8) != 0) { + return -1; + } + offset = 8; + res->host = res->hostport = &uri[offset]; + res->hostlen = 0; + if (uri[offset] == '[') { + /* IPv6 literal address */ + ++offset; + ++res->host; + ipv6addr = 1; + for (i = offset; i < len; ++i) { + if (uri[i] == ']') { + res->hostlen = i - offset; + offset = i + 1; + break; + } + } + } else { + const char delims[] = ":/?#"; + for (i = offset; i < len; ++i) { + if (strchr(delims, uri[i]) != NULL) { + break; + } + } + res->hostlen = i - offset; + offset = i; + } + if (res->hostlen == 0) { + return -1; + } + /* Assuming https */ + res->port = 443; + if (offset < len) { + if (uri[offset] == ':') { + /* port */ + const char delims[] = "/?#"; + int port = 0; + ++offset; + for (i = offset; i < len; ++i) { + if (strchr(delims, uri[i]) != NULL) { + break; + } + if ('0' <= uri[i] && uri[i] <= '9') { + port *= 10; + port += uri[i] - '0'; + if (port > 65535) { + return -1; + } + } else { + return -1; + } + } + if (port == 0) { + return -1; + } + offset = i; + res->port = (uint16_t)port; + } + } + res->hostportlen = (size_t)(uri + offset + ipv6addr - res->host); + for (i = offset; i < len; ++i) { + if (uri[i] == '#') { + break; + } + } + if (i - offset == 0) { + res->path = "/"; + res->pathlen = 1; + } else { + res->path = &uri[offset]; + res->pathlen = i - offset; + } + return 0; +} + +int main(int argc, char **argv) { + struct URI uri; + struct sigaction act; + int rv; + + if (argc < 2) { + die("Specify a https URI"); + } + + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, 0); + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + /* No explicit initialization is required. */ +#elif defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + CRYPTO_library_init(); +#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + OPENSSL_config(NULL); + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + + rv = parse_uri(&uri, argv[1]); + if (rv != 0) { + die("parse_uri failed"); + } + fetch_uri(&uri); + return EXIT_SUCCESS; +} diff --git a/lib/nghttp2/examples/deflate.c b/lib/nghttp2/examples/deflate.c new file mode 100644 index 00000000000..df1cb92025d --- /dev/null +++ b/lib/nghttp2/examples/deflate.c @@ -0,0 +1,206 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 +#endif /* !HAVE_CONFIG_H */ + +#include +#include + +#include + +#define MAKE_NV(K, V) \ + { \ + (uint8_t *)K, (uint8_t *)V, sizeof(K) - 1, sizeof(V) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +static void deflate(nghttp2_hd_deflater *deflater, + nghttp2_hd_inflater *inflater, const nghttp2_nv *const nva, + size_t nvlen); + +static int inflate_header_block(nghttp2_hd_inflater *inflater, uint8_t *in, + size_t inlen, int final); + +int main(void) { + int rv; + nghttp2_hd_deflater *deflater; + nghttp2_hd_inflater *inflater; + /* Define 1st header set. This is looks like a HTTP request. */ + nghttp2_nv nva1[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"), + MAKE_NV(":path", "/"), MAKE_NV("user-agent", "libnghttp2"), + MAKE_NV("accept-encoding", "gzip, deflate")}; + /* Define 2nd header set */ + nghttp2_nv nva2[] = {MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "example.org"), + MAKE_NV(":path", "/stylesheet/style.css"), + MAKE_NV("user-agent", "libnghttp2"), + MAKE_NV("accept-encoding", "gzip, deflate"), + MAKE_NV("referer", "https://example.org")}; + + rv = nghttp2_hd_deflate_new(&deflater, 4096); + + if (rv != 0) { + fprintf(stderr, "nghttp2_hd_deflate_init failed with error: %s\n", + nghttp2_strerror(rv)); + exit(EXIT_FAILURE); + } + + rv = nghttp2_hd_inflate_new(&inflater); + + if (rv != 0) { + fprintf(stderr, "nghttp2_hd_inflate_init failed with error: %s\n", + nghttp2_strerror(rv)); + exit(EXIT_FAILURE); + } + + /* Encode and decode 1st header set */ + deflate(deflater, inflater, nva1, sizeof(nva1) / sizeof(nva1[0])); + + /* Encode and decode 2nd header set, using differential encoding + using state after encoding 1st header set. */ + deflate(deflater, inflater, nva2, sizeof(nva2) / sizeof(nva2[0])); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + + return 0; +} + +static void deflate(nghttp2_hd_deflater *deflater, + nghttp2_hd_inflater *inflater, const nghttp2_nv *const nva, + size_t nvlen) { + ssize_t rv; + uint8_t *buf; + size_t buflen; + size_t outlen; + size_t i; + size_t sum; + + sum = 0; + + for (i = 0; i < nvlen; ++i) { + sum += nva[i].namelen + nva[i].valuelen; + } + + printf("Input (%zu byte(s)):\n\n", sum); + + for (i = 0; i < nvlen; ++i) { + fwrite(nva[i].name, 1, nva[i].namelen, stdout); + printf(": "); + fwrite(nva[i].value, 1, nva[i].valuelen, stdout); + printf("\n"); + } + + buflen = nghttp2_hd_deflate_bound(deflater, nva, nvlen); + buf = malloc(buflen); + + rv = nghttp2_hd_deflate_hd(deflater, buf, buflen, nva, nvlen); + + if (rv < 0) { + fprintf(stderr, "nghttp2_hd_deflate_hd() failed with error: %s\n", + nghttp2_strerror((int)rv)); + + free(buf); + + exit(EXIT_FAILURE); + } + + outlen = (size_t)rv; + + printf("\nDeflate (%zu byte(s), ratio %.02f):\n\n", outlen, + sum == 0 ? 0 : (double)outlen / (double)sum); + + for (i = 0; i < outlen; ++i) { + if ((i & 0x0fu) == 0) { + printf("%08zX: ", i); + } + + printf("%02X ", buf[i]); + + if (((i + 1) & 0x0fu) == 0) { + printf("\n"); + } + } + + printf("\n\nInflate:\n\n"); + + /* We pass 1 to final parameter, because buf contains whole deflated + header data. */ + rv = inflate_header_block(inflater, buf, outlen, 1); + + if (rv != 0) { + free(buf); + + exit(EXIT_FAILURE); + } + + printf("\n-----------------------------------------------------------" + "--------------------\n"); + + free(buf); +} + +int inflate_header_block(nghttp2_hd_inflater *inflater, uint8_t *in, + size_t inlen, int final) { + ssize_t rv; + + for (;;) { + nghttp2_nv nv; + int inflate_flags = 0; + size_t proclen; + + rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, in, inlen, final); + + if (rv < 0) { + fprintf(stderr, "inflate failed with error code %zd", rv); + return -1; + } + + proclen = (size_t)rv; + + in += proclen; + inlen -= proclen; + + if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + fwrite(nv.name, 1, nv.namelen, stderr); + fprintf(stderr, ": "); + fwrite(nv.value, 1, nv.valuelen, stderr); + fprintf(stderr, "\n"); + } + + if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + nghttp2_hd_inflate_end_headers(inflater); + break; + } + + if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) { + break; + } + } + + return 0; +} diff --git a/lib/nghttp2/examples/libevent-client.c b/lib/nghttp2/examples/libevent-client.c new file mode 100644 index 00000000000..be117a2eaf7 --- /dev/null +++ b/lib/nghttp2/examples/libevent-client.c @@ -0,0 +1,635 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 __sgi +# include +# define errx(exitcode, format, args...) \ + { \ + warnx(format, ##args); \ + exit(exitcode); \ + } +# define warnx(format, args...) fprintf(stderr, format "\n", ##args) +char *strndup(const char *s, size_t size); +#endif + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#ifdef HAVE_SYS_SOCKET_H +# include +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +#include +#ifndef __sgi +# include +#endif +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "url-parser/url_parser.h" + +#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) + +typedef struct { + /* The NULL-terminated URI string to retrieve. */ + const char *uri; + /* Parsed result of the |uri| */ + struct http_parser_url *u; + /* The authority portion of the |uri|, not NULL-terminated */ + char *authority; + /* The path portion of the |uri|, including query, not + NULL-terminated */ + char *path; + /* The length of the |authority| */ + size_t authoritylen; + /* The length of the |path| */ + size_t pathlen; + /* The stream ID of this stream */ + int32_t stream_id; +} http2_stream_data; + +typedef struct { + nghttp2_session *session; + struct evdns_base *dnsbase; + struct bufferevent *bev; + http2_stream_data *stream_data; +} http2_session_data; + +static http2_stream_data *create_http2_stream_data(const char *uri, + struct http_parser_url *u) { + /* MAX 5 digits (max 65535) + 1 ':' + 1 NULL (because of snprintf) */ + size_t extra = 7; + http2_stream_data *stream_data = malloc(sizeof(http2_stream_data)); + + stream_data->uri = uri; + stream_data->u = u; + stream_data->stream_id = -1; + + stream_data->authoritylen = u->field_data[UF_HOST].len; + stream_data->authority = malloc(stream_data->authoritylen + extra); + memcpy(stream_data->authority, &uri[u->field_data[UF_HOST].off], + u->field_data[UF_HOST].len); + if (u->field_set & (1 << UF_PORT)) { + stream_data->authoritylen += + (size_t)snprintf(stream_data->authority + u->field_data[UF_HOST].len, + extra, ":%u", u->port); + } + + /* If we don't have path in URI, we use "/" as path. */ + stream_data->pathlen = 1; + if (u->field_set & (1 << UF_PATH)) { + stream_data->pathlen = u->field_data[UF_PATH].len; + } + if (u->field_set & (1 << UF_QUERY)) { + /* +1 for '?' character */ + stream_data->pathlen += (size_t)(u->field_data[UF_QUERY].len + 1); + } + + stream_data->path = malloc(stream_data->pathlen); + if (u->field_set & (1 << UF_PATH)) { + memcpy(stream_data->path, &uri[u->field_data[UF_PATH].off], + u->field_data[UF_PATH].len); + } else { + stream_data->path[0] = '/'; + } + if (u->field_set & (1 << UF_QUERY)) { + stream_data->path[stream_data->pathlen - u->field_data[UF_QUERY].len - 1] = + '?'; + memcpy(stream_data->path + stream_data->pathlen - + u->field_data[UF_QUERY].len, + &uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len); + } + + return stream_data; +} + +static void delete_http2_stream_data(http2_stream_data *stream_data) { + free(stream_data->path); + free(stream_data->authority); + free(stream_data); +} + +/* Initializes |session_data| */ +static http2_session_data * +create_http2_session_data(struct event_base *evbase) { + http2_session_data *session_data = malloc(sizeof(http2_session_data)); + + memset(session_data, 0, sizeof(http2_session_data)); + session_data->dnsbase = evdns_base_new(evbase, 1); + return session_data; +} + +static void delete_http2_session_data(http2_session_data *session_data) { + SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); + + if (ssl) { + SSL_shutdown(ssl); + } + bufferevent_free(session_data->bev); + session_data->bev = NULL; + evdns_base_free(session_data->dnsbase, 1); + session_data->dnsbase = NULL; + nghttp2_session_del(session_data->session); + session_data->session = NULL; + if (session_data->stream_data) { + delete_http2_stream_data(session_data->stream_data); + session_data->stream_data = NULL; + } + free(session_data); +} + +static void print_header(FILE *f, const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen) { + fwrite(name, 1, namelen, f); + fprintf(f, ": "); + fwrite(value, 1, valuelen, f); + fprintf(f, "\n"); +} + +/* Print HTTP headers to |f|. Please note that this function does not + take into account that header name and value are sequence of + octets, therefore they may contain non-printable characters. */ +static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen) { + size_t i; + for (i = 0; i < nvlen; ++i) { + print_header(f, nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen); + } + fprintf(f, "\n"); +} + +/* nghttp2_send_callback. Here we transmit the |data|, |length| bytes, + to the network. Because we are using libevent bufferevent, we just + write those bytes into bufferevent buffer. */ +static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, + size_t length, int flags, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + struct bufferevent *bev = session_data->bev; + (void)session; + (void)flags; + + bufferevent_write(bev, data, length); + return (ssize_t)length; +} + +/* nghttp2_on_header_callback: Called when nghttp2 library emits + single header name/value pair. */ +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + (void)session; + (void)flags; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + /* Print response headers for the initiated request. */ + print_header(stderr, name, namelen, value, valuelen); + break; + } + } + return 0; +} + +/* nghttp2_on_begin_headers_callback: Called when nghttp2 library gets + started to receive header block. */ +static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + (void)session; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + fprintf(stderr, "Response headers for stream ID=%d:\n", + frame->hd.stream_id); + } + break; + } + return 0; +} + +/* nghttp2_on_frame_recv_callback: Called when nghttp2 library + received a complete frame from the remote peer. */ +static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + (void)session; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE && + session_data->stream_data->stream_id == frame->hd.stream_id) { + fprintf(stderr, "All headers received\n"); + } + break; + } + return 0; +} + +/* nghttp2_on_data_chunk_recv_callback: Called when DATA frame is + received from the remote peer. In this implementation, if the frame + is meant to the stream we initiated, print the received data in + stdout, so that the user can redirect its output to the file + easily. */ +static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + (void)session; + (void)flags; + + if (session_data->stream_data->stream_id == stream_id) { + fwrite(data, 1, len, stdout); + } + return 0; +} + +/* nghttp2_on_stream_close_callback: Called when a stream is about to + closed. This example program only deals with 1 HTTP request (1 + stream), if it is closed, we send GOAWAY and tear down the + session */ +static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + int rv; + + if (session_data->stream_data->stream_id == stream_id) { + fprintf(stderr, "Stream %d closed with error_code=%u\n", stream_id, + error_code); + rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +#ifndef OPENSSL_NO_NEXTPROTONEG +/* NPN TLS extension client callback. We check that server advertised + the HTTP/2 protocol the nghttp2 library supports. If not, exit + the program. */ +static int select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + (void)ssl; + (void)arg; + + if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { + errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID); + } + return SSL_TLSEXT_ERR_OK; +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +/* Create SSL_CTX. */ +static SSL_CTX *create_ssl_ctx(void) { + SSL_CTX *ssl_ctx; + ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + errx(1, "Could not create SSL/TLS context: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + + return ssl_ctx; +} + +/* Create SSL object */ +static SSL *create_ssl(SSL_CTX *ssl_ctx) { + SSL *ssl; + ssl = SSL_new(ssl_ctx); + if (!ssl) { + errx(1, "Could not create SSL/TLS session object: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + return ssl; +} + +static void initialize_nghttp2_session(http2_session_data *session_data) { + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_client_new(&session_data->session, callbacks, session_data); + + nghttp2_session_callbacks_del(callbacks); +} + +static void send_client_connection_header(http2_session_data *session_data) { + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + /* client 24 bytes magic string will be sent by nghttp2 library */ + rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, + ARRLEN(iv)); + if (rv != 0) { + errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv)); + } +} + +#define MAKE_NV(NAME, VALUE, VALUELEN) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \ + NGHTTP2_NV_FLAG_NONE \ + } + +#define MAKE_NV2(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +/* Send HTTP request to the remote peer */ +static void submit_request(http2_session_data *session_data) { + int32_t stream_id; + http2_stream_data *stream_data = session_data->stream_data; + const char *uri = stream_data->uri; + const struct http_parser_url *u = stream_data->u; + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "GET"), + MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off], + u->field_data[UF_SCHEMA].len), + MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen), + MAKE_NV(":path", stream_data->path, stream_data->pathlen)}; + fprintf(stderr, "Request headers:\n"); + print_headers(stderr, hdrs, ARRLEN(hdrs)); + stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs, + ARRLEN(hdrs), NULL, stream_data); + if (stream_id < 0) { + errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id)); + } + + stream_data->stream_id = stream_id; +} + +/* Serialize the frame and send (or buffer) the data to + bufferevent. */ +static int session_send(http2_session_data *session_data) { + int rv; + + rv = nghttp2_session_send(session_data->session); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +/* readcb for bufferevent. Here we get the data from the input buffer + of bufferevent and feed them to nghttp2 library. This may invoke + nghttp2 callbacks. It may also queues the frame in nghttp2 session + context. To send them, we call session_send() in the end. */ +static void readcb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + ssize_t readlen; + struct evbuffer *input = bufferevent_get_input(bev); + size_t datalen = evbuffer_get_length(input); + unsigned char *data = evbuffer_pullup(input, -1); + + readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); + if (readlen < 0) { + warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + delete_http2_session_data(session_data); + return; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + warnx("Fatal error: evbuffer_drain failed"); + delete_http2_session_data(session_data); + return; + } + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } +} + +/* writecb for bufferevent. To greaceful shutdown after sending or + receiving GOAWAY, we check the some conditions on the nghttp2 + library and output buffer of bufferevent. If it indicates we have + no business to this session, tear down the connection. */ +static void writecb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + (void)bev; + + if (nghttp2_session_want_read(session_data->session) == 0 && + nghttp2_session_want_write(session_data->session) == 0 && + evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) { + delete_http2_session_data(session_data); + } +} + +/* eventcb for bufferevent. For the purpose of simplicity and + readability of the example program, we omitted the certificate and + peer verification. After SSL/TLS handshake is over, initialize + nghttp2 library session, and send client connection header. Then + send HTTP request. */ +static void eventcb(struct bufferevent *bev, short events, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (events & BEV_EVENT_CONNECTED) { + int fd = bufferevent_getfd(bev); + int val = 1; + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL *ssl; + + fprintf(stderr, "Connected\n"); + + ssl = bufferevent_openssl_get_ssl(session_data->bev); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (alpn == NULL) { + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "h2 is not negotiated\n"); + delete_http2_session_data(session_data); + return; + } + + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); + initialize_nghttp2_session(session_data); + send_client_connection_header(session_data); + submit_request(session_data); + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + } + return; + } + if (events & BEV_EVENT_EOF) { + warnx("Disconnected from the remote host"); + } else if (events & BEV_EVENT_ERROR) { + warnx("Network error"); + } else if (events & BEV_EVENT_TIMEOUT) { + warnx("Timeout"); + } + delete_http2_session_data(session_data); +} + +/* Start connecting to the remote peer |host:port| */ +static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx, + const char *host, uint16_t port, + http2_session_data *session_data) { + int rv; + struct bufferevent *bev; + SSL *ssl; + + ssl = create_ssl(ssl_ctx); + bev = bufferevent_openssl_socket_new( + evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE); + bufferevent_enable(bev, EV_READ | EV_WRITE); + bufferevent_setcb(bev, readcb, writecb, eventcb, session_data); + rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase, + AF_UNSPEC, host, port); + + if (rv != 0) { + errx(1, "Could not connect to the remote host %s", host); + } + session_data->bev = bev; +} + +/* Get resource denoted by the |uri|. The debug and error messages are + printed in stderr, while the response body is printed in stdout. */ +static void run(const char *uri) { + struct http_parser_url u; + char *host; + uint16_t port; + int rv; + SSL_CTX *ssl_ctx; + struct event_base *evbase; + http2_session_data *session_data; + + /* Parse the |uri| and stores its components in |u| */ + rv = http_parser_parse_url(uri, strlen(uri), 0, &u); + if (rv != 0) { + errx(1, "Could not parse URI %s", uri); + } + host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len); + if (!(u.field_set & (1 << UF_PORT))) { + port = 443; + } else { + port = u.port; + } + + ssl_ctx = create_ssl_ctx(); + + evbase = event_base_new(); + + session_data = create_http2_session_data(evbase); + session_data->stream_data = create_http2_stream_data(uri, &u); + + initiate_connection(evbase, ssl_ctx, host, port, session_data); + free(host); + host = NULL; + + event_base_loop(evbase, 0); + + event_base_free(evbase); + SSL_CTX_free(ssl_ctx); +} + +int main(int argc, char **argv) { + struct sigaction act; + + if (argc < 2) { + fprintf(stderr, "Usage: libevent-client HTTPS_URI\n"); + exit(EXIT_FAILURE); + } + + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, NULL); + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + /* No explicit initialization is required. */ +#elif defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + CRYPTO_library_init(); +#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + OPENSSL_config(NULL); + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + + run(argv[1]); + return 0; +} diff --git a/lib/nghttp2/examples/libevent-server.c b/lib/nghttp2/examples/libevent-server.c new file mode 100644 index 00000000000..bca8b645153 --- /dev/null +++ b/lib/nghttp2/examples/libevent-server.c @@ -0,0 +1,835 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 __sgi +# define errx(exitcode, format, args...) \ + { \ + warnx(format, ##args); \ + exit(exitcode); \ + } +# define warn(format, args...) warnx(format ": %s", ##args, strerror(errno)) +# define warnx(format, args...) fprintf(stderr, format "\n", ##args) +#endif + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif /* HAVE_SYS_SOCKET_H */ +#ifdef HAVE_NETDB_H +# include +#endif /* HAVE_NETDB_H */ +#include +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#include +#ifdef HAVE_FCNTL_H +# include +#endif /* HAVE_FCNTL_H */ +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ +#include +#ifndef __sgi +# include +#endif +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16) + +#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +struct app_context; +typedef struct app_context app_context; + +typedef struct http2_stream_data { + struct http2_stream_data *prev, *next; + char *request_path; + int32_t stream_id; + int fd; +} http2_stream_data; + +typedef struct http2_session_data { + struct http2_stream_data root; + struct bufferevent *bev; + app_context *app_ctx; + nghttp2_session *session; + char *client_addr; +} http2_session_data; + +struct app_context { + SSL_CTX *ssl_ctx; + struct event_base *evbase; +}; + +static unsigned char next_proto_list[256]; +static size_t next_proto_list_len; + +#ifndef OPENSSL_NO_NEXTPROTONEG +static int next_proto_cb(SSL *ssl, const unsigned char **data, + unsigned int *len, void *arg) { + (void)ssl; + (void)arg; + + *data = next_proto_list; + *len = (unsigned int)next_proto_list_len; + return SSL_TLSEXT_ERR_OK; +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + int rv; + (void)ssl; + (void)arg; + + rv = nghttp2_select_next_protocol((unsigned char **)out, outlen, in, inlen); + + if (rv != 1) { + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; +} +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + +/* Create SSL_CTX. */ +static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { + SSL_CTX *ssl_ctx; + + ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + errx(1, "Could not create SSL/TLS context: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_options(ssl_ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { + errx(1, "SSL_CTX_set1_curves_list failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + { + EC_KEY *ecdh; + ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (!ecdh) { + errx(1, "EC_KEY_new_by_curv_name failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); + } +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { + errx(1, "Could not read private key file %s", key_file); + } + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + errx(1, "Could not read certificate file %s", cert_file); + } + + next_proto_list[0] = NGHTTP2_PROTO_VERSION_ID_LEN; + memcpy(&next_proto_list[1], NGHTTP2_PROTO_VERSION_ID, + NGHTTP2_PROTO_VERSION_ID_LEN); + next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, NULL); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + + return ssl_ctx; +} + +/* Create SSL object */ +static SSL *create_ssl(SSL_CTX *ssl_ctx) { + SSL *ssl; + ssl = SSL_new(ssl_ctx); + if (!ssl) { + errx(1, "Could not create SSL/TLS session object: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + return ssl; +} + +static void add_stream(http2_session_data *session_data, + http2_stream_data *stream_data) { + stream_data->next = session_data->root.next; + session_data->root.next = stream_data; + stream_data->prev = &session_data->root; + if (stream_data->next) { + stream_data->next->prev = stream_data; + } +} + +static void remove_stream(http2_session_data *session_data, + http2_stream_data *stream_data) { + (void)session_data; + + stream_data->prev->next = stream_data->next; + if (stream_data->next) { + stream_data->next->prev = stream_data->prev; + } +} + +static http2_stream_data * +create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) { + http2_stream_data *stream_data; + stream_data = malloc(sizeof(http2_stream_data)); + memset(stream_data, 0, sizeof(http2_stream_data)); + stream_data->stream_id = stream_id; + stream_data->fd = -1; + + add_stream(session_data, stream_data); + return stream_data; +} + +static void delete_http2_stream_data(http2_stream_data *stream_data) { + if (stream_data->fd != -1) { + close(stream_data->fd); + } + free(stream_data->request_path); + free(stream_data); +} + +static http2_session_data *create_http2_session_data(app_context *app_ctx, + int fd, + struct sockaddr *addr, + int addrlen) { + int rv; + http2_session_data *session_data; + SSL *ssl; + char host[NI_MAXHOST]; + int val = 1; + + ssl = create_ssl(app_ctx->ssl_ctx); + session_data = malloc(sizeof(http2_session_data)); + memset(session_data, 0, sizeof(http2_session_data)); + session_data->app_ctx = app_ctx; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); + session_data->bev = bufferevent_openssl_socket_new( + app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); + bufferevent_enable(session_data->bev, EV_READ | EV_WRITE); + rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0, + NI_NUMERICHOST); + if (rv != 0) { + session_data->client_addr = strdup("(unknown)"); + } else { + session_data->client_addr = strdup(host); + } + + return session_data; +} + +static void delete_http2_session_data(http2_session_data *session_data) { + http2_stream_data *stream_data; + SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); + fprintf(stderr, "%s disconnected\n", session_data->client_addr); + if (ssl) { + SSL_shutdown(ssl); + } + bufferevent_free(session_data->bev); + nghttp2_session_del(session_data->session); + for (stream_data = session_data->root.next; stream_data;) { + http2_stream_data *next = stream_data->next; + delete_http2_stream_data(stream_data); + stream_data = next; + } + free(session_data->client_addr); + free(session_data); +} + +/* Serialize the frame and send (or buffer) the data to + bufferevent. */ +static int session_send(http2_session_data *session_data) { + int rv; + rv = nghttp2_session_send(session_data->session); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +/* Read the data in the bufferevent and feed them into nghttp2 library + function. Invocation of nghttp2_session_mem_recv() may make + additional pending frames, so call session_send() at the end of the + function. */ +static int session_recv(http2_session_data *session_data) { + ssize_t readlen; + struct evbuffer *input = bufferevent_get_input(session_data->bev); + size_t datalen = evbuffer_get_length(input); + unsigned char *data = evbuffer_pullup(input, -1); + + readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); + if (readlen < 0) { + warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); + return -1; + } + if (evbuffer_drain(input, (size_t)readlen) != 0) { + warnx("Fatal error: evbuffer_drain failed"); + return -1; + } + if (session_send(session_data) != 0) { + return -1; + } + return 0; +} + +static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, + size_t length, int flags, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + struct bufferevent *bev = session_data->bev; + (void)session; + (void)flags; + + /* Avoid excessive buffering in server side. */ + if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= + OUTPUT_WOULDBLOCK_THRESHOLD) { + return NGHTTP2_ERR_WOULDBLOCK; + } + bufferevent_write(bev, data, length); + return (ssize_t)length; +} + +/* Returns nonzero if the string |s| ends with the substring |sub| */ +static int ends_with(const char *s, const char *sub) { + size_t slen = strlen(s); + size_t sublen = strlen(sub); + if (slen < sublen) { + return 0; + } + return memcmp(s + slen - sublen, sub, sublen) == 0; +} + +/* Returns int value of hex string character |c| */ +static uint8_t hex_to_uint(uint8_t c) { + if ('0' <= c && c <= '9') { + return (uint8_t)(c - '0'); + } + if ('A' <= c && c <= 'F') { + return (uint8_t)(c - 'A' + 10); + } + if ('a' <= c && c <= 'f') { + return (uint8_t)(c - 'a' + 10); + } + return 0; +} + +/* Decodes percent-encoded byte string |value| with length |valuelen| + and returns the decoded byte string in allocated buffer. The return + value is NULL terminated. The caller must free the returned + string. */ +static char *percent_decode(const uint8_t *value, size_t valuelen) { + char *res; + + res = malloc(valuelen + 1); + if (valuelen > 3) { + size_t i, j; + for (i = 0, j = 0; i < valuelen - 2;) { + if (value[i] != '%' || !isxdigit(value[i + 1]) || + !isxdigit(value[i + 2])) { + res[j++] = (char)value[i++]; + continue; + } + res[j++] = + (char)((hex_to_uint(value[i + 1]) << 4) + hex_to_uint(value[i + 2])); + i += 3; + } + memcpy(&res[j], &value[i], 2); + res[j + 2] = '\0'; + } else { + memcpy(res, value, valuelen); + res[valuelen] = '\0'; + } + return res; +} + +static ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, + uint8_t *buf, size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + int fd = source->fd; + ssize_t r; + (void)session; + (void)stream_id; + (void)user_data; + + while ((r = read(fd, buf, length)) == -1 && errno == EINTR) + ; + if (r == -1) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + if (r == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return r; +} + +static int send_response(nghttp2_session *session, int32_t stream_id, + nghttp2_nv *nva, size_t nvlen, int fd) { + int rv; + nghttp2_data_provider data_prd; + data_prd.source.fd = fd; + data_prd.read_callback = data_source_read_callback; + + rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +static const char ERROR_HTML[] = "404" + "

404 Not Found

"; + +static int error_reply(nghttp2_session *session, + http2_stream_data *stream_data) { + int rv; + ssize_t writelen; + int pipefd[2]; + nghttp2_nv hdrs[] = {MAKE_NV(":status", "404")}; + + rv = pipe(pipefd); + if (rv != 0) { + warn("Could not create pipe"); + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + stream_data->stream_id, + NGHTTP2_INTERNAL_ERROR); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; + } + + writelen = write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1); + close(pipefd[1]); + + if (writelen != sizeof(ERROR_HTML) - 1) { + close(pipefd[0]); + return -1; + } + + stream_data->fd = pipefd[0]; + + if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), + pipefd[0]) != 0) { + close(pipefd[0]); + return -1; + } + return 0; +} + +/* nghttp2_on_header_callback: Called when nghttp2 library emits + single header name/value pair. */ +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags, void *user_data) { + http2_stream_data *stream_data; + const char PATH[] = ":path"; + (void)flags; + (void)user_data; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + if (!stream_data || stream_data->request_path) { + break; + } + if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { + size_t j; + for (j = 0; j < valuelen && value[j] != '?'; ++j) + ; + stream_data->request_path = percent_decode(value, j); + } + break; + } + return 0; +} + +static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); + nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, + stream_data); + return 0; +} + +/* Minimum check for directory traversal. Returns nonzero if it is + safe. */ +static int check_path(const char *path) { + /* We don't like '\' in url. */ + return path[0] && path[0] == '/' && strchr(path, '\\') == NULL && + strstr(path, "/../") == NULL && strstr(path, "/./") == NULL && + !ends_with(path, "/..") && !ends_with(path, "/."); +} + +static int on_request_recv(nghttp2_session *session, + http2_session_data *session_data, + http2_stream_data *stream_data) { + int fd; + nghttp2_nv hdrs[] = {MAKE_NV(":status", "200")}; + char *rel_path; + + if (!stream_data->request_path) { + if (error_reply(session, stream_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } + fprintf(stderr, "%s GET %s\n", session_data->client_addr, + stream_data->request_path); + if (!check_path(stream_data->request_path)) { + if (error_reply(session, stream_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } + for (rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path) + ; + fd = open(rel_path, O_RDONLY); + if (fd == -1) { + if (error_reply(session, stream_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; + } + stream_data->fd = fd; + + if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), fd) != + 0) { + close(fd); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: + /* Check that the client request has finished */ + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + stream_data = + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + /* For DATA and HEADERS frame, this callback may be called after + on_stream_close_callback. Check that stream still alive. */ + if (!stream_data) { + return 0; + } + return on_request_recv(session, session_data, stream_data); + } + break; + default: + break; + } + return 0; +} + +static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + http2_session_data *session_data = (http2_session_data *)user_data; + http2_stream_data *stream_data; + (void)error_code; + + stream_data = nghttp2_session_get_stream_user_data(session, stream_id); + if (!stream_data) { + return 0; + } + remove_stream(session_data, stream_data); + delete_http2_stream_data(stream_data); + return 0; +} + +static void initialize_nghttp2_session(http2_session_data *session_data) { + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_server_new(&session_data->session, callbacks, session_data); + + nghttp2_session_callbacks_del(callbacks); +} + +/* Send HTTP/2 client connection header, which includes 24 bytes + magic octets and SETTINGS frame */ +static int send_server_connection_header(http2_session_data *session_data) { + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, + ARRLEN(iv)); + if (rv != 0) { + warnx("Fatal error: %s", nghttp2_strerror(rv)); + return -1; + } + return 0; +} + +/* readcb for bufferevent after client connection header was + checked. */ +static void readcb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + (void)bev; + + if (session_recv(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } +} + +/* writecb for bufferevent. To greaceful shutdown after sending or + receiving GOAWAY, we check the some conditions on the nghttp2 + library and output buffer of bufferevent. If it indicates we have + no business to this session, tear down the connection. If the + connection is not going to shutdown, we call session_send() to + process pending data in the output buffer. This is necessary + because we have a threshold on the buffer size to avoid too much + buffering. See send_callback(). */ +static void writecb(struct bufferevent *bev, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { + return; + } + if (nghttp2_session_want_read(session_data->session) == 0 && + nghttp2_session_want_write(session_data->session) == 0) { + delete_http2_session_data(session_data); + return; + } + if (session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } +} + +/* eventcb for bufferevent */ +static void eventcb(struct bufferevent *bev, short events, void *ptr) { + http2_session_data *session_data = (http2_session_data *)ptr; + if (events & BEV_EVENT_CONNECTED) { + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL *ssl; + (void)bev; + + fprintf(stderr, "%s connected\n", session_data->client_addr); + + ssl = bufferevent_openssl_get_ssl(session_data->bev); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (alpn == NULL) { + SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + + if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { + fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); + delete_http2_session_data(session_data); + return; + } + + initialize_nghttp2_session(session_data); + + if (send_server_connection_header(session_data) != 0 || + session_send(session_data) != 0) { + delete_http2_session_data(session_data); + return; + } + + return; + } + if (events & BEV_EVENT_EOF) { + fprintf(stderr, "%s EOF\n", session_data->client_addr); + } else if (events & BEV_EVENT_ERROR) { + fprintf(stderr, "%s network error\n", session_data->client_addr); + } else if (events & BEV_EVENT_TIMEOUT) { + fprintf(stderr, "%s timeout\n", session_data->client_addr); + } + delete_http2_session_data(session_data); +} + +/* callback for evconnlistener */ +static void acceptcb(struct evconnlistener *listener, int fd, + struct sockaddr *addr, int addrlen, void *arg) { + app_context *app_ctx = (app_context *)arg; + http2_session_data *session_data; + (void)listener; + + session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); + + bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); +} + +static void start_listen(struct event_base *evbase, const char *service, + app_context *app_ctx) { + int rv; + struct addrinfo hints; + struct addrinfo *res, *rp; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif /* AI_ADDRCONFIG */ + + rv = getaddrinfo(NULL, service, &hints, &res); + if (rv != 0) { + errx(1, "Could not resolve server address"); + } + for (rp = res; rp; rp = rp->ai_next) { + struct evconnlistener *listener; + listener = evconnlistener_new_bind( + evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + 16, rp->ai_addr, (int)rp->ai_addrlen); + if (listener) { + freeaddrinfo(res); + + return; + } + } + errx(1, "Could not start listener"); +} + +static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx, + struct event_base *evbase) { + memset(app_ctx, 0, sizeof(app_context)); + app_ctx->ssl_ctx = ssl_ctx; + app_ctx->evbase = evbase; +} + +static void run(const char *service, const char *key_file, + const char *cert_file) { + SSL_CTX *ssl_ctx; + app_context app_ctx; + struct event_base *evbase; + + ssl_ctx = create_ssl_ctx(key_file, cert_file); + evbase = event_base_new(); + initialize_app_context(&app_ctx, ssl_ctx, evbase); + start_listen(evbase, service, &app_ctx); + + event_base_loop(evbase, 0); + + event_base_free(evbase); + SSL_CTX_free(ssl_ctx); +} + +int main(int argc, char **argv) { + struct sigaction act; + + if (argc < 4) { + fprintf(stderr, "Usage: libevent-server PORT KEY_FILE CERT_FILE\n"); + exit(EXIT_FAILURE); + } + + memset(&act, 0, sizeof(struct sigaction)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, NULL); + +#if OPENSSL_VERSION_NUMBER >= 0x1010000fL + /* No explicit initialization is required. */ +#elif defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + CRYPTO_library_init(); +#else /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + OPENSSL_config(NULL); + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x1010000fL) && \ + !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC) */ + + run(argv[1], argv[2], argv[3]); + return 0; +} diff --git a/lib/nghttp2/fedora/spdylay.spec b/lib/nghttp2/fedora/spdylay.spec new file mode 100644 index 00000000000..967d908fc76 --- /dev/null +++ b/lib/nghttp2/fedora/spdylay.spec @@ -0,0 +1,75 @@ +Prefix: %{_usr} +Name: spdylay +Version: 0.3.7 +Release: 1%{?dist} +Summary: The experimental SPDY protocol version 2 and 3 implementation in C + +Group: System Environment/Libraries +License: MIT +URL: http://sourceforge.net/projects/spdylay/ +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: pkgconfig >= 0.20, zlib >= 1.2.3, gcc, gcc-c++, make +BuildRequires: openssl-devel, CUnit-devel + +#Requires: + +%description +This is an experimental implementation of Google's SPDY protocol in C. +This library provides SPDY version 2 and 3 framing layer implementation. It does not +perform any I/O operations. When the library needs them, it calls the callback functions +provided by the application. It also does not include any event polling mechanism, +so the application can freely choose the way of handling events. This library code does +not depend on any particular SSL library (except for example programs which depend on +OpenSSL 1.0.1 or later). + +%package devel +Summary: Development files for %{name} +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%description devel +The %{name}-devel package contains libraries and header files for +developing applications that use %{name}. + +%prep +%setup -q + +%build +autoreconf -i +%{__automake} +%{__autoconf} +%configure --disable-static --enable-examples --disable-xmltest +%{__make} %{?_smp_mflags} + +%install +rm -rf $RPM_BUILD_ROOT +%{__make} install DESTDIR=$RPM_BUILD_ROOT + +%clean +rm -rf $RPM_BUILD_ROOT + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + +%files +%defattr(-,root,root,-) +%doc +%{_libdir}/*.so.* +%exclude %{_libdir}/*.la +%{_bindir}/shrpx +%{_bindir}/spdycat +%{_bindir}/spdyd + +%files devel +%defattr(-,root,root,-) +%doc %{_docdir}/%{name} +%{_includedir}/* +%{_libdir}/*.so +%{_libdir}/pkgconfig/*.pc + +%changelog +* Sat Oct 27 2012 Raul Gutierrez Segales 0.3.7-DEV +- Initial RPM release. diff --git a/lib/nghttp2/fuzz/README.rst b/lib/nghttp2/fuzz/README.rst new file mode 100644 index 00000000000..54ae8325b6c --- /dev/null +++ b/lib/nghttp2/fuzz/README.rst @@ -0,0 +1,33 @@ +Fuzzer +====== + +This directory contains fuzzer target mainly written to integrate +nghttp2 into `oss-fuzz `_. + +fuzz_target.cc contains an entry point of fuzzer. corpus directory +contains initial data for fuzzer. + +The file name of initial data under corpus is the lower-cased hex +string of SHA-256 hash of its own content. + +corpus/h2spec contains input data which was recorded when we ran +`h2spec `_ against nghttpd. + +corpus/nghttp contains input data which was recorded when we ran +nghttp against nghttpd with some varying command line options of +nghttp. + + +To build fuzz_target.cc, make sure that libnghttp2 is built with +following compiler/linker flags: + +.. code-block:: text + + CPPFLAGS="-fsanitize-coverage=edge -fsanitize=address" + LDFLAGS="-fsanitize-coverage=edge -fsanitize=address" + +Then, fuzz_target.cc can be built using the following command: + +.. code-block:: text + + $ clang++ -fsanitize-coverage=edge -fsanitize=address -I../lib/includes -std=c++11 fuzz_target.cc ../lib/.libs/libnghttp2.a /usr/lib/llvm-3.9/lib/libFuzzer.a -o nghttp2_fuzzer diff --git a/lib/nghttp2/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 b/lib/nghttp2/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 new file mode 100644 index 00000000000..f2bc466b0e4 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/025ca25c8427361ea5498e4c3ba49d20eac5b4332f7b75b8f74bfba5e43f59f8 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a b/lib/nghttp2/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a new file mode 100644 index 00000000000..629ce585859 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/0276779c73bddcebc63b863c23a338b4c827bf6164640ff20a2d64d45a6b3f5a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 b/lib/nghttp2/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 new file mode 100644 index 00000000000..61c63835baf Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/0428d1e3b2364efcc93ffd8fcfff43b378a92c7da44268b9dda2bf32a1178c66 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a b/lib/nghttp2/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a new file mode 100644 index 00000000000..f8651f1c385 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/06bc5f79b7e68e005bd4382bd3a6c6b1b6005c5f7d5783e99baf2f8f7432d71a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd b/lib/nghttp2/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd new file mode 100644 index 00000000000..15c71aad065 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/09f76550ec065944a5d1d52f5d07b1dd87de1f651f80ef82c2815b0248b7dccd differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f b/lib/nghttp2/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f new file mode 100644 index 00000000000..31a5ddcadcd --- /dev/null +++ b/lib/nghttp2/fuzz/corpus/h2spec/0b39d9df6e1721030667980a41547272ad42377149edcf130b2bf0b76804c61f @@ -0,0 +1,2 @@ +INVALID CONNECTION PREFACE + diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 b/lib/nghttp2/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 new file mode 100644 index 00000000000..cf66978abea Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/0bb4365b02c05540936f9606ca725770a731e73c2144c7b81953dcc4b4f73c32 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 b/lib/nghttp2/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 new file mode 100644 index 00000000000..981e66ad916 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/0d577f6eb853e987b8fdab6ca4615a351ab74bfc75eb0d227acbef6a35bcae39 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 b/lib/nghttp2/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 new file mode 100644 index 00000000000..fb65c24434c Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/0df702020c019dd33d0643c5a2b9a9637d325c8f38b4cc6d3f808b5b2a4169a9 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 b/lib/nghttp2/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 new file mode 100644 index 00000000000..62d58871c25 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/0f8054152149c73e64c9f3e83f97e6585c8a51ec2413e7a2e8dfcc444082a5c5 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a b/lib/nghttp2/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a new file mode 100644 index 00000000000..e5285080e90 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/105f72bc9184bf47a857ed84e8c2f917946ec7ef3f4720535478b41e097a798a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc b/lib/nghttp2/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc new file mode 100644 index 00000000000..4cd627b01da Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1368ed7160cc4115e31a8a158af429421570e7363a3b75441edc5d740513b0dc differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 b/lib/nghttp2/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 new file mode 100644 index 00000000000..d5c77dea58f Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1402c49b963994284b0d429edfac603133e0144dba08836f90b1ae164b328800 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 b/lib/nghttp2/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 new file mode 100644 index 00000000000..e8ac9ee03d9 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1468c2cddae629788f6957847b76c09921e984796f6dc482859b119cf4879300 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 b/lib/nghttp2/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 new file mode 100644 index 00000000000..da1612a7f29 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/14f66ce296f03e52f039f4fad189d3d70aebe70ecb14ffb1ffe2cd5fc5d1e5f0 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab b/lib/nghttp2/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab new file mode 100644 index 00000000000..ab72d093230 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/17caaf734401d2d25d09a65432789b45aff588c606536e93824b89739a6d07ab differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 b/lib/nghttp2/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 new file mode 100644 index 00000000000..a9123c4eb7d Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/195b4a74a62fabc877052454d935ebc543f4d1305e318ccd2ff407517636bed8 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 b/lib/nghttp2/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 new file mode 100644 index 00000000000..b6b54dcf276 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1960fc215485486f3e8ab97f853954e6f11c1f4754ccd83b1603b808878cfa76 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 b/lib/nghttp2/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 new file mode 100644 index 00000000000..9338c998551 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1a56272611761f0687dfb0ea37c900f13f429b750c87e6175b234b881bda6248 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 b/lib/nghttp2/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 new file mode 100644 index 00000000000..aa67cbc7e85 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1d31cd88fae35f2329e201983d11256d2432fcdeb55bfba9634aa88e3794adc6 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 b/lib/nghttp2/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 new file mode 100644 index 00000000000..ad145181e17 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1e27187b10c02fe7e151818ddd0722f69830ac04975ddb5a9d83cdc406cbb678 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df b/lib/nghttp2/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df new file mode 100644 index 00000000000..e36ad42eba5 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1ecace234d8542fbaab35c7c55330e80d8121a0cff19633a56eba8f2182a59df differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f b/lib/nghttp2/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f new file mode 100644 index 00000000000..a0c5dc36af0 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/1f4f3a16f5ad0425e0b38601339096b80a382afa1083a19c4deab11be847502f differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 b/lib/nghttp2/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 new file mode 100644 index 00000000000..9176566da80 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/203a798d4b658be744fe34042038692eaede4d2c1f9e05a27f2410a6e0230132 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce b/lib/nghttp2/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce new file mode 100644 index 00000000000..1eb839a9807 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/21904e842e90becb56ff9748ae962bb543dd5ca188dabc30897726f87403fbce differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 b/lib/nghttp2/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 new file mode 100644 index 00000000000..5e6fd536f5c Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/23df7e0419240a9709b55af68a89c9750332ae5063e36401eae150ce63188fe0 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 b/lib/nghttp2/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 new file mode 100644 index 00000000000..e8e09a8cefa Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/245ba702520fa32cf41d994f5d37e4111fe6203bac35b220d50362d5e986aa91 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 b/lib/nghttp2/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 new file mode 100644 index 00000000000..f631036ccf1 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/274faf343feb9cb44079316401fee50c647552c99c0550ebfd7a3b736e8db9e5 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 b/lib/nghttp2/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 new file mode 100644 index 00000000000..c99b1d80056 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/2b042a1dfa3aeed6af58c58a4336f1386633bac75dea2c4b64c02541e7320933 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a b/lib/nghttp2/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a new file mode 100644 index 00000000000..d168ca6b7a4 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/2d8ec606661a9f12960893aab9a74dd392cbdae104307e8512e5e4113739e93a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c b/lib/nghttp2/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c new file mode 100644 index 00000000000..cf176ebaf39 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/2e0c8a3ce53e8e3711f781b480efaf9e2526f4ae87c5f5a585d68d6f7f7da13c differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d b/lib/nghttp2/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d new file mode 100644 index 00000000000..07a4f677c14 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/315e6acba7d715333d0865a8dfc0cd0e7aef8a1f5f420eae3d39067ad78df17d differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f b/lib/nghttp2/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f new file mode 100644 index 00000000000..73f52d1ba7a Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/3376a2cdde0b98759f14490881328f80b5d3c942de3b1304a0382923ce896f8f differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca b/lib/nghttp2/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca new file mode 100644 index 00000000000..a27ca46aa8b Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/35c2719913a19f197fb6484a34c3574da63554ff06f52377b73a9cfc24eb02ca differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c b/lib/nghttp2/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c new file mode 100644 index 00000000000..f5a2f2f22fb Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/35ddf0611cd98d025f6a625e7e4a102ba74721a04dfa1811e0968e9a4966d92c differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf b/lib/nghttp2/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf new file mode 100644 index 00000000000..bdb70efdc14 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/37e9eab291d6bca69510354e1d029cbbbb6113071b2bb13fc9646b5a0447d2cf differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 b/lib/nghttp2/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 new file mode 100644 index 00000000000..b5a05b67076 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/381c81f5e4d1b02de39c4f99f21e9793f6ffc82ae0ef6917a8611e8879e05941 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a b/lib/nghttp2/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a new file mode 100644 index 00000000000..fa18efff7a6 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/38ac32c81952cc832ade7aea13b0740f76898ccbb1da25f2281da76e50c1d04a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 b/lib/nghttp2/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 new file mode 100644 index 00000000000..b8e0b33df75 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/3e297dd8fcdb50a751c397a505d84e76374b064aa5c71aab33bd9650c9a9d801 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 b/lib/nghttp2/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 new file mode 100644 index 00000000000..2be33f54ad9 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/3e5a57c30a97d3f06a3181f4baf3996053b8572da5f2deee3a636c3bc8dfcc60 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 b/lib/nghttp2/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 new file mode 100644 index 00000000000..a7d696a598d Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/420b9790375f59a6e8c326391023a0981789c2351817996e0c253bfed708ad82 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab b/lib/nghttp2/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab new file mode 100644 index 00000000000..2f00755ed85 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/43df3c3af62ddd1393269ffcf964f1897063e81da79c971e8af8c1fefa3e3cab differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 b/lib/nghttp2/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 new file mode 100644 index 00000000000..d212c233885 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/443f39c99e1c9ca1908b54153c480754054a57777f22a00d377d745d78e9d193 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 b/lib/nghttp2/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 new file mode 100644 index 00000000000..43a84230834 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/44f3fc1504a14e693fde420da94f77bf4a44e4e741420291491343f7ae4ecc16 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 b/lib/nghttp2/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 new file mode 100644 index 00000000000..43cf9725093 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/4528e6beb34f695f4df8ddbb7ac85f76a91229d9ba675fc9e09fe12f4a497937 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 b/lib/nghttp2/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 new file mode 100644 index 00000000000..e41d6379601 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/4534032d57020d2910641561a9f9da021f0fe52ebdbb148ee776ced87bac9b13 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a b/lib/nghttp2/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a new file mode 100644 index 00000000000..cc92edc5c3c Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/47c5e9b339f9e7f1dccad5c9f51f211183795660ec81a6bdb5614031d39ebe3a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 b/lib/nghttp2/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 new file mode 100644 index 00000000000..e1597d967e2 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/48ca2b3f63206aa8f774c3cb33958a806a1debf3d9ccf7b09c2d31256498cda6 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e b/lib/nghttp2/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e new file mode 100644 index 00000000000..11175d83dbc Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/4ddbb54259df7ee7ecbdf9f8b4a0e8f7756b9846f2e2add8dd0df825296d993e differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa b/lib/nghttp2/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa new file mode 100644 index 00000000000..4d233a0f4e7 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/4e612f3c1dfa468d94bbc3bde202c732b06a9b5f6bc5471c879fa56ec2daa4aa differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a b/lib/nghttp2/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a new file mode 100644 index 00000000000..71ab2d8a7fa Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/55860c89ef796d41b06b3c0fe60a3e6f90709c6a0e7063a8b4057dafa57c878a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 b/lib/nghttp2/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 new file mode 100644 index 00000000000..979e96aa7ae Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/5748e7a24e8d9ecb43de7d1e14519f10d8c669a5a2602fc948bc9a80e6114b63 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 b/lib/nghttp2/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 new file mode 100644 index 00000000000..5b47f4f3d12 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/5a13c8e09802e07fd3ceee625307fe48ef29bc66641c4f80ed4593bf8b773f88 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 b/lib/nghttp2/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 new file mode 100644 index 00000000000..1ebf432cb98 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/5aa30337198b482522a55c90554c93278034ebacc24792509a32aeba466df4e8 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 b/lib/nghttp2/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 new file mode 100644 index 00000000000..f2e3ff2b59b Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/5f3ff3c345ade163ba1ba889d60c1995b7fab68ded6ab052814008d990862c23 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e b/lib/nghttp2/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e new file mode 100644 index 00000000000..22137b3fd4e Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/5f88a17509a8843ab761bc8cbcfe1a511670ae1a4a434f3d483f942738933a3e differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c b/lib/nghttp2/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c new file mode 100644 index 00000000000..570e98ae285 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/60a288333ea7f01d380f2661d387692063ce2ae73b3e5401b716326967b4ce0c differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d b/lib/nghttp2/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d new file mode 100644 index 00000000000..c0f50b7a96a Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/63ae750f5fe9469664b6f79cb48c502c3bfc4cb0a950aeba998a72ea6a3d5b2d differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 b/lib/nghttp2/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 new file mode 100644 index 00000000000..5a4c52ad507 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/67abeaacb21769a9fb521efa7ebdc8d9ff3443ad5892d75dd6d4f7d541713d33 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 b/lib/nghttp2/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 new file mode 100644 index 00000000000..a7fa65ddd3c Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/6e3b8913d874a18ec3ab9f74d4fab435b7738e1a14d0754fb79229c4bda9f604 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 b/lib/nghttp2/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 new file mode 100644 index 00000000000..c89a9a7df93 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/6fe31187ce1a64bffb0b31ee59618a2ebd483812410e9f8ae5a92fb72ef70885 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e b/lib/nghttp2/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e new file mode 100644 index 00000000000..e42f5f421c1 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/71d3c74882a100eaa5aaf9f62659d3b26bcbb8f2055f1add504f599f9051f61e differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea b/lib/nghttp2/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea new file mode 100644 index 00000000000..32000271188 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/7232f506e00bee175a3df8d33933fae10c67e501d6cea8e73ce76f4363d0bbea differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f b/lib/nghttp2/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f new file mode 100644 index 00000000000..28709d4b00e Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/7425039321dcbecb1a1ef28849f277f914a889a54d44c1f2566b6ddd5bc83b4f differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 b/lib/nghttp2/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 new file mode 100644 index 00000000000..46316c1dfaa Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/7487341c630472c46a534223da1173666aaeae9788b144fa2c723204d55cc0a2 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 b/lib/nghttp2/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 new file mode 100644 index 00000000000..68a831a4163 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/79207f7d09b6145f3dbfcb9e19835f34e56c7927fda22859e960f5f13bc847a0 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a b/lib/nghttp2/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a new file mode 100644 index 00000000000..582b979f4bd Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/7a1e1268d329e5f71ebdf74677a6c1a118994d7534d1fb08d631898d67372f5a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 b/lib/nghttp2/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 new file mode 100644 index 00000000000..ea39871a708 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/7c954b010232be9461483803e3e553623d4fc382324d8b8ba53ebf83f0457707 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 b/lib/nghttp2/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 new file mode 100644 index 00000000000..38e577422c0 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/7ce8914993956b04baafaad0668e5c26a87a1c4cf70a6566aa0f199fe3c1dc18 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 b/lib/nghttp2/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 new file mode 100644 index 00000000000..7a8ad5d1cb0 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/7d230ff71bac867a9820e75328f893972df210ab75cdb67f620b370ee5cddf45 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada b/lib/nghttp2/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada new file mode 100644 index 00000000000..063bdab6e8f Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/85a985b9011e356e11a24c2d0a01173ea80ccc584b659947b64ffefddab7fada differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 b/lib/nghttp2/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 new file mode 100644 index 00000000000..7eed6150000 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/8b165b8b94a9d120edf139fbd63cb6b161131d5722f201f2f4ba0984b46a3ca5 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a b/lib/nghttp2/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a new file mode 100644 index 00000000000..aa862db4abe Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/8f5fd3dd5c0eb40ceb409c0f7d85086319d4177524fad58dc01743434765902a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 b/lib/nghttp2/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 new file mode 100644 index 00000000000..1c2503ec9ee Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/9223480b7c4b0d1cb95eb33a7a52dc7494b53a0f8a93fbc1816c6c4f347780b0 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 b/lib/nghttp2/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 new file mode 100644 index 00000000000..4f6a2ebbc8f Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/9248ee16c602d45651b0045e9cc4e407fc62ce5688e1c6636f482ea02314c357 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 b/lib/nghttp2/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 new file mode 100644 index 00000000000..0c101941100 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/979b96b7806f61081a48ff556bfbdb3e1c74e04f7d2cf88eab49b0fd89845453 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb b/lib/nghttp2/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb new file mode 100644 index 00000000000..b92e520ac82 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/97f2f674b859ff1adb2e9548550f07fa8818d1ee8edae39ca50f516a57a12edb differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef b/lib/nghttp2/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef new file mode 100644 index 00000000000..cd87803f0e8 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/9984490c02b1604423a8679caf527d5f10667e0a38790f28f32af61efa930eef differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 b/lib/nghttp2/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 new file mode 100644 index 00000000000..63ca53a9c60 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/9a648e49f93b60cf578c87d187c8acb61d3a638bc30568bdcc6be30fd9defd43 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 b/lib/nghttp2/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 new file mode 100644 index 00000000000..08dd2bdb340 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/9af5c7a8538fb02b0a836b88a40d0b144f11ee98624e3686c0f43684e34e6838 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 b/lib/nghttp2/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 new file mode 100644 index 00000000000..51c931c7cb4 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/9b24f66bc7c47e677e40f8b07b2fd54985ef27c99670bed582ce904569b95702 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 b/lib/nghttp2/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 new file mode 100644 index 00000000000..35134714362 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/9fc2eee916b1cfb002a487c37e73af29a0fbb29e47bf36839a762bb26fea3ec7 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 b/lib/nghttp2/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 new file mode 100644 index 00000000000..3f897bd94d9 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/9ff0fc476b3d27f5dc9803d38ef10be0d08b5e096630308f0d6f57a6f8ee5d88 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 b/lib/nghttp2/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 new file mode 100644 index 00000000000..a8b06a9be11 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/a46866d1875d0c06ec3ead73ecca531ef0dc92a67a233ebc8d1e2fff79f50a07 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 b/lib/nghttp2/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 new file mode 100644 index 00000000000..dfa976699ed Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/a71bcbf6a6668aa019d38cc3527d5ecf2f4e538dfedddf34ff484e29d6fd26d1 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 b/lib/nghttp2/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 new file mode 100644 index 00000000000..1b8b19e0f1f Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/ad0d3509e08424d21d87c64a0969b588dc9281ea98fd744acd9b8bd1daf72225 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 b/lib/nghttp2/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 new file mode 100644 index 00000000000..e8d7b2d3b54 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/adaa168d63fe063455c1e0c304c9c9ba6b43e13849235339710d6b5f941e80a1 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f b/lib/nghttp2/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f new file mode 100644 index 00000000000..1b4ae593007 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/aee251ccb027a2676ad1261b48d08b52752a41633279ff2e9e474eebf508250f differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 b/lib/nghttp2/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 new file mode 100644 index 00000000000..7d81f74a7fb Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/b5b546cf87a6d23c6f6ee0e44db5b90a4bb23e0558873f159bf09140782989d8 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 b/lib/nghttp2/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 new file mode 100644 index 00000000000..eae40c48ee1 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/b8fffa51391680139ea773ff40a58a1f24e9b1a8c530823d7d12053ec4aabd76 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b b/lib/nghttp2/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b new file mode 100644 index 00000000000..875250c1125 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/b904fd3aa656603b26572deb105290328add76123b4a99ad4e78189e1337ae1b differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 b/lib/nghttp2/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 new file mode 100644 index 00000000000..4ca7ddb6c9a Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/bbda8e26f356aa635f7774ec483a4b493668ca1448948c62f641d176838306d5 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb b/lib/nghttp2/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb new file mode 100644 index 00000000000..8c90cf33646 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/bc35711cdc43b868c59515211893e7681fef6da4b623392d402fb40736dc1beb differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 b/lib/nghttp2/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 new file mode 100644 index 00000000000..3aff7238593 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/bd25bb84dd44c7e09d9e723016c49cc2a868a1bfc007528138a28ea1c0abfda7 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 b/lib/nghttp2/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 new file mode 100644 index 00000000000..20ed145a26d Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/c23df1d03e3c1039692ea3d9897e41ceb2add1ebdec0937a64321c536eef71f7 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a b/lib/nghttp2/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a new file mode 100644 index 00000000000..adf60dfb1f3 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/c2e6cf1692ef3a4bc88af94bb9e6c9011855bbf954c273f45eb3ea97bb491c9a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 b/lib/nghttp2/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 new file mode 100644 index 00000000000..28b6df13b53 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/c3b0ea2a8874777b9805018c177382ab3278a019935fa50b3e0d7971c28c40d9 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 b/lib/nghttp2/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 new file mode 100644 index 00000000000..6c1c4bc8b06 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/c9dfe97833473610816085c5a009696cd5f659f85fc10ef76dc140851ffcc423 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d b/lib/nghttp2/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d new file mode 100644 index 00000000000..e290f86fe3f Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/ca19cba772c047e5e1f229e5de18d06d885b50be9136778b4937437f0d70738d differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e b/lib/nghttp2/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e new file mode 100644 index 00000000000..4b92a834359 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/ca6e1239c11d08940c991f77470859ccb4ec9fa5e8c30de7b40521d620b87a1e differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 b/lib/nghttp2/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 new file mode 100644 index 00000000000..5759f66944b Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/cb09d2148ae1c8b054cdbafcf3f3e41e75bae978dcfc8886981479d723fc44e9 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc b/lib/nghttp2/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc new file mode 100644 index 00000000000..abfa58f6c07 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/cd35ff680e23f67fe52b722a88c9537bee642b8a7a8a388cb4952f3bf60e64cc differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad b/lib/nghttp2/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad new file mode 100644 index 00000000000..e3ef3dccc63 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/cd6d3880ee87c6b716749cb9a30f8faa658ee49f6ce90f3e34df70560a0477ad differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b b/lib/nghttp2/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b new file mode 100644 index 00000000000..da91c8d57f5 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/cd7b24cfe10fc4346a91f04b1a0d0e22054f76bf704db8e19d73cb9bf792a89b differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 b/lib/nghttp2/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 new file mode 100644 index 00000000000..a418c41b850 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/cea2c4c70f94e90c4c4a6b63f7c212d2465936090c06ba4db92071a3c247ca11 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 b/lib/nghttp2/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 new file mode 100644 index 00000000000..40659b535bb Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/d26a0d653a01c6bf9403e0bc0fa5ea05ea4dd7b163e8d85287b19ff257a88ea7 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 b/lib/nghttp2/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 new file mode 100644 index 00000000000..98b11ed9edd Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/d3dec3f7485c6c3f8b8949db68bd212ef16a7f1f41047e290d14f9cd6dae91a0 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 b/lib/nghttp2/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 new file mode 100644 index 00000000000..a88435f17f0 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/d43f2a0606841580986981ec0bec10473e79c9097bfd8fd81d1a239f146f31d3 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 b/lib/nghttp2/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 new file mode 100644 index 00000000000..783b6a3b9ed Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/d4d5fe38e4bafa733182eb5aaad19a6ff59c8316908b20d3c94cdc29a92964e6 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 b/lib/nghttp2/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 new file mode 100644 index 00000000000..3ec3fbbb61a Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/d69256403d5d27244080b8b53931aa6bfd4ce95771c748372626414d5c37e105 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d b/lib/nghttp2/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d new file mode 100644 index 00000000000..348471d4a46 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/d9b617f62de41c1cb02ff91cef9c3f753d440c75efa489a952fdcd314d27ee1d differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf b/lib/nghttp2/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf new file mode 100644 index 00000000000..004ea71cc6c Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/dc57f64202486572ef99d4ff4970fb339f440867ebedf02eaab75fb555e293cf differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a b/lib/nghttp2/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a new file mode 100644 index 00000000000..11e84b4c686 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/e11a6036e2c0bde71f3eabac3f98734af2cdcfe3ebb6e02dcce9b7f4c4bcc99a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 b/lib/nghttp2/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 new file mode 100644 index 00000000000..cf972d1730c Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/e26ce028366bb4ff566972a945b7fd0035f6dba48d886160fdf1974aae8dee65 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 b/lib/nghttp2/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 new file mode 100644 index 00000000000..d894be7e13a Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/e35a4d079adfe4d399f026c711940e4917d5dae3dc2723a034f44d2b53a34a11 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 b/lib/nghttp2/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 new file mode 100644 index 00000000000..303b68a4ba2 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/e3666122dbe804ac609c0ae717a9e6aa8bb2842953e4528230a5bcfc3a59c120 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 b/lib/nghttp2/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 new file mode 100644 index 00000000000..b19f4f64201 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/e59961f75a4cfe33bc4ce9290f938c5bc247c440a2e572ab18021c8223c55bc7 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df b/lib/nghttp2/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df new file mode 100644 index 00000000000..e5c39ccd9f7 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/e7b11cf0762255ad6741aa3d6e269f8b4bc785089040be666f480464cb13b4df differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 b/lib/nghttp2/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 new file mode 100644 index 00000000000..81e30d797f0 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/e89af554621f1ce6262d47a68efea1d8d304ae595a094ebc955bceb6d06ed629 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 b/lib/nghttp2/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 new file mode 100644 index 00000000000..0af0a7d69fb Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/e9d399b6dc6b7d18bac97e5556875ab6df561f1ca718f1fc716a929d3c706f14 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 b/lib/nghttp2/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 new file mode 100644 index 00000000000..27dec296578 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/eb733425f0fc1f0cf7f74e1c1ef87680a96a1aca613180110df26259eb36c433 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 b/lib/nghttp2/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 new file mode 100644 index 00000000000..d528c3f1a74 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/ec399d3511fa4a30df9b3c51637a357cc1c84d30e3d48bccc9b97564c8a60b73 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee b/lib/nghttp2/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee new file mode 100644 index 00000000000..ac58d53e233 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/ef73cbf3d98059b13b30db1089ad6af12beea18f895be6f18d42962721d6e3ee differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 b/lib/nghttp2/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 new file mode 100644 index 00000000000..317ead97b86 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/efc0f664cf2ebac4e05e6acac77778fe630b278f167321a46d861ac8ad56fd76 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a b/lib/nghttp2/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a new file mode 100644 index 00000000000..ff4af29f08d Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/f139f9c20bcdc6bbe0301c98bdd719b37b4a98fe3b1414b583ddb5dc17f62e3a differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 b/lib/nghttp2/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 new file mode 100644 index 00000000000..2019289459d Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/f5318eb5ea6dcdf630a2ab157dbfa122f6de9b6f4e5a3a036c17f32da3030877 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc b/lib/nghttp2/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc new file mode 100644 index 00000000000..1514db50c4f Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/f5f4973e9e8fb6fb8834a612a9b8b0419fbae7c0934dda22e61f11556918f1cc differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 b/lib/nghttp2/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 new file mode 100644 index 00000000000..45db5f02181 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/f932da1aefb3b8d9918f46bd936130b0d06332ab062a48f41b206ce696428e03 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e b/lib/nghttp2/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e new file mode 100644 index 00000000000..76f3f051d2c Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/fbfa931f27b0173613b0e04af58d8bba7df12c1cd15c404d95680df6fc1cb89e differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 b/lib/nghttp2/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 new file mode 100644 index 00000000000..f7cf1d7a284 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/fc30ab2ea532f953350f0de7ff3c0422328c131f4642d30a4c88bdf43bcd8d98 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 b/lib/nghttp2/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 new file mode 100644 index 00000000000..14891ed8b95 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/fc7e85c3af87f3c0b482cb57fde916a7d8db293427159f3b31bbc23b6b285116 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 b/lib/nghttp2/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 new file mode 100644 index 00000000000..95f5a2ebcd6 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/fcfcfe84724a9b7c7c8277057b557ab044d24130bd360fe087e9f55bef2cadc6 differ diff --git a/lib/nghttp2/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 b/lib/nghttp2/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 new file mode 100644 index 00000000000..8b31bfd2fa2 Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/h2spec/ff00f50eada19c5354a579ef7f1af5952ecb2df2423022dd5483d8fede26d6e5 differ diff --git a/lib/nghttp2/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb b/lib/nghttp2/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb new file mode 100644 index 00000000000..a2142c69b7b Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/nghttp/9c8ed8981065d28ce8a5a04ac6fc7a87ffaf9f9c6ce4323e6e0fefaabb2393cb differ diff --git a/lib/nghttp2/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 b/lib/nghttp2/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 new file mode 100644 index 00000000000..6f9f9ac215d Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/nghttp/d53b58a8685030918fda36a704db43cdfec99fc1b9de83c195227161f4bdb911 differ diff --git a/lib/nghttp2/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa b/lib/nghttp2/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa new file mode 100644 index 00000000000..b3e2459bdfd Binary files /dev/null and b/lib/nghttp2/fuzz/corpus/nghttp/f0a8cacb9f31b53d237628084e3946d556086c9991cce7962e9e69a3eed406aa differ diff --git a/lib/nghttp2/fuzz/fuzz_frames.cc b/lib/nghttp2/fuzz/fuzz_frames.cc new file mode 100644 index 00000000000..2a20c42a60c --- /dev/null +++ b/lib/nghttp2/fuzz/fuzz_frames.cc @@ -0,0 +1,160 @@ +#include +#include + +extern "C" { +#include +#include "nghttp2_hd.h" +#include "nghttp2_frame.h" + +#include "nghttp2_test_helper.h" + +#define HEADERS_LENGTH 7 + +static nghttp2_nv fuzz_make_nv(std::string s1, std::string s2) { + nghttp2_nv nv; + uint8_t *n = (uint8_t *)malloc(s1.size()); + memcpy(n, s1.c_str(), s1.size()); + + uint8_t *v = (uint8_t *)malloc(s2.size()); + memcpy(v, s2.c_str(), s2.size()); + + nv.name = n; + nv.value = v; + nv.namelen = s1.size(); + nv.valuelen = s2.size(); + nv.flags = NGHTTP2_NV_FLAG_NONE; + + return nv; +} + +static void fuzz_free_nv(nghttp2_nv *nv) { + free(nv->name); + free(nv->value); +} + +void check_frame_pack_headers(FuzzedDataProvider *data_provider) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_headers frame, oframe; + nghttp2_bufs bufs; + nghttp2_nv *nva; + nghttp2_priority_spec pri_spec; + size_t nvlen; + nva_out out; + size_t hdblocklen; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + /* Create a set of headers seeded with data from the fuzzer */ + nva = (nghttp2_nv *)mem->malloc(sizeof(nghttp2_nv) * HEADERS_LENGTH, NULL); + for (int i = 0; i < HEADERS_LENGTH; i++) { + nva[i] = fuzz_make_nv(data_provider->ConsumeRandomLengthString(30), + data_provider->ConsumeRandomLengthString(300)); + } + + nvlen = HEADERS_LENGTH; + nghttp2_priority_spec_default_init(&pri_spec); + nghttp2_frame_headers_init( + &frame, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, 1000000007, + NGHTTP2_HCAT_REQUEST, &pri_spec, nva, nvlen); + + /* Perform a set of operations with the fuzz data */ + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); + if (rv == 0) { + unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + } + + nghttp2_nv *nva2 = NULL; + rv = nghttp2_nv_array_copy(&nva2, nva, nvlen, mem); + if (rv == 0) { + nghttp2_nv_array_del(nva2, mem); + } + + /* Cleanup */ + for (int i = 0; i < HEADERS_LENGTH; i++) { + fuzz_free_nv(&nva[i]); + } + + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void check_frame_push_promise(FuzzedDataProvider *data_provider) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_push_promise frame, oframe; + nghttp2_bufs bufs; + nghttp2_nv *nva; + nghttp2_priority_spec pri_spec; + size_t nvlen; + nva_out out; + size_t hdblocklen; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + /* Create a set of headers seeded with data from the fuzzer */ + nva = (nghttp2_nv *)mem->malloc(sizeof(nghttp2_nv) * HEADERS_LENGTH, NULL); + for (int i = 0; i < HEADERS_LENGTH; i++) { + nva[i] = fuzz_make_nv(data_provider->ConsumeRandomLengthString(30), + data_provider->ConsumeRandomLengthString(300)); + } + nvlen = HEADERS_LENGTH; + nghttp2_priority_spec_default_init(&pri_spec); + + /* Perform a set of operations with the fuzz data */ + nghttp2_frame_push_promise_init(&frame, NGHTTP2_FLAG_END_HEADERS, 1000000007, + (1U << 31) - 1, nva, nvlen); + + rv = nghttp2_frame_pack_push_promise(&bufs, &frame, &deflater); + if (rv == 0) { + unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + } + + nghttp2_nv *nva2 = NULL; + rv = nghttp2_nv_array_copy(&nva2, nva, nvlen, mem); + if (rv == 0) { + nghttp2_nv_array_del(nva2, mem); + } + + /* Cleanup */ + for (int i = 0; i < HEADERS_LENGTH; i++) { + fuzz_free_nv(&nva[i]); + } + + nghttp2_bufs_reset(&bufs); + nghttp2_bufs_free(&bufs); + + nghttp2_frame_push_promise_free(&frame, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + FuzzedDataProvider data_provider(data, size); + + check_frame_pack_headers(&data_provider); + check_frame_push_promise(&data_provider); + return 0; +} + +} // extern C diff --git a/lib/nghttp2/fuzz/fuzz_target.cc b/lib/nghttp2/fuzz/fuzz_target.cc new file mode 100644 index 00000000000..4adc5ed658f --- /dev/null +++ b/lib/nghttp2/fuzz/fuzz_target.cc @@ -0,0 +1,79 @@ +#include + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame, + nghttp2_rcbuf *name, nghttp2_rcbuf *value, + uint8_t flags, void *user_data) { + return 0; +} +} // namespace + +namespace { +int before_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + return 0; +} +} // namespace + +namespace { +void send_pending(nghttp2_session *session) { + for (;;) { + const uint8_t *data; + auto n = nghttp2_session_mem_send(session, &data); + if (n == 0) { + return; + } + } +} +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + nghttp2_session *session; + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + nghttp2_session_callbacks_set_on_header_callback2(callbacks, + on_header_callback2); + nghttp2_session_callbacks_set_before_frame_send_callback( + callbacks, before_frame_send_callback); + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + + nghttp2_session_server_new(&session, callbacks, nullptr); + nghttp2_session_callbacks_del(callbacks); + + nghttp2_settings_entry iv{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}; + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + send_pending(session); + nghttp2_session_mem_recv(session, data, size); + send_pending(session); + + nghttp2_session_del(session); + + return 0; +} diff --git a/lib/nghttp2/fuzz/fuzz_target_fdp.cc b/lib/nghttp2/fuzz/fuzz_target_fdp.cc new file mode 100644 index 00000000000..f94b96433d2 --- /dev/null +++ b/lib/nghttp2/fuzz/fuzz_target_fdp.cc @@ -0,0 +1,99 @@ +#include +#include +#include + +#include + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame, + nghttp2_rcbuf *name, nghttp2_rcbuf *value, + uint8_t flags, void *user_data) { + return 0; +} +} // namespace + +namespace { +int before_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + return 0; +} +} // namespace + +namespace { +int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + return 0; +} +} // namespace + +namespace { +void send_pending(nghttp2_session *session) { + for (;;) { + const uint8_t *data; + auto n = nghttp2_session_mem_send(session, &data); + if (n == 0) { + return; + } + } +} +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + nghttp2_session *session; + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + nghttp2_session_callbacks_set_on_header_callback2(callbacks, + on_header_callback2); + nghttp2_session_callbacks_set_before_frame_send_callback( + callbacks, before_frame_send_callback); + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + + nghttp2_session_server_new(&session, callbacks, nullptr); + nghttp2_session_callbacks_del(callbacks); + + FuzzedDataProvider data_provider(data, size); + + /* Initialise a random iv */ + nghttp2_settings_entry *iv; + int size_of_iv = data_provider.ConsumeIntegralInRange(1, 10); + iv = (nghttp2_settings_entry *)malloc(sizeof(nghttp2_settings_entry) * + size_of_iv); + for (int i = 0; i < size_of_iv; i++) { + iv[i].settings_id = data_provider.ConsumeIntegralInRange(0, 1000); + iv[i].value = data_provider.ConsumeIntegralInRange(0, 1000); + } + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, size_of_iv); + send_pending(session); + + std::vector d = data_provider.ConsumeRemainingBytes(); + nghttp2_session_mem_recv(session, d.data(), d.size()); + + send_pending(session); + + nghttp2_session_del(session); + + free(iv); + + return 0; +} diff --git a/lib/nghttp2/genauthoritychartbl.py b/lib/nghttp2/genauthoritychartbl.py new file mode 100755 index 00000000000..1b100283d36 --- /dev/null +++ b/lib/nghttp2/genauthoritychartbl.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x21: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ', + 'SPC '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) in [ + "-", ".", "_", "~", + "!", "$", "&", "'", "(", ")", + "*", "+", ",", ";", "=", + "%", "@", ":", "[", "]"] or\ + ('0' <= chr(i) and chr(i) <= '9') or \ + ('A' <= chr(i) and chr(i) <= 'Z') or \ + ('a' <= chr(i) and chr(i) <= 'z'): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('0 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('0 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/lib/nghttp2/gendowncasetbl.py b/lib/nghttp2/gendowncasetbl.py new file mode 100755 index 00000000000..04aeda75366 --- /dev/null +++ b/lib/nghttp2/gendowncasetbl.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x20: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) == ' ': + sys.stdout.write('{} /* SPC */, '.format(i)) + elif chr(i) == '\t': + sys.stdout.write('{} /* HT */, '.format(i)) + elif 'A' <= chr(i) and chr(i) <= 'Z': + sys.stdout.write('{} /* {} */, '.format(i - ord('A') + ord('a'), chr(i))) + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('{} /* {} */, '.format(i, chr(i))) + elif 0x80 <= i: + sys.stdout.write('{} /* {} */, '.format(i, hex(i))) + elif 0 == i: + sys.stdout.write('{} /* NUL */, '.format(i)) + else: + sys.stdout.write('{} /* {} */, '.format(i, name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/lib/nghttp2/genheaderfunc.py b/lib/nghttp2/genheaderfunc.py new file mode 100755 index 00000000000..2ac3c373415 --- /dev/null +++ b/lib/nghttp2/genheaderfunc.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +from gentokenlookup import gentokenlookup + +HEADERS = [ + ':authority', + ':method', + ':path', + ':scheme', + ':status', + ':host', # for spdy + ':protocol', + 'expect', + 'host', + 'if-modified-since', + "te", + "cookie", + "http2-settings", + "server", + "via", + "forwarded", + "x-forwarded-for", + "x-forwarded-proto", + "alt-svc", + "content-length", + "location", + "trailer", + "link", + "accept-encoding", + "accept-language", + "cache-control", + "user-agent", + "date", + "content-type", + "early-data", + "sec-websocket-accept", + "sec-websocket-key", + "priority", + # disallowed h1 headers + 'connection', + 'keep-alive', + 'proxy-connection', + 'transfer-encoding', + 'upgrade' +] + +if __name__ == '__main__': + gentokenlookup(HEADERS, 'HD_') diff --git a/lib/nghttp2/genlibtokenlookup.py b/lib/nghttp2/genlibtokenlookup.py new file mode 100755 index 00000000000..d15d0b500c4 --- /dev/null +++ b/lib/nghttp2/genlibtokenlookup.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 + +HEADERS = [ + (':authority', 0), + (':method', 1), + (':method', 2), + (':path', 3), + (':path', 4), + (':scheme', 5), + (':scheme', 6), + (':status', 7), + (':status', 8), + (':status', 9), + (':status', 10), + (':status', 11), + (':status', 12), + (':status', 13), + ('accept-charset', 14), + ('accept-encoding', 15), + ('accept-language', 16), + ('accept-ranges', 17), + ('accept', 18), + ('access-control-allow-origin', 19), + ('age', 20), + ('allow', 21), + ('authorization', 22), + ('cache-control', 23), + ('content-disposition', 24), + ('content-encoding', 25), + ('content-language', 26), + ('content-length', 27), + ('content-location', 28), + ('content-range', 29), + ('content-type', 30), + ('cookie', 31), + ('date', 32), + ('etag', 33), + ('expect', 34), + ('expires', 35), + ('from', 36), + ('host', 37), + ('if-match', 38), + ('if-modified-since', 39), + ('if-none-match', 40), + ('if-range', 41), + ('if-unmodified-since', 42), + ('last-modified', 43), + ('link', 44), + ('location', 45), + ('max-forwards', 46), + ('proxy-authenticate', 47), + ('proxy-authorization', 48), + ('range', 49), + ('referer', 50), + ('refresh', 51), + ('retry-after', 52), + ('server', 53), + ('set-cookie', 54), + ('strict-transport-security', 55), + ('transfer-encoding', 56), + ('user-agent', 57), + ('vary', 58), + ('via', 59), + ('www-authenticate', 60), + ('te', None), + ('connection', None), + ('keep-alive',None), + ('proxy-connection', None), + ('upgrade', None), + (':protocol', None), + ('priority', None), +] + +def to_enum_hd(k): + res = 'NGHTTP2_TOKEN_' + for c in k.upper(): + if c == ':' or c == '-': + res += '_' + continue + res += c + return res + +def build_header(headers): + res = {} + for k, _ in headers: + size = len(k) + if size not in res: + res[size] = {} + ent = res[size] + c = k[-1] + if c not in ent: + ent[c] = [] + if k not in ent[c]: + ent[c].append(k) + + return res + +def gen_enum(): + name = '' + print('typedef enum {') + for k, token in HEADERS: + if token is None: + print(' {},'.format(to_enum_hd(k))) + else: + if name != k: + name = k + print(' {} = {},'.format(to_enum_hd(k), token)) + print('} nghttp2_token;') + +def gen_index_header(): + print('''\ +static int32_t lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) {''') + b = build_header(HEADERS) + for size in sorted(b.keys()): + ents = b[size] + print('''\ + case {}:'''.format(size)) + print('''\ + switch (name[{}]) {{'''.format(size - 1)) + for c in sorted(ents.keys()): + headers = sorted(ents[c]) + print('''\ + case '{}':'''.format(c)) + for k in headers: + print('''\ + if (memeq("{}", name, {})) {{ + return {}; + }}'''.format(k[:-1], size - 1, to_enum_hd(k))) + print('''\ + break;''') + print('''\ + } + break;''') + print('''\ + } + return -1; +}''') + +if __name__ == '__main__': + gen_enum() + print() + gen_index_header() diff --git a/lib/nghttp2/genmethodchartbl.py b/lib/nghttp2/genmethodchartbl.py new file mode 100755 index 00000000000..a0c3a378e38 --- /dev/null +++ b/lib/nghttp2/genmethodchartbl.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x21: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ', + 'SPC '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) in ["!" , "#" , "$" , "%" , "&" , "'" , "*", + "+" , "-" , "." , "^" , "_" , "`" , "|" , "~"] or\ + ('0' <= chr(i) and chr(i) <= '9') or \ + ('A' <= chr(i) and chr(i) <= 'Z') or \ + ('a' <= chr(i) and chr(i) <= 'z'): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('0 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('0 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/lib/nghttp2/genmethodfunc.py b/lib/nghttp2/genmethodfunc.py new file mode 100755 index 00000000000..436a77a8409 --- /dev/null +++ b/lib/nghttp2/genmethodfunc.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +from io import StringIO + +from gentokenlookup import gentokenlookup + +# copied from llhttp.h, and stripped trailing spaces and backslashes. +SRC = ''' + XX(0, DELETE, DELETE) + XX(1, GET, GET) + XX(2, HEAD, HEAD) + XX(3, POST, POST) + XX(4, PUT, PUT) + XX(5, CONNECT, CONNECT) + XX(6, OPTIONS, OPTIONS) + XX(7, TRACE, TRACE) + XX(8, COPY, COPY) + XX(9, LOCK, LOCK) + XX(10, MKCOL, MKCOL) + XX(11, MOVE, MOVE) + XX(12, PROPFIND, PROPFIND) + XX(13, PROPPATCH, PROPPATCH) + XX(14, SEARCH, SEARCH) + XX(15, UNLOCK, UNLOCK) + XX(16, BIND, BIND) + XX(17, REBIND, REBIND) + XX(18, UNBIND, UNBIND) + XX(19, ACL, ACL) + XX(20, REPORT, REPORT) + XX(21, MKACTIVITY, MKACTIVITY) + XX(22, CHECKOUT, CHECKOUT) + XX(23, MERGE, MERGE) + XX(24, MSEARCH, M-SEARCH) + XX(25, NOTIFY, NOTIFY) + XX(26, SUBSCRIBE, SUBSCRIBE) + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) + XX(28, PATCH, PATCH) + XX(29, PURGE, PURGE) + XX(30, MKCALENDAR, MKCALENDAR) + XX(31, LINK, LINK) + XX(32, UNLINK, UNLINK) + XX(33, SOURCE, SOURCE) +''' + +if __name__ == '__main__': + methods = [] + for line in StringIO(SRC): + line = line.strip() + if not line.startswith('XX'): + continue + _, m, _ = line.split(',', 2) + methods.append(m.strip()) + gentokenlookup(methods, 'HTTP_') diff --git a/lib/nghttp2/gennghttpxfun.py b/lib/nghttp2/gennghttpxfun.py new file mode 100755 index 00000000000..ffd253f019f --- /dev/null +++ b/lib/nghttp2/gennghttpxfun.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 + +from gentokenlookup import gentokenlookup + +OPTIONS = [ + "private-key-file", + "private-key-passwd-file", + "certificate-file", + "dh-param-file", + "subcert", + "backend", + "frontend", + "workers", + "http2-max-concurrent-streams", + "log-level", + "daemon", + "http2-proxy", + "http2-bridge", + "client-proxy", + "add-x-forwarded-for", + "strip-incoming-x-forwarded-for", + "no-via", + "frontend-http2-read-timeout", + "frontend-read-timeout", + "frontend-write-timeout", + "backend-read-timeout", + "backend-write-timeout", + "stream-read-timeout", + "stream-write-timeout", + "accesslog-file", + "accesslog-syslog", + "accesslog-format", + "errorlog-file", + "errorlog-syslog", + "backend-keep-alive-timeout", + "frontend-http2-window-bits", + "backend-http2-window-bits", + "frontend-http2-connection-window-bits", + "backend-http2-connection-window-bits", + "frontend-no-tls", + "backend-no-tls", + "backend-tls-sni-field", + "pid-file", + "user", + "syslog-facility", + "backlog", + "ciphers", + "client", + "insecure", + "cacert", + "backend-ipv4", + "backend-ipv6", + "backend-http-proxy-uri", + "read-rate", + "read-burst", + "write-rate", + "write-burst", + "worker-read-rate", + "worker-read-burst", + "worker-write-rate", + "worker-write-burst", + "npn-list", + "tls-proto-list", + "verify-client", + "verify-client-cacert", + "client-private-key-file", + "client-cert-file", + "frontend-http2-dump-request-header", + "frontend-http2-dump-response-header", + "http2-no-cookie-crumbling", + "frontend-frame-debug", + "padding", + "altsvc", + "add-request-header", + "add-response-header", + "worker-frontend-connections", + "no-location-rewrite", + "no-host-rewrite", + "backend-http1-connections-per-host", + "backend-http1-connections-per-frontend", + "listener-disable-timeout", + "tls-ticket-key-file", + "rlimit-nofile", + "backend-request-buffer", + "backend-response-buffer", + "no-server-push", + "backend-http2-connections-per-worker", + "fetch-ocsp-response-file", + "ocsp-update-interval", + "no-ocsp", + "include", + "tls-ticket-key-cipher", + "host-rewrite", + "tls-session-cache-memcached", + "tls-session-cache-memcached-tls", + "tls-ticket-key-memcached", + "tls-ticket-key-memcached-interval", + "tls-ticket-key-memcached-max-retry", + "tls-ticket-key-memcached-max-fail", + "mruby-file", + "accept-proxy-protocol", + "conf", + "fastopen", + "tls-dyn-rec-warmup-threshold", + "tls-dyn-rec-idle-timeout", + "add-forwarded", + "strip-incoming-forwarded", + "forwarded-by", + "forwarded-for", + "response-header-field-buffer", + "max-response-header-fields", + "request-header-field-buffer", + "max-request-header-fields", + "header-field-buffer", + "max-header-fields", + "no-http2-cipher-block-list", + "no-http2-cipher-black-list", + "backend-http1-tls", + "tls-session-cache-memcached-cert-file", + "tls-session-cache-memcached-private-key-file", + "tls-session-cache-memcached-address-family", + "tls-ticket-key-memcached-tls", + "tls-ticket-key-memcached-cert-file", + "tls-ticket-key-memcached-private-key-file", + "tls-ticket-key-memcached-address-family", + "backend-address-family", + "frontend-http2-max-concurrent-streams", + "backend-http2-max-concurrent-streams", + "backend-connections-per-frontend", + "backend-tls", + "backend-connections-per-host", + "error-page", + "no-kqueue", + "frontend-http2-settings-timeout", + "backend-http2-settings-timeout", + "api-max-request-body", + "backend-max-backoff", + "server-name", + "no-server-rewrite", + "frontend-http2-optimize-write-buffer-size", + "frontend-http2-optimize-window-size", + "frontend-http2-window-size", + "frontend-http2-connection-window-size", + "backend-http2-window-size", + "backend-http2-connection-window-size", + "frontend-http2-encoder-dynamic-table-size", + "frontend-http2-decoder-dynamic-table-size", + "backend-http2-encoder-dynamic-table-size", + "backend-http2-decoder-dynamic-table-size", + "ecdh-curves", + "tls-sct-dir", + "backend-connect-timeout", + "dns-cache-timeout", + "dns-lookup-timeout", + "dns-max-try", + "frontend-keep-alive-timeout", + "psk-secrets", + "client-psk-secrets", + "client-no-http2-cipher-block-list", + "client-no-http2-cipher-black-list", + "client-ciphers", + "accesslog-write-early", + "tls-min-proto-version", + "tls-max-proto-version", + "redirect-https-port", + "frontend-max-requests", + "single-thread", + "single-process", + "no-add-x-forwarded-proto", + "no-strip-incoming-x-forwarded-proto", + "ocsp-startup", + "no-verify-ocsp", + "verify-client-tolerate-expired", + "ignore-per-pattern-mruby-error", + "tls-no-postpone-early-data", + "tls-max-early-data", + "tls13-ciphers", + "tls13-client-ciphers", + "no-strip-incoming-early-data", + "quic-bpf-program-file", + "no-quic-bpf", + "http2-altsvc", + "frontend-http3-read-timeout", + "frontend-quic-idle-timeout", + "frontend-quic-debug-log", + "frontend-http3-window-size", + "frontend-http3-connection-window-size", + "frontend-http3-max-window-size", + "frontend-http3-max-connection-window-size", + "frontend-http3-max-concurrent-streams", + "frontend-quic-early-data", + "frontend-quic-qlog-dir", + "frontend-quic-require-token", + "frontend-quic-congestion-controller", + "quic-server-id", + "frontend-quic-secret-file", + "rlimit-memlock", + "max-worker-processes", + "worker-process-grace-shutdown-period", + "frontend-quic-initial-rtt", + "require-http-scheme", + "tls-ktls", +] + +LOGVARS = [ + "remote_addr", + "time_local", + "time_iso8601", + "request", + "status", + "body_bytes_sent", + "remote_port", + "server_port", + "request_time", + "pid", + "alpn", + "ssl_cipher", + "ssl_protocol", + "ssl_session_id", + "ssl_session_reused", + "tls_cipher", + "tls_protocol", + "tls_session_id", + "tls_session_reused", + "tls_sni", + "tls_client_fingerprint_sha256", + "tls_client_fingerprint_sha1", + "tls_client_subject_name", + "tls_client_issuer_name", + "tls_client_serial", + "backend_host", + "backend_port", + "method", + "path", + "path_without_query", + "protocol_version", +] + +if __name__ == '__main__': + gentokenlookup(OPTIONS, 'SHRPX_OPTID_', value_type='char', comp_fun='util::strieq_l') + gentokenlookup(LOGVARS, 'LogFragmentType::', value_type='char', comp_fun='util::strieq_l', return_type='LogFragmentType', fail_value='LogFragmentType::NONE') diff --git a/lib/nghttp2/gennmchartbl.py b/lib/nghttp2/gennmchartbl.py new file mode 100755 index 00000000000..b75b9a6732a --- /dev/null +++ b/lib/nghttp2/gennmchartbl.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x21: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ', + 'SPC '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) in ["!" , "#" , "$" , "%" , "&" , "'" , "*", + "+" , "-" , "." , "^" , "_" , "`" , "|" , "~"] or\ + ('0' <= chr(i) and chr(i) <= '9') or \ + ('a' <= chr(i) and chr(i) <= 'z'): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('0 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('0 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/lib/nghttp2/genpathchartbl.py b/lib/nghttp2/genpathchartbl.py new file mode 100755 index 00000000000..cd8c7144932 --- /dev/null +++ b/lib/nghttp2/genpathchartbl.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x21: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ', + 'SPC '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if (0x21 <= i and i < 0x7f): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('1 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/lib/nghttp2/gentokenlookup.py b/lib/nghttp2/gentokenlookup.py new file mode 100644 index 00000000000..82778580cc5 --- /dev/null +++ b/lib/nghttp2/gentokenlookup.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +def to_enum_hd(k, prefix): + res = prefix + for c in k.upper(): + if c == ':' or c == '-': + res += '_' + continue + res += c + return res + +def build_header(headers): + res = {} + for k in headers: + size = len(k) + if size not in res: + res[size] = {} + ent = res[size] + c = k[-1] + if c not in ent: + ent[c] = [] + ent[c].append(k) + + return res + +def gen_enum(tokens, prefix): + print('''\ +enum {''') + for k in sorted(tokens): + print('''\ + {},'''.format(to_enum_hd(k, prefix))) + print('''\ + {}MAXIDX, +}};'''.format(prefix)) + +def gen_index_header(tokens, prefix, value_type, comp_fun, return_type, fail_value): + print('''\ +{} lookup_token(const {} *name, size_t namelen) {{ + switch (namelen) {{'''.format(return_type, value_type)) + b = build_header(tokens) + for size in sorted(b.keys()): + ents = b[size] + print('''\ + case {}:'''.format(size)) + print('''\ + switch (name[{}]) {{'''.format(size - 1)) + for c in sorted(ents.keys()): + headers = sorted(ents[c]) + print('''\ + case '{}':'''.format(c)) + for k in headers: + print('''\ + if ({}("{}", name, {})) {{ + return {}; + }}'''.format(comp_fun, k[:-1], size - 1, to_enum_hd(k, prefix))) + print('''\ + break;''') + print('''\ + } + break;''') + print('''\ + }} + return {}; +}}'''.format(fail_value)) + +def gentokenlookup(tokens, prefix, value_type='uint8_t', comp_fun='util::streq_l', return_type='int', fail_value='-1'): + gen_enum(tokens, prefix) + print() + gen_index_header(tokens, prefix, value_type, comp_fun, return_type, fail_value) diff --git a/lib/nghttp2/genvchartbl.py b/lib/nghttp2/genvchartbl.py new file mode 100755 index 00000000000..a4ff8d1feb0 --- /dev/null +++ b/lib/nghttp2/genvchartbl.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import sys + +def name(i): + if i < 0x20: + return \ + ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ', + 'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ', + 'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ', + 'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US '][i] + elif i == 0x7f: + return 'DEL ' + +for i in range(256): + if chr(i) == ' ': + sys.stdout.write('1 /* SPC */, ') + elif chr(i) == '\t': + sys.stdout.write('1 /* HT */, ') + elif (0x21 <= i and i < 0x7f): + sys.stdout.write('1 /* {} */, '.format(chr(i))) + elif 0x80 <= i: + sys.stdout.write('1 /* {} */, '.format(hex(i))) + else: + sys.stdout.write('0 /* {} */, '.format(name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') diff --git a/lib/nghttp2/git-clang-format b/lib/nghttp2/git-clang-format new file mode 100755 index 00000000000..6a0db27fa9f --- /dev/null +++ b/lib/nghttp2/git-clang-format @@ -0,0 +1,484 @@ +#!/usr/bin/env python +# +#===- git-clang-format - ClangFormat Git Integration ---------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +r""" +clang-format git integration +============================ + +This file provides a clang-format integration for git. Put it somewhere in your +path and ensure that it is executable. Then, "git clang-format" will invoke +clang-format on the changes in current files or a specific commit. + +For further details, run: +git clang-format -h + +Requires Python 2.7 +""" + +import argparse +import collections +import contextlib +import errno +import os +import re +import subprocess +import sys + +usage = 'git clang-format [OPTIONS] [] [--] [...]' + +desc = ''' +Run clang-format on all lines that differ between the working directory +and , which defaults to HEAD. Changes are only applied to the working +directory. + +The following git-config settings set the default of the corresponding option: + clangFormat.binary + clangFormat.commit + clangFormat.extension + clangFormat.style +''' + +# Name of the temporary index file in which save the output of clang-format. +# This file is created within the .git directory. +temp_index_basename = 'clang-format-index' + + +Range = collections.namedtuple('Range', 'start, count') + + +def main(): + config = load_git_config() + + # In order to keep '--' yet allow options after positionals, we need to + # check for '--' ourselves. (Setting nargs='*' throws away the '--', while + # nargs=argparse.REMAINDER disallows options after positionals.) + argv = sys.argv[1:] + try: + idx = argv.index('--') + except ValueError: + dash_dash = [] + else: + dash_dash = argv[idx:] + argv = argv[:idx] + + default_extensions = ','.join([ + # From clang/lib/Frontend/FrontendOptions.cpp, all lower case + 'c', 'h', # C + 'm', # ObjC + 'mm', # ObjC++ + 'cc', 'cp', 'cpp', 'c++', 'cxx', 'hpp', # C++ + # Other languages that clang-format supports + 'proto', 'protodevel', # Protocol Buffers + 'js', # JavaScript + ]) + + p = argparse.ArgumentParser( + usage=usage, formatter_class=argparse.RawDescriptionHelpFormatter, + description=desc) + p.add_argument('--binary', + default=config.get('clangformat.binary', 'clang-format'), + help='path to clang-format'), + p.add_argument('--commit', + default=config.get('clangformat.commit', 'HEAD'), + help='default commit to use if none is specified'), + p.add_argument('--diff', action='store_true', + help='print a diff instead of applying the changes') + p.add_argument('--extensions', + default=config.get('clangformat.extensions', + default_extensions), + help=('comma-separated list of file extensions to format, ' + 'excluding the period and case-insensitive')), + p.add_argument('-f', '--force', action='store_true', + help='allow changes to unstaged files') + p.add_argument('-p', '--patch', action='store_true', + help='select hunks interactively') + p.add_argument('-q', '--quiet', action='count', default=0, + help='print less information') + p.add_argument('--style', + default=config.get('clangformat.style', None), + help='passed to clang-format'), + p.add_argument('-v', '--verbose', action='count', default=0, + help='print extra information') + # We gather all the remaining positional arguments into 'args' since we need + # to use some heuristics to determine whether or not was present. + # However, to print pretty messages, we make use of metavar and help. + p.add_argument('args', nargs='*', metavar='', + help='revision from which to compute the diff') + p.add_argument('ignored', nargs='*', metavar='...', + help='if specified, only consider differences in these files') + opts = p.parse_args(argv) + + opts.verbose -= opts.quiet + del opts.quiet + + commit, files = interpret_args(opts.args, dash_dash, opts.commit) + changed_lines = compute_diff_and_extract_lines(commit, files) + if opts.verbose >= 1: + ignored_files = set(changed_lines) + filter_by_extension(changed_lines, opts.extensions.lower().split(',')) + if opts.verbose >= 1: + ignored_files.difference_update(changed_lines) + if ignored_files: + print 'Ignoring changes in the following files (wrong extension):' + for filename in ignored_files: + print ' ', filename + if changed_lines: + print 'Running clang-format on the following files:' + for filename in changed_lines: + print ' ', filename + if not changed_lines: + print 'no modified files to format' + return + # The computed diff outputs absolute paths, so we must cd before accessing + # those files. + cd_to_toplevel() + old_tree = create_tree_from_workdir(changed_lines) + new_tree = run_clang_format_and_save_to_tree(changed_lines, + binary=opts.binary, + style=opts.style) + if opts.verbose >= 1: + print 'old tree:', old_tree + print 'new tree:', new_tree + if old_tree == new_tree: + if opts.verbose >= 0: + print 'clang-format did not modify any files' + elif opts.diff: + print_diff(old_tree, new_tree) + else: + changed_files = apply_changes(old_tree, new_tree, force=opts.force, + patch_mode=opts.patch) + if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1: + print 'changed files:' + for filename in changed_files: + print ' ', filename + + +def load_git_config(non_string_options=None): + """Return the git configuration as a dictionary. + + All options are assumed to be strings unless in `non_string_options`, in which + is a dictionary mapping option name (in lower case) to either "--bool" or + "--int".""" + if non_string_options is None: + non_string_options = {} + out = {} + for entry in run('git', 'config', '--list', '--null').split('\0'): + if entry: + name, value = entry.split('\n', 1) + if name in non_string_options: + value = run('git', 'config', non_string_options[name], name) + out[name] = value + return out + + +def interpret_args(args, dash_dash, default_commit): + """Interpret `args` as "[commit] [--] [files...]" and return (commit, files). + + It is assumed that "--" and everything that follows has been removed from + args and placed in `dash_dash`. + + If "--" is present (i.e., `dash_dash` is non-empty), the argument to its + left (if present) is taken as commit. Otherwise, the first argument is + checked if it is a commit or a file. If commit is not given, + `default_commit` is used.""" + if dash_dash: + if len(args) == 0: + commit = default_commit + elif len(args) > 1: + die('at most one commit allowed; %d given' % len(args)) + else: + commit = args[0] + object_type = get_object_type(commit) + if object_type not in ('commit', 'tag'): + if object_type is None: + die("'%s' is not a commit" % commit) + else: + die("'%s' is a %s, but a commit was expected" % (commit, object_type)) + files = dash_dash[1:] + elif args: + if disambiguate_revision(args[0]): + commit = args[0] + files = args[1:] + else: + commit = default_commit + files = args + else: + commit = default_commit + files = [] + return commit, files + + +def disambiguate_revision(value): + """Returns True if `value` is a revision, False if it is a file, or dies.""" + # If `value` is ambiguous (neither a commit nor a file), the following + # command will die with an appropriate error message. + run('git', 'rev-parse', value, verbose=False) + object_type = get_object_type(value) + if object_type is None: + return False + if object_type in ('commit', 'tag'): + return True + die('`%s` is a %s, but a commit or filename was expected' % + (value, object_type)) + + +def get_object_type(value): + """Returns a string description of an object's type, or None if it is not + a valid git object.""" + cmd = ['git', 'cat-file', '-t', value] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + if p.returncode != 0: + return None + return stdout.strip() + + +def compute_diff_and_extract_lines(commit, files): + """Calls compute_diff() followed by extract_lines().""" + diff_process = compute_diff(commit, files) + changed_lines = extract_lines(diff_process.stdout) + diff_process.stdout.close() + diff_process.wait() + if diff_process.returncode != 0: + # Assume error was already printed to stderr. + sys.exit(2) + return changed_lines + + +def compute_diff(commit, files): + """Return a subprocess object producing the diff from `commit`. + + The return value's `stdin` file object will produce a patch with the + differences between the working directory and `commit`, filtered on `files` + (if non-empty). Zero context lines are used in the patch.""" + cmd = ['git', 'diff-index', '-p', '-U0', commit, '--'] + cmd.extend(files) + p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + p.stdin.close() + return p + + +def extract_lines(patch_file): + """Extract the changed lines in `patch_file`. + + The return value is a dictionary mapping filename to a list of (start_line, + line_count) pairs. + + The input must have been produced with ``-U0``, meaning unidiff format with + zero lines of context. The return value is a dict mapping filename to a + list of line `Range`s.""" + matches = {} + for line in patch_file: + match = re.search(r'^\+\+\+\ [^/]+/(.*)', line) + if match: + filename = match.group(1).rstrip('\r\n') + match = re.search(r'^@@ -[0-9,]+ \+(\d+)(,(\d+))?', line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count > 0: + matches.setdefault(filename, []).append(Range(start_line, line_count)) + return matches + + +def filter_by_extension(dictionary, allowed_extensions): + """Delete every key in `dictionary` that doesn't have an allowed extension. + + `allowed_extensions` must be a collection of lowercase file extensions, + excluding the period.""" + allowed_extensions = frozenset(allowed_extensions) + for filename in dictionary.keys(): + base_ext = filename.rsplit('.', 1) + if len(base_ext) == 1 or base_ext[1].lower() not in allowed_extensions: + del dictionary[filename] + + +def cd_to_toplevel(): + """Change to the top level of the git repository.""" + toplevel = run('git', 'rev-parse', '--show-toplevel') + os.chdir(toplevel) + + +def create_tree_from_workdir(filenames): + """Create a new git tree with the given files from the working directory. + + Returns the object ID (SHA-1) of the created tree.""" + return create_tree(filenames, '--stdin') + + +def run_clang_format_and_save_to_tree(changed_lines, binary='clang-format', + style=None): + """Run clang-format on each file and save the result to a git tree. + + Returns the object ID (SHA-1) of the created tree.""" + def index_info_generator(): + for filename, line_ranges in changed_lines.iteritems(): + mode = oct(os.stat(filename).st_mode) + blob_id = clang_format_to_blob(filename, line_ranges, binary=binary, + style=style) + yield '%s %s\t%s' % (mode, blob_id, filename) + return create_tree(index_info_generator(), '--index-info') + + +def create_tree(input_lines, mode): + """Create a tree object from the given input. + + If mode is '--stdin', it must be a list of filenames. If mode is + '--index-info' is must be a list of values suitable for "git update-index + --index-info", such as " ". Any other mode + is invalid.""" + assert mode in ('--stdin', '--index-info') + cmd = ['git', 'update-index', '--add', '-z', mode] + with temporary_index_file(): + p = subprocess.Popen(cmd, stdin=subprocess.PIPE) + for line in input_lines: + p.stdin.write('%s\0' % line) + p.stdin.close() + if p.wait() != 0: + die('`%s` failed' % ' '.join(cmd)) + tree_id = run('git', 'write-tree') + return tree_id + + +def clang_format_to_blob(filename, line_ranges, binary='clang-format', + style=None): + """Run clang-format on the given file and save the result to a git blob. + + Returns the object ID (SHA-1) of the created blob.""" + clang_format_cmd = [binary, filename] + if style: + clang_format_cmd.extend(['-style='+style]) + clang_format_cmd.extend([ + '-lines=%s:%s' % (start_line, start_line+line_count-1) + for start_line, line_count in line_ranges]) + try: + clang_format = subprocess.Popen(clang_format_cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + except OSError as e: + if e.errno == errno.ENOENT: + die('cannot find executable "%s"' % binary) + else: + raise + clang_format.stdin.close() + hash_object_cmd = ['git', 'hash-object', '-w', '--path='+filename, '--stdin'] + hash_object = subprocess.Popen(hash_object_cmd, stdin=clang_format.stdout, + stdout=subprocess.PIPE) + clang_format.stdout.close() + stdout = hash_object.communicate()[0] + if hash_object.returncode != 0: + die('`%s` failed' % ' '.join(hash_object_cmd)) + if clang_format.wait() != 0: + die('`%s` failed' % ' '.join(clang_format_cmd)) + return stdout.rstrip('\r\n') + + +@contextlib.contextmanager +def temporary_index_file(tree=None): + """Context manager for setting GIT_INDEX_FILE to a temporary file and deleting + the file afterward.""" + index_path = create_temporary_index(tree) + old_index_path = os.environ.get('GIT_INDEX_FILE') + os.environ['GIT_INDEX_FILE'] = index_path + try: + yield + finally: + if old_index_path is None: + del os.environ['GIT_INDEX_FILE'] + else: + os.environ['GIT_INDEX_FILE'] = old_index_path + os.remove(index_path) + + +def create_temporary_index(tree=None): + """Create a temporary index file and return the created file's path. + + If `tree` is not None, use that as the tree to read in. Otherwise, an + empty index is created.""" + gitdir = run('git', 'rev-parse', '--git-dir') + path = os.path.join(gitdir, temp_index_basename) + if tree is None: + tree = '--empty' + run('git', 'read-tree', '--index-output='+path, tree) + return path + + +def print_diff(old_tree, new_tree): + """Print the diff between the two trees to stdout.""" + # We use the porcelain 'diff' and not plumbing 'diff-tree' because the output + # is expected to be viewed by the user, and only the former does nice things + # like color and pagination. + subprocess.check_call(['git', 'diff', old_tree, new_tree, '--']) + + +def apply_changes(old_tree, new_tree, force=False, patch_mode=False): + """Apply the changes in `new_tree` to the working directory. + + Bails if there are local changes in those files and not `force`. If + `patch_mode`, runs `git checkout --patch` to select hunks interactively.""" + changed_files = run('git', 'diff-tree', '-r', '-z', '--name-only', old_tree, + new_tree).rstrip('\0').split('\0') + if not force: + unstaged_files = run('git', 'diff-files', '--name-status', *changed_files) + if unstaged_files: + print >>sys.stderr, ('The following files would be modified but ' + 'have unstaged changes:') + print >>sys.stderr, unstaged_files + print >>sys.stderr, 'Please commit, stage, or stash them first.' + sys.exit(2) + if patch_mode: + # In patch mode, we could just as well create an index from the new tree + # and checkout from that, but then the user will be presented with a + # message saying "Discard ... from worktree". Instead, we use the old + # tree as the index and checkout from new_tree, which gives the slightly + # better message, "Apply ... to index and worktree". This is not quite + # right, since it won't be applied to the user's index, but oh well. + with temporary_index_file(old_tree): + subprocess.check_call(['git', 'checkout', '--patch', new_tree]) + index_tree = old_tree + else: + with temporary_index_file(new_tree): + run('git', 'checkout-index', '-a', '-f') + return changed_files + + +def run(*args, **kwargs): + stdin = kwargs.pop('stdin', '') + verbose = kwargs.pop('verbose', True) + strip = kwargs.pop('strip', True) + for name in kwargs: + raise TypeError("run() got an unexpected keyword argument '%s'" % name) + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + stdout, stderr = p.communicate(input=stdin) + if p.returncode == 0: + if stderr: + if verbose: + print >>sys.stderr, '`%s` printed to stderr:' % ' '.join(args) + print >>sys.stderr, stderr.rstrip() + if strip: + stdout = stdout.rstrip('\r\n') + return stdout + if verbose: + print >>sys.stderr, '`%s` returned %s' % (' '.join(args), p.returncode) + if stderr: + print >>sys.stderr, stderr.rstrip() + sys.exit(2) + + +def die(message): + print >>sys.stderr, 'error:', message + sys.exit(2) + + +if __name__ == '__main__': + main() diff --git a/lib/nghttp2/go.mod b/lib/nghttp2/go.mod new file mode 100644 index 00000000000..a6a6a6a7721 --- /dev/null +++ b/lib/nghttp2/go.mod @@ -0,0 +1,26 @@ +module github.com/nghttp2/nghttp2 + +go 1.20 + +require ( + github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 + github.com/quic-go/quic-go v0.35.1 + github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90 + golang.org/x/net v0.18.0 +) + +require ( + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/onsi/ginkgo/v2 v2.2.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qtls-go1-19 v0.3.2 // indirect + github.com/quic-go/qtls-go1-20 v0.2.2 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.6.0 // indirect +) diff --git a/lib/nghttp2/go.sum b/lib/nghttp2/go.sum new file mode 100644 index 00000000000..a91f2789154 --- /dev/null +++ b/lib/nghttp2/go.sum @@ -0,0 +1,78 @@ +github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 h1:wAIE/kN63Oig1DdOzN7O+k4AbFh2cCJoKMFXrwRJtzk= +github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= +github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U= +github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= +github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E= +github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= +github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62poo= +github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90 h1:ccVm9C6f5YMcVv6t9MXahIDkqVvzD6vklkJTIE4D2nY= +github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90/go.mod h1:YZhsh86DfZgAShPKeg1eBLVrmuQxWcR9H4TdpgNvSnw= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/lib/nghttp2/help2rst.py b/lib/nghttp2/help2rst.py new file mode 100755 index 00000000000..245f669a349 --- /dev/null +++ b/lib/nghttp2/help2rst.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# script to produce rst file from program's help output. + +import sys +import re +import argparse + +arg_indent = ' ' * 14 + +def help2man(infile): + # We assume that first line is usage line like this: + # + # Usage: nghttp [OPTIONS]... URI... + # + # The second line is description of the command. Multiple lines + # are permitted. The blank line signals the end of this section. + # After that, we parses positional and optional arguments. + # + # The positional argument is enclosed with < and >: + # + # + # + # We may describe default behavior without any options by encoding + # ( and ): + # + # (default mode) + # + # "Options:" is treated specially and produces "OPTIONS" section. + # We allow subsection under OPTIONS. Lines not starting with (, < + # and Options: are treated as subsection name and produces section + # one level down: + # + # TLS/SSL: + # + # The above is an example of subsection. + # + # The description of arguments must be indented by len(arg_indent) + # characters. The default value should be placed in separate line + # and should be start with "Default: " after indentation. + + line = infile.readline().strip() + m = re.match(r'^Usage: (.*)', line) + if not m: + print('usage line is invalid. Expected following lines:') + print('Usage: cmdname ...') + sys.exit(1) + synopsis = m.group(1).split(' ', 1) + if len(synopsis) == 2: + cmdname, args = synopsis + else: + cmdname, args = synopsis[0], '' + + description = [] + for line in infile: + line = line.strip() + if not line: + break + description.append(line) + + print(''' +.. GENERATED by help2rst.py. DO NOT EDIT DIRECTLY. + +.. program:: {cmdname} + +{cmdname}(1) +{cmdnameunderline} + +SYNOPSIS +-------- + +**{cmdname}** {args} + +DESCRIPTION +----------- + +{description} +'''.format(cmdname=cmdname, args=args, + cmdnameunderline='=' * (len(cmdname) + 3), + synopsis=synopsis, description=format_text('\n'.join(description)))) + + in_arg = False + in_footer = False + + for line in infile: + line = line.rstrip() + + if not line.strip() and in_arg: + print() + continue + if line.startswith(' ') and in_arg: + if not line.startswith(arg_indent): + sys.stderr.write('warning: argument description is not indented correctly. We need {} spaces as indentation.\n'.format(len(arg_indent))) + print('{}'.format(format_arg_text(line[len(arg_indent):]))) + continue + + if in_arg: + print() + in_arg = False + + if line == '--': + in_footer = True + continue + + if in_footer: + print(line.strip()) + continue + + if line == 'Options:': + print('OPTIONS') + print('-------') + print() + continue + + if line.startswith(' <'): + # positional argument + m = re.match(r'^(?:\s+)([a-zA-Z0-9-_<>]+)(.*)', line) + argname, rest = m.group(1), m.group(2) + print('.. describe:: {}'.format(argname)) + print() + print('{}'.format(format_arg_text(rest.strip()))) + in_arg = True + continue + + if line.startswith(' ('): + # positional argument + m = re.match(r'^(?:\s+)(\([a-zA-Z0-9-_<> ]+\))(.*)', line) + argname, rest = m.group(1), m.group(2) + print('.. describe:: {}'.format(argname)) + print() + print('{}'.format(format_arg_text(rest.strip()))) + in_arg = True + continue + + if line.startswith(' -'): + # optional argument + m = re.match( + r'^(?:\s+)(-\S+?(?:, -\S+?)*)($| .*)', + line) + argname, rest = m.group(1), m.group(2) + print('.. option:: {}'.format(argname)) + print() + rest = rest.strip() + if len(rest): + print('{}'.format(format_arg_text(rest))) + in_arg = True + continue + + if not line.startswith(' ') and line.endswith(':'): + # subsection + subsec = line.strip()[:-1] + print('{}'.format(subsec)) + print('{}'.format('~' * len(subsec))) + print() + continue + + print(line.strip()) + +def format_text(text): + # escape *, but don't escape * if it is used as bullet list. + m = re.match(r'^\s*\*\s+', text) + if m: + text = text[:len(m.group(0))] + re.sub(r'\*', r'\*', text[len(m.group(0)):]) + else: + text = re.sub(r'\*', r'\*', text) + # markup option reference + text = re.sub(r'(^|\s)(-[a-zA-Z]|--[a-zA-Z0-9-]+)', + r'\1:option:`\2`', text) + # sphinx does not like markup like ':option:`-f`='. We need + # backslash between ` and =. + text = re.sub(r'(:option:`.*?`)(\S)', r'\1\\\2', text) + # file path should be italic + text = re.sub(r'(^|\s|\'|")(/[^\s\'"]*)', r'\1*\2*', text) + return text + +def format_arg_text(text): + if text.strip().startswith('Default: '): + return '\n ' + re.sub(r'^(\s*Default: )(.*)$', r'\1``\2``', text) + return ' {}'.format(format_text(text)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Produces rst document from help output.') + parser.add_argument('-i', '--include', metavar='FILE', + help='include content of as verbatim. It should be ReST formatted text.') + args = parser.parse_args() + help2man(sys.stdin) + if args.include: + print() + with open(args.include) as f: + sys.stdout.write(f.read()) diff --git a/lib/nghttp2/integration-tests/.gitignore b/lib/nghttp2/integration-tests/.gitignore new file mode 100644 index 00000000000..f40c109af7d --- /dev/null +++ b/lib/nghttp2/integration-tests/.gitignore @@ -0,0 +1,3 @@ +# generated files +config.go +setenv diff --git a/lib/nghttp2/integration-tests/CMakeLists.txt b/lib/nghttp2/integration-tests/CMakeLists.txt new file mode 100644 index 00000000000..cc92e9fdc7d --- /dev/null +++ b/lib/nghttp2/integration-tests/CMakeLists.txt @@ -0,0 +1,45 @@ +set(GO_FILES + nghttpx_http1_test.go + nghttpx_http2_test.go + server_tester.go + server_tester_http3.go +) + +# XXX unused +set(EXTRA_DIST + ${GO_FILES} + server.key + server.crt + alt-server.key + alt-server.crt + setenv + req-set-header.rb + resp-set-header.rb + req-return.rb + resp-return.rb +) + +# 'go test' requires both config.go and the test files in the same directory. +# For out-of-tree builds, config.go is normally not placed next to the source +# files, so copy the tests to the build directory as a workaround. +set(GO_BUILD_FILES) +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + foreach(gofile IN LISTS GO_FILES) + set(outfile "${CMAKE_CURRENT_BINARY_DIR}/${gofile}") + add_custom_command(OUTPUT "${outfile}" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/${gofile}" "${outfile}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${gofile}" + ) + list(APPEND GO_BUILD_FILES "${outfile}") + endforeach() +endif() + +if(ENABLE_HTTP3) + set(GO_TEST_TAGS quic) +endif() + +add_custom_target(it + COMMAND sh setenv go test -v --tags=${GO_TEST_TAGS} + DEPENDS ${GO_BUILD_FILES} +) diff --git a/lib/nghttp2/integration-tests/Makefile.am b/lib/nghttp2/integration-tests/Makefile.am new file mode 100644 index 00000000000..21166eda498 --- /dev/null +++ b/lib/nghttp2/integration-tests/Makefile.am @@ -0,0 +1,52 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2015 Tatsuhiro Tsujikawa + +# 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. + +GO_FILES = \ + nghttpx_http1_test.go \ + nghttpx_http2_test.go \ + nghttpx_http3_test.go \ + server_tester.go \ + server_tester_http3.go + +EXTRA_DIST = \ + CMakeLists.txt \ + $(GO_FILES) \ + server.key \ + server.crt \ + alt-server.key \ + alt-server.crt \ + setenv \ + req-set-header.rb \ + resp-set-header.rb \ + req-return.rb \ + resp-return.rb + +GO_TEST_TAGS = + +if ENABLE_HTTP3 +GO_TEST_TAGS += quic +endif # ENABLE_HTTP3 + +it: + for i in $(GO_FILES); do [ -e $(builddir)/$$i ] || cp $(srcdir)/$$i $(builddir); done + sh setenv go test -v --tags=${GO_TEST_TAGS} diff --git a/lib/nghttp2/integration-tests/alt-server.crt b/lib/nghttp2/integration-tests/alt-server.crt new file mode 100644 index 00000000000..f003eb1481a --- /dev/null +++ b/lib/nghttp2/integration-tests/alt-server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhzCCAm+gAwIBAgIJANfuEldiquMNMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMCmFsdC1kb21haW4wHhcNMTUwMTI1MDYy +NTQxWhcNMjUwMTIyMDYyNTQxWjBaMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29t +ZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYD +VQQDDAphbHQtZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +0IwhDOGDipGrJQ9IoRSzPdkU/Ii4aJgGKHlXminym42X0VI3IW61RLvOHRlHVmVH +JQjFuDo2x+y81t9NlDg3HGUbSpzOzpm6StiutB7c4hreT5G4r0YKya1ugiemN0+p +qjIPJWm2jVnf448eZvUKRKEQ9W0MLZjiNjVGKrKlwo7fIlXg4N3+YixLYffAT1NV +d1T6V5jzlbruj15gK2nGjMQ9D1h1t9vTbTxY+mtk72aX0Y64IE6pPBWLFSSH8ozU +idDoL3AZwz2Jker+ALKK8CM4uho/RPpyW1C06HH+HLdH2MqEjDOROde/Nzxm668O +gK/JWGIEyUqYiUXx0yhFxwIDAQABo1AwTjAdBgNVHQ4EFgQU/Y0GDN2uPjbyePcu +95ZvYEK/gHIwHwYDVR0jBBgwFoAU/Y0GDN2uPjbyePcu95ZvYEK/gHIwDAYDVR0T +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAodD6LVCzL3wfsZ6TxTzf9TfgIdbj +ilL3SEMT/xnfTXT3SLYScTRqQIAI29Y7dOLMq89p4hY2wmeUEhBUAz+y9G2JVr8o +6EbxXrQpWgNJogELqoNnMdrDxB5RsmDDKEJ/rLjDfSkjWbK7B2PZsqVTDgjekCFw +u6FqTIjn/O1O/L5tjwxwxjHmQod/maFCvXoDOVBuwdHnkp298tqlvsHfHO8m++Wj ++XYB8plMIjpeTh9v4w9Jc4QZ59lK/3Tt4qaENeQrMEubKSY/Zen7L2bzhk+cChWT +GSGz9uNXieoZaH79D0wnyZaSZ5Ds4ActMevnGg3iYXuzuFqx8Pungn74Vg== +-----END CERTIFICATE----- diff --git a/lib/nghttp2/integration-tests/config.go.in b/lib/nghttp2/integration-tests/config.go.in new file mode 100644 index 00000000000..3d79297e144 --- /dev/null +++ b/lib/nghttp2/integration-tests/config.go.in @@ -0,0 +1,6 @@ +package nghttp2 + +const ( + buildDir = "@top_builddir@" + sourceDir = "@top_srcdir@" +) diff --git a/lib/nghttp2/integration-tests/nghttpx_http1_test.go b/lib/nghttp2/integration-tests/nghttpx_http1_test.go new file mode 100644 index 00000000000..740396d8f8e --- /dev/null +++ b/lib/nghttp2/integration-tests/nghttpx_http1_test.go @@ -0,0 +1,1631 @@ +package nghttp2 + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "regexp" + "syscall" + "testing" + "time" + + "golang.org/x/net/http2/hpack" + "golang.org/x/net/websocket" +) + +// TestH1H1PlainGET tests whether simple HTTP/1 GET request works. +func TestH1H1PlainGET(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1PlainGET", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH1H1PlainGETClose tests whether simple HTTP/1 GET request with +// Connection: close request header field works. +func TestH1H1PlainGETClose(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1PlainGETClose", + header: []hpack.HeaderField{ + pair("Connection", "close"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH1H1InvalidMethod tests that server rejects invalid method with +// 501 status code +func TestH1H1InvalidMethod(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1InvalidMethod", + method: "get", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotImplemented; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH1H1MultipleRequestCL tests that server rejects request which +// contains multiple Content-Length header fields. +func TestH1H1MultipleRequestCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1MultipleRequestCL\r\nContent-Length: 0\r\nContent-Length: 0\r\n\r\n", + st.authority)); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// // TestH1H1ConnectFailure tests that server handles the situation that +// // connection attempt to HTTP/1 backend failed. +// func TestH1H1ConnectFailure(t *testing.T) { +// st := newServerTester(t, options{}) +// defer st.Close() + +// // shutdown backend server to simulate backend connect failure +// st.ts.Close() + +// res, err := st.http1(requestParam{ +// name: "TestH1H1ConnectFailure", +// }) +// if err != nil { +// t.Fatalf("Error st.http1() = %v", err) +// } +// want := 503 +// if got := res.status; got != want { +// t.Errorf("status: %v; want %v", got, want) +// } +// } + +// TestH1H1AffinityCookie tests that affinity cookie is sent back in +// cleartext http. +func TestH1H1AffinityCookie(t *testing.T) { + opts := options{ + args: []string{"--affinity-cookie"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1AffinityCookie", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar` + validCookie := regexp.MustCompile(pattern) + if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) { + t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern) + } +} + +// TestH1H1AffinityCookieTLS tests that affinity cookie is sent back +// in https. +func TestH1H1AffinityCookieTLS(t *testing.T) { + opts := options{ + args: []string{"--alpn-h1", "--affinity-cookie"}, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1AffinityCookieTLS", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure` + validCookie := regexp.MustCompile(pattern) + if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) { + t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern) + } +} + +// TestH1H1GracefulShutdown tests graceful shutdown. +func TestH1H1GracefulShutdown(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1GracefulShutdown-1", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil { + t.Fatalf("Error st.cmd.Process.Signal() = %v", err) + } + + time.Sleep(150 * time.Millisecond) + + res, err = st.http1(requestParam{ + name: "TestH1H1GracefulShutdown-2", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + if got, want := res.connClose, true; got != want { + t.Errorf("res.connClose: %v; want %v", got, want) + } + + want := io.EOF + b := make([]byte, 256) + if _, err := st.conn.Read(b); err == nil || err != want { + t.Errorf("st.conn.Read(): %v; want %v", err, want) + } +} + +// TestH1H1HostRewrite tests that server rewrites Host header field +func TestH1H1HostRewrite(t *testing.T) { + opts := options{ + args: []string{"--host-rewrite"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HostRewrite", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := res.header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H1BadHost tests that server rejects request including bad +// characters in host header field. +func TestH1H1BadHost(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H1HBadHost\r\nHost: foo\"bar\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1BadAuthority tests that server rejects request including +// bad characters in authority component of requset URI. +func TestH1H1BadAuthority(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET http://foo\"bar/ HTTP/1.1\r\nTest-Case: TestH1H1HBadAuthority\r\nHost: foobar\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1BadScheme tests that server rejects request including +// bad characters in scheme component of requset URI. +func TestH1H1BadScheme(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET http*://example.com/ HTTP/1.1\r\nTest-Case: TestH1H1HBadScheme\r\nHost: example.com\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1HTTP10 tests that server can accept HTTP/1.0 request +// without Host header field +func TestH1H1HTTP10(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1HTTP10\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := resp.Header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H1HTTP10NoHostRewrite tests that server generates host header +// field using actual backend server even if --no-http-rewrite is +// used. +func TestH1H1HTTP10NoHostRewrite(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1HTTP10NoHostRewrite\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := resp.Header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H1RequestTrailer tests request trailer part is forwarded to +// backend. +func TestH1H1RequestTrailer(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + buf := make([]byte, 4096) + for { + _, err := r.Body.Read(buf) + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("r.Body.Read() = %v", err) + } + } + if got, want := r.Trailer.Get("foo"), "bar"; got != want { + t.Errorf("r.Trailer.Get(foo): %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1RequestTrailer", + body: []byte("1"), + trailer: []hpack.HeaderField{ + pair("foo", "bar"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH1H1HeaderFieldBufferPath tests that request with request path +// larger than configured buffer size is rejected. +func TestH1H1HeaderFieldBufferPath(t *testing.T) { + // The value 100 is chosen so that sum of header fields bytes + // does not exceed it. We use > 100 bytes URI to exceed this + // limit. + opts := options{ + args: []string{"--request-header-field-buffer=100"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFieldBufferPath", + path: "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1HeaderFieldBuffer tests that request with header fields +// larger than configured buffer size is rejected. +func TestH1H1HeaderFieldBuffer(t *testing.T) { + opts := options{ + args: []string{"--request-header-field-buffer=10"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFieldBuffer", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1HeaderFields tests that request with header fields more +// than configured number is rejected. +func TestH1H1HeaderFields(t *testing.T) { + opts := options{ + args: []string{"--max-request-header-fields=1"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFields", + header: []hpack.HeaderField{ + // Add extra header field to ensure that + // header field limit exceeds + pair("Connection", "close"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1Websocket tests that HTTP Upgrade to WebSocket works. +func TestH1H1Websocket(t *testing.T) { + opts := options{ + handler: websocket.Handler(func(ws *websocket.Conn) { + if _, err := io.Copy(ws, ws); err != nil { + t.Fatalf("Error io.Copy() = %v", err) + } + }).ServeHTTP, + } + st := newServerTester(t, opts) + defer st.Close() + + content := []byte("hello world") + res := st.websocket(requestParam{ + name: "TestH1H1Websocket", + body: content, + }) + if got, want := res.body, content; !bytes.Equal(got, want) { + t.Errorf("echo: %q; want %q", got, want) + } +} + +// TestH1H1ReqPhaseSetHeader tests mruby request phase hook +// modifies request header fields. +func TestH1H1ReqPhaseSetHeader(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-set-header.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("User-Agent"), "mruby"; got != want { + t.Errorf("User-Agent = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1ReqPhaseSetHeader", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH1H1ReqPhaseReturn tests mruby request phase hook returns +// custom response. +func TestH1H1ReqPhaseReturn(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1ReqPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from req"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH1H1ReqPhaseReturnCONNECTMethod tests that mruby request phase +// hook resets llhttp HPE_PAUSED_UPGRADE. +func TestH1H1ReqPhaseReturnCONNECTMethod(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1ReqPhaseReturnCONNECTMethod\r\nHost: 127.0.0.1:443\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusNotFound; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + hdCheck := func() { + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + + for _, tt := range hdtests { + if got, want := resp.Header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if _, err := io.ReadAll(resp.Body); err != nil { + t.Fatalf("Error io.ReadAll() = %v", err) + } + } + + hdCheck() + + if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1ReqPhaseReturnCONNECTMethod\r\nHost: 127.0.0.1:443\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err = http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusNotFound; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + hdCheck() + + if _, err := io.ReadAll(resp.Body); err != nil { + t.Fatalf("Error io.ReadAll() = %v", err) + } +} + +// TestH1H1RespPhaseSetHeader tests mruby response phase hook modifies +// response header fields. +func TestH1H1RespPhaseSetHeader(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/resp-set-header.rb"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1RespPhaseSetHeader", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + if got, want := res.header.Get("alpha"), "bravo"; got != want { + t.Errorf("alpha = %v; want %v", got, want) + } +} + +// TestH1H1RespPhaseReturn tests mruby response phase hook returns +// custom response. +func TestH1H1RespPhaseReturn(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/resp-return.rb"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1RespPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "21"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from resp"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH1H1HTTPSRedirect tests that the request to the backend which +// requires TLS is redirected to https URI. +func TestH1H1HTTPSRedirect(t *testing.T) { + opts := options{ + args: []string{"--redirect-if-not-tls"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HTTPSRedirect", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusPermanentRedirect; got != want { + t.Errorf("status = %v; want %v", got, want) + } + if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want { + t.Errorf("location: %v; want %v", got, want) + } +} + +// TestH1H1HTTPSRedirectPort tests that the request to the backend +// which requires TLS is redirected to https URI with given port. +func TestH1H1HTTPSRedirectPort(t *testing.T) { + opts := options{ + args: []string{ + "--redirect-if-not-tls", + "--redirect-https-port=8443", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + path: "/foo?bar", + name: "TestH1H1HTTPSRedirectPort", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusPermanentRedirect; got != want { + t.Errorf("status = %v; want %v", got, want) + } + if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want { + t.Errorf("location: %v; want %v", got, want) + } +} + +// TestH1H1POSTRequests tests that server can handle 2 requests with +// request body. +func TestH1H1POSTRequests(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1POSTRequestsNo1", + body: make([]byte, 1), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + res, err = st.http1(requestParam{ + name: "TestH1H1POSTRequestsNo2", + body: make([]byte, 65536), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH1H1CONNECTMethodFailure tests that CONNECT method failure +// resets llhttp HPE_PAUSED_UPGRADE. +func TestH1H1CONNECTMethodFailure(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("required-header") == "" { + w.WriteHeader(http.StatusNotFound) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1CONNECTMethodFailure\r\nHost: 127.0.0.1:443\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusNotFound; got != want { + t.Errorf("status: %v; want %v", got, want) + } + + if _, err := io.ReadAll(resp.Body); err != nil { + t.Fatalf("Error io.ReadAll() = %v", err) + } + + if _, err := io.WriteString(st.conn, "CONNECT 127.0.0.1:443 HTTP/1.1\r\nTest-Case: TestH1H1CONNECTMethodFailure\r\nHost: 127.0.0.1:443\r\nrequired-header: foo\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err = http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// // TestH1H2ConnectFailure tests that server handles the situation that +// // connection attempt to HTTP/2 backend failed. +// func TestH1H2ConnectFailure(t *testing.T) { +// opts := options{ +// args: []string{"--http2-bridge"}, +// } +// st := newServerTester(t, opts) +// defer st.Close() + +// // simulate backend connect attempt failure +// st.ts.Close() + +// res, err := st.http1(requestParam{ +// name: "TestH1H2ConnectFailure", +// }) +// if err != nil { +// t.Fatalf("Error st.http1() = %v", err) +// } +// want := 503 +// if got := res.status; got != want { +// t.Errorf("status: %v; want %v", got, want) +// } +// } + +// TestH1H2NoHost tests that server rejects request without Host +// header field for HTTP/2 backend. +func TestH1H2NoHost(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + // without Host header field, we expect 400 response + if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H2NoHost\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H2HTTP10 tests that server can accept HTTP/1.0 request +// without Host header field +func TestH1H2HTTP10(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H2HTTP10\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := resp.Header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H2HTTP10NoHostRewrite tests that server generates host header +// field using actual backend server even if --no-http-rewrite is +// used. +func TestH1H2HTTP10NoHostRewrite(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H2HTTP10NoHostRewrite\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := resp.Header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH1H2CrumbleCookie tests that Cookies are crumbled and assembled +// when forwarding to HTTP/2 backend link. go-nghttp2 server +// concatenates crumbled Cookies automatically, so this test is not +// much effective now. +func TestH1H2CrumbleCookie(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Cookie"), "alpha; bravo; charlie"; got != want { + t.Errorf("Cookie: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2CrumbleCookie", + header: []hpack.HeaderField{ + pair("Cookie", "alpha; bravo; charlie"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H2GenerateVia tests that server generates Via header field to and +// from backend server. +func TestH1H2GenerateVia(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2GenerateVia", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.header.Get("Via"), "2 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH1H2AppendVia tests that server adds value to existing Via +// header field to and from backend server. +func TestH1H2AppendVia(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo, 1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2AppendVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.header.Get("Via"), "bar, 2 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH1H2NoVia tests that server does not add value to existing Via +// header field to and from backend server. +func TestH1H2NoVia(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--no-via"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2NoVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.header.Get("Via"), "bar"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH1H2ReqPhaseReturn tests mruby request phase hook returns +// custom response. +func TestH1H2ReqPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/req-return.rb", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2ReqPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from req"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH1H2RespPhaseReturn tests mruby response phase hook returns +// custom response. +func TestH1H2RespPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/resp-return.rb", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2RespPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "21"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from resp"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH1H2TE tests that "te: trailers" header is forwarded to HTTP/2 +// backend server by stripping other encodings. +func TestH1H2TE(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("te"), "trailers"; got != want { + t.Errorf("te: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H2TE", + header: []hpack.HeaderField{ + pair("te", "foo,trailers,bar"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1APIBackendconfig exercise backendconfig API endpoint routine +// for successful case. +func TestH1APIBackendconfig(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APIBackendconfig", + path: "/api/v1beta1/backendconfig", + method: "PUT", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH1APIBackendconfigQuery exercise backendconfig API endpoint +// routine with query. +func TestH1APIBackendconfigQuery(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APIBackendconfigQuery", + path: "/api/v1beta1/backendconfig?foo=bar", + method: "PUT", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH1APIBackendconfigBadMethod exercise backendconfig API endpoint +// routine with bad method. +func TestH1APIBackendconfigBadMethod(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APIBackendconfigBadMethod", + path: "/api/v1beta1/backendconfig", + method: "GET", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusMethodNotAllowed; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Failure"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 405; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH1APIConfigrevision tests configrevision API. +func TestH1APIConfigrevision(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APIConfigrevision", + path: "/api/v1beta1/configrevision", + method: "GET", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want = %v", got, want) + } + + var apiResp APIResponse + d := json.NewDecoder(bytes.NewBuffer(res.body)) + d.UseNumber() + err = d.Decode(&apiResp) + if err != nil { + t.Fatalf("Error unmarshalling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Data["configRevision"], json.Number("0"); got != want { + t.Errorf(`apiResp.Data["configRevision"]: %v %t; want %v`, got, got, want) + } +} + +// TestH1APINotFound exercise backendconfig API endpoint routine when +// API endpoint is not found. +func TestH1APINotFound(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1APINotFound", + path: "/api/notfound", + method: "GET", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Failure"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 404; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH1Healthmon tests health monitor endpoint. +func TestH1Healthmon(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3011;healthmon;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3011, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1Healthmon", + path: "/alpha/bravo", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH1ResponseBeforeRequestEnd tests the situation where response +// ends before request body finishes. +func TestH1ResponseBeforeRequestEnd(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, fmt.Sprintf("POST / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1ResponseBeforeRequestEnd\r\nContent-Length: 1000000\r\n\r\n", + st.authority)); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusNotFound; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1ChunkedEndsPrematurely tests that an HTTP/1.1 request fails +// if the backend chunked encoded response ends prematurely. +func TestH1H1ChunkedEndsPrematurely(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + _, err := st.http1(requestParam{ + name: "TestH1H1ChunkedEndsPrematurely", + }) + if err == nil { + t.Fatal("st.http1() should fail") + } +} + +// TestH1H1RequestMalformedTransferEncoding tests that server rejects +// request which contains malformed transfer-encoding. +func TestH1H1RequestMalformedTransferEncoding(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1RequestMalformedTransferEncoding\r\nTransfer-Encoding: ,chunked\r\n\r\n", + st.authority)); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1ResponseMalformedTransferEncoding tests a request fails if +// its response contains malformed transfer-encoding. +func TestH1H1ResponseMalformedTransferEncoding(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: ,chunked\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1ResponseMalformedTransferEncoding", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, http.StatusBadGateway; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH1H1ResponseUnknownTransferEncoding tests a request succeeds if +// its response contains unknown transfer-encoding. +func TestH1H1ResponseUnknownTransferEncoding(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: foo\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %v\r\nTest-Case: TestH1H1ResponseUnknownTransferEncoding\r\n\r\n", + st.authority)); err != nil { + t.Fatalf("Error: io.WriteString() = %v", err) + } + + r := bufio.NewReader(st.conn) + + resp := make([]byte, 4096) + + resplen, err := r.Read(resp) + if err != nil { + t.Fatalf("Error: r.Read() = %v", err) + } + + resp = resp[:resplen] + + const expect = "HTTP/1.1 200 OK\r\nTransfer-Encoding: foo\r\nConnection: close\r\nServer: nghttpx\r\nVia: 1.1 nghttpx\r\n\r\n" + + if got, want := string(resp), expect; got != want { + t.Errorf("resp = %v, want %v", got, want) + } +} + +// TestH1H1RequestHTTP10TransferEncoding tests that server rejects +// HTTP/1.0 request which contains transfer-encoding. +func TestH1H1RequestHTTP10TransferEncoding(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1RequestHTTP10TransferEncoding\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil { + t.Fatalf("Error io.WriteString() = %v", err) + } + + resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil) + if err != nil { + t.Fatalf("Error http.ReadResponse() = %v", err) + } + + defer resp.Body.Close() + + if got, want := resp.StatusCode, http.StatusBadRequest; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} diff --git a/lib/nghttp2/integration-tests/nghttpx_http2_test.go b/lib/nghttp2/integration-tests/nghttpx_http2_test.go new file mode 100644 index 00000000000..5324a18c525 --- /dev/null +++ b/lib/nghttp2/integration-tests/nghttpx_http2_test.go @@ -0,0 +1,3740 @@ +package nghttp2 + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "regexp" + "strings" + "syscall" + "testing" + "time" + + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" +) + +// TestH2H1PlainGET tests whether simple HTTP/2 GET request works. +func TestH2H1PlainGET(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1PlainGET", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AddXfp tests that server appends :scheme to the existing +// x-forwarded-proto header field. +func TestH2H1AddXfp(t *testing.T) { + opts := options{ + args: []string{"--no-strip-incoming-x-forwarded-proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "foo, http"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1NoAddXfp tests that server does not append :scheme to the +// existing x-forwarded-proto header field. +func TestH2H1NoAddXfp(t *testing.T) { + opts := options{ + args: []string{ + "--no-add-x-forwarded-proto", + "--no-strip-incoming-x-forwarded-proto", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "foo"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1NoAddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1StripXfp tests that server strips incoming +// x-forwarded-proto header field. +func TestH2H1StripXfp(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "http"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1StripNoAddXfp tests that server strips incoming +// x-forwarded-proto header field, and does not add another. +func TestH2H1StripNoAddXfp(t *testing.T) { + opts := options{ + args: []string{"--no-add-x-forwarded-proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, found := r.Header["X-Forwarded-Proto"]; found { + t.Errorf("X-Forwarded-Proto = %q; want nothing", got) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripNoAddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AddXff tests that server generates X-Forwarded-For header +// field when forwarding request to backend. +func TestH2H1AddXff(t *testing.T) { + opts := options{ + args: []string{"--add-x-forwarded-for"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddXff", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AddXff2 tests that server appends X-Forwarded-For header +// field to existing one when forwarding request to backend. +func TestH2H1AddXff2(t *testing.T) { + opts := options{ + args: []string{"--add-x-forwarded-for"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "host, 127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddXff2", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1StripXff tests that --strip-incoming-x-forwarded-for +// option. +func TestH2H1StripXff(t *testing.T) { + opts := options{ + args: []string{"--strip-incoming-x-forwarded-for"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if xff, found := r.Header["X-Forwarded-For"]; found { + t.Errorf("X-Forwarded-For = %v; want nothing", xff) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripXff", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1StripAddXff tests that --strip-incoming-x-forwarded-for and +// --add-x-forwarded-for options. +func TestH2H1StripAddXff(t *testing.T) { + opts := options{ + args: []string{ + "--strip-incoming-x-forwarded-for", + "--add-x-forwarded-for", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripAddXff", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedObfuscated tests that server generates +// Forwarded header field with obfuscated "by" and "for" parameters. +func TestH2H1AddForwardedObfuscated(t *testing.T) { + opts := options{ + args: []string{"--add-forwarded=by,for,host,proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + pattern := fmt.Sprintf(`by=_[^;]+;for=_[^;]+;host="127\.0\.0\.1:%v";proto=http`, serverPort) + validFwd := regexp.MustCompile(pattern) + got := r.Header.Get("Forwarded") + + if !validFwd.MatchString(got) { + t.Errorf("Forwarded = %v; want pattern %v", got, pattern) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedObfuscated", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedByIP tests that server generates Forwarded header +// field with IP address in "by" parameter. +func TestH2H1AddForwardedByIP(t *testing.T) { + opts := options{ + args: []string{"--add-forwarded=by,for", "--forwarded-by=ip"}, + handler: func(w http.ResponseWriter, r *http.Request) { + pattern := fmt.Sprintf(`by="127\.0\.0\.1:%v";for=_[^;]+`, serverPort) + validFwd := regexp.MustCompile(pattern) + if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) { + t.Errorf("Forwarded = %v; want pattern %v", got, pattern) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedByIP", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedForIP tests that server generates Forwarded header +// field with IP address in "for" parameters. +func TestH2H1AddForwardedForIP(t *testing.T) { + opts := options{ + args: []string{ + "--add-forwarded=by,for,host,proto", + "--forwarded-by=_alpha", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + want := fmt.Sprintf(`by=_alpha;for=127.0.0.1;host="127.0.0.1:%v";proto=http`, serverPort) + if got := r.Header.Get("Forwarded"); got != want { + t.Errorf("Forwarded = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedForIP", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedMerge tests that server generates Forwarded +// header field with IP address in "by" and "for" parameters. The +// generated values must be appended to the existing value. +func TestH2H1AddForwardedMerge(t *testing.T) { + opts := options{ + args: []string{"--add-forwarded=proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Forwarded"), `host=foo, proto=http`; got != want { + t.Errorf("Forwarded = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedMerge", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedStrip tests that server generates Forwarded +// header field with IP address in "by" and "for" parameters. The +// generated values must not include the existing value. +func TestH2H1AddForwardedStrip(t *testing.T) { + opts := options{ + args: []string{ + "--strip-incoming-forwarded", + "--add-forwarded=proto", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Forwarded"), `proto=http`; got != want { + t.Errorf("Forwarded = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedStrip", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1StripForwarded tests that server strips incoming Forwarded +// header field. +func TestH2H1StripForwarded(t *testing.T) { + opts := options{ + args: []string{"--strip-incoming-forwarded"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, found := r.Header["Forwarded"]; found { + t.Errorf("Forwarded = %v; want nothing", got) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1StripForwarded", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1AddForwardedStatic tests that server generates Forwarded +// header field with the given static obfuscated string for "by" +// parameter. +func TestH2H1AddForwardedStatic(t *testing.T) { + opts := options{ + args: []string{ + "--add-forwarded=by,for", + "--forwarded-by=_alpha", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + pattern := `by=_alpha;for=_[^;]+` + validFwd := regexp.MustCompile(pattern) + if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) { + t.Errorf("Forwarded = %v; want pattern %v", got, pattern) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AddForwardedStatic", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1GenerateVia tests that server generates Via header field to and +// from backend server. +func TestH2H1GenerateVia(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "2 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1GenerateVia", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH2H1AppendVia tests that server adds value to existing Via +// header field to and from backend server. +func TestH2H1AppendVia(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo, 2 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AppendVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH2H1NoVia tests that server does not add value to existing Via +// header field to and from backend server. +func TestH2H1NoVia(t *testing.T) { + opts := options{ + args: []string{"--no-via"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1NoVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.header.Get("Via"), "bar"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH2H1HostRewrite tests that server rewrites host header field +func TestH2H1HostRewrite(t *testing.T) { + opts := options{ + args: []string{"--host-rewrite"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1HostRewrite", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := res.header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH2H1NoHostRewrite tests that server does not rewrite host +// header field +func TestH2H1NoHostRewrite(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1NoHostRewrite", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := res.header.Get("request-host"), st.frontendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH2H1BadRequestCL tests that server rejects request whose +// content-length header field value does not match its request body +// size. +func TestH2H1BadRequestCL(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + // we set content-length: 1024, but the actual request body is + // 3 bytes. + res, err := st.http2(requestParam{ + name: "TestH2H1BadRequestCL", + method: "POST", + header: []hpack.HeaderField{ + pair("content-length", "1024"), + }, + body: []byte("foo"), + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + want := http2.ErrCodeProtocol + if res.errCode != want { + t.Errorf("res.errCode = %v; want %v", res.errCode, want) + } +} + +// TestH2H1BadResponseCL tests that server returns error when +// content-length response header field value does not match its +// response body size. +func TestH2H1BadResponseCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + // we set content-length: 1024, but only send 3 bytes. + w.Header().Add("Content-Length", "1024") + if _, err := w.Write([]byte("foo")); err != nil { + t.Fatalf("Error w.Write() = %v", err) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1BadResponseCL", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + want := http2.ErrCodeInternal + if res.errCode != want { + t.Errorf("res.errCode = %v; want %v", res.errCode, want) + } +} + +// TestH2H1LocationRewrite tests location header field rewriting +// works. +func TestH2H1LocationRewrite(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + // TODO we cannot get st.ts's port number + // here.. 8443 is just a place holder. We + // ignore it on rewrite. + w.Header().Add("Location", "http://127.0.0.1:8443/p/q?a=b#fragment") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1LocationRewrite", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + want := fmt.Sprintf("http://127.0.0.1:%v/p/q?a=b#fragment", serverPort) + if got := res.header.Get("Location"); got != want { + t.Errorf("Location: %v; want %v", got, want) + } +} + +// TestH2H1ChunkedRequestBody tests that chunked request body works. +func TestH2H1ChunkedRequestBody(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + want := "[chunked]" + if got := fmt.Sprint(r.TransferEncoding); got != want { + t.Errorf("Transfer-Encoding: %v; want %v", got, want) + } + body, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("Error reading r.body: %v", err) + } + want = "foo" + if got := string(body); got != want { + t.Errorf("body: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ChunkedRequestBody", + method: "POST", + body: []byte("foo"), + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1MultipleRequestCL tests that server rejects request with +// multiple Content-Length request header fields. +func TestH2H1MultipleRequestCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1MultipleRequestCL", + header: []hpack.HeaderField{ + pair("content-length", "1"), + pair("content-length", "1"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// TestH2H1InvalidRequestCL tests that server rejects request with +// Content-Length which cannot be parsed as a number. +func TestH2H1InvalidRequestCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1InvalidRequestCL", + header: []hpack.HeaderField{ + pair("content-length", ""), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// // TestH2H1ConnectFailure tests that server handles the situation that +// // connection attempt to HTTP/1 backend failed. +// func TestH2H1ConnectFailure(t *testing.T) { +// st := newServerTester(t, options{}) +// defer st.Close() + +// // shutdown backend server to simulate backend connect failure +// st.ts.Close() + +// res, err := st.http2(requestParam{ +// name: "TestH2H1ConnectFailure", +// }) +// if err != nil { +// t.Fatalf("Error st.http2() = %v", err) +// } +// want := 503 +// if got := res.status; got != want { +// t.Errorf("status: %v; want %v", got, want) +// } +// } + +// TestH2H1InvalidMethod tests that server rejects invalid method with +// 501. +func TestH2H1InvalidMethod(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1InvalidMethod", + method: "get", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusNotImplemented; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1BadAuthority tests that server rejects request including +// bad characters in :authority header field. +func TestH2H1BadAuthority(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1BadAuthority", + authority: `foo\bar`, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// TestH2H1BadScheme tests that server rejects request including +// bad characters in :scheme header field. +func TestH2H1BadScheme(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1BadScheme", + scheme: "http*", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// TestH2H1AssembleCookies tests that crumbled cookies in HTTP/2 +// request is assembled into 1 when forwarding to HTTP/1 backend link. +func TestH2H1AssembleCookies(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Cookie"), "alpha; bravo; charlie"; got != want { + t.Errorf("Cookie: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AssembleCookies", + header: []hpack.HeaderField{ + pair("cookie", "alpha"), + pair("cookie", "bravo"), + pair("cookie", "charlie"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1TETrailers tests that server accepts TE request header +// field if it has trailers only. +func TestH2H1TETrailers(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1TETrailers", + header: []hpack.HeaderField{ + pair("te", "trailers"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1TEGzip tests that server resets stream if TE request header +// field contains gzip. +func TestH2H1TEGzip(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + t.Error("server should not forward bad request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1TEGzip", + header: []hpack.HeaderField{ + pair("te", "gzip"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeProtocol; got != want { + t.Errorf("res.errCode = %v; want %v", res.errCode, want) + } +} + +// TestH2H1SNI tests server's TLS SNI extension feature. It must +// choose appropriate certificate depending on the indicated +// server_name from client. +func TestH2H1SNI(t *testing.T) { + opts := options{ + args: []string{"--subcert=" + testDir + "/alt-server.key:" + testDir + "/alt-server.crt"}, + tls: true, + tlsConfig: &tls.Config{ + ServerName: "alt-domain", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + tlsConn := st.conn.(*tls.Conn) + connState := tlsConn.ConnectionState() + cert := connState.PeerCertificates[0] + + if got, want := cert.Subject.CommonName, "alt-domain"; got != want { + t.Errorf("CommonName: %v; want %v", got, want) + } +} + +// TestH2H1TLSXfp tests nghttpx sends x-forwarded-proto header field +// with http value since :scheme is http, even if the frontend +// connection is encrypted. +func TestH2H1TLSXfp(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("x-forwarded-proto"), "http"; got != want { + t.Errorf("x-forwarded-proto: want %v; got %v", want, got) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1TLSXfp", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ServerPush tests server push using Link header field from +// backend server. +func TestH2H1ServerPush(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + // only resources marked as rel=preload are pushed + if !strings.HasPrefix(r.URL.Path, "/css/") { + w.Header().Add("Link", "; rel=preload, , ; rel=preload") + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ServerPush", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + if got, want := len(res.pushResponse), 2; got != want { + t.Fatalf("len(res.pushResponse): %v; want %v", got, want) + } + mainCSS := res.pushResponse[0] + if got, want := mainCSS.status, http.StatusOK; got != want { + t.Errorf("mainCSS.status: %v; want %v", got, want) + } + themeCSS := res.pushResponse[1] + if got, want := themeCSS.status, http.StatusOK; got != want { + t.Errorf("themeCSS.status: %v; want %v", got, want) + } +} + +// TestH2H1RequestTrailer tests request trailer part is forwarded to +// backend. +func TestH2H1RequestTrailer(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + buf := make([]byte, 4096) + for { + _, err := r.Body.Read(buf) + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("r.Body.Read() = %v", err) + } + } + if got, want := r.Trailer.Get("foo"), "bar"; got != want { + t.Errorf("r.Trailer.Get(foo): %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1RequestTrailer", + body: []byte("1"), + trailer: []hpack.HeaderField{ + pair("foo", "bar"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1HeaderFieldBuffer tests that request with header fields +// larger than configured buffer size is rejected. +func TestH2H1HeaderFieldBuffer(t *testing.T) { + opts := options{ + args: []string{"--request-header-field-buffer=10"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1HeaderFieldBuffer", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1HeaderFields tests that request with header fields more +// than configured number is rejected. +func TestH2H1HeaderFields(t *testing.T) { + opts := options{ + args: []string{"--max-request-header-fields=1"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1HeaderFields", + // we have at least 4 pseudo-header fields sent, and + // that ensures that buffer limit exceeds. + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusRequestHeaderFieldsTooLarge; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1ReqPhaseSetHeader tests mruby request phase hook +// modifies request header fields. +func TestH2H1ReqPhaseSetHeader(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-set-header.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("User-Agent"), "mruby"; got != want { + t.Errorf("User-Agent = %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ReqPhaseSetHeader", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1ReqPhaseReturn tests mruby request phase hook returns +// custom response. +func TestH2H1ReqPhaseReturn(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ReqPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from req"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH2H1RespPhaseSetHeader tests mruby response phase hook modifies +// response header fields. +func TestH2H1RespPhaseSetHeader(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/resp-set-header.rb"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1RespPhaseSetHeader", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + if got, want := res.header.Get("alpha"), "bravo"; got != want { + t.Errorf("alpha = %v; want %v", got, want) + } +} + +// TestH2H1RespPhaseReturn tests mruby response phase hook returns +// custom response. +func TestH2H1RespPhaseReturn(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/resp-return.rb"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1RespPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "21"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from resp"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH2H1Upgrade tests HTTP Upgrade to HTTP/2 +func TestH2H1Upgrade(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH2H1Upgrade", + header: []hpack.HeaderField{ + pair("Connection", "Upgrade, HTTP2-Settings"), + pair("Upgrade", "h2c"), + pair("HTTP2-Settings", "AAMAAABkAAQAAP__"), + }, + }) + + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + + if got, want := res.status, http.StatusSwitchingProtocols; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + res, err = st.http2(requestParam{ + httpUpgrade: true, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV1ForwardedForObfuscated tests that Forwarded +// header field includes obfuscated address even if PROXY protocol +// version 1 containing TCP4 entry is accepted. +func TestH2H1ProxyProtocolV1ForwardedForObfuscated(t *testing.T) { + pattern := `^for=_[^;]+$` + validFwd := regexp.MustCompile(pattern) + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=obfuscated", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) { + t.Errorf("Forwarded: %v; want pattern %v", got, pattern) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP4 192.168.0.2 192.168.0.100 12345 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1ForwardedForObfuscated", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV1TCP4 tests PROXY protocol version 1 +// containing TCP4 entry is accepted and X-Forwarded-For contains +// advertised src address. +func TestH2H1ProxyProtocolV1TCP4(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), "for=192.168.0.2"; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP4 192.168.0.2 192.168.0.100 12345 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1TCP4", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV1TCP6 tests PROXY protocol version 1 +// containing TCP6 entry is accepted and X-Forwarded-For contains +// advertised src address. +func TestH2H1ProxyProtocolV1TCP6(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), `for="[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"`; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 ::1 12345 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1TCP6", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV1TCP4TLS tests PROXY protocol version 1 over +// TLS containing TCP4 entry is accepted and X-Forwarded-For contains +// advertised src address. +func TestH2H1ProxyProtocolV1TCP4TLS(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), "for=192.168.0.2"; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + tls: true, + tcpData: []byte("PROXY TCP4 192.168.0.2 192.168.0.100 12345 8080\r\n"), + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1TCP4TLS", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV1TCP6TLS tests PROXY protocol version 1 over +// TLS containing TCP6 entry is accepted and X-Forwarded-For contains +// advertised src address. +func TestH2H1ProxyProtocolV1TCP6TLS(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), `for="[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"`; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + tls: true, + tcpData: []byte("PROXY TCP6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 ::1 12345 8080\r\n"), + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1TCP6TLS", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV1Unknown tests PROXY protocol version 1 +// containing UNKNOWN entry is accepted. +func TestH2H1ProxyProtocolV1Unknown(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, notWant := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got == notWant { + t.Errorf("X-Forwarded-For: %v; want something else", got) + } + if got, notWant := r.Header.Get("Forwarded"), "for=192.168.0.2"; got == notWant { + t.Errorf("Forwarded: %v; want something else", got) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY UNKNOWN 192.168.0.2 192.168.0.100 12345 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1Unknown", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV1JustUnknown tests PROXY protocol version 1 +// containing only "PROXY UNKNOWN" is accepted. +func TestH2H1ProxyProtocolV1JustUnknown(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY UNKNOWN\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1JustUnknown", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV1TooLongLine tests PROXY protocol version 1 +// line longer than 107 bytes must be rejected +func TestH2H1ProxyProtocolV1TooLongLine(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY UNKNOWN ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 655350\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1TooLongLine", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1BadLineEnd tests that PROXY protocol version +// 1 line ending without \r\n should be rejected. +func TestH2H1ProxyProtocolV1BadLineEnd(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 8080\r \n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1BadLineEnd", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1NoEnd tests that PROXY protocol version 1 +// line containing no \r\n should be rejected. +func TestH2H1ProxyProtocolV1NoEnd(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 8080")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1NoEnd", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1EmbeddedNULL tests that PROXY protocol +// version 1 line containing NULL character should be rejected. +func TestH2H1ProxyProtocolV1EmbeddedNULL(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + b := []byte("PROXY TCP6 ::1*foo ::1 12345 8080\r\n") + b[14] = 0 + if _, err := st.conn.Write(b); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1EmbeddedNULL", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1MissingSrcPort tests that PROXY protocol +// version 1 line without src port should be rejected. +func TestH2H1ProxyProtocolV1MissingSrcPort(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1MissingSrcPort", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1MissingDstPort tests that PROXY protocol +// version 1 line without dst port should be rejected. +func TestH2H1ProxyProtocolV1MissingDstPort(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 \r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1MissingDstPort", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1InvalidSrcPort tests that PROXY protocol +// containing invalid src port should be rejected. +func TestH2H1ProxyProtocolV1InvalidSrcPort(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 123x 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1InvalidSrcPort", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1InvalidDstPort tests that PROXY protocol +// containing invalid dst port should be rejected. +func TestH2H1ProxyProtocolV1InvalidDstPort(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 123456 80x\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1InvalidDstPort", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1LeadingZeroPort tests that PROXY protocol +// version 1 line with non zero port with leading zero should be +// rejected. +func TestH2H1ProxyProtocolV1LeadingZeroPort(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 03000 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1LeadingZeroPort", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1TooLargeSrcPort tests that PROXY protocol +// containing too large src port should be rejected. +func TestH2H1ProxyProtocolV1TooLargeSrcPort(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 65536 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1TooLargeSrcPort", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1TooLargeDstPort tests that PROXY protocol +// containing too large dst port should be rejected. +func TestH2H1ProxyProtocolV1TooLargeDstPort(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 65536\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1TooLargeDstPort", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1InvalidSrcAddr tests that PROXY protocol +// containing invalid src addr should be rejected. +func TestH2H1ProxyProtocolV1InvalidSrcAddr(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 192.168.0.1 ::1 12345 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1InvalidSrcAddr", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1InvalidDstAddr tests that PROXY protocol +// containing invalid dst addr should be rejected. +func TestH2H1ProxyProtocolV1InvalidDstAddr(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY TCP6 ::1 192.168.0.1 12345 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1InvalidDstAddr", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1InvalidProtoFamily tests that PROXY protocol +// containing invalid protocol family should be rejected. +func TestH2H1ProxyProtocolV1InvalidProtoFamily(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PROXY UNIX ::1 ::1 12345 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1InvalidProtoFamily", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV1InvalidID tests that PROXY protocol +// containing invalid PROXY protocol version 1 ID should be rejected. +func TestH2H1ProxyProtocolV1InvalidID(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + if _, err := st.conn.Write([]byte("PR0XY TCP6 ::1 ::1 12345 8080\r\n")); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV1InvalidID", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV2TCP4 tests PROXY protocol version 2 +// containing AF_INET family is accepted and X-Forwarded-For contains +// advertised src address. +func TestH2H1ProxyProtocolV2TCP4(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), "for=192.168.0.2"; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + var b bytes.Buffer + if err := writeProxyProtocolV2(&b, proxyProtocolV2{ + command: proxyProtocolV2CommandProxy, + sourceAddress: &net.TCPAddr{ + IP: net.ParseIP("192.168.0.2").To4(), + Port: 12345, + }, + destinationAddress: &net.TCPAddr{ + IP: net.ParseIP("192.168.0.100").To4(), + Port: 8080, + }, + additionalData: []byte("foobar"), + }); err != nil { + t.Fatalf("Error writeProxyProtocolV2() = %v", err) + } + + if _, err := st.conn.Write(b.Bytes()); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV2TCP4", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV2TCP6 tests PROXY protocol version 2 +// containing AF_INET6 family is accepted and X-Forwarded-For contains +// advertised src address. +func TestH2H1ProxyProtocolV2TCP6(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "2001:db8:85a3::8a2e:370:7334"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), `for="[2001:db8:85a3::8a2e:370:7334]"`; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + var b bytes.Buffer + if err := writeProxyProtocolV2(&b, proxyProtocolV2{ + command: proxyProtocolV2CommandProxy, + sourceAddress: &net.TCPAddr{ + IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + Port: 12345, + }, + destinationAddress: &net.TCPAddr{ + IP: net.ParseIP("::1"), + Port: 8080, + }, + additionalData: []byte("foobar"), + }); err != nil { + t.Fatalf("Error writeProxyProtocolV2() = %v", err) + } + + if _, err := st.conn.Write(b.Bytes()); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV2TCP6", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV2TCP4TLS tests PROXY protocol version 2 over +// TLS containing AF_INET family is accepted and X-Forwarded-For +// contains advertised src address. +func TestH2H1ProxyProtocolV2TCP4TLS(t *testing.T) { + var v2Hdr bytes.Buffer + if err := writeProxyProtocolV2(&v2Hdr, proxyProtocolV2{ + command: proxyProtocolV2CommandProxy, + sourceAddress: &net.TCPAddr{ + IP: net.ParseIP("192.168.0.2").To4(), + Port: 12345, + }, + destinationAddress: &net.TCPAddr{ + IP: net.ParseIP("192.168.0.100").To4(), + Port: 8080, + }, + additionalData: []byte("foobar"), + }); err != nil { + t.Fatalf("Error writeProxyProtocolV2() = %v", err) + } + + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), "for=192.168.0.2"; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + tls: true, + tcpData: v2Hdr.Bytes(), + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV2TCP4TLS", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV2TCP6TLS tests PROXY protocol version 2 over +// TLS containing AF_INET6 family is accepted and X-Forwarded-For +// contains advertised src address. +func TestH2H1ProxyProtocolV2TCP6TLS(t *testing.T) { + var v2Hdr bytes.Buffer + if err := writeProxyProtocolV2(&v2Hdr, proxyProtocolV2{ + command: proxyProtocolV2CommandProxy, + sourceAddress: &net.TCPAddr{ + IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), + Port: 12345, + }, + destinationAddress: &net.TCPAddr{ + IP: net.ParseIP("::1"), + Port: 8080, + }, + additionalData: []byte("foobar"), + }); err != nil { + t.Fatalf("Error writeProxyProtocolV2() = %v", err) + } + + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "2001:db8:85a3::8a2e:370:7334"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), `for="[2001:db8:85a3::8a2e:370:7334]"`; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + tls: true, + tcpData: v2Hdr.Bytes(), + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV2TCP6TLS", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV2Local tests PROXY protocol version 2 +// containing cmd == Local is ignored. +func TestH2H1ProxyProtocolV2Local(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "127.0.0.1"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), "for=127.0.0.1"; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + var b bytes.Buffer + if err := writeProxyProtocolV2(&b, proxyProtocolV2{ + command: proxyProtocolV2CommandLocal, + sourceAddress: &net.TCPAddr{ + IP: net.ParseIP("192.168.0.2").To4(), + Port: 12345, + }, + destinationAddress: &net.TCPAddr{ + IP: net.ParseIP("192.168.0.100").To4(), + Port: 8080, + }, + additionalData: []byte("foobar"), + }); err != nil { + t.Fatalf("Error writeProxyProtocolV2() = %v", err) + } + + if _, err := st.conn.Write(b.Bytes()); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV2Local", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV2UnknownCmd tests PROXY protocol version 2 +// containing unknown cmd should be rejected. +func TestH2H1ProxyProtocolV2UnknownCmd(t *testing.T) { + opts := options{ + args: []string{"--accept-proxy-protocol"}, + } + st := newServerTester(t, opts) + defer st.Close() + + var b bytes.Buffer + if err := writeProxyProtocolV2(&b, proxyProtocolV2{ + command: 0xf, + sourceAddress: &net.TCPAddr{ + IP: net.ParseIP("192.168.0.2").To4(), + Port: 12345, + }, + destinationAddress: &net.TCPAddr{ + IP: net.ParseIP("192.168.0.100").To4(), + Port: 8080, + }, + additionalData: []byte("foobar"), + }); err != nil { + t.Fatalf("Error writeProxyProtocolV2() = %v", err) + } + + if _, err := st.conn.Write(b.Bytes()); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + _, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV2UnknownCmd", + }) + + if err == nil { + t.Fatalf("connection was not terminated") + } +} + +// TestH2H1ProxyProtocolV2Unix tests PROXY protocol version 2 +// containing AF_UNIX family is ignored. +func TestH2H1ProxyProtocolV2Unix(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "127.0.0.1"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), "for=127.0.0.1"; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + var b bytes.Buffer + if err := writeProxyProtocolV2(&b, proxyProtocolV2{ + command: proxyProtocolV2CommandProxy, + sourceAddress: &net.UnixAddr{ + Name: "/foo", + Net: "unix", + }, + destinationAddress: &net.UnixAddr{ + Name: "/bar", + Net: "unix", + }, + additionalData: []byte("foobar"), + }); err != nil { + t.Fatalf("Error writeProxyProtocolV2() = %v", err) + } + + if _, err := st.conn.Write(b.Bytes()); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV2Unix", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ProxyProtocolV2Unspec tests PROXY protocol version 2 +// containing AF_UNSPEC family is ignored. +func TestH2H1ProxyProtocolV2Unspec(t *testing.T) { + opts := options{ + args: []string{ + "--accept-proxy-protocol", + "--add-x-forwarded-for", + "--add-forwarded=for", + "--forwarded-for=ip", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("X-Forwarded-For"), "127.0.0.1"; got != want { + t.Errorf("X-Forwarded-For: %v; want %v", got, want) + } + if got, want := r.Header.Get("Forwarded"), "for=127.0.0.1"; got != want { + t.Errorf("Forwarded: %v; want %v", got, want) + } + }, + } + st := newServerTester(t, opts) + defer st.Close() + + var b bytes.Buffer + if err := writeProxyProtocolV2(&b, proxyProtocolV2{ + command: proxyProtocolV2CommandProxy, + additionalData: []byte("foobar"), + }); err != nil { + t.Fatalf("Error writeProxyProtocolV2() = %v", err) + } + + if _, err := st.conn.Write(b.Bytes()); err != nil { + t.Fatalf("Error st.conn.Write() = %v", err) + } + + res, err := st.http2(requestParam{ + name: "TestH2H1ProxyProtocolV2Unspec", + }) + + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ExternalDNS tests that DNS resolution using external DNS +// with HTTP/1 backend works. +func TestH2H1ExternalDNS(t *testing.T) { + opts := options{ + args: []string{"--external-dns"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ExternalDNS", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1DNS tests that DNS resolution without external DNS with +// HTTP/1 backend works. +func TestH2H1DNS(t *testing.T) { + opts := options{ + args: []string{"--dns"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1DNS", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1HTTPSRedirect tests that the request to the backend which +// requires TLS is redirected to https URI. +func TestH2H1HTTPSRedirect(t *testing.T) { + opts := options{ + args: []string{"--redirect-if-not-tls"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1HTTPSRedirect", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusPermanentRedirect; got != want { + t.Errorf("status = %v; want %v", got, want) + } + if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want { + t.Errorf("location: %v; want %v", got, want) + } +} + +// TestH2H1HTTPSRedirectPort tests that the request to the backend +// which requires TLS is redirected to https URI with given port. +func TestH2H1HTTPSRedirectPort(t *testing.T) { + opts := options{ + args: []string{ + "--redirect-if-not-tls", + "--redirect-https-port=8443", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + path: "/foo?bar", + name: "TestH2H1HTTPSRedirectPort", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusPermanentRedirect; got != want { + t.Errorf("status = %v; want %v", got, want) + } + if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want { + t.Errorf("location: %v; want %v", got, want) + } +} + +// TestH2H1Code204 tests that 204 response without content-length, and +// transfer-encoding is valid. +func TestH2H1Code204(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1Code204", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusNoContent; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1Code204CL0 tests that 204 response with content-length: 0 +// is allowed. +func TestH2H1Code204CL0(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 204\r\nContent-Length: 0\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1Code204CL0", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusNoContent; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + if got, found := res.header["Content-Length"]; found { + t.Errorf("Content-Length = %v, want nothing", got) + } +} + +// TestH2H1Code204CLNonzero tests that 204 response with nonzero +// content-length is not allowed. +func TestH2H1Code204CLNonzero(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 204\r\nContent-Length: 1\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1Code204CLNonzero", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusBadGateway; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1Code204TE tests that 204 response with transfer-encoding is +// not allowed. +func TestH2H1Code204TE(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 204\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1Code204TE", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusBadGateway; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1AffinityCookie tests that affinity cookie is sent back in +// cleartext http. +func TestH2H1AffinityCookie(t *testing.T) { + opts := options{ + args: []string{"--affinity-cookie"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AffinityCookie", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar` + validCookie := regexp.MustCompile(pattern) + if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) { + t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern) + } +} + +// TestH2H1AffinityCookieTLS tests that affinity cookie is sent back +// in https. +func TestH2H1AffinityCookieTLS(t *testing.T) { + opts := options{ + args: []string{"--affinity-cookie"}, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1AffinityCookieTLS", + scheme: "https", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure` + validCookie := regexp.MustCompile(pattern) + if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) { + t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern) + } +} + +// TestH2H1GracefulShutdown tests graceful shutdown. +func TestH2H1GracefulShutdown(t *testing.T) { + st := newServerTester(t, options{}) + defer st.Close() + + fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") + if err := st.fr.WriteSettings(); err != nil { + t.Fatalf("st.fr.WriteSettings(): %v", err) + } + + header := []hpack.HeaderField{ + pair(":method", "GET"), + pair(":scheme", "http"), + pair(":authority", st.authority), + pair(":path", "/"), + } + + for _, h := range header { + _ = st.enc.WriteField(h) + } + + if err := st.fr.WriteHeaders(http2.HeadersFrameParam{ + StreamID: 1, + EndStream: false, + EndHeaders: true, + BlockFragment: st.headerBlkBuf.Bytes(), + }); err != nil { + t.Fatalf("st.fr.WriteHeaders(): %v", err) + } + + // send SIGQUIT signal to nghttpx to perform graceful shutdown + if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil { + t.Fatalf("Error st.cmd.Process.Signal() = %v", err) + } + + time.Sleep(150 * time.Millisecond) + + // after signal, finish request body + if err := st.fr.WriteData(1, true, nil); err != nil { + t.Fatalf("st.fr.WriteData(): %v", err) + } + + numGoAway := 0 + + for { + fr, err := st.readFrame() + if err != nil { + if err == io.EOF { + want := 2 + if got := numGoAway; got != want { + t.Fatalf("numGoAway: %v; want %v", got, want) + } + return + } + t.Fatalf("st.readFrame(): %v", err) + } + switch f := fr.(type) { + case *http2.GoAwayFrame: + numGoAway++ + want := http2.ErrCodeNo + if got := f.ErrCode; got != want { + t.Fatalf("f.ErrCode(%v): %v; want %v", numGoAway, got, want) + } + switch numGoAway { + case 1: + want := (uint32(1) << 31) - 1 + if got := f.LastStreamID; got != want { + t.Fatalf("f.LastStreamID(%v): %v; want %v", numGoAway, got, want) + } + case 2: + want := uint32(1) + if got := f.LastStreamID; got != want { + t.Fatalf("f.LastStreamID(%v): %v; want %v", numGoAway, got, want) + } + case 3: + t.Fatalf("too many GOAWAYs received") + } + } + } +} + +// TestH2H2MultipleResponseCL tests that server returns error if +// multiple Content-Length response header fields are received. +func TestH2H2MultipleResponseCL(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("content-length", "1") + w.Header().Add("content-length", "1") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2MultipleResponseCL", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeInternal; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// TestH2H2InvalidResponseCL tests that server returns error if +// Content-Length response header field value cannot be parsed as a +// number. +func TestH2H2InvalidResponseCL(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("content-length", "") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2InvalidResponseCL", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.errCode, http2.ErrCodeInternal; got != want { + t.Errorf("res.errCode: %v; want %v", got, want) + } +} + +// // TestH2H2ConnectFailure tests that server handles the situation that +// // connection attempt to HTTP/2 backend failed. +// func TestH2H2ConnectFailure(t *testing.T) { +// opts := options{ +// args: []string{"--http2-bridge"}, +// } +// st := newServerTester(t, opts) +// defer st.Close() + +// // simulate backend connect attempt failure +// st.ts.Close() + +// res, err := st.http2(requestParam{ +// name: "TestH2H2ConnectFailure", +// }) +// if err != nil { +// t.Fatalf("Error st.http2() = %v", err) +// } +// want := 503 +// if got := res.status; got != want { +// t.Errorf("status: %v; want %v", got, want) +// } +// } + +// TestH2H2HostRewrite tests that server rewrites host header field +func TestH2H2HostRewrite(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--host-rewrite"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2HostRewrite", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := res.header.Get("request-host"), st.backendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH2H2NoHostRewrite tests that server does not rewrite host +// header field +func TestH2H2NoHostRewrite(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.Header().Add("request-host", r.Host) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2NoHostRewrite", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } + if got, want := res.header.Get("request-host"), st.frontendHost; got != want { + t.Errorf("request-host: %v; want %v", got, want) + } +} + +// TestH2H2TLSXfp tests nghttpx sends x-forwarded-proto header field +// with http value since :scheme is http, even if the frontend +// connection is encrypted. +func TestH2H2TLSXfp(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("x-forwarded-proto"), "http"; got != want { + t.Errorf("x-forwarded-proto: want %v; got %v", want, got) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2TLSXfp", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H2AddXfp tests that server appends :scheme to the existing +// x-forwarded-proto header field. +func TestH2H2AddXfp(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--no-strip-incoming-x-forwarded-proto", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "foo, http"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2AddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2NoAddXfp tests that server does not append :scheme to the +// existing x-forwarded-proto header field. +func TestH2H2NoAddXfp(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--no-add-x-forwarded-proto", + "--no-strip-incoming-x-forwarded-proto", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "foo"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2NoAddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2StripXfp tests that server strips incoming +// x-forwarded-proto header field. +func TestH2H2StripXfp(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xfp := r.Header.Get("X-Forwarded-Proto") + if got, want := xfp, "http"; got != want { + t.Errorf("X-Forwarded-Proto = %q; want %q", got, want) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2StripXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2StripNoAddXfp tests that server strips incoming +// x-forwarded-proto header field, and does not add another. +func TestH2H2StripNoAddXfp(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--no-add-x-forwarded-proto"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, found := r.Header["X-Forwarded-Proto"]; found { + t.Errorf("X-Forwarded-Proto = %q; want nothing", got) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2StripNoAddXfp", + header: []hpack.HeaderField{ + pair("x-forwarded-proto", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2AddXff tests that server generates X-Forwarded-For header +// field when forwarding request to backend. +func TestH2H2AddXff(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--add-x-forwarded-for"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2AddXff", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2AddXff2 tests that server appends X-Forwarded-For header +// field to existing one when forwarding request to backend. +func TestH2H2AddXff2(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--add-x-forwarded-for"}, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "host, 127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2AddXff2", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2StripXff tests that --strip-incoming-x-forwarded-for +// option. +func TestH2H2StripXff(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--strip-incoming-x-forwarded-for", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + if xff, found := r.Header["X-Forwarded-For"]; found { + t.Errorf("X-Forwarded-For = %v; want nothing", xff) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2StripXff", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2StripAddXff tests that --strip-incoming-x-forwarded-for and +// --add-x-forwarded-for options. +func TestH2H2StripAddXff(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--strip-incoming-x-forwarded-for", + "--add-x-forwarded-for", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + xff := r.Header.Get("X-Forwarded-For") + want := "127.0.0.1" + if xff != want { + t.Errorf("X-Forwarded-For = %v; want %v", xff, want) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2StripAddXff", + header: []hpack.HeaderField{ + pair("x-forwarded-for", "host"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2AddForwarded tests that server generates Forwarded header +// field using static obfuscated "by" parameter. +func TestH2H2AddForwarded(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--add-forwarded=by,for,host,proto", + "--forwarded-by=_alpha", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + pattern := fmt.Sprintf(`by=_alpha;for=_[^;]+;host="127\.0\.0\.1:%v";proto=https`, serverPort) + validFwd := regexp.MustCompile(pattern) + if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) { + t.Errorf("Forwarded = %v; want pattern %v", got, pattern) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2AddForwarded", + scheme: "https", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H2AddForwardedMerge tests that server generates Forwarded +// header field using static obfuscated "by" parameter, and +// existing Forwarded header field. +func TestH2H2AddForwardedMerge(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--add-forwarded=by,host,proto", + "--forwarded-by=_alpha", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + want := fmt.Sprintf(`host=foo, by=_alpha;host="127.0.0.1:%v";proto=https`, serverPort) + if got := r.Header.Get("Forwarded"); got != want { + t.Errorf("Forwarded = %v; want %v", got, want) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2AddForwardedMerge", + scheme: "https", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H2AddForwardedStrip tests that server generates Forwarded +// header field using static obfuscated "by" parameter, and +// existing Forwarded header field stripped. +func TestH2H2AddForwardedStrip(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--strip-incoming-forwarded", + "--add-forwarded=by,host,proto", + "--forwarded-by=_alpha", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + want := fmt.Sprintf(`by=_alpha;host="127.0.0.1:%v";proto=https`, serverPort) + if got := r.Header.Get("Forwarded"); got != want { + t.Errorf("Forwarded = %v; want %v", got, want) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2AddForwardedStrip", + scheme: "https", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H2StripForwarded tests that server strips incoming Forwarded +// header field. +func TestH2H2StripForwarded(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--strip-incoming-forwarded"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, found := r.Header["Forwarded"]; found { + t.Errorf("Forwarded = %v; want nothing", got) + } + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2StripForwarded", + scheme: "https", + header: []hpack.HeaderField{ + pair("forwarded", "host=foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H2ReqPhaseReturn tests mruby request phase hook returns +// custom response. +func TestH2H2ReqPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/req-return.rb", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2ReqPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from req"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH2H2RespPhaseReturn tests mruby response phase hook returns +// custom response. +func TestH2H2RespPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/resp-return.rb", + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2RespPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "21"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from resp"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH2H2ExternalDNS tests that DNS resolution using external DNS +// with HTTP/2 backend works. +func TestH2H2ExternalDNS(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--external-dns"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2ExternalDNS", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2DNS tests that DNS resolution without external DNS with +// HTTP/2 backend works. +func TestH2H2DNS(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge", "--dns"}, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2DNS", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H2Code204 tests that 204 response without content-length, and +// transfer-encoding is valid. +func TestH2H2Code204(t *testing.T) { + opts := options{ + args: []string{"--http2-bridge"}, + handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H2Code204", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusNoContent; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2APIBackendconfig exercise backendconfig API endpoint routine +// for successful case. +func TestH2APIBackendconfig(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2APIBackendconfig", + path: "/api/v1beta1/backendconfig", + method: "PUT", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH2APIBackendconfigQuery exercise backendconfig API endpoint +// routine with query. +func TestH2APIBackendconfigQuery(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2APIBackendconfigQuery", + path: "/api/v1beta1/backendconfig?foo=bar", + method: "PUT", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH2APIBackendconfigBadMethod exercise backendconfig API endpoint +// routine with bad method. +func TestH2APIBackendconfigBadMethod(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2APIBackendconfigBadMethod", + path: "/api/v1beta1/backendconfig", + method: "GET", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusMethodNotAllowed; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Failure"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 405; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH2APIConfigrevision tests configrevision API. +func TestH2APIConfigrevision(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2APIConfigrevision", + path: "/api/v1beta1/configrevision", + method: "GET", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want = %v", got, want) + } + + var apiResp APIResponse + d := json.NewDecoder(bytes.NewBuffer(res.body)) + d.UseNumber() + err = d.Decode(&apiResp) + if err != nil { + t.Fatalf("Error unmarshalling API response: %v", err) + } + if got, want := apiResp.Status, "Success"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 200; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Data["configRevision"], json.Number("0"); got != want { + t.Errorf(`apiResp.Data["configRevision"]: %v %t; want %v`, got, got, want) + } +} + +// TestH2APINotFound exercise backendconfig API endpoint routine when +// API endpoint is not found. +func TestH2APINotFound(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3010;api;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3010, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2APINotFound", + path: "/api/notfound", + method: "GET", + body: []byte(`# comment +backend=127.0.0.1,3011 + +`), + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } + + var apiResp APIResponse + err = json.Unmarshal(res.body, &apiResp) + if err != nil { + t.Fatalf("Error unmarshaling API response: %v", err) + } + if got, want := apiResp.Status, "Failure"; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } + if got, want := apiResp.Code, 404; got != want { + t.Errorf("apiResp.Status: %v; want %v", got, want) + } +} + +// TestH2Healthmon tests health monitor endpoint. +func TestH2Healthmon(t *testing.T) { + opts := options{ + args: []string{"-f127.0.0.1,3011;healthmon;no-tls"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + connectPort: 3011, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2Healthmon", + path: "/alpha/bravo", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2ResponseBeforeRequestEnd tests the situation where response +// ends before request body finishes. +func TestH2ResponseBeforeRequestEnd(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("request should not be forwarded") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2ResponseBeforeRequestEnd", + noEndStream: true, + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH2H1ChunkedEndsPrematurely tests that a stream is reset if the +// backend chunked encoded response ends prematurely. +func TestH2H1ChunkedEndsPrematurely(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1ChunkedEndsPrematurely", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.errCode, http2.ErrCodeInternal; got != want { + t.Errorf("res.errCode = %v; want %v", got, want) + } +} + +// TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption verifies that https +// scheme in non-encrypted connection is treated as error. +func TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption(t *testing.T) { + opts := options{ + args: []string{"--require-http-scheme"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1RequireHTTPSchemeHTTPSWithoutEncryption", + scheme: "https", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusBadRequest; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1RequireHTTPSchemeHTTPWithEncryption verifies that http +// scheme in encrypted connection is treated as error. +func TestH2H1RequireHTTPSchemeHTTPWithEncryption(t *testing.T) { + opts := options{ + args: []string{"--require-http-scheme"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1RequireHTTPSchemeHTTPWithEncryption", + scheme: "http", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusBadRequest; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption verifies +// that unknown scheme in non-encrypted connection is treated as +// error. +func TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption(t *testing.T) { + opts := options{ + args: []string{"--require-http-scheme"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1RequireHTTPSchemeUnknownSchemeWithoutEncryption", + scheme: "unknown", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusBadRequest; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption verifies that +// unknown scheme in encrypted connection is treated as error. +func TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption(t *testing.T) { + opts := options{ + args: []string{"--require-http-scheme"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Errorf("server should not forward this request") + }, + tls: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1RequireHTTPSchemeUnknownSchemeWithEncryption", + scheme: "unknown", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + + if got, want := res.status, http.StatusBadRequest; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} diff --git a/lib/nghttp2/integration-tests/nghttpx_http3_test.go b/lib/nghttp2/integration-tests/nghttpx_http3_test.go new file mode 100644 index 00000000000..9ea85d7447c --- /dev/null +++ b/lib/nghttp2/integration-tests/nghttpx_http3_test.go @@ -0,0 +1,393 @@ +//go:build quic + +package nghttp2 + +import ( + "bytes" + "crypto/rand" + "io" + "net/http" + "regexp" + "testing" + + "golang.org/x/net/http2/hpack" +) + +// TestH3H1PlainGET tests whether simple HTTP/3 GET request works. +func TestH3H1PlainGET(t *testing.T) { + st := newServerTester(t, options{ + quic: true, + }) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1PlainGET", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH3H1RequestBody tests HTTP/3 request with body works. +func TestH3H1RequestBody(t *testing.T) { + body := make([]byte, 3333) + _, err := rand.Read(body) + if err != nil { + t.Fatalf("Unable to create request body: %v", err) + } + + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + buf := make([]byte, 4096) + buflen := 0 + p := buf + + for { + if len(p) == 0 { + t.Fatal("Request body is too large") + } + + n, err := r.Body.Read(p) + + p = p[n:] + buflen += n + + if err != nil { + if err == io.EOF { + break + } + + t.Fatalf("r.Body.Read() = %v", err) + } + } + + buf = buf[:buflen] + + if got, want := buf, body; !bytes.Equal(got, want) { + t.Fatalf("buf = %v; want %v", got, want) + } + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1RequestBody", + body: body, + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH3H1GenerateVia tests that server generates Via header field to +// and from backend server. +func TestH3H1GenerateVia(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "3 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1GenerateVia", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH3H1AppendVia tests that server adds value to existing Via +// header field to and from backend server. +func TestH3H1AppendVia(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo, 3 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1AppendVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH3H1NoVia tests that server does not add value to existing Via +// header field to and from backend server. +func TestH3H1NoVia(t *testing.T) { + opts := options{ + args: []string{"--no-via"}, + handler: func(w http.ResponseWriter, r *http.Request) { + if got, want := r.Header.Get("Via"), "foo"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } + w.Header().Add("Via", "bar") + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1NoVia", + header: []hpack.HeaderField{ + pair("via", "foo"), + }, + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.header.Get("Via"), "bar"; got != want { + t.Errorf("Via: %v; want %v", got, want) + } +} + +// TestH3H1BadResponseCL tests that server returns error when +// content-length response header field value does not match its +// response body size. +func TestH3H1BadResponseCL(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + // we set content-length: 1024, but only send 3 bytes. + w.Header().Add("Content-Length", "1024") + if _, err := w.Write([]byte("foo")); err != nil { + t.Fatalf("Error w.Write() = %v", err) + } + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + _, err := st.http3(requestParam{ + name: "TestH3H1BadResponseCL", + }) + if err == nil { + t.Fatal("st.http3() should fail") + } +} + +// TestH3H1HTTPSRedirect tests that HTTPS redirect should not happen +// with HTTP/3. +func TestH3H1HTTPSRedirect(t *testing.T) { + opts := options{ + args: []string{"--redirect-if-not-tls"}, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1HTTPSRedirect", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } +} + +// TestH3H1AffinityCookieTLS tests that affinity cookie is sent back +// in https. +func TestH3H1AffinityCookieTLS(t *testing.T) { + opts := options{ + args: []string{"--affinity-cookie"}, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H1AffinityCookieTLS", + scheme: "https", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusOK; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + const pattern = `affinity=[0-9a-f]{8}; Path=/foo/bar; Secure` + validCookie := regexp.MustCompile(pattern) + if got := res.header.Get("Set-Cookie"); !validCookie.MatchString(got) { + t.Errorf("Set-Cookie: %v; want pattern %v", got, pattern) + } +} + +// TestH3H2ReqPhaseReturn tests mruby request phase hook returns +// custom response. +func TestH3H2ReqPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/req-return.rb", + }, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("request should not be forwarded") + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H2ReqPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "20"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from req"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH3H2RespPhaseReturn tests mruby response phase hook returns +// custom response. +func TestH3H2RespPhaseReturn(t *testing.T) { + opts := options{ + args: []string{ + "--http2-bridge", + "--mruby-file=" + testDir + "/resp-return.rb", + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3H2RespPhaseReturn", + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("status = %v; want %v", got, want) + } + + hdtests := []struct { + k, v string + }{ + {"content-length", "21"}, + {"from", "mruby"}, + } + for _, tt := range hdtests { + if got, want := res.header.Get(tt.k), tt.v; got != want { + t.Errorf("%v = %v; want %v", tt.k, got, want) + } + } + + if got, want := string(res.body), "Hello World from resp"; got != want { + t.Errorf("body = %v; want %v", got, want) + } +} + +// TestH3ResponseBeforeRequestEnd tests the situation where response +// ends before request body finishes. +func TestH3ResponseBeforeRequestEnd(t *testing.T) { + opts := options{ + args: []string{"--mruby-file=" + testDir + "/req-return.rb"}, + handler: func(w http.ResponseWriter, r *http.Request) { + t.Fatal("request should not be forwarded") + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + res, err := st.http3(requestParam{ + name: "TestH3ResponseBeforeRequestEnd", + noEndStream: true, + }) + if err != nil { + t.Fatalf("Error st.http3() = %v", err) + } + if got, want := res.status, http.StatusNotFound; got != want { + t.Errorf("res.status: %v; want %v", got, want) + } +} + +// TestH3H1ChunkedEndsPrematurely tests that a stream is reset if the +// backend chunked encoded response ends prematurely. +func TestH3H1ChunkedEndsPrematurely(t *testing.T) { + opts := options{ + handler: func(w http.ResponseWriter, r *http.Request) { + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Could not hijack the connection", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + if _, err := bufrw.WriteString("HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n"); err != nil { + t.Fatalf("Error bufrw.WriteString() = %v", err) + } + bufrw.Flush() + }, + quic: true, + } + st := newServerTester(t, opts) + defer st.Close() + + _, err := st.http3(requestParam{ + name: "TestH3H1ChunkedEndsPrematurely", + }) + if err == nil { + t.Fatal("st.http3() should fail") + } +} diff --git a/lib/nghttp2/integration-tests/req-return.rb b/lib/nghttp2/integration-tests/req-return.rb new file mode 100644 index 00000000000..51d315efe35 --- /dev/null +++ b/lib/nghttp2/integration-tests/req-return.rb @@ -0,0 +1,12 @@ +class App + def on_req(env) + resp = env.resp + + resp.clear_headers + resp.status = 404 + resp.add_header "from", "mruby" + resp.return "Hello World from req" + end +end + +App.new diff --git a/lib/nghttp2/integration-tests/req-set-header.rb b/lib/nghttp2/integration-tests/req-set-header.rb new file mode 100644 index 00000000000..986f128a7f8 --- /dev/null +++ b/lib/nghttp2/integration-tests/req-set-header.rb @@ -0,0 +1,7 @@ +class App + def on_req(env) + env.req.set_header "User-Agent", "mruby" + end +end + +App.new diff --git a/lib/nghttp2/integration-tests/resp-return.rb b/lib/nghttp2/integration-tests/resp-return.rb new file mode 100644 index 00000000000..fbbd775c332 --- /dev/null +++ b/lib/nghttp2/integration-tests/resp-return.rb @@ -0,0 +1,12 @@ +class App + def on_resp(env) + resp = env.resp + + resp.clear_headers + resp.status = 404 + resp.add_header "from", "mruby" + resp.return "Hello World from resp" + end +end + +App.new diff --git a/lib/nghttp2/integration-tests/resp-set-header.rb b/lib/nghttp2/integration-tests/resp-set-header.rb new file mode 100644 index 00000000000..228837a0ec9 --- /dev/null +++ b/lib/nghttp2/integration-tests/resp-set-header.rb @@ -0,0 +1,7 @@ +class App + def on_resp(env) + env.resp.set_header "Alpha", "bravo" + end +end + +App.new diff --git a/lib/nghttp2/integration-tests/server.crt b/lib/nghttp2/integration-tests/server.crt new file mode 100644 index 00000000000..c50fdaa1341 --- /dev/null +++ b/lib/nghttp2/integration-tests/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhTCCAm2gAwIBAgIJAOvIx8xIxgyOMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCTEyNy4wLjAuMTAeFw0xNTAxMjMxMjI0 +MjdaFw0yNTAxMjAxMjI0MjdaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l +LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV +BAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMuI +QZRI/iBaxPTjTWGemt8tCEfzZWxuIW3hY/gIhwJDfH2SbourBh1s9vqcqhBq5vmo +kdfVQXAnNLjIG1uhWmcHuNnKrE5hU82N6i9RsmuM5TQRvhsamHri4G+EXJMu9GqF +Mso8g7MWpRSGKf+8gfjAVNwfCHFiu8oBcMmy3l54MFHgRLSveAMhiPB0e3Xlnpr5 +2bS/oGTx5ynwPgBpEn2FrpT4Z/aLCLzJ/ysgNH8BXEh7n/v7xM3vd5grqB039rd5 +JoxlWvp+4XpzKp5upaqmOcVUq4pDSFUQ3w6C+v33Z3OK6Qaon7GMxLv3Us3b7PZ3 +1CLoWJR2o3OSnUfO/gUCAwEAAaNQME4wHQYDVR0OBBYEFLc5JWPUUVx4GJesogMV +w2Rz0L3yMB8GA1UdIwQYMBaAFLc5JWPUUVx4GJesogMVw2Rz0L3yMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAP/cJWpM+GEjmVYHFacKTdbXBMox2Xn +QY2NLm00WPOGvKnO7czMFfX/pEmiq71kD45rLLfbaJP205QpxqiAIvhFhuq50Co7 +sTDtwcDTPLX9H7Ugjt4sTMPiwC14uVXFfoT/J46zMjXwP00qKyfszc2tkIgHfrTl +h4M1hkdfmMximir/Ii7TdYYJ3oGS8tdcYb6D4DZwAljKmxF6iUOwFCUgpTmqDBT5 +irXY8D27DzuNN5Pg07rwAlwXLCzrJE10UtO4MmRVXwpzmoaRQD4/tna6bZzdetvs +gPdGP6W1o0q85gullieMJWeKyQA/wasoE7fypn4pHAdTZm/vH+v7GHg= +-----END CERTIFICATE----- diff --git a/lib/nghttp2/integration-tests/server_tester.go b/lib/nghttp2/integration-tests/server_tester.go new file mode 100644 index 00000000000..6a4ce650d7c --- /dev/null +++ b/lib/nghttp2/integration-tests/server_tester.go @@ -0,0 +1,819 @@ +package nghttp2 + +import ( + "bufio" + "bytes" + "context" + "crypto/tls" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "os/exec" + "sort" + "strconv" + "strings" + "syscall" + "testing" + "time" + + "github.com/tatsuhiro-t/go-nghttp2" + "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" + "golang.org/x/net/websocket" +) + +const ( + serverBin = buildDir + "/src/nghttpx" + serverPort = 3009 + testDir = sourceDir + "/integration-tests" + logDir = buildDir + "/integration-tests" +) + +func pair(name, value string) hpack.HeaderField { + return hpack.HeaderField{ + Name: name, + Value: value, + } +} + +type serverTester struct { + cmd *exec.Cmd // test frontend server process, which is test subject + url string // test frontend server URL + t *testing.T + ts *httptest.Server // backend server + frontendHost string // frontend server host + backendHost string // backend server host + conn net.Conn // connection to frontend server + h2PrefaceSent bool // HTTP/2 preface was sent in conn + nextStreamID uint32 // next stream ID + fr *http2.Framer // HTTP/2 framer + headerBlkBuf bytes.Buffer // buffer to store encoded header block + enc *hpack.Encoder // HTTP/2 HPACK encoder + header http.Header // received header fields + dec *hpack.Decoder // HTTP/2 HPACK decoder + authority string // server's host:port + frCh chan http2.Frame // used for incoming HTTP/2 frame + errCh chan error +} + +type options struct { + // args is the additional arguments to nghttpx. + args []string + // handler is the handler to handle the request. It defaults + // to noopHandler. + handler http.HandlerFunc + // connectPort is the server side port where client connection + // is made. It defaults to serverPort. + connectPort int + // tls, if set to true, sets up TLS frontend connection. + tls bool + // tlsConfig is the client side TLS configuration that is used + // when tls is true. + tlsConfig *tls.Config + // tcpData is additional data that are written to connection + // before TLS handshake starts. This field is ignored if tls + // is false. + tcpData []byte + // quic, if set to true, sets up QUIC frontend connection. + // quic implies tls = true. + quic bool +} + +// newServerTester creates test context. +func newServerTester(t *testing.T, opts options) *serverTester { + if opts.quic { + opts.tls = true + } + + if opts.handler == nil { + opts.handler = noopHandler + } + if opts.connectPort == 0 { + opts.connectPort = serverPort + } + + ts := httptest.NewUnstartedServer(opts.handler) + + var args []string + var backendTLS, dns, externalDNS, acceptProxyProtocol, redirectIfNotTLS, affinityCookie, alpnH1 bool + + for _, k := range opts.args { + switch k { + case "--http2-bridge": + backendTLS = true + case "--dns": + dns = true + case "--external-dns": + dns = true + externalDNS = true + case "--accept-proxy-protocol": + acceptProxyProtocol = true + case "--redirect-if-not-tls": + redirectIfNotTLS = true + case "--affinity-cookie": + affinityCookie = true + case "--alpn-h1": + alpnH1 = true + default: + args = append(args, k) + } + } + if backendTLS { + nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{}) + // According to httptest/server.go, we have to set + // NextProtos separately for ts.TLS. NextProtos set + // in nghttp2.ConfigureServer is effectively ignored. + ts.TLS = new(tls.Config) + ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2") + ts.StartTLS() + args = append(args, "-k") + } else { + ts.Start() + } + scheme := "http" + if opts.tls { + scheme = "https" + args = append(args, testDir+"/server.key", testDir+"/server.crt") + } + + backendURL, err := url.Parse(ts.URL) + if err != nil { + t.Fatalf("Error parsing URL from httptest.Server: %v", err) + } + + // URL.Host looks like "127.0.0.1:8080", but we want + // "127.0.0.1,8080" + b := "-b" + if !externalDNS { + b += fmt.Sprintf("%v;", strings.Replace(backendURL.Host, ":", ",", -1)) + } else { + sep := strings.LastIndex(backendURL.Host, ":") + if sep == -1 { + t.Fatalf("backendURL.Host %v does not contain separator ':'", backendURL.Host) + } + // We use awesome service nip.io. + b += fmt.Sprintf("%v.nip.io,%v;", backendURL.Host[:sep], backendURL.Host[sep+1:]) + } + + if backendTLS { + b += ";proto=h2;tls" + } + if dns { + b += ";dns" + } + + if redirectIfNotTLS { + b += ";redirect-if-not-tls" + } + + if affinityCookie { + b += ";affinity=cookie;affinity-cookie-name=affinity;affinity-cookie-path=/foo/bar" + } + + noTLS := ";no-tls" + if opts.tls { + noTLS = "" + } + + var proxyProto string + if acceptProxyProtocol { + proxyProto = ";proxyproto" + } + + args = append(args, fmt.Sprintf("-f127.0.0.1,%v%v%v", serverPort, noTLS, proxyProto), b, + "--errorlog-file="+logDir+"/log.txt", "-LINFO") + + if opts.quic { + args = append(args, + fmt.Sprintf("-f127.0.0.1,%v;quic", serverPort), + "--no-quic-bpf") + } + + authority := fmt.Sprintf("127.0.0.1:%v", opts.connectPort) + + st := &serverTester{ + cmd: exec.Command(serverBin, args...), + t: t, + ts: ts, + url: fmt.Sprintf("%v://%v", scheme, authority), + frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort), + backendHost: backendURL.Host, + nextStreamID: 1, + authority: authority, + frCh: make(chan http2.Frame), + errCh: make(chan error), + } + + st.cmd.Stdout = os.Stdout + st.cmd.Stderr = os.Stderr + + if err := st.cmd.Start(); err != nil { + st.t.Fatalf("Error starting %v: %v", serverBin, err) + } + + retry := 0 + for { + time.Sleep(50 * time.Millisecond) + + conn, err := net.Dial("tcp", authority) + if err == nil && opts.tls { + if len(opts.tcpData) > 0 { + if _, err := conn.Write(opts.tcpData); err != nil { + st.Close() + st.t.Fatal("Error writing TCP data") + } + } + + var tlsConfig *tls.Config + if opts.tlsConfig == nil { + tlsConfig = new(tls.Config) + } else { + tlsConfig = opts.tlsConfig.Clone() + } + tlsConfig.InsecureSkipVerify = true + if alpnH1 { + tlsConfig.NextProtos = []string{"http/1.1"} + } else { + tlsConfig.NextProtos = []string{"h2"} + } + tlsConn := tls.Client(conn, tlsConfig) + err = tlsConn.Handshake() + if err == nil { + conn = tlsConn + } + } + if err != nil { + retry++ + if retry >= 100 { + st.Close() + st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid") + } + continue + } + st.conn = conn + break + } + + st.fr = http2.NewFramer(st.conn, st.conn) + st.enc = hpack.NewEncoder(&st.headerBlkBuf) + st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) { + st.header.Add(f.Name, f.Value) + }) + + return st +} + +func (st *serverTester) Close() { + if st.conn != nil { + st.conn.Close() + } + if st.cmd != nil { + done := make(chan struct{}) + go func() { + if err := st.cmd.Wait(); err != nil { + st.t.Errorf("Error st.cmd.Wait() = %v", err) + } + close(done) + }() + + if err := st.cmd.Process.Signal(syscall.SIGQUIT); err != nil { + st.t.Errorf("Error st.cmd.Process.Signal() = %v", err) + } + + select { + case <-done: + case <-time.After(10 * time.Second): + if err := st.cmd.Process.Kill(); err != nil { + st.t.Errorf("Error st.cmd.Process.Kill() = %v", err) + } + <-done + } + } + if st.ts != nil { + st.ts.Close() + } +} + +func (st *serverTester) readFrame() (http2.Frame, error) { + go func() { + f, err := st.fr.ReadFrame() + if err != nil { + st.errCh <- err + return + } + st.frCh <- f + }() + + select { + case f := <-st.frCh: + return f, nil + case err := <-st.errCh: + return nil, err + case <-time.After(5 * time.Second): + return nil, errors.New("timeout waiting for frame") + } +} + +type requestParam struct { + name string // name for this request to identify the request in log easily + streamID uint32 // stream ID, automatically assigned if 0 + method string // method, defaults to GET + scheme string // scheme, defaults to http + authority string // authority, defaults to backend server address + path string // path, defaults to / + header []hpack.HeaderField // additional request header fields + body []byte // request body + trailer []hpack.HeaderField // trailer part + httpUpgrade bool // true if upgraded to HTTP/2 through HTTP Upgrade + noEndStream bool // true if END_STREAM should not be sent +} + +// wrapper for request body to set trailer part +type chunkedBodyReader struct { + trailer []hpack.HeaderField + trailerWritten bool + body io.Reader + req *http.Request +} + +func (cbr *chunkedBodyReader) Read(p []byte) (n int, err error) { + // document says that we have to set http.Request.Trailer + // after request was sent and before body returns EOF. + if !cbr.trailerWritten { + cbr.trailerWritten = true + for _, h := range cbr.trailer { + cbr.req.Trailer.Set(h.Name, h.Value) + } + } + return cbr.body.Read(p) +} + +func (st *serverTester) websocket(rp requestParam) *serverResponse { + urlstring := st.url + "/echo" + + config, err := websocket.NewConfig(urlstring, st.url) + if err != nil { + st.t.Fatalf("websocket.NewConfig(%q, %q) returned error: %v", urlstring, st.url, err) + } + + config.Header.Add("Test-Case", rp.name) + for _, h := range rp.header { + config.Header.Add(h.Name, h.Value) + } + + ws, err := websocket.NewClient(config, st.conn) + if err != nil { + st.t.Fatalf("Error creating websocket client: %v", err) + } + + if _, err := ws.Write(rp.body); err != nil { + st.t.Fatalf("ws.Write() returned error: %v", err) + } + + msg := make([]byte, 1024) + var n int + if n, err = ws.Read(msg); err != nil { + st.t.Fatalf("ws.Read() returned error: %v", err) + } + + res := &serverResponse{ + body: msg[:n], + } + + return res +} + +func (st *serverTester) http1(rp requestParam) (*serverResponse, error) { + method := "GET" + if rp.method != "" { + method = rp.method + } + + var body io.Reader + var cbr *chunkedBodyReader + if rp.body != nil { + body = bytes.NewBuffer(rp.body) + if len(rp.trailer) != 0 { + cbr = &chunkedBodyReader{ + trailer: rp.trailer, + body: body, + } + body = cbr + } + } + + reqURL := st.url + + if rp.path != "" { + u, err := url.Parse(st.url) + if err != nil { + st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err) + } + u.Path = "" + u.RawQuery = "" + reqURL = u.String() + rp.path + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, method, reqURL, body) + if err != nil { + return nil, err + } + for _, h := range rp.header { + req.Header.Add(h.Name, h.Value) + } + req.Header.Add("Test-Case", rp.name) + if cbr != nil { + cbr.req = req + // this makes request use chunked encoding + req.ContentLength = -1 + req.Trailer = make(http.Header) + for _, h := range cbr.trailer { + req.Trailer.Set(h.Name, "") + } + } + if err := req.Write(st.conn); err != nil { + return nil, err + } + resp, err := http.ReadResponse(bufio.NewReader(st.conn), req) + if err != nil { + return nil, err + } + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + resp.Body.Close() + + res := &serverResponse{ + status: resp.StatusCode, + header: resp.Header, + body: respBody, + connClose: resp.Close, + } + + return res, nil +} + +func (st *serverTester) http2(rp requestParam) (*serverResponse, error) { + st.headerBlkBuf.Reset() + st.header = make(http.Header) + + var id uint32 + if rp.streamID != 0 { + id = rp.streamID + if id >= st.nextStreamID && id%2 == 1 { + st.nextStreamID = id + 2 + } + } else { + id = st.nextStreamID + st.nextStreamID += 2 + } + + if !st.h2PrefaceSent { + st.h2PrefaceSent = true + fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") + if err := st.fr.WriteSettings(); err != nil { + return nil, err + } + } + + res := &serverResponse{ + streamID: id, + } + + streams := make(map[uint32]*serverResponse) + streams[id] = res + + if !rp.httpUpgrade { + method := "GET" + if rp.method != "" { + method = rp.method + } + _ = st.enc.WriteField(pair(":method", method)) + + scheme := "http" + if rp.scheme != "" { + scheme = rp.scheme + } + _ = st.enc.WriteField(pair(":scheme", scheme)) + + authority := st.authority + if rp.authority != "" { + authority = rp.authority + } + _ = st.enc.WriteField(pair(":authority", authority)) + + path := "/" + if rp.path != "" { + path = rp.path + } + _ = st.enc.WriteField(pair(":path", path)) + + _ = st.enc.WriteField(pair("test-case", rp.name)) + + for _, h := range rp.header { + _ = st.enc.WriteField(h) + } + + err := st.fr.WriteHeaders(http2.HeadersFrameParam{ + StreamID: id, + EndStream: len(rp.body) == 0 && len(rp.trailer) == 0 && !rp.noEndStream, + EndHeaders: true, + BlockFragment: st.headerBlkBuf.Bytes(), + }) + if err != nil { + return nil, err + } + + if len(rp.body) != 0 { + // TODO we assume rp.body fits in 1 frame + if err := st.fr.WriteData(id, len(rp.trailer) == 0 && !rp.noEndStream, rp.body); err != nil { + return nil, err + } + } + + if len(rp.trailer) != 0 { + st.headerBlkBuf.Reset() + for _, h := range rp.trailer { + _ = st.enc.WriteField(h) + } + err := st.fr.WriteHeaders(http2.HeadersFrameParam{ + StreamID: id, + EndStream: true, + EndHeaders: true, + BlockFragment: st.headerBlkBuf.Bytes(), + }) + if err != nil { + return nil, err + } + } + } +loop: + for { + fr, err := st.readFrame() + if err != nil { + return res, err + } + switch f := fr.(type) { + case *http2.HeadersFrame: + _, err := st.dec.Write(f.HeaderBlockFragment()) + if err != nil { + return res, err + } + sr, ok := streams[f.FrameHeader.StreamID] + if !ok { + st.header = make(http.Header) + break + } + sr.header = cloneHeader(st.header) + var status int + status, err = strconv.Atoi(sr.header.Get(":status")) + if err != nil { + return res, fmt.Errorf("Error parsing status code: %w", err) + } + sr.status = status + if f.StreamEnded() { + if streamEnded(res, streams, sr) { + break loop + } + } + case *http2.PushPromiseFrame: + _, err := st.dec.Write(f.HeaderBlockFragment()) + if err != nil { + return res, err + } + sr := &serverResponse{ + streamID: f.PromiseID, + reqHeader: cloneHeader(st.header), + } + streams[sr.streamID] = sr + case *http2.DataFrame: + sr, ok := streams[f.FrameHeader.StreamID] + if !ok { + break + } + sr.body = append(sr.body, f.Data()...) + if f.StreamEnded() { + if streamEnded(res, streams, sr) { + break loop + } + } + case *http2.RSTStreamFrame: + sr, ok := streams[f.FrameHeader.StreamID] + if !ok { + break + } + sr.errCode = f.ErrCode + if streamEnded(res, streams, sr) { + break loop + } + case *http2.GoAwayFrame: + if f.ErrCode == http2.ErrCodeNo { + break + } + res.errCode = f.ErrCode + res.connErr = true + break loop + case *http2.SettingsFrame: + if f.IsAck() { + break + } + if err := st.fr.WriteSettingsAck(); err != nil { + return res, err + } + } + } + sort.Sort(ByStreamID(res.pushResponse)) + return res, nil +} + +func streamEnded(mainSr *serverResponse, streams map[uint32]*serverResponse, sr *serverResponse) bool { + delete(streams, sr.streamID) + if mainSr.streamID != sr.streamID { + mainSr.pushResponse = append(mainSr.pushResponse, sr) + } + return len(streams) == 0 +} + +type serverResponse struct { + status int // HTTP status code + header http.Header // response header fields + body []byte // response body + streamID uint32 // stream ID in HTTP/2 + errCode http2.ErrCode // error code received in HTTP/2 RST_STREAM or GOAWAY + connErr bool // true if HTTP/2 connection error + connClose bool // Connection: close is included in response header in HTTP/1 test + reqHeader http.Header // http request header, currently only sotres pushed request header + pushResponse []*serverResponse // pushed response +} + +type ByStreamID []*serverResponse + +func (b ByStreamID) Len() int { + return len(b) +} + +func (b ByStreamID) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} + +func (b ByStreamID) Less(i, j int) bool { + return b[i].streamID < b[j].streamID +} + +func cloneHeader(h http.Header) http.Header { + h2 := make(http.Header, len(h)) + for k, vv := range h { + vv2 := make([]string, len(vv)) + copy(vv2, vv) + h2[k] = vv2 + } + return h2 +} + +func noopHandler(w http.ResponseWriter, r *http.Request) { + if _, err := io.ReadAll(r.Body); err != nil { + http.Error(w, fmt.Sprintf("Error io.ReadAll() = %v", err), http.StatusInternalServerError) + } +} + +type APIResponse struct { + Status string `json:"status,omitempty"` + Code int `json:"code,omitempty"` + Data map[string]interface{} `json:"data,omitempty"` +} + +type proxyProtocolV2 struct { + command proxyProtocolV2Command + sourceAddress net.Addr + destinationAddress net.Addr + additionalData []byte +} + +type proxyProtocolV2Command int + +const ( + proxyProtocolV2CommandLocal proxyProtocolV2Command = 0x0 + proxyProtocolV2CommandProxy proxyProtocolV2Command = 0x1 +) + +type proxyProtocolV2Family int + +const ( + proxyProtocolV2FamilyUnspec proxyProtocolV2Family = 0x0 + proxyProtocolV2FamilyInet proxyProtocolV2Family = 0x1 + proxyProtocolV2FamilyInet6 proxyProtocolV2Family = 0x2 + proxyProtocolV2FamilyUnix proxyProtocolV2Family = 0x3 +) + +type proxyProtocolV2Protocol int + +const ( + proxyProtocolV2ProtocolUnspec proxyProtocolV2Protocol = 0x0 + proxyProtocolV2ProtocolStream proxyProtocolV2Protocol = 0x1 + proxyProtocolV2ProtocolDgram proxyProtocolV2Protocol = 0x2 +) + +func writeProxyProtocolV2(w io.Writer, hdr proxyProtocolV2) error { + if _, err := w.Write([]byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}); err != nil { + return err + } + if _, err := w.Write([]byte{byte(0x20 | hdr.command)}); err != nil { + return err + } + + switch srcAddr := hdr.sourceAddress.(type) { + case *net.TCPAddr: + dstAddr := hdr.destinationAddress.(*net.TCPAddr) + if len(srcAddr.IP) != len(dstAddr.IP) { + panic("len(srcAddr.IP) != len(dstAddr.IP)") + } + var fam byte + if len(srcAddr.IP) == 4 { + fam = byte(proxyProtocolV2FamilyInet << 4) + } else { + fam = byte(proxyProtocolV2FamilyInet6 << 4) + } + fam |= byte(proxyProtocolV2ProtocolStream) + if _, err := w.Write([]byte{fam}); err != nil { + return err + } + length := uint16(len(srcAddr.IP)*2 + 4 + len(hdr.additionalData)) + if err := binary.Write(w, binary.BigEndian, length); err != nil { + return err + } + if _, err := w.Write(srcAddr.IP); err != nil { + return err + } + if _, err := w.Write(dstAddr.IP); err != nil { + return err + } + if err := binary.Write(w, binary.BigEndian, uint16(srcAddr.Port)); err != nil { + return err + } + if err := binary.Write(w, binary.BigEndian, uint16(dstAddr.Port)); err != nil { + return err + } + case *net.UnixAddr: + dstAddr := hdr.destinationAddress.(*net.UnixAddr) + if len(srcAddr.Name) > 108 { + panic("too long Unix source address") + } + if len(dstAddr.Name) > 108 { + panic("too long Unix destination address") + } + fam := byte(proxyProtocolV2FamilyUnix << 4) + switch srcAddr.Net { + case "unix": + fam |= byte(proxyProtocolV2ProtocolStream) + case "unixdgram": + fam |= byte(proxyProtocolV2ProtocolDgram) + default: + fam |= byte(proxyProtocolV2ProtocolUnspec) + } + if _, err := w.Write([]byte{fam}); err != nil { + return err + } + length := uint16(216 + len(hdr.additionalData)) + if err := binary.Write(w, binary.BigEndian, length); err != nil { + return err + } + zeros := make([]byte, 108) + if _, err := w.Write([]byte(srcAddr.Name)); err != nil { + return err + } + if _, err := w.Write(zeros[:108-len(srcAddr.Name)]); err != nil { + return err + } + if _, err := w.Write([]byte(dstAddr.Name)); err != nil { + return err + } + if _, err := w.Write(zeros[:108-len(dstAddr.Name)]); err != nil { + return err + } + default: + fam := byte(proxyProtocolV2FamilyUnspec<<4) | byte(proxyProtocolV2ProtocolUnspec) + if _, err := w.Write([]byte{fam}); err != nil { + return err + } + length := uint16(len(hdr.additionalData)) + if err := binary.Write(w, binary.BigEndian, length); err != nil { + return err + } + } + + if _, err := w.Write(hdr.additionalData); err != nil { + return err + } + + return nil +} diff --git a/lib/nghttp2/integration-tests/server_tester_http3.go b/lib/nghttp2/integration-tests/server_tester_http3.go new file mode 100644 index 00000000000..7bbc4e74c02 --- /dev/null +++ b/lib/nghttp2/integration-tests/server_tester_http3.go @@ -0,0 +1,90 @@ +//go:build quic + +package nghttp2 + +import ( + "bytes" + "context" + "crypto/tls" + "io" + "net/http" + "net/url" + "time" + + "github.com/quic-go/quic-go/http3" +) + +func (st *serverTester) http3(rp requestParam) (*serverResponse, error) { + rt := &http3.RoundTripper{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + + defer rt.Close() + + c := &http.Client{ + Transport: rt, + } + + method := "GET" + if rp.method != "" { + method = rp.method + } + + var body io.Reader + + if rp.body != nil { + body = bytes.NewBuffer(rp.body) + } + + reqURL := st.url + + if rp.path != "" { + u, err := url.Parse(st.url) + if err != nil { + st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err) + } + u.Path = "" + u.RawQuery = "" + reqURL = u.String() + rp.path + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, method, reqURL, body) + if err != nil { + return nil, err + } + + for _, h := range rp.header { + req.Header.Add(h.Name, h.Value) + } + + req.Header.Add("Test-Case", rp.name) + + // TODO http3 package does not support trailer at the time of + // this writing. + + resp, err := c.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + res := &serverResponse{ + status: resp.StatusCode, + header: resp.Header, + body: respBody, + connClose: resp.Close, + } + + return res, nil +} diff --git a/lib/nghttp2/integration-tests/setenv.in b/lib/nghttp2/integration-tests/setenv.in new file mode 100644 index 00000000000..7177200e9e0 --- /dev/null +++ b/lib/nghttp2/integration-tests/setenv.in @@ -0,0 +1,13 @@ +#!/bin/sh -e + +libdir="@abs_top_builddir@/lib" +if [ -d "$libdir/.libs" ]; then + libdir="$libdir/.libs" +fi + +export CGO_CFLAGS="-I@abs_top_srcdir@/lib/includes -I@abs_top_builddir@/lib/includes @CFLAGS@" +export CGO_CPPFLAGS="@CPPFLAGS@" +export CGO_LDFLAGS="-L$libdir @LDFLAGS@" +export LD_LIBRARY_PATH="$libdir" +export GODEBUG=cgocheck=0 +"$@" diff --git a/lib/nghttp2/lib/.gitignore b/lib/nghttp2/lib/.gitignore new file mode 100644 index 00000000000..f64f46bea3e --- /dev/null +++ b/lib/nghttp2/lib/.gitignore @@ -0,0 +1,3 @@ +# generated files +includes/nghttp2/nghttp2ver.h +libnghttp2.pc diff --git a/lib/nghttp2/lib/CMakeLists.txt b/lib/nghttp2/lib/CMakeLists.txt new file mode 100644 index 00000000000..7adba3a3ffa --- /dev/null +++ b/lib/nghttp2/lib/CMakeLists.txt @@ -0,0 +1,80 @@ +add_subdirectory(includes) + +include_directories( + "${CMAKE_CURRENT_SOURCE_DIR}/includes" + "${CMAKE_CURRENT_BINARY_DIR}/includes" +) + +add_definitions(-DBUILDING_NGHTTP2) + +set(NGHTTP2_SOURCES + nghttp2_pq.c nghttp2_map.c nghttp2_queue.c + nghttp2_frame.c + nghttp2_buf.c + nghttp2_stream.c nghttp2_outbound_item.c + nghttp2_session.c nghttp2_submit.c + nghttp2_helper.c + nghttp2_npn.c + nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c + nghttp2_version.c + nghttp2_priority_spec.c + nghttp2_option.c + nghttp2_callbacks.c + nghttp2_mem.c + nghttp2_http.c + nghttp2_rcbuf.c + nghttp2_extpri.c + nghttp2_ratelim.c + nghttp2_time.c + nghttp2_debug.c + sfparse.c +) + +set(NGHTTP2_RES "") + +if(WIN32) + configure_file( + version.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/version.rc + @ONLY) + + set(NGHTTP2_RES ${CMAKE_CURRENT_BINARY_DIR}/version.rc) +endif() + +# Public shared library +if(ENABLE_SHARED_LIB) + add_library(nghttp2 SHARED ${NGHTTP2_SOURCES} ${NGHTTP2_RES}) + set_target_properties(nghttp2 PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + C_VISIBILITY_PRESET hidden + ) + target_include_directories(nghttp2 INTERFACE + "${CMAKE_CURRENT_BINARY_DIR}/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/includes" + ) + + install(TARGETS nghttp2 + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() + +if(HAVE_CUNIT OR ENABLE_STATIC_LIB) + # Static library (for unittests because of symbol visibility) + add_library(nghttp2_static STATIC ${NGHTTP2_SOURCES}) + set_target_properties(nghttp2_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + ARCHIVE_OUTPUT_NAME nghttp2${STATIC_LIB_SUFFIX} + ) + target_compile_definitions(nghttp2_static PUBLIC "-DNGHTTP2_STATICLIB") + if(ENABLE_STATIC_LIB) + install(TARGETS nghttp2_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + endif() +endif() + + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnghttp2.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/lib/nghttp2/lib/Makefile.am b/lib/nghttp2/lib/Makefile.am new file mode 100644 index 00000000000..c3ace4029a6 --- /dev/null +++ b/lib/nghttp2/lib/Makefile.am @@ -0,0 +1,81 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = includes + +EXTRA_DIST = Makefile.msvc CMakeLists.txt version.rc.in + +AM_CFLAGS = $(WARNCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes -DBUILDING_NGHTTP2 \ + @DEFS@ +AM_LDFLAGS = @LIBTOOL_LDFLAGS@ + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libnghttp2.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LTLIBRARIES = libnghttp2.la + +OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \ + nghttp2_frame.c \ + nghttp2_buf.c \ + nghttp2_stream.c nghttp2_outbound_item.c \ + nghttp2_session.c nghttp2_submit.c \ + nghttp2_helper.c \ + nghttp2_npn.c \ + nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c \ + nghttp2_version.c \ + nghttp2_priority_spec.c \ + nghttp2_option.c \ + nghttp2_callbacks.c \ + nghttp2_mem.c \ + nghttp2_http.c \ + nghttp2_rcbuf.c \ + nghttp2_extpri.c \ + nghttp2_ratelim.c \ + nghttp2_time.c \ + nghttp2_debug.c \ + sfparse.c + +HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \ + nghttp2_frame.h \ + nghttp2_buf.h \ + nghttp2_session.h nghttp2_helper.h nghttp2_stream.h nghttp2_int.h \ + nghttp2_npn.h \ + nghttp2_submit.h nghttp2_outbound_item.h \ + nghttp2_net.h \ + nghttp2_hd.h nghttp2_hd_huffman.h \ + nghttp2_priority_spec.h \ + nghttp2_option.h \ + nghttp2_callbacks.h \ + nghttp2_mem.h \ + nghttp2_http.h \ + nghttp2_rcbuf.h \ + nghttp2_extpri.h \ + nghttp2_ratelim.h \ + nghttp2_time.h \ + nghttp2_debug.h \ + sfparse.h + +libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS) +libnghttp2_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined \ + -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) diff --git a/lib/nghttp2/lib/Makefile.msvc b/lib/nghttp2/lib/Makefile.msvc new file mode 100644 index 00000000000..611b39d0b1d --- /dev/null +++ b/lib/nghttp2/lib/Makefile.msvc @@ -0,0 +1,254 @@ +# +# GNU Makefile for nghttp2 / MSVC. +# +# By G. Vanem 2013 +# Updated 3/2015 by Remo Eichenberger @remoe +# The MIT License apply. +# + +THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST)) + +_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -e 's/-DEV//g' -e 's/], //g') +_VERSION := $(subst ., ,$(_VERSION)) +VER_MAJOR := $(word 1,$(_VERSION)) +VER_MINOR := $(word 2,$(_VERSION)) +VER_MICRO := $(word 3,$(_VERSION)) +VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_MICRO) +VERSION_NUM := (($(VER_MAJOR) << 16) + ($(VER_MINOR) << 8) + $(VER_MICRO)) + +GENERATED := 'Generated by $(realpath Makefile.MSVC)' + +OBJ_DIR := MSVC_obj +#SUFFIX :=-vc90-mt-x86 + +# +# Where to copy nghttp2.dll + lib + headers to. +# Note: 'make install' is not in default targets. Do it explicitly. +# +TARGET_DIR ?= ../_VC_ROOT +VC_ROOT := $(abspath $(TARGET_DIR)) +INSTALL_BIN := $(VC_ROOT)/bin +INSTALL_LIB := $(VC_ROOT)/lib +INSTALL_HDR := $(VC_ROOT)/include +DLL_R := $(OBJ_DIR)/nghttp2$(SUFFIX).dll +DLL_D := $(OBJ_DIR)/nghttp2d$(SUFFIX).dll +LIB_R := $(OBJ_DIR)/nghttp2-static.lib +LIB_D := $(OBJ_DIR)/nghttp2d-static.lib +IMP_R := $(OBJ_DIR)/nghttp2.lib +IMP_D := $(OBJ_DIR)/nghttp2d.lib + +# +# Build for DEBUG-model and RELEASE at the same time. +# +TARGETS := $(LIB_R) $(DLL_R) $(IMP_R) \ + $(LIB_D) $(DLL_D) $(IMP_D) + +EXT_LIBS = + +NGHTTP2_PDB_R := $(OBJ_DIR)/nghttp2.pdb +NGHTTP2_PDB_D := $(OBJ_DIR)/nghttp2d.pdb + +CC = cl +LD := link +AR := lib +#CC := icl +#LD := xilink +#AR := xilib +RC := rc +CFLAGS := -I./includes -Dssize_t=long + +CFLAGS_R := -nologo -MD -W3 -Z7 -DBUILDING_NGHTTP2 +CFLAGS_D := -nologo -MDd -W3 -Z7 -DBUILDING_NGHTTP2 \ + -Ot -D_DEBUG -GF -RTCs -RTCu # -RTCc -GS + +LDFLAGS := -nologo -MAP -debug -incremental:no -opt:ref,icf -MANIFEST # -verbose + + +NGHTTP2_SRC := nghttp2_pq.c \ + nghttp2_map.c \ + nghttp2_queue.c \ + nghttp2_frame.c \ + nghttp2_buf.c \ + nghttp2_stream.c \ + nghttp2_outbound_item.c \ + nghttp2_session.c \ + nghttp2_submit.c \ + nghttp2_helper.c \ + nghttp2_npn.c \ + nghttp2_hd.c \ + nghttp2_hd_huffman.c \ + nghttp2_hd_huffman_data.c \ + nghttp2_version.c \ + nghttp2_priority_spec.c \ + nghttp2_option.c \ + nghttp2_callbacks.c \ + nghttp2_mem.c \ + nghttp2_http.c \ + nghttp2_rcbuf.c + +NGHTTP2_OBJ_R := $(addprefix $(OBJ_DIR)/r_, $(notdir $(NGHTTP2_SRC:.c=.obj))) +NGHTTP2_OBJ_D := $(addprefix $(OBJ_DIR)/d_, $(notdir $(NGHTTP2_SRC:.c=.obj))) + +.PHONY: all intro test_ver install copy_headers_and_libs \ + install_nghttp2_pyd_0 install_nghttp2_pyd_1 \ + build_nghttp2_pyd_0 build_nghttp2_pyd_1 \ + clean_nghttp2_pyd_0 clean_nghttp2_pyd_1 + + +all: intro includes/nghttp2/nghttp2ver.h $(OBJ_DIR) $(TARGETS) + @echo 'Welcome to NgHTTP2 (release + debug).' + @echo 'Do a "make -f Makefile.MSVC install" at own risk!' + +intro: + @echo 'Building NgHTTP (MSVC) ver. "$(VERSION)".' + +test_ver: + @echo '$$(VERSION): "$(VERSION)".' + @echo '$$(_VERSION): "$(_VERSION)".' + @echo '$$(VER_MAJOR): "$(VER_MAJOR)".' + @echo '$$(VER_MINOR): "$(VER_MINOR)".' + @echo '$$(VER_MICRO): "$(VER_MICRO)".' + +$(OBJ_DIR): + - mkdir $(OBJ_DIR) + +install: includes/nghttp2/nghttp2.h includes/nghttp2/nghttp2ver.h \ + $(TARGETS) \ + copy_headers_and_libs + +# +# This MUST be done before using the 'install_nghttp2_pyd_1' rule. +# +copy_headers_and_libs: + - mkdir -p $(INSTALL_HDR)/nghttp2 $(INSTALL_BIN) $(INSTALL_LIB) + cp --update $(addprefix includes/nghttp2/, nghttp2.h nghttp2ver.h) $(INSTALL_HDR)/nghttp2 + cp --update $(DLL_R) $(DLL_D) $(NGHTTP2_PDB_R) $(NGHTTP2_PDB_D) $(INSTALL_BIN) + cp --update $(IMP_R) $(IMP_D) $(LIB_R) $(LIB_D) $(INSTALL_LIB) + @echo + +$(LIB_R): $(NGHTTP2_OBJ_R) + $(AR) -nologo -out:$@ $^ + @echo + +$(LIB_D): $(NGHTTP2_OBJ_D) + $(AR) -nologo -out:$@ $^ + @echo + + +$(IMP_R): $(DLL_R) + +$(DLL_R): $(NGHTTP2_OBJ_R) $(OBJ_DIR)/r_nghttp2.res + $(LD) $(LDFLAGS) -dll -out:$@ -implib:$(IMP_R) $(NGHTTP2_OBJ_R) -PDB:$(NGHTTP2_PDB_R) $(OBJ_DIR)/r_nghttp2.res $(EXT_LIBS) + mt -nologo -manifest $@.manifest -outputresource:$@\;2 + @echo + +$(IMP_D): $(DLL_D) + +$(DLL_D): $(NGHTTP2_OBJ_D) $(OBJ_DIR)/d_nghttp2.res + $(LD) $(LDFLAGS) -dll -out:$@ -implib:$(IMP_D) $(NGHTTP2_OBJ_D) -PDB:$(NGHTTP2_PDB_D) $(OBJ_DIR)/d_nghttp2.res $(EXT_LIBS) + mt -nologo -manifest $@.manifest -outputresource:$@\;2 + @echo + + +WIN_OBJDIR:=$(shell cygpath -w $(abspath $(OBJ_DIR))) +WIN_OBJDIR:=$(subst \,/,$(WIN_OBJDIR)) + +$(OBJ_DIR)/r_%.obj: %.c $(THIS_MAKEFILE) + $(CC) $(CFLAGS_R) $(CFLAGS) -Fo$@ -c $< + @echo + +$(OBJ_DIR)/d_%.obj: %.c $(THIS_MAKEFILE) + $(CC) $(CFLAGS_D) $(CFLAGS) -Fo$@ -c $< + @echo + +$(OBJ_DIR)/r_nghttp2.res: $(OBJ_DIR)/nghttp2.rc $(THIS_MAKEFILE) + $(RC) -D_RELEASE -Fo $@ $< + @echo + +$(OBJ_DIR)/d_nghttp2.res: $(OBJ_DIR)/nghttp2.rc $(THIS_MAKEFILE) + $(RC) -D_DEBUG -Fo $@ $< + @echo + +includes/nghttp2/nghttp2ver.h: includes/nghttp2/nghttp2ver.h.in $(THIS_MAKEFILE) + sed < includes/nghttp2/nghttp2ver.h.in \ + -e 's/@PACKAGE_VERSION@/$(VERSION)/g' \ + -e 's/@PACKAGE_VERSION_NUM@/$(VERSION_NUM)/g' > $@ + touch --reference=includes/nghttp2/nghttp2ver.h.in $@ + + +define RES_FILE + #include + + VS_VERSION_INFO VERSIONINFO + FILEVERSION $(VER_MAJOR), $(VER_MINOR), $(VER_MICRO), 0 + PRODUCTVERSION $(VER_MAJOR), $(VER_MINOR), $(VER_MICRO), 0 + FILEFLAGSMASK 0x3fL + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L + #ifdef _DEBUG + #define VER_STR "$(VERSION).0 (MSVC debug)" + #define DBG "d" + FILEFLAGS 0x1L + #else + #define VER_STR "$(VERSION).0 (MSVC release)" + #define DBG "" + FILEFLAGS 0x0L + #endif + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "http://tatsuhiro-t.github.io/nghttp2/" + VALUE "FileDescription", "nghttp2; HTTP/2 C library" + VALUE "FileVersion", VER_STR + VALUE "InternalName", "nghttp2" DBG + VALUE "LegalCopyright", "The MIT License" + VALUE "LegalTrademarks", "" + VALUE "OriginalFilename", "nghttp2" DBG ".dll" + VALUE "ProductName", "NGHTTP2." + VALUE "ProductVersion", VER_STR + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END + END +endef + +export RES_FILE + +$(OBJ_DIR)/nghttp2.rc: Makefile.MSVC + @echo 'Generating $@...' + @echo ' /* $(GENERATED). DO NOT EDIT.' > $@ + @echo ' */' >> $@ + @echo "$$RES_FILE" >> $@ + +clean: + rm -f $(OBJ_DIR)/* includes/nghttp2/nghttp2ver.h + @echo + +vclean realclean: clean + - rm -rf $(OBJ_DIR) + - rm -f .depend.MSVC + +# +# Use gcc to generated the dependencies. No MSVC specific args please! +# +REPLACE_R = 's/\(.*\)\.o: /\n$$(OBJ_DIR)\/r_\1.obj: /' +REPLACE_D = 's/\(.*\)\.o: /\n$$(OBJ_DIR)\/d_\1.obj: /' + +depend: includes/nghttp2/nghttp2ver.h + @echo '# $(GENERATED). DO NOT EDIT.' > .depend.MSVC + gcc -MM $(CFLAGS) $(NGHTTP2_SRC) >> .depend.tmp + @echo '#' >> .depend.MSVC + @echo '# Release lib objects:' >> .depend.MSVC + sed -e $(REPLACE_R) .depend.tmp >> .depend.MSVC + @echo '#' >> .depend.MSVC + @echo '# Debug lib objects:' >> .depend.MSVC + sed -e $(REPLACE_D) .depend.tmp >> .depend.MSVC + rm -f .depend.tmp + +-include .depend.MSVC diff --git a/lib/nghttp2/lib/includes/CMakeLists.txt b/lib/nghttp2/lib/includes/CMakeLists.txt new file mode 100644 index 00000000000..17de2ec691e --- /dev/null +++ b/lib/nghttp2/lib/includes/CMakeLists.txt @@ -0,0 +1,4 @@ +install(FILES + nghttp2/nghttp2.h + "${CMAKE_CURRENT_BINARY_DIR}/nghttp2/nghttp2ver.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/nghttp2") diff --git a/lib/nghttp2/lib/includes/Makefile.am b/lib/nghttp2/lib/includes/Makefile.am new file mode 100644 index 00000000000..c07cb4d2c02 --- /dev/null +++ b/lib/nghttp2/lib/includes/Makefile.am @@ -0,0 +1,26 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +EXTRA_DIST = CMakeLists.txt + +nobase_include_HEADERS = nghttp2/nghttp2.h nghttp2/nghttp2ver.h diff --git a/lib/nghttp2/lib/includes/nghttp2/nghttp2.h b/lib/nghttp2/lib/includes/nghttp2/nghttp2.h new file mode 100644 index 00000000000..66ea3c63c1d --- /dev/null +++ b/lib/nghttp2/lib/includes/nghttp2/nghttp2.h @@ -0,0 +1,5881 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013, 2014 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_H +#define NGHTTP2_H + +/* Define WIN32 when build target is Win32 API (borrowed from + libcurl) */ +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif + +/* Compatibility for non-Clang compilers */ +#ifndef __has_declspec_attribute +# define __has_declspec_attribute(x) 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#if defined(_MSC_VER) && (_MSC_VER < 1800) +/* MSVC < 2013 does not have inttypes.h because it is not C99 + compliant. See compiler macros and version number in + https://sourceforge.net/p/predef/wiki/Compilers/ */ +# include +#else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +# include +#endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +#include +#include + +#include + +#ifdef NGHTTP2_STATICLIB +# define NGHTTP2_EXTERN +#elif defined(WIN32) || (__has_declspec_attribute(dllexport) && \ + __has_declspec_attribute(dllimport)) +# ifdef BUILDING_NGHTTP2 +# define NGHTTP2_EXTERN __declspec(dllexport) +# else /* !BUILDING_NGHTTP2 */ +# define NGHTTP2_EXTERN __declspec(dllimport) +# endif /* !BUILDING_NGHTTP2 */ +#else /* !defined(WIN32) */ +# ifdef BUILDING_NGHTTP2 +# define NGHTTP2_EXTERN __attribute__((visibility("default"))) +# else /* !BUILDING_NGHTTP2 */ +# define NGHTTP2_EXTERN +# endif /* !BUILDING_NGHTTP2 */ +#endif /* !defined(WIN32) */ + +/** + * @macro + * + * The protocol version identification string of this library + * supports. This identifier is used if HTTP/2 is used over TLS. + */ +#define NGHTTP2_PROTO_VERSION_ID "h2" +/** + * @macro + * + * The length of :macro:`NGHTTP2_PROTO_VERSION_ID`. + */ +#define NGHTTP2_PROTO_VERSION_ID_LEN 2 + +/** + * @macro + * + * The serialized form of ALPN protocol identifier this library + * supports. Notice that first byte is the length of following + * protocol identifier. This is the same wire format of `TLS ALPN + * extension `_. This is useful + * to process incoming ALPN tokens in wire format. + */ +#define NGHTTP2_PROTO_ALPN "\x2h2" + +/** + * @macro + * + * The length of :macro:`NGHTTP2_PROTO_ALPN`. + */ +#define NGHTTP2_PROTO_ALPN_LEN (sizeof(NGHTTP2_PROTO_ALPN) - 1) + +/** + * @macro + * + * The protocol version identification string of this library + * supports. This identifier is used if HTTP/2 is used over cleartext + * TCP. + */ +#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c" + +/** + * @macro + * + * The length of :macro:`NGHTTP2_CLEARTEXT_PROTO_VERSION_ID`. + */ +#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID_LEN 3 + +struct nghttp2_session; +/** + * @struct + * + * The primary structure to hold the resources needed for a HTTP/2 + * session. The details of this structure are intentionally hidden + * from the public API. + */ +typedef struct nghttp2_session nghttp2_session; + +/** + * @macro + * + * The age of :type:`nghttp2_info` + */ +#define NGHTTP2_VERSION_AGE 1 + +/** + * @struct + * + * This struct is what `nghttp2_version()` returns. It holds + * information about the particular nghttp2 version. + */ +typedef struct { + /** + * Age of this struct. This instance of nghttp2 sets it to + * :macro:`NGHTTP2_VERSION_AGE` but a future version may bump it and + * add more struct fields at the bottom + */ + int age; + /** + * the :macro:`NGHTTP2_VERSION_NUM` number (since age ==1) + */ + int version_num; + /** + * points to the :macro:`NGHTTP2_VERSION` string (since age ==1) + */ + const char *version_str; + /** + * points to the :macro:`NGHTTP2_PROTO_VERSION_ID` string this + * instance implements (since age ==1) + */ + const char *proto_str; + /* -------- the above fields all exist when age == 1 */ +} nghttp2_info; + +/** + * @macro + * + * The default weight of stream dependency. + */ +#define NGHTTP2_DEFAULT_WEIGHT 16 + +/** + * @macro + * + * The maximum weight of stream dependency. + */ +#define NGHTTP2_MAX_WEIGHT 256 + +/** + * @macro + * + * The minimum weight of stream dependency. + */ +#define NGHTTP2_MIN_WEIGHT 1 + +/** + * @macro + * + * The maximum window size + */ +#define NGHTTP2_MAX_WINDOW_SIZE ((int32_t)((1U << 31) - 1)) + +/** + * @macro + * + * The initial window size for stream level flow control. + */ +#define NGHTTP2_INITIAL_WINDOW_SIZE ((1 << 16) - 1) +/** + * @macro + * + * The initial window size for connection level flow control. + */ +#define NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE ((1 << 16) - 1) + +/** + * @macro + * + * The default header table size. + */ +#define NGHTTP2_DEFAULT_HEADER_TABLE_SIZE (1 << 12) + +/** + * @macro + * + * The client magic string, which is the first 24 bytes byte string of + * client connection preface. + */ +#define NGHTTP2_CLIENT_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + +/** + * @macro + * + * The length of :macro:`NGHTTP2_CLIENT_MAGIC`. + */ +#define NGHTTP2_CLIENT_MAGIC_LEN 24 + +/** + * @macro + * + * The default max number of settings per SETTINGS frame + */ +#define NGHTTP2_DEFAULT_MAX_SETTINGS 32 + +/** + * @enum + * + * Error codes used in this library. The code range is [-999, -500], + * inclusive. The following values are defined: + */ +typedef enum { + /** + * Invalid argument passed. + */ + NGHTTP2_ERR_INVALID_ARGUMENT = -501, + /** + * Out of buffer space. + */ + NGHTTP2_ERR_BUFFER_ERROR = -502, + /** + * The specified protocol version is not supported. + */ + NGHTTP2_ERR_UNSUPPORTED_VERSION = -503, + /** + * Used as a return value from :type:`nghttp2_send_callback`, + * :type:`nghttp2_recv_callback` and + * :type:`nghttp2_send_data_callback` to indicate that the operation + * would block. + */ + NGHTTP2_ERR_WOULDBLOCK = -504, + /** + * General protocol error + */ + NGHTTP2_ERR_PROTO = -505, + /** + * The frame is invalid. + */ + NGHTTP2_ERR_INVALID_FRAME = -506, + /** + * The peer performed a shutdown on the connection. + */ + NGHTTP2_ERR_EOF = -507, + /** + * Used as a return value from + * :func:`nghttp2_data_source_read_callback` to indicate that data + * transfer is postponed. See + * :func:`nghttp2_data_source_read_callback` for details. + */ + NGHTTP2_ERR_DEFERRED = -508, + /** + * Stream ID has reached the maximum value. Therefore no stream ID + * is available. + */ + NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE = -509, + /** + * The stream is already closed; or the stream ID is invalid. + */ + NGHTTP2_ERR_STREAM_CLOSED = -510, + /** + * RST_STREAM has been added to the outbound queue. The stream is + * in closing state. + */ + NGHTTP2_ERR_STREAM_CLOSING = -511, + /** + * The transmission is not allowed for this stream (e.g., a frame + * with END_STREAM flag set has already sent). + */ + NGHTTP2_ERR_STREAM_SHUT_WR = -512, + /** + * The stream ID is invalid. + */ + NGHTTP2_ERR_INVALID_STREAM_ID = -513, + /** + * The state of the stream is not valid (e.g., DATA cannot be sent + * to the stream if response HEADERS has not been sent). + */ + NGHTTP2_ERR_INVALID_STREAM_STATE = -514, + /** + * Another DATA frame has already been deferred. + */ + NGHTTP2_ERR_DEFERRED_DATA_EXIST = -515, + /** + * Starting new stream is not allowed (e.g., GOAWAY has been sent + * and/or received). + */ + NGHTTP2_ERR_START_STREAM_NOT_ALLOWED = -516, + /** + * GOAWAY has already been sent. + */ + NGHTTP2_ERR_GOAWAY_ALREADY_SENT = -517, + /** + * The received frame contains the invalid header block (e.g., There + * are duplicate header names; or the header names are not encoded + * in US-ASCII character set and not lower cased; or the header name + * is zero-length string; or the header value contains multiple + * in-sequence NUL bytes). + */ + NGHTTP2_ERR_INVALID_HEADER_BLOCK = -518, + /** + * Indicates that the context is not suitable to perform the + * requested operation. + */ + NGHTTP2_ERR_INVALID_STATE = -519, + /** + * The user callback function failed due to the temporal error. + */ + NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE = -521, + /** + * The length of the frame is invalid, either too large or too small. + */ + NGHTTP2_ERR_FRAME_SIZE_ERROR = -522, + /** + * Header block inflate/deflate error. + */ + NGHTTP2_ERR_HEADER_COMP = -523, + /** + * Flow control error + */ + NGHTTP2_ERR_FLOW_CONTROL = -524, + /** + * Insufficient buffer size given to function. + */ + NGHTTP2_ERR_INSUFF_BUFSIZE = -525, + /** + * Callback was paused by the application + */ + NGHTTP2_ERR_PAUSE = -526, + /** + * There are too many in-flight SETTING frame and no more + * transmission of SETTINGS is allowed. + */ + NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS = -527, + /** + * The server push is disabled. + */ + NGHTTP2_ERR_PUSH_DISABLED = -528, + /** + * DATA or HEADERS frame for a given stream has been already + * submitted and has not been fully processed yet. Application + * should wait for the transmission of the previously submitted + * frame before submitting another. + */ + NGHTTP2_ERR_DATA_EXIST = -529, + /** + * The current session is closing due to a connection error or + * `nghttp2_session_terminate_session()` is called. + */ + NGHTTP2_ERR_SESSION_CLOSING = -530, + /** + * Invalid HTTP header field was received and stream is going to be + * closed. + */ + NGHTTP2_ERR_HTTP_HEADER = -531, + /** + * Violation in HTTP messaging rule. + */ + NGHTTP2_ERR_HTTP_MESSAGING = -532, + /** + * Stream was refused. + */ + NGHTTP2_ERR_REFUSED_STREAM = -533, + /** + * Unexpected internal error, but recovered. + */ + NGHTTP2_ERR_INTERNAL = -534, + /** + * Indicates that a processing was canceled. + */ + NGHTTP2_ERR_CANCEL = -535, + /** + * When a local endpoint expects to receive SETTINGS frame, it + * receives an other type of frame. + */ + NGHTTP2_ERR_SETTINGS_EXPECTED = -536, + /** + * When a local endpoint receives too many settings entries + * in a single SETTINGS frame. + */ + NGHTTP2_ERR_TOO_MANY_SETTINGS = -537, + /** + * The errors < :enum:`nghttp2_error.NGHTTP2_ERR_FATAL` mean that + * the library is under unexpected condition and processing was + * terminated (e.g., out of memory). If application receives this + * error code, it must stop using that :type:`nghttp2_session` + * object and only allowed operation for that object is deallocate + * it using `nghttp2_session_del()`. + */ + NGHTTP2_ERR_FATAL = -900, + /** + * Out of memory. This is a fatal error. + */ + NGHTTP2_ERR_NOMEM = -901, + /** + * The user callback function failed. This is a fatal error. + */ + NGHTTP2_ERR_CALLBACK_FAILURE = -902, + /** + * Invalid client magic (see :macro:`NGHTTP2_CLIENT_MAGIC`) was + * received and further processing is not possible. + */ + NGHTTP2_ERR_BAD_CLIENT_MAGIC = -903, + /** + * Possible flooding by peer was detected in this HTTP/2 session. + * Flooding is measured by how many PING and SETTINGS frames with + * ACK flag set are queued for transmission. These frames are + * response for the peer initiated frames, and peer can cause memory + * exhaustion on server side to send these frames forever and does + * not read network. + */ + NGHTTP2_ERR_FLOODED = -904 +} nghttp2_error; + +/** + * @struct + * + * The object representing single contiguous buffer. + */ +typedef struct { + /** + * The pointer to the buffer. + */ + uint8_t *base; + /** + * The length of the buffer. + */ + size_t len; +} nghttp2_vec; + +struct nghttp2_rcbuf; + +/** + * @struct + * + * The object representing reference counted buffer. The details of + * this structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_rcbuf nghttp2_rcbuf; + +/** + * @function + * + * Increments the reference count of |rcbuf| by 1. + */ +NGHTTP2_EXTERN void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf); + +/** + * @function + * + * Decrements the reference count of |rcbuf| by 1. If the reference + * count becomes zero, the object pointed by |rcbuf| will be freed. + * In this case, application must not use |rcbuf| again. + */ +NGHTTP2_EXTERN void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf); + +/** + * @function + * + * Returns the underlying buffer managed by |rcbuf|. + */ +NGHTTP2_EXTERN nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf); + +/** + * @function + * + * Returns nonzero if the underlying buffer is statically allocated, + * and 0 otherwise. This can be useful for language bindings that wish + * to avoid creating duplicate strings for these buffers. + */ +NGHTTP2_EXTERN int nghttp2_rcbuf_is_static(const nghttp2_rcbuf *rcbuf); + +/** + * @enum + * + * The flags for header field name/value pair. + */ +typedef enum { + /** + * No flag set. + */ + NGHTTP2_NV_FLAG_NONE = 0, + /** + * Indicates that this name/value pair must not be indexed ("Literal + * Header Field never Indexed" representation must be used in HPACK + * encoding). Other implementation calls this bit as "sensitive". + */ + NGHTTP2_NV_FLAG_NO_INDEX = 0x01, + /** + * This flag is set solely by application. If this flag is set, the + * library does not make a copy of header field name. This could + * improve performance. + */ + NGHTTP2_NV_FLAG_NO_COPY_NAME = 0x02, + /** + * This flag is set solely by application. If this flag is set, the + * library does not make a copy of header field value. This could + * improve performance. + */ + NGHTTP2_NV_FLAG_NO_COPY_VALUE = 0x04 +} nghttp2_nv_flag; + +/** + * @struct + * + * The name/value pair, which mainly used to represent header fields. + */ +typedef struct { + /** + * The |name| byte string. If this struct is presented from library + * (e.g., :type:`nghttp2_on_frame_recv_callback`), |name| is + * guaranteed to be NULL-terminated. For some callbacks + * (:type:`nghttp2_before_frame_send_callback`, + * :type:`nghttp2_on_frame_send_callback`, and + * :type:`nghttp2_on_frame_not_send_callback`), it may not be + * NULL-terminated if header field is passed from application with + * the flag :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`). + * When application is constructing this struct, |name| is not + * required to be NULL-terminated. + */ + uint8_t *name; + /** + * The |value| byte string. If this struct is presented from + * library (e.g., :type:`nghttp2_on_frame_recv_callback`), |value| + * is guaranteed to be NULL-terminated. For some callbacks + * (:type:`nghttp2_before_frame_send_callback`, + * :type:`nghttp2_on_frame_send_callback`, and + * :type:`nghttp2_on_frame_not_send_callback`), it may not be + * NULL-terminated if header field is passed from application with + * the flag :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE`). + * When application is constructing this struct, |value| is not + * required to be NULL-terminated. + */ + uint8_t *value; + /** + * The length of the |name|, excluding terminating NULL. + */ + size_t namelen; + /** + * The length of the |value|, excluding terminating NULL. + */ + size_t valuelen; + /** + * Bitwise OR of one or more of :type:`nghttp2_nv_flag`. + */ + uint8_t flags; +} nghttp2_nv; + +/** + * @enum + * + * The frame types in HTTP/2 specification. + */ +typedef enum { + /** + * The DATA frame. + */ + NGHTTP2_DATA = 0, + /** + * The HEADERS frame. + */ + NGHTTP2_HEADERS = 0x01, + /** + * The PRIORITY frame. + */ + NGHTTP2_PRIORITY = 0x02, + /** + * The RST_STREAM frame. + */ + NGHTTP2_RST_STREAM = 0x03, + /** + * The SETTINGS frame. + */ + NGHTTP2_SETTINGS = 0x04, + /** + * The PUSH_PROMISE frame. + */ + NGHTTP2_PUSH_PROMISE = 0x05, + /** + * The PING frame. + */ + NGHTTP2_PING = 0x06, + /** + * The GOAWAY frame. + */ + NGHTTP2_GOAWAY = 0x07, + /** + * The WINDOW_UPDATE frame. + */ + NGHTTP2_WINDOW_UPDATE = 0x08, + /** + * The CONTINUATION frame. This frame type won't be passed to any + * callbacks because the library processes this frame type and its + * preceding HEADERS/PUSH_PROMISE as a single frame. + */ + NGHTTP2_CONTINUATION = 0x09, + /** + * The ALTSVC frame, which is defined in `RFC 7383 + * `_. + */ + NGHTTP2_ALTSVC = 0x0a, + /** + * The ORIGIN frame, which is defined by `RFC 8336 + * `_. + */ + NGHTTP2_ORIGIN = 0x0c, + /** + * The PRIORITY_UPDATE frame, which is defined by :rfc:`9218`. + */ + NGHTTP2_PRIORITY_UPDATE = 0x10 +} nghttp2_frame_type; + +/** + * @enum + * + * The flags for HTTP/2 frames. This enum defines all flags for all + * frames. + */ +typedef enum { + /** + * No flag set. + */ + NGHTTP2_FLAG_NONE = 0, + /** + * The END_STREAM flag. + */ + NGHTTP2_FLAG_END_STREAM = 0x01, + /** + * The END_HEADERS flag. + */ + NGHTTP2_FLAG_END_HEADERS = 0x04, + /** + * The ACK flag. + */ + NGHTTP2_FLAG_ACK = 0x01, + /** + * The PADDED flag. + */ + NGHTTP2_FLAG_PADDED = 0x08, + /** + * The PRIORITY flag. + */ + NGHTTP2_FLAG_PRIORITY = 0x20 +} nghttp2_flag; + +/** + * @enum + * The SETTINGS ID. + */ +typedef enum { + /** + * SETTINGS_HEADER_TABLE_SIZE + */ + NGHTTP2_SETTINGS_HEADER_TABLE_SIZE = 0x01, + /** + * SETTINGS_ENABLE_PUSH + */ + NGHTTP2_SETTINGS_ENABLE_PUSH = 0x02, + /** + * SETTINGS_MAX_CONCURRENT_STREAMS + */ + NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 0x03, + /** + * SETTINGS_INITIAL_WINDOW_SIZE + */ + NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04, + /** + * SETTINGS_MAX_FRAME_SIZE + */ + NGHTTP2_SETTINGS_MAX_FRAME_SIZE = 0x05, + /** + * SETTINGS_MAX_HEADER_LIST_SIZE + */ + NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x06, + /** + * SETTINGS_ENABLE_CONNECT_PROTOCOL + * (`RFC 8441 `_) + */ + NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08, + /** + * SETTINGS_NO_RFC7540_PRIORITIES (:rfc:`9218`) + */ + NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES = 0x09 +} nghttp2_settings_id; +/* Note: If we add SETTINGS, update the capacity of + NGHTTP2_INBOUND_NUM_IV as well */ + +/** + * @macro + * + * .. warning:: + * + * Deprecated. The initial max concurrent streams is 0xffffffffu. + * + * Default maximum number of incoming concurrent streams. Use + * `nghttp2_submit_settings()` with + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS` + * to change the maximum number of incoming concurrent streams. + * + * .. note:: + * + * The maximum number of outgoing concurrent streams is 100 by + * default. + */ +#define NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) + +/** + * @enum + * The status codes for the RST_STREAM and GOAWAY frames. + */ +typedef enum { + /** + * No errors. + */ + NGHTTP2_NO_ERROR = 0x00, + /** + * PROTOCOL_ERROR + */ + NGHTTP2_PROTOCOL_ERROR = 0x01, + /** + * INTERNAL_ERROR + */ + NGHTTP2_INTERNAL_ERROR = 0x02, + /** + * FLOW_CONTROL_ERROR + */ + NGHTTP2_FLOW_CONTROL_ERROR = 0x03, + /** + * SETTINGS_TIMEOUT + */ + NGHTTP2_SETTINGS_TIMEOUT = 0x04, + /** + * STREAM_CLOSED + */ + NGHTTP2_STREAM_CLOSED = 0x05, + /** + * FRAME_SIZE_ERROR + */ + NGHTTP2_FRAME_SIZE_ERROR = 0x06, + /** + * REFUSED_STREAM + */ + NGHTTP2_REFUSED_STREAM = 0x07, + /** + * CANCEL + */ + NGHTTP2_CANCEL = 0x08, + /** + * COMPRESSION_ERROR + */ + NGHTTP2_COMPRESSION_ERROR = 0x09, + /** + * CONNECT_ERROR + */ + NGHTTP2_CONNECT_ERROR = 0x0a, + /** + * ENHANCE_YOUR_CALM + */ + NGHTTP2_ENHANCE_YOUR_CALM = 0x0b, + /** + * INADEQUATE_SECURITY + */ + NGHTTP2_INADEQUATE_SECURITY = 0x0c, + /** + * HTTP_1_1_REQUIRED + */ + NGHTTP2_HTTP_1_1_REQUIRED = 0x0d +} nghttp2_error_code; + +/** + * @struct + * The frame header. + */ +typedef struct { + /** + * The length field of this frame, excluding frame header. + */ + size_t length; + /** + * The stream identifier (aka, stream ID) + */ + int32_t stream_id; + /** + * The type of this frame. See `nghttp2_frame_type`. + */ + uint8_t type; + /** + * The flags. + */ + uint8_t flags; + /** + * Reserved bit in frame header. Currently, this is always set to 0 + * and application should not expect something useful in here. + */ + uint8_t reserved; +} nghttp2_frame_hd; + +/** + * @union + * + * This union represents the some kind of data source passed to + * :type:`nghttp2_data_source_read_callback`. + */ +typedef union { + /** + * The integer field, suitable for a file descriptor. + */ + int fd; + /** + * The pointer to an arbitrary object. + */ + void *ptr; +} nghttp2_data_source; + +/** + * @enum + * + * The flags used to set in |data_flags| output parameter in + * :type:`nghttp2_data_source_read_callback`. + */ +typedef enum { + /** + * No flag set. + */ + NGHTTP2_DATA_FLAG_NONE = 0, + /** + * Indicates EOF was sensed. + */ + NGHTTP2_DATA_FLAG_EOF = 0x01, + /** + * Indicates that END_STREAM flag must not be set even if + * NGHTTP2_DATA_FLAG_EOF is set. Usually this flag is used to send + * trailer fields with `nghttp2_submit_request()` or + * `nghttp2_submit_response()`. + */ + NGHTTP2_DATA_FLAG_NO_END_STREAM = 0x02, + /** + * Indicates that application will send complete DATA frame in + * :type:`nghttp2_send_data_callback`. + */ + NGHTTP2_DATA_FLAG_NO_COPY = 0x04 +} nghttp2_data_flag; + +/** + * @functypedef + * + * Callback function invoked when the library wants to read data from + * the |source|. The read data is sent in the stream |stream_id|. + * The implementation of this function must read at most |length| + * bytes of data from |source| (or possibly other places) and store + * them in |buf| and return number of data stored in |buf|. If EOF is + * reached, set :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag + * in |*data_flags|. + * + * Sometime it is desirable to avoid copying data into |buf| and let + * application to send data directly. To achieve this, set + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` to + * |*data_flags| (and possibly other flags, just like when we do + * copy), and return the number of bytes to send without copying data + * into |buf|. The library, seeing + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY`, will invoke + * :type:`nghttp2_send_data_callback`. The application must send + * complete DATA frame in that callback. + * + * If this callback is set by `nghttp2_submit_request()`, + * `nghttp2_submit_response()` or `nghttp2_submit_headers()` and + * `nghttp2_submit_data()` with flag parameter + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` set, and + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag is set to + * |*data_flags|, DATA frame will have END_STREAM flag set. Usually, + * this is expected behaviour and all are fine. One exception is send + * trailer fields. You cannot send trailer fields after sending frame + * with END_STREAM set. To avoid this problem, one can set + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM` along + * with :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` to signal the + * library not to set END_STREAM in DATA frame. Then application can + * use `nghttp2_submit_trailer()` to send trailer fields. + * `nghttp2_submit_trailer()` can be called inside this callback. + * + * If the application wants to postpone DATA frames (e.g., + * asynchronous I/O, or reading data blocks for long time), it is + * achieved by returning :enum:`nghttp2_error.NGHTTP2_ERR_DEFERRED` + * without reading any data in this invocation. The library removes + * DATA frame from the outgoing queue temporarily. To move back + * deferred DATA frame to outgoing queue, call + * `nghttp2_session_resume_data()`. + * + * By default, |length| is limited to 16KiB at maximum. If peer + * allows larger frames, application can enlarge transmission buffer + * size. See :type:`nghttp2_data_source_read_length_callback` for + * more details. + * + * If the application just wants to return from + * `nghttp2_session_send()` or `nghttp2_session_mem_send()` without + * sending anything, return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. + * + * In case of error, there are 2 choices. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will + * close the stream by issuing RST_STREAM with + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. If a different + * error code is desirable, use `nghttp2_submit_rst_stream()` with a + * desired error code and then return + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * Returning :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will + * signal the entire session failure. + */ +typedef ssize_t (*nghttp2_data_source_read_callback)( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data); + +/** + * @struct + * + * This struct represents the data source and the way to read a chunk + * of data from it. + */ +typedef struct { + /** + * The data source. + */ + nghttp2_data_source source; + /** + * The callback function to read a chunk of data from the |source|. + */ + nghttp2_data_source_read_callback read_callback; +} nghttp2_data_provider; + +/** + * @struct + * + * The DATA frame. The received data is delivered via + * :type:`nghttp2_on_data_chunk_recv_callback`. + */ +typedef struct { + nghttp2_frame_hd hd; + /** + * The length of the padding in this frame. This includes PAD_HIGH + * and PAD_LOW. + */ + size_t padlen; +} nghttp2_data; + +/** + * @enum + * + * The category of HEADERS, which indicates the role of the frame. In + * HTTP/2 spec, request, response, push response and other arbitrary + * headers (e.g., trailer fields) are all called just HEADERS. To + * give the application the role of incoming HEADERS frame, we define + * several categories. + */ +typedef enum { + /** + * The HEADERS frame is opening new stream, which is analogous to + * SYN_STREAM in SPDY. + */ + NGHTTP2_HCAT_REQUEST = 0, + /** + * The HEADERS frame is the first response headers, which is + * analogous to SYN_REPLY in SPDY. + */ + NGHTTP2_HCAT_RESPONSE = 1, + /** + * The HEADERS frame is the first headers sent against reserved + * stream. + */ + NGHTTP2_HCAT_PUSH_RESPONSE = 2, + /** + * The HEADERS frame which does not apply for the above categories, + * which is analogous to HEADERS in SPDY. If non-final response + * (e.g., status 1xx) is used, final response HEADERS frame will be + * categorized here. + */ + NGHTTP2_HCAT_HEADERS = 3 +} nghttp2_headers_category; + +/** + * @struct + * + * The structure to specify stream dependency. + */ +typedef struct { + /** + * The stream ID of the stream to depend on. Specifying 0 makes + * stream not depend any other stream. + */ + int32_t stream_id; + /** + * The weight of this dependency. + */ + int32_t weight; + /** + * nonzero means exclusive dependency + */ + uint8_t exclusive; +} nghttp2_priority_spec; + +/** + * @struct + * + * The HEADERS frame. It has the following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The length of the padding in this frame. This includes PAD_HIGH + * and PAD_LOW. + */ + size_t padlen; + /** + * The priority specification + */ + nghttp2_priority_spec pri_spec; + /** + * The name/value pairs. + */ + nghttp2_nv *nva; + /** + * The number of name/value pairs in |nva|. + */ + size_t nvlen; + /** + * The category of this HEADERS frame. + */ + nghttp2_headers_category cat; +} nghttp2_headers; + +/** + * @struct + * + * The PRIORITY frame. It has the following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The priority specification. + */ + nghttp2_priority_spec pri_spec; +} nghttp2_priority; + +/** + * @struct + * + * The RST_STREAM frame. It has the following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The error code. See :type:`nghttp2_error_code`. + */ + uint32_t error_code; +} nghttp2_rst_stream; + +/** + * @struct + * + * The SETTINGS ID/Value pair. It has the following members: + */ +typedef struct { + /** + * The SETTINGS ID. See :type:`nghttp2_settings_id`. + */ + int32_t settings_id; + /** + * The value of this entry. + */ + uint32_t value; +} nghttp2_settings_entry; + +/** + * @struct + * + * The SETTINGS frame. It has the following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The number of SETTINGS ID/Value pairs in |iv|. + */ + size_t niv; + /** + * The pointer to the array of SETTINGS ID/Value pair. + */ + nghttp2_settings_entry *iv; +} nghttp2_settings; + +/** + * @struct + * + * The PUSH_PROMISE frame. It has the following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The length of the padding in this frame. This includes PAD_HIGH + * and PAD_LOW. + */ + size_t padlen; + /** + * The name/value pairs. + */ + nghttp2_nv *nva; + /** + * The number of name/value pairs in |nva|. + */ + size_t nvlen; + /** + * The promised stream ID + */ + int32_t promised_stream_id; + /** + * Reserved bit. Currently this is always set to 0 and application + * should not expect something useful in here. + */ + uint8_t reserved; +} nghttp2_push_promise; + +/** + * @struct + * + * The PING frame. It has the following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The opaque data + */ + uint8_t opaque_data[8]; +} nghttp2_ping; + +/** + * @struct + * + * The GOAWAY frame. It has the following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The last stream stream ID. + */ + int32_t last_stream_id; + /** + * The error code. See :type:`nghttp2_error_code`. + */ + uint32_t error_code; + /** + * The additional debug data + */ + uint8_t *opaque_data; + /** + * The length of |opaque_data| member. + */ + size_t opaque_data_len; + /** + * Reserved bit. Currently this is always set to 0 and application + * should not expect something useful in here. + */ + uint8_t reserved; +} nghttp2_goaway; + +/** + * @struct + * + * The WINDOW_UPDATE frame. It has the following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The window size increment. + */ + int32_t window_size_increment; + /** + * Reserved bit. Currently this is always set to 0 and application + * should not expect something useful in here. + */ + uint8_t reserved; +} nghttp2_window_update; + +/** + * @struct + * + * The extension frame. It has following members: + */ +typedef struct { + /** + * The frame header. + */ + nghttp2_frame_hd hd; + /** + * The pointer to extension payload. The exact pointer type is + * determined by hd.type. + * + * Currently, no extension is supported. This is a place holder for + * the future extensions. + */ + void *payload; +} nghttp2_extension; + +/** + * @union + * + * This union includes all frames to pass them to various function + * calls as nghttp2_frame type. The CONTINUATION frame is omitted + * from here because the library deals with it internally. + */ +typedef union { + /** + * The frame header, which is convenient to inspect frame header. + */ + nghttp2_frame_hd hd; + /** + * The DATA frame. + */ + nghttp2_data data; + /** + * The HEADERS frame. + */ + nghttp2_headers headers; + /** + * The PRIORITY frame. + */ + nghttp2_priority priority; + /** + * The RST_STREAM frame. + */ + nghttp2_rst_stream rst_stream; + /** + * The SETTINGS frame. + */ + nghttp2_settings settings; + /** + * The PUSH_PROMISE frame. + */ + nghttp2_push_promise push_promise; + /** + * The PING frame. + */ + nghttp2_ping ping; + /** + * The GOAWAY frame. + */ + nghttp2_goaway goaway; + /** + * The WINDOW_UPDATE frame. + */ + nghttp2_window_update window_update; + /** + * The extension frame. + */ + nghttp2_extension ext; +} nghttp2_frame; + +/** + * @functypedef + * + * Callback function invoked when |session| wants to send data to the + * remote peer. The implementation of this function must send at most + * |length| bytes of data stored in |data|. The |flags| is currently + * not used and always 0. It must return the number of bytes sent if + * it succeeds. If it cannot send any single byte without blocking, + * it must return :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. For + * other errors, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The + * |user_data| pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * This callback is required if the application uses + * `nghttp2_session_send()` to send data to the remote endpoint. If + * the application uses solely `nghttp2_session_mem_send()` instead, + * this callback function is unnecessary. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_send_callback()`. + * + * .. note:: + * + * The |length| may be very small. If that is the case, and + * application disables Nagle algorithm (``TCP_NODELAY``), then just + * writing |data| to the network stack leads to very small packet, + * and it is very inefficient. An application should be responsible + * to buffer up small chunks of data as necessary to avoid this + * situation. + */ +typedef ssize_t (*nghttp2_send_callback)(nghttp2_session *session, + const uint8_t *data, size_t length, + int flags, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in + * :type:`nghttp2_data_source_read_callback` to send complete DATA + * frame. + * + * The |frame| is a DATA frame to send. The |framehd| is the + * serialized frame header (9 bytes). The |length| is the length of + * application data to send (this does not include padding). The + * |source| is the same pointer passed to + * :type:`nghttp2_data_source_read_callback`. + * + * The application first must send frame header |framehd| of length 9 + * bytes. If ``frame->data.padlen > 0``, send 1 byte of value + * ``frame->data.padlen - 1``. Then send exactly |length| bytes of + * application data. Finally, if ``frame->data.padlen > 1``, send + * ``frame->data.padlen - 1`` bytes of zero as padding. + * + * The application has to send complete DATA frame in this callback. + * If all data were written successfully, return 0. + * + * If it cannot send any data at all, just return + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`; the library will call + * this callback with the same parameters later (It is recommended to + * send complete DATA frame at once in this function to deal with + * error; if partial frame data has already sent, it is impossible to + * send another data in that state, and all we can do is tear down + * connection). When data is fully processed, but application wants + * to make `nghttp2_session_mem_send()` or `nghttp2_session_send()` + * return immediately without processing next frames, return + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. If application decided to + * reset this stream, return + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`, then + * the library will send RST_STREAM with INTERNAL_ERROR as error code. + * The application can also return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, which will + * result in connection closure. Returning any other value is treated + * as :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. + */ +typedef int (*nghttp2_send_data_callback)(nghttp2_session *session, + nghttp2_frame *frame, + const uint8_t *framehd, size_t length, + nghttp2_data_source *source, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when |session| wants to receive data from + * the remote peer. The implementation of this function must read at + * most |length| bytes of data and store it in |buf|. The |flags| is + * currently not used and always 0. It must return the number of + * bytes written in |buf| if it succeeds. If it cannot read any + * single byte without blocking, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. If it gets EOF + * before it reads any single byte, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_EOF`. For other errors, it must + * return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * Returning 0 is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. The |user_data| + * pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * This callback is required if the application uses + * `nghttp2_session_recv()` to receive data from the remote endpoint. + * If the application uses solely `nghttp2_session_mem_recv()` + * instead, this callback function is unnecessary. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_recv_callback()`. + */ +typedef ssize_t (*nghttp2_recv_callback)(nghttp2_session *session, uint8_t *buf, + size_t length, int flags, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv()` when a frame is received. The + * |user_data| pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen`` + * member of their data structure are always ``NULL`` and 0 + * respectively. The header name/value pairs are emitted via + * :type:`nghttp2_on_header_callback`. + * + * Only HEADERS and DATA frame can signal the end of incoming data. + * If ``frame->hd.flags & NGHTTP2_FLAG_END_STREAM`` is nonzero, the + * |frame| is the last frame from the remote peer in this stream. + * + * This callback won't be called for CONTINUATION frames. + * HEADERS/PUSH_PROMISE + CONTINUATIONs are treated as single frame. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero value is returned, it is treated as fatal error and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_frame_recv_callback()`. + */ +typedef int (*nghttp2_on_frame_recv_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv()` when an invalid non-DATA frame is + * received. The error is indicated by the |lib_error_code|, which is + * one of the values defined in :type:`nghttp2_error`. When this + * callback function is invoked, the library automatically submits + * either RST_STREAM or GOAWAY frame. The |user_data| pointer is the + * third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen`` + * member of their data structure are always ``NULL`` and 0 + * respectively. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_invalid_frame_recv_callback()`. + */ +typedef int (*nghttp2_on_invalid_frame_recv_callback)( + nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a chunk of data in DATA frame is + * received. The |stream_id| is the stream ID this DATA frame belongs + * to. The |flags| is the flags of DATA frame which this data chunk + * is contained. ``(flags & NGHTTP2_FLAG_END_STREAM) != 0`` does not + * necessarily mean this chunk of data is the last one in the stream. + * You should use :type:`nghttp2_on_frame_recv_callback` to know all + * data frames are received. The |user_data| pointer is the third + * argument passed in to the call to `nghttp2_session_client_new()` or + * `nghttp2_session_server_new()`. + * + * If the application uses `nghttp2_session_mem_recv()`, it can return + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` to make + * `nghttp2_session_mem_recv()` return without processing further + * input bytes. The memory by pointed by the |data| is retained until + * `nghttp2_session_mem_recv()` or `nghttp2_session_recv()` is called. + * The application must retain the input bytes which was used to + * produce the |data| parameter, because it may refer to the memory + * region included in the input bytes. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error, and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_data_chunk_recv_callback()`. + */ +typedef int (*nghttp2_on_data_chunk_recv_callback)(nghttp2_session *session, + uint8_t flags, + int32_t stream_id, + const uint8_t *data, + size_t len, void *user_data); + +/** + * @functypedef + * + * Callback function invoked just before the non-DATA frame |frame| is + * sent. The |user_data| pointer is the third argument passed in to + * the call to `nghttp2_session_client_new()` or + * `nghttp2_session_server_new()`. + * + * The implementation of this function must return 0 if it succeeds. + * It can also return :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL` to + * cancel the transmission of the given frame. + * + * If there is a fatal error while executing this callback, the + * implementation should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, which makes + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * If the other value is returned, it is treated as if + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. + * But the implementation should not rely on this since the library + * may define new return value to extend its capability. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_before_frame_send_callback()`. + */ +typedef int (*nghttp2_before_frame_send_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked after the frame |frame| is sent. The + * |user_data| pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error and + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_frame_send_callback()`. + */ +typedef int (*nghttp2_on_frame_send_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked after the non-DATA frame |frame| is not + * sent because of the error. The error is indicated by the + * |lib_error_code|, which is one of the values defined in + * :type:`nghttp2_error`. The |user_data| pointer is the third + * argument passed in to the call to `nghttp2_session_client_new()` or + * `nghttp2_session_server_new()`. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error and + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * `nghttp2_session_get_stream_user_data()` can be used to get + * associated data. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_frame_not_send_callback()`. + */ +typedef int (*nghttp2_on_frame_not_send_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when the stream |stream_id| is closed. + * The reason of closure is indicated by the |error_code|. The + * |error_code| is usually one of :enum:`nghttp2_error_code`, but that + * is not guaranteed. The stream_user_data, which was specified in + * `nghttp2_submit_request()` or `nghttp2_submit_headers()`, is still + * available in this function. The |user_data| pointer is the third + * argument passed in to the call to `nghttp2_session_client_new()` or + * `nghttp2_session_server_new()`. + * + * This function is also called for a stream in reserved state. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error and + * `nghttp2_session_recv()`, `nghttp2_session_mem_recv()`, + * `nghttp2_session_send()`, and `nghttp2_session_mem_send()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_stream_close_callback()`. + */ +typedef int (*nghttp2_on_stream_close_callback)(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when the reception of header block in + * HEADERS or PUSH_PROMISE is started. Each header name/value pair + * will be emitted by :type:`nghttp2_on_header_callback`. + * + * The ``frame->hd.flags`` may not have + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_HEADERS` flag set, which + * indicates that one or more CONTINUATION frames are involved. But + * the application does not need to care about that because the header + * name/value pairs are emitted transparently regardless of + * CONTINUATION frames. + * + * The server applications probably create an object to store + * information about new stream if ``frame->hd.type == + * NGHTTP2_HEADERS`` and ``frame->headers.cat == + * NGHTTP2_HCAT_REQUEST``. If |session| is configured as server side, + * ``frame->headers.cat`` is either ``NGHTTP2_HCAT_REQUEST`` + * containing request headers or ``NGHTTP2_HCAT_HEADERS`` containing + * trailer fields and never get PUSH_PROMISE in this callback. + * + * For the client applications, ``frame->hd.type`` is either + * ``NGHTTP2_HEADERS`` or ``NGHTTP2_PUSH_PROMISE``. In case of + * ``NGHTTP2_HEADERS``, ``frame->headers.cat == + * NGHTTP2_HCAT_RESPONSE`` means that it is the first response + * headers, but it may be non-final response which is indicated by 1xx + * status code. In this case, there may be zero or more HEADERS frame + * with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS`` which has + * non-final response code and finally client gets exactly one HEADERS + * frame with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS`` + * containing final response headers (non-1xx status code). The + * trailer fields also has ``frame->headers.cat == + * NGHTTP2_HCAT_HEADERS`` which does not contain any status code. + * + * Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will + * close the stream (promised stream if frame is PUSH_PROMISE) by + * issuing RST_STREAM with + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. In this case, + * :type:`nghttp2_on_header_callback` and + * :type:`nghttp2_on_frame_recv_callback` will not be invoked. If a + * different error code is desirable, use + * `nghttp2_submit_rst_stream()` with a desired error code and then + * return :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * Again, use ``frame->push_promise.promised_stream_id`` as stream_id + * parameter in `nghttp2_submit_rst_stream()` if frame is + * PUSH_PROMISE. + * + * The implementation of this function must return 0 if it succeeds. + * It can return + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` to + * reset the stream (promised stream if frame is PUSH_PROMISE). For + * critical errors, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * value is returned, it is treated as if + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. If + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned, + * `nghttp2_session_mem_recv()` function will immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_begin_headers_callback()`. + */ +typedef int (*nghttp2_on_begin_headers_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a header name/value pair is received + * for the |frame|. The |name| of length |namelen| is header name. + * The |value| of length |valuelen| is header value. The |flags| is + * bitwise OR of one or more of :type:`nghttp2_nv_flag`. + * + * If :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_INDEX` is set in + * |flags|, the receiver must not index this name/value pair when + * forwarding it to the next hop. More specifically, "Literal Header + * Field never Indexed" representation must be used in HPACK encoding. + * + * When this callback is invoked, ``frame->hd.type`` is either + * :enum:`nghttp2_frame_type.NGHTTP2_HEADERS` or + * :enum:`nghttp2_frame_type.NGHTTP2_PUSH_PROMISE`. After all header + * name/value pairs are processed with this callback, and no error has + * been detected, :type:`nghttp2_on_frame_recv_callback` will be + * invoked. If there is an error in decompression, + * :type:`nghttp2_on_frame_recv_callback` for the |frame| will not be + * invoked. + * + * Both |name| and |value| are guaranteed to be NULL-terminated. The + * |namelen| and |valuelen| do not include terminal NULL. If + * `nghttp2_option_set_no_http_messaging()` is used with nonzero + * value, NULL character may be included in |name| or |value| before + * terminating NULL. + * + * Please note that unless `nghttp2_option_set_no_http_messaging()` is + * used, nghttp2 library does perform validation against the |name| + * and the |value| using `nghttp2_check_header_name()` and + * `nghttp2_check_header_value()`. In addition to this, nghttp2 + * performs validation based on HTTP Messaging rule, which is briefly + * explained in :ref:`http-messaging` section. + * + * If the application uses `nghttp2_session_mem_recv()`, it can return + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` to make + * `nghttp2_session_mem_recv()` return without processing further + * input bytes. The memory pointed by |frame|, |name| and |value| + * parameters are retained until `nghttp2_session_mem_recv()` or + * `nghttp2_session_recv()` is called. The application must retain + * the input bytes which was used to produce these parameters, because + * it may refer to the memory region included in the input bytes. + * + * Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will + * close the stream (promised stream if frame is PUSH_PROMISE) by + * issuing RST_STREAM with + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. In this case, + * :type:`nghttp2_on_header_callback` and + * :type:`nghttp2_on_frame_recv_callback` will not be invoked. If a + * different error code is desirable, use + * `nghttp2_submit_rst_stream()` with a desired error code and then + * return :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * Again, use ``frame->push_promise.promised_stream_id`` as stream_id + * parameter in `nghttp2_submit_rst_stream()` if frame is + * PUSH_PROMISE. + * + * The implementation of this function must return 0 if it succeeds. + * It may return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` or + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. For + * other critical failures, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * nonzero value is returned, it is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_header_callback()`. + * + * .. warning:: + * + * Application should properly limit the total buffer size to store + * incoming header fields. Without it, peer may send large number + * of header fields or large header fields to cause out of memory in + * local endpoint. Due to how HPACK works, peer can do this + * effectively without using much memory on their own. + */ +typedef int (*nghttp2_on_header_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a header name/value pair is received + * for the |frame|. The |name| is header name. The |value| is header + * value. The |flags| is bitwise OR of one or more of + * :type:`nghttp2_nv_flag`. + * + * This callback behaves like :type:`nghttp2_on_header_callback`, + * except that |name| and |value| are stored in reference counted + * buffer. If application wishes to keep these references without + * copying them, use `nghttp2_rcbuf_incref()` to increment their + * reference count. It is the application's responsibility to call + * `nghttp2_rcbuf_decref()` if they called `nghttp2_rcbuf_incref()` so + * as not to leak memory. If the |session| is created by + * `nghttp2_session_server_new3()` or `nghttp2_session_client_new3()`, + * the function to free memory is the one belongs to the mem + * parameter. As long as this free function alives, |name| and + * |value| can live after |session| was destroyed. + */ +typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session, + const nghttp2_frame *frame, + nghttp2_rcbuf *name, + nghttp2_rcbuf *value, uint8_t flags, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a invalid header name/value pair is + * received for the |frame|. + * + * The parameter and behaviour are similar to + * :type:`nghttp2_on_header_callback`. The difference is that this + * callback is only invoked when a invalid header name/value pair is + * received which is treated as stream error if this callback is not + * set. Only invalid regular header field are passed to this + * callback. In other words, invalid pseudo header field is not + * passed to this callback. Also header fields which includes upper + * cased latter are also treated as error without passing them to this + * callback. + * + * This callback is only considered if HTTP messaging validation is + * turned on (which is on by default, see + * `nghttp2_option_set_no_http_messaging()`). + * + * With this callback, application inspects the incoming invalid + * field, and it also can reset stream from this callback by returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By + * default, the error code is + * :enum:`nghttp2_error_code.NGHTTP2_PROTOCOL_ERROR`. To change the + * error code, call `nghttp2_submit_rst_stream()` with the error code + * of choice in addition to returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * + * If 0 is returned, the header field is ignored, and the stream is + * not reset. + */ +typedef int (*nghttp2_on_invalid_header_callback)( + nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a invalid header name/value pair is + * received for the |frame|. + * + * The parameter and behaviour are similar to + * :type:`nghttp2_on_header_callback2`. The difference is that this + * callback is only invoked when a invalid header name/value pair is + * received which is silently ignored if this callback is not set. + * Only invalid regular header field are passed to this callback. In + * other words, invalid pseudo header field is not passed to this + * callback. Also header fields which includes upper cased latter are + * also treated as error without passing them to this callback. + * + * This callback is only considered if HTTP messaging validation is + * turned on (which is on by default, see + * `nghttp2_option_set_no_http_messaging()`). + * + * With this callback, application inspects the incoming invalid + * field, and it also can reset stream from this callback by returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By + * default, the error code is + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. To change the + * error code, call `nghttp2_submit_rst_stream()` with the error code + * of choice in addition to returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_on_invalid_header_callback2)( + nghttp2_session *session, const nghttp2_frame *frame, nghttp2_rcbuf *name, + nghttp2_rcbuf *value, uint8_t flags, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when the library asks application how + * many padding bytes are required for the transmission of the + * |frame|. The application must choose the total length of payload + * including padded bytes in range [frame->hd.length, max_payloadlen], + * inclusive. Choosing number not in this range will be treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Returning + * ``frame->hd.length`` means no padding is added. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will make + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_select_padding_callback()`. + */ +typedef ssize_t (*nghttp2_select_padding_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + size_t max_payloadlen, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library wants to get max length of + * data to send data to the remote peer. The implementation of this + * function should return a value in the following range. [1, + * min(|session_remote_window_size|, |stream_remote_window_size|, + * |remote_max_frame_size|)]. If a value greater than this range is + * returned than the max allow value will be used. Returning a value + * smaller than this range is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The + * |frame_type| is provided for future extensibility and identifies + * the type of frame (see :type:`nghttp2_frame_type`) for which to get + * the length for. Currently supported frame types are: + * :enum:`nghttp2_frame_type.NGHTTP2_DATA`. + * + * This callback can be used to control the length in bytes for which + * :type:`nghttp2_data_source_read_callback` is allowed to send to the + * remote endpoint. This callback is optional. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will signal the + * entire session failure. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_data_source_read_length_callback()`. + */ +typedef ssize_t (*nghttp2_data_source_read_length_callback)( + nghttp2_session *session, uint8_t frame_type, int32_t stream_id, + int32_t session_remote_window_size, int32_t stream_remote_window_size, + uint32_t remote_max_frame_size, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a frame header is received. The + * |hd| points to received frame header. + * + * Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will + * also be called when frame header of CONTINUATION frame is received. + * + * If both :type:`nghttp2_on_begin_frame_callback` and + * :type:`nghttp2_on_begin_headers_callback` are set and HEADERS or + * PUSH_PROMISE is received, :type:`nghttp2_on_begin_frame_callback` + * will be called first. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero value is returned, it is treated as fatal error and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_begin_frame_callback()`. + */ +typedef int (*nghttp2_on_begin_frame_callback)(nghttp2_session *session, + const nghttp2_frame_hd *hd, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when chunk of extension frame payload is + * received. The |hd| points to frame header. The received + * chunk is |data| of length |len|. + * + * The implementation of this function must return 0 if it succeeds. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_on_extension_chunk_recv_callback)( + nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data, + size_t len, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library asks the application to + * unpack extension payload from its wire format. The extension + * payload has been passed to the application using + * :type:`nghttp2_on_extension_chunk_recv_callback`. The frame header + * is already unpacked by the library and provided as |hd|. + * + * To receive extension frames, the application must tell desired + * extension frame type to the library using + * `nghttp2_option_set_user_recv_extension_type()`. + * + * The implementation of this function may store the pointer to the + * created object as a result of unpacking in |*payload|, and returns + * 0. The pointer stored in |*payload| is opaque to the library, and + * the library does not own its pointer. |*payload| is initialized as + * ``NULL``. The |*payload| is available as ``frame->ext.payload`` in + * :type:`nghttp2_on_frame_recv_callback`. Therefore if application + * can free that memory inside :type:`nghttp2_on_frame_recv_callback` + * callback. Of course, application has a liberty not ot use + * |*payload|, and do its own mechanism to process extension frames. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session, + void **payload, + const nghttp2_frame_hd *hd, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library asks the application to pack + * extension payload in its wire format. The frame header will be + * packed by library. Application must pack payload only. + * ``frame->ext.payload`` is the object passed to + * `nghttp2_submit_extension()` as payload parameter. Application + * must pack extension payload to the |buf| of its capacity |len| + * bytes. The |len| is at least 16KiB. + * + * The implementation of this function should return the number of + * bytes written into |buf| when it succeeds. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`, and + * :type:`nghttp2_on_frame_not_send_callback` will be invoked. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the return + * value is strictly larger than |len|, it is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef ssize_t (*nghttp2_pack_extension_callback)(nghttp2_session *session, + uint8_t *buf, size_t len, + const nghttp2_frame *frame, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library provides the error message + * intended for human consumption. This callback is solely for + * debugging purpose. The |msg| is typically NULL-terminated string + * of length |len|. |len| does not include the sentinel NULL + * character. + * + * This function is deprecated. The new application should use + * :type:`nghttp2_error_callback2`. + * + * The format of error message may change between nghttp2 library + * versions. The application should not depend on the particular + * format. + * + * Normally, application should return 0 from this callback. If fatal + * error occurred while doing something in this callback, application + * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * In this case, library will return immediately with return value + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if + * nonzero value is returned from this callback, they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application + * should not rely on this details. + */ +typedef int (*nghttp2_error_callback)(nghttp2_session *session, const char *msg, + size_t len, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library provides the error code, and + * message. This callback is solely for debugging purpose. + * |lib_error_code| is one of error code defined in + * :enum:`nghttp2_error`. The |msg| is typically NULL-terminated + * string of length |len|, and intended for human consumption. |len| + * does not include the sentinel NULL character. + * + * The format of error message may change between nghttp2 library + * versions. The application should not depend on the particular + * format. + * + * Normally, application should return 0 from this callback. If fatal + * error occurred while doing something in this callback, application + * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * In this case, library will return immediately with return value + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if + * nonzero value is returned from this callback, they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application + * should not rely on this details. + */ +typedef int (*nghttp2_error_callback2)(nghttp2_session *session, + int lib_error_code, const char *msg, + size_t len, void *user_data); + +struct nghttp2_session_callbacks; + +/** + * @struct + * + * Callback functions for :type:`nghttp2_session`. The details of + * this structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_session_callbacks nghttp2_session_callbacks; + +/** + * @function + * + * Initializes |*callbacks_ptr| with NULL values. + * + * The initialized object can be used when initializing multiple + * :type:`nghttp2_session` objects. + * + * When the application finished using this object, it can use + * `nghttp2_session_callbacks_del()` to free its memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_callbacks_new(nghttp2_session_callbacks **callbacks_ptr); + +/** + * @function + * + * Frees any resources allocated for |callbacks|. If |callbacks| is + * ``NULL``, this function does nothing. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks); + +/** + * @function + * + * Sets callback function invoked when a session wants to send data to + * the remote peer. This callback is not necessary if the application + * uses solely `nghttp2_session_mem_send()` to serialize data to + * transmit. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_callback( + nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback); + +/** + * @function + * + * Sets callback function invoked when the a session wants to receive + * data from the remote peer. This callback is not necessary if the + * application uses solely `nghttp2_session_mem_recv()` to process + * received data. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_recv_callback( + nghttp2_session_callbacks *cbs, nghttp2_recv_callback recv_callback); + +/** + * @function + * + * Sets callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv()` when a frame is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_recv_callback on_frame_recv_callback); + +/** + * @function + * + * Sets callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv()` when an invalid non-DATA frame is + * received. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback); + +/** + * @function + * + * Sets callback function invoked when a chunk of data in DATA frame + * is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback); + +/** + * @function + * + * Sets callback function invoked before a non-DATA frame is sent. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_before_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_before_frame_send_callback before_frame_send_callback); + +/** + * @function + * + * Sets callback function invoked after a frame is sent. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_send_callback on_frame_send_callback); + +/** + * @function + * + * Sets callback function invoked when a non-DATA frame is not sent + * because of an error. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_not_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_not_send_callback on_frame_not_send_callback); + +/** + * @function + * + * Sets callback function invoked when the stream is closed. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_stream_close_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_stream_close_callback on_stream_close_callback); + +/** + * @function + * + * Sets callback function invoked when the reception of header block + * in HEADERS or PUSH_PROMISE is started. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_headers_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_headers_callback on_begin_headers_callback); + +/** + * @function + * + * Sets callback function invoked when a header name/value pair is + * received. If both + * `nghttp2_session_callbacks_set_on_header_callback()` and + * `nghttp2_session_callbacks_set_on_header_callback2()` are used to + * set callbacks, the latter has the precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback on_header_callback); + +/** + * @function + * + * Sets callback function invoked when a header name/value pair is + * received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback2 on_header_callback2); + +/** + * @function + * + * Sets callback function invoked when a invalid header name/value + * pair is received. If both + * `nghttp2_session_callbacks_set_on_invalid_header_callback()` and + * `nghttp2_session_callbacks_set_on_invalid_header_callback2()` are + * used to set callbacks, the latter takes the precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback on_invalid_header_callback); + +/** + * @function + * + * Sets callback function invoked when a invalid header name/value + * pair is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback2 on_invalid_header_callback2); + +/** + * @function + * + * Sets callback function invoked when the library asks application + * how many padding bytes are required for the transmission of the + * given frame. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_select_padding_callback( + nghttp2_session_callbacks *cbs, + nghttp2_select_padding_callback select_padding_callback); + +/** + * @function + * + * Sets callback function determine the length allowed in + * :type:`nghttp2_data_source_read_callback`. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_data_source_read_length_callback( + nghttp2_session_callbacks *cbs, + nghttp2_data_source_read_length_callback data_source_read_length_callback); + +/** + * @function + * + * Sets callback function invoked when a frame header is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_frame_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_frame_callback on_begin_frame_callback); + +/** + * @function + * + * Sets callback function invoked when + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in + * :type:`nghttp2_data_source_read_callback` to avoid data copy. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_data_callback( + nghttp2_session_callbacks *cbs, + nghttp2_send_data_callback send_data_callback); + +/** + * @function + * + * Sets callback function invoked when the library asks the + * application to pack extension frame payload in wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_pack_extension_callback pack_extension_callback); + +/** + * @function + * + * Sets callback function invoked when the library asks the + * application to unpack extension frame payload from wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_unpack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_unpack_extension_callback unpack_extension_callback); + +/** + * @function + * + * Sets callback function invoked when chunk of extension frame + * payload is received. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback); + +/** + * @function + * + * Sets callback function invoked when library tells error message to + * the application. + * + * This function is deprecated. The new application should use + * `nghttp2_session_callbacks_set_error_callback2()`. + * + * If both :type:`nghttp2_error_callback` and + * :type:`nghttp2_error_callback2` are set, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback( + nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback); + +/** + * @function + * + * Sets callback function invoked when library tells error code, and + * message to the application. + * + * If both :type:`nghttp2_error_callback` and + * :type:`nghttp2_error_callback2` are set, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback2( + nghttp2_session_callbacks *cbs, nghttp2_error_callback2 error_callback2); + +/** + * @functypedef + * + * Custom memory allocator to replace malloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_malloc)(size_t size, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace free(). The |mem_user_data| is + * the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void (*nghttp2_free)(void *ptr, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace calloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_calloc)(size_t nmemb, size_t size, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace realloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_realloc)(void *ptr, size_t size, void *mem_user_data); + +/** + * @struct + * + * Custom memory allocator functions and user defined pointer. The + * |mem_user_data| member is passed to each allocator function. This + * can be used, for example, to achieve per-session memory pool. + * + * In the following example code, ``my_malloc``, ``my_free``, + * ``my_calloc`` and ``my_realloc`` are the replacement of the + * standard allocators ``malloc``, ``free``, ``calloc`` and + * ``realloc`` respectively:: + * + * void *my_malloc_cb(size_t size, void *mem_user_data) { + * return my_malloc(size); + * } + * + * void my_free_cb(void *ptr, void *mem_user_data) { my_free(ptr); } + * + * void *my_calloc_cb(size_t nmemb, size_t size, void *mem_user_data) { + * return my_calloc(nmemb, size); + * } + * + * void *my_realloc_cb(void *ptr, size_t size, void *mem_user_data) { + * return my_realloc(ptr, size); + * } + * + * void session_new() { + * nghttp2_session *session; + * nghttp2_session_callbacks *callbacks; + * nghttp2_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb, + * my_realloc_cb}; + * + * ... + * + * nghttp2_session_client_new3(&session, callbacks, NULL, NULL, &mem); + * + * ... + * } + */ +typedef struct { + /** + * An arbitrary user supplied data. This is passed to each + * allocator function. + */ + void *mem_user_data; + /** + * Custom allocator function to replace malloc(). + */ + nghttp2_malloc malloc; + /** + * Custom allocator function to replace free(). + */ + nghttp2_free free; + /** + * Custom allocator function to replace calloc(). + */ + nghttp2_calloc calloc; + /** + * Custom allocator function to replace realloc(). + */ + nghttp2_realloc realloc; +} nghttp2_mem; + +struct nghttp2_option; + +/** + * @struct + * + * Configuration options for :type:`nghttp2_session`. The details of + * this structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_option nghttp2_option; + +/** + * @function + * + * Initializes |*option_ptr| with default values. + * + * When the application finished using this object, it can use + * `nghttp2_option_del()` to free its memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_option_new(nghttp2_option **option_ptr); + +/** + * @function + * + * Frees any resources allocated for |option|. If |option| is + * ``NULL``, this function does nothing. + */ +NGHTTP2_EXTERN void nghttp2_option_del(nghttp2_option *option); + +/** + * @function + * + * This option prevents the library from sending WINDOW_UPDATE for a + * connection automatically. If this option is set to nonzero, the + * library won't send WINDOW_UPDATE for DATA until application calls + * `nghttp2_session_consume()` to indicate the consumed amount of + * data. Don't use `nghttp2_submit_window_update()` for this purpose. + * By default, this option is set to zero. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val); + +/** + * @function + * + * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of + * remote endpoint as if it is received in SETTINGS frame. Without + * specifying this option, the maximum number of outgoing concurrent + * streams is initially limited to 100 to avoid issues when the local + * endpoint submits lots of requests before receiving initial SETTINGS + * frame from the remote endpoint, since sending them at once to the + * remote endpoint could lead to rejection of some of the requests. + * This value will be overwritten when the local endpoint receives + * initial SETTINGS frame from the remote endpoint, either to the + * value advertised in SETTINGS_MAX_CONCURRENT_STREAMS or to the + * default value (unlimited) if none was advertised. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, + uint32_t val); + +/** + * @function + * + * By default, nghttp2 library, if configured as server, requires + * first 24 bytes of client magic byte string (MAGIC). In most cases, + * this will simplify the implementation of server. But sometimes + * server may want to detect the application protocol based on first + * few bytes on clear text communication. + * + * If this option is used with nonzero |val|, nghttp2 library does not + * handle MAGIC. It still checks following SETTINGS frame. This + * means that applications should deal with MAGIC by themselves. + * + * If this option is not used or used with zero value, if MAGIC does + * not match :macro:`NGHTTP2_CLIENT_MAGIC`, `nghttp2_session_recv()` + * and `nghttp2_session_mem_recv()` will return error + * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC`, which is fatal + * error. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_no_recv_client_magic(nghttp2_option *option, int val); + +/** + * @function + * + * By default, nghttp2 library enforces subset of HTTP Messaging rules + * described in `HTTP/2 specification, section 8 + * `_. See + * :ref:`http-messaging` section for details. For those applications + * who use nghttp2 library as non-HTTP use, give nonzero to |val| to + * disable this enforcement. Please note that disabling this feature + * does not change the fundamental client and server model of HTTP. + * That is, even if the validation is disabled, only client can send + * requests. + */ +NGHTTP2_EXTERN void nghttp2_option_set_no_http_messaging(nghttp2_option *option, + int val); + +/** + * @function + * + * RFC 7540 does not enforce any limit on the number of incoming + * reserved streams (in RFC 7540 terms, streams in reserved (remote) + * state). This only affects client side, since only server can push + * streams. Malicious server can push arbitrary number of streams, + * and make client's memory exhausted. This option can set the + * maximum number of such incoming streams to avoid possible memory + * exhaustion. If this option is set, and pushed streams are + * automatically closed on reception, without calling user provided + * callback, if they exceed the given limit. The default value is + * 200. If session is configured as server side, this option has no + * effect. Server can control the number of streams to push. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, + uint32_t val); + +/** + * @function + * + * Sets extension frame type the application is willing to handle with + * user defined callbacks (see + * :type:`nghttp2_on_extension_chunk_recv_callback` and + * :type:`nghttp2_unpack_extension_callback`). The |type| is + * extension frame type, and must be strictly greater than 0x9. + * Otherwise, this function does nothing. The application can call + * this function multiple times to set more than one frame type to + * receive. The application does not have to call this function if it + * just sends extension frames. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, + uint8_t type); + +/** + * @function + * + * Sets extension frame type the application is willing to receive + * using builtin handler. The |type| is the extension frame type to + * receive, and must be strictly greater than 0x9. Otherwise, this + * function does nothing. The application can call this function + * multiple times to set more than one frame type to receive. The + * application does not have to call this function if it just sends + * extension frames. + * + * If same frame type is passed to both + * `nghttp2_option_set_builtin_recv_extension_type()` and + * `nghttp2_option_set_user_recv_extension_type()`, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, + uint8_t type); + +/** + * @function + * + * This option prevents the library from sending PING frame with ACK + * flag set automatically when PING frame without ACK flag set is + * received. If this option is set to nonzero, the library won't send + * PING frame with ACK flag set in the response for incoming PING + * frame. The application can send PING frame with ACK flag set using + * `nghttp2_submit_ping()` with :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` + * as flags parameter. + */ +NGHTTP2_EXTERN void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, + int val); + +/** + * @function + * + * This option sets the maximum length of header block (a set of + * header fields per one HEADERS frame) to send. The length of a + * given set of header fields is calculated using + * `nghttp2_hd_deflate_bound()`. The default value is 64KiB. If + * application attempts to send header fields larger than this limit, + * the transmission of the frame fails with error code + * :enum:`nghttp2_error.NGHTTP2_ERR_FRAME_SIZE_ERROR`. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, + size_t val); + +/** + * @function + * + * This option sets the maximum dynamic table size for deflating + * header fields. The default value is 4KiB. In HTTP/2, receiver of + * deflated header block can specify maximum dynamic table size. The + * actual maximum size is the minimum of the size receiver specified + * and this option value. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option, + size_t val); + +/** + * @function + * + * This option prevents the library from retaining closed streams to + * maintain the priority tree. If this option is set to nonzero, + * applications can discard closed stream completely to save memory. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is submitted via `nghttp2_submit_settings()`, any + * closed streams are not retained regardless of this option. + */ +NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option, + int val); + +/** + * @function + * + * This function sets the maximum number of outgoing SETTINGS ACK and + * PING ACK frames retained in :type:`nghttp2_session` object. If + * more than those frames are retained, the peer is considered to be + * misbehaving and session will be closed. The default value is 1000. + */ +NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, + size_t val); + +/** + * @function + * + * This function sets the maximum number of SETTINGS entries per + * SETTINGS frame that will be accepted. If more than those entries + * are received, the peer is considered to be misbehaving and session + * will be closed. The default value is 32. + */ +NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option, + size_t val); + +/** + * @function + * + * This option, if set to nonzero, allows server to fallback to + * :rfc:`7540` priorities if SETTINGS_NO_RFC7540_PRIORITIES was not + * received from client, and server submitted + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * = 1 via `nghttp2_submit_settings()`. Most of the advanced + * functionality for RFC 7540 priorities are still disabled. This + * fallback only enables the minimal feature set of RFC 7540 + * priorities to deal with priority signaling from client. + * + * Client session ignores this option. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_server_fallback_rfc7540_priorities(nghttp2_option *option, + int val); + +/** + * @function + * + * This option, if set to nonzero, turns off RFC 9113 leading and + * trailing white spaces validation against HTTP field value. Some + * important fields, such as HTTP/2 pseudo header fields, are + * validated more strictly and this option does not apply to them. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation( + nghttp2_option *option, int val); + +/** + * @function + * + * This function sets the rate limit for the incoming stream reset + * (RST_STREAM frame). It is server use only. It is a token-bucket + * based rate limiter. |burst| specifies the number of tokens that is + * initially available. The maximum number of tokens is capped to + * this value. |rate| specifies the number of tokens that are + * regenerated per second. An incoming RST_STREAM consumes one token. + * If there is no token available, GOAWAY is sent to tear down the + * connection. |burst| and |rate| default to 1000 and 33 + * respectively. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option, + uint64_t burst, uint64_t rate); + +/** + * @function + * + * Initializes |*session_ptr| for client use. The all members of + * |callbacks| are copied to |*session_ptr|. Therefore |*session_ptr| + * does not store |callbacks|. The |user_data| is an arbitrary user + * supplied data, which will be passed to the callback functions. + * + * The :type:`nghttp2_send_callback` must be specified. If the + * application code uses `nghttp2_session_recv()`, the + * :type:`nghttp2_recv_callback` must be specified. The other members + * of |callbacks| can be ``NULL``. + * + * If this function fails, |*session_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_client_new(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data); + +/** + * @function + * + * Initializes |*session_ptr| for server use. The all members of + * |callbacks| are copied to |*session_ptr|. Therefore |*session_ptr| + * does not store |callbacks|. The |user_data| is an arbitrary user + * supplied data, which will be passed to the callback functions. + * + * The :type:`nghttp2_send_callback` must be specified. If the + * application code uses `nghttp2_session_recv()`, the + * :type:`nghttp2_recv_callback` must be specified. The other members + * of |callbacks| can be ``NULL``. + * + * If this function fails, |*session_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_server_new(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data); + +/** + * @function + * + * Like `nghttp2_session_client_new()`, but with additional options + * specified in the |option|. + * + * The |option| can be ``NULL`` and the call is equivalent to + * `nghttp2_session_client_new()`. + * + * This function does not take ownership |option|. The application is + * responsible for freeing |option| if it finishes using the object. + * + * The library code does not refer to |option| after this function + * returns. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_client_new2(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option); + +/** + * @function + * + * Like `nghttp2_session_server_new()`, but with additional options + * specified in the |option|. + * + * The |option| can be ``NULL`` and the call is equivalent to + * `nghttp2_session_server_new()`. + * + * This function does not take ownership |option|. The application is + * responsible for freeing |option| if it finishes using the object. + * + * The library code does not refer to |option| after this function + * returns. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_server_new2(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option); + +/** + * @function + * + * Like `nghttp2_session_client_new2()`, but with additional custom + * memory allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_session_client_new2()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_session_client_new3( + nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option, nghttp2_mem *mem); + +/** + * @function + * + * Like `nghttp2_session_server_new2()`, but with additional custom + * memory allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_session_server_new2()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_session_server_new3( + nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option, nghttp2_mem *mem); + +/** + * @function + * + * Frees any resources allocated for |session|. If |session| is + * ``NULL``, this function does nothing. + */ +NGHTTP2_EXTERN void nghttp2_session_del(nghttp2_session *session); + +/** + * @function + * + * Sends pending frames to the remote peer. + * + * This function retrieves the highest prioritized frame from the + * outbound queue and sends it to the remote peer. It does this as + * many times as possible until the user callback + * :type:`nghttp2_send_callback` returns + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`, the outbound queue + * becomes empty or flow control is triggered (remote window size + * becomes depleted or maximum number of concurrent streams is + * reached). This function calls several callback functions which are + * passed when initializing the |session|. Here is the simple time + * chart which tells when each callback is invoked: + * + * 1. Get the next frame to send from outbound queue. + * + * 2. Prepare transmission of the frame. + * + * 3. If the control frame cannot be sent because some preconditions + * are not met (e.g., request HEADERS cannot be sent after GOAWAY), + * :type:`nghttp2_on_frame_not_send_callback` is invoked. Abort + * the following steps. + * + * 4. If the frame is HEADERS, PUSH_PROMISE or DATA, + * :type:`nghttp2_select_padding_callback` is invoked. + * + * 5. If the frame is request HEADERS, the stream is opened here. + * + * 6. :type:`nghttp2_before_frame_send_callback` is invoked. + * + * 7. If :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL` is returned from + * :type:`nghttp2_before_frame_send_callback`, the current frame + * transmission is canceled, and + * :type:`nghttp2_on_frame_not_send_callback` is invoked. Abort + * the following steps. + * + * 8. :type:`nghttp2_send_callback` is invoked one or more times to + * send the frame. + * + * 9. :type:`nghttp2_on_frame_send_callback` is invoked. + * + * 10. If the transmission of the frame triggers closure of the + * stream, the stream is closed and + * :type:`nghttp2_on_stream_close_callback` is invoked. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` + * The callback function failed. + */ +NGHTTP2_EXTERN int nghttp2_session_send(nghttp2_session *session); + +/** + * @function + * + * Returns the serialized data to send. + * + * This function behaves like `nghttp2_session_send()` except that it + * does not use :type:`nghttp2_send_callback` to transmit data. + * Instead, it assigns the pointer to the serialized data to the + * |*data_ptr| and returns its length. The other callbacks are called + * in the same way as they are in `nghttp2_session_send()`. + * + * If no data is available to send, this function returns 0. + * + * This function may not return all serialized data in one invocation. + * To get all data, call this function repeatedly until it returns 0 + * or one of negative error codes. + * + * The assigned |*data_ptr| is valid until the next call of + * `nghttp2_session_mem_send()` or `nghttp2_session_send()`. + * + * The caller must send all data before sending the next chunk of + * data. + * + * This function returns the length of the data pointed by the + * |*data_ptr| if it succeeds, or one of the following negative error + * codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * + * .. note:: + * + * This function may produce very small byte string. If that is the + * case, and application disables Nagle algorithm (``TCP_NODELAY``), + * then writing this small chunk leads to very small packet, and it + * is very inefficient. An application should be responsible to + * buffer up small chunks of data as necessary to avoid this + * situation. + */ +NGHTTP2_EXTERN ssize_t nghttp2_session_mem_send(nghttp2_session *session, + const uint8_t **data_ptr); + +/** + * @function + * + * Receives frames from the remote peer. + * + * This function receives as many frames as possible until the user + * callback :type:`nghttp2_recv_callback` returns + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. This function calls + * several callback functions which are passed when initializing the + * |session|. Here is the simple time chart which tells when each + * callback is invoked: + * + * 1. :type:`nghttp2_recv_callback` is invoked one or more times to + * receive frame header. + * + * 2. When frame header is received, + * :type:`nghttp2_on_begin_frame_callback` is invoked. + * + * 3. If the frame is DATA frame: + * + * 1. :type:`nghttp2_recv_callback` is invoked to receive DATA + * payload. For each chunk of data, + * :type:`nghttp2_on_data_chunk_recv_callback` is invoked. + * + * 2. If one DATA frame is completely received, + * :type:`nghttp2_on_frame_recv_callback` is invoked. If the + * reception of the frame triggers the closure of the stream, + * :type:`nghttp2_on_stream_close_callback` is invoked. + * + * 4. If the frame is the control frame: + * + * 1. :type:`nghttp2_recv_callback` is invoked one or more times to + * receive whole frame. + * + * 2. If the received frame is valid, then following actions are + * taken. If the frame is either HEADERS or PUSH_PROMISE, + * :type:`nghttp2_on_begin_headers_callback` is invoked. Then + * :type:`nghttp2_on_header_callback` is invoked for each header + * name/value pair. For invalid header field, + * :type:`nghttp2_on_invalid_header_callback` is called. After + * all name/value pairs are emitted successfully, + * :type:`nghttp2_on_frame_recv_callback` is invoked. For other + * frames, :type:`nghttp2_on_frame_recv_callback` is invoked. + * If the reception of the frame triggers the closure of the + * stream, :type:`nghttp2_on_stream_close_callback` is invoked. + * + * 3. If the received frame is unpacked but is interpreted as + * invalid, :type:`nghttp2_on_invalid_frame_recv_callback` is + * invoked. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_EOF` + * The remote peer did shutdown on the connection. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` + * The callback function failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC` + * Invalid client magic was detected. This error only returns + * when |session| was configured as server and + * `nghttp2_option_set_no_recv_client_magic()` is not used with + * nonzero value. + * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED` + * Flooding was detected in this HTTP/2 session, and it must be + * closed. This is most likely caused by misbehaviour of peer. + */ +NGHTTP2_EXTERN int nghttp2_session_recv(nghttp2_session *session); + +/** + * @function + * + * Processes data |in| as an input from the remote endpoint. The + * |inlen| indicates the number of bytes to receive in the |in|. + * + * This function behaves like `nghttp2_session_recv()` except that it + * does not use :type:`nghttp2_recv_callback` to receive data; the + * |in| is the only data for the invocation of this function. If all + * bytes are processed, this function returns. The other callbacks + * are called in the same way as they are in `nghttp2_session_recv()`. + * + * In the current implementation, this function always tries to + * processes |inlen| bytes of input data unless either an error occurs or + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is returned from + * :type:`nghttp2_on_header_callback` or + * :type:`nghttp2_on_data_chunk_recv_callback`. If + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is used, the return value + * includes the number of bytes which was used to produce the data or + * frame for the callback. + * + * This function returns the number of processed bytes, or one of the + * following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` + * The callback function failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC` + * Invalid client magic was detected. This error only returns + * when |session| was configured as server and + * `nghttp2_option_set_no_recv_client_magic()` is not used with + * nonzero value. + * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED` + * Flooding was detected in this HTTP/2 session, and it must be + * closed. This is most likely caused by misbehaviour of peer. + */ +NGHTTP2_EXTERN ssize_t nghttp2_session_mem_recv(nghttp2_session *session, + const uint8_t *in, + size_t inlen); + +/** + * @function + * + * Puts back previously deferred DATA frame in the stream |stream_id| + * to the outbound queue. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The stream does not exist; or no deferred data exist. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_session_resume_data(nghttp2_session *session, + int32_t stream_id); + +/** + * @function + * + * Returns nonzero value if |session| wants to receive data from the + * remote peer. + * + * If both `nghttp2_session_want_read()` and + * `nghttp2_session_want_write()` return 0, the application should + * drop the connection. + */ +NGHTTP2_EXTERN int nghttp2_session_want_read(nghttp2_session *session); + +/** + * @function + * + * Returns nonzero value if |session| wants to send data to the remote + * peer. + * + * If both `nghttp2_session_want_read()` and + * `nghttp2_session_want_write()` return 0, the application should + * drop the connection. + */ +NGHTTP2_EXTERN int nghttp2_session_want_write(nghttp2_session *session); + +/** + * @function + * + * Returns stream_user_data for the stream |stream_id|. The + * stream_user_data is provided by `nghttp2_submit_request()`, + * `nghttp2_submit_headers()` or + * `nghttp2_session_set_stream_user_data()`. Unless it is set using + * `nghttp2_session_set_stream_user_data()`, if the stream is + * initiated by the remote endpoint, stream_user_data is always + * ``NULL``. If the stream does not exist, this function returns + * ``NULL``. + */ +NGHTTP2_EXTERN void * +nghttp2_session_get_stream_user_data(nghttp2_session *session, + int32_t stream_id); + +/** + * @function + * + * Sets the |stream_user_data| to the stream denoted by the + * |stream_id|. If a stream user data is already set to the stream, + * it is replaced with the |stream_user_data|. It is valid to specify + * ``NULL`` in the |stream_user_data|, which nullifies the associated + * data pointer. + * + * It is valid to set the |stream_user_data| to the stream reserved by + * PUSH_PROMISE frame. + * + * This function returns 0 if it succeeds, or one of following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The stream does not exist + */ +NGHTTP2_EXTERN int +nghttp2_session_set_stream_user_data(nghttp2_session *session, + int32_t stream_id, void *stream_user_data); + +/** + * @function + * + * Sets |user_data| to |session|, overwriting the existing user data + * specified in `nghttp2_session_client_new()`, or + * `nghttp2_session_server_new()`. + */ +NGHTTP2_EXTERN void nghttp2_session_set_user_data(nghttp2_session *session, + void *user_data); + +/** + * @function + * + * Returns the number of frames in the outbound queue. This does not + * include the deferred DATA frames. + */ +NGHTTP2_EXTERN size_t +nghttp2_session_get_outbound_queue_size(nghttp2_session *session); + +/** + * @function + * + * Returns the number of DATA payload in bytes received without + * WINDOW_UPDATE transmission for the stream |stream_id|. The local + * (receive) window size can be adjusted by + * `nghttp2_submit_window_update()`. This function takes into account + * that and returns effective data length. In particular, if the + * local window size is reduced by submitting negative + * window_size_increment with `nghttp2_submit_window_update()`, this + * function returns the number of bytes less than actually received. + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_recv_data_length( + nghttp2_session *session, int32_t stream_id); + +/** + * @function + * + * Returns the local (receive) window size for the stream |stream_id|. + * The local window size can be adjusted by + * `nghttp2_submit_window_update()`. This function takes into account + * that and returns effective window size. + * + * This function does not take into account the amount of received + * data from the remote endpoint. Use + * `nghttp2_session_get_stream_local_window_size()` to know the amount + * of data the remote endpoint can send without receiving stream level + * WINDOW_UPDATE frame. Note that each stream is still subject to the + * connection level flow control. + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_local_window_size( + nghttp2_session *session, int32_t stream_id); + +/** + * @function + * + * Returns the amount of flow-controlled payload (e.g., DATA) that the + * remote endpoint can send without receiving stream level + * WINDOW_UPDATE frame. It is also subject to the connection level + * flow control. So the actual amount of data to send is + * min(`nghttp2_session_get_stream_local_window_size()`, + * `nghttp2_session_get_local_window_size()`). + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_local_window_size( + nghttp2_session *session, int32_t stream_id); + +/** + * @function + * + * Returns the number of DATA payload in bytes received without + * WINDOW_UPDATE transmission for a connection. The local (receive) + * window size can be adjusted by `nghttp2_submit_window_update()`. + * This function takes into account that and returns effective data + * length. In particular, if the local window size is reduced by + * submitting negative window_size_increment with + * `nghttp2_submit_window_update()`, this function returns the number + * of bytes less than actually received. + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_effective_recv_data_length(nghttp2_session *session); + +/** + * @function + * + * Returns the local (receive) window size for a connection. The + * local window size can be adjusted by + * `nghttp2_submit_window_update()`. This function takes into account + * that and returns effective window size. + * + * This function does not take into account the amount of received + * data from the remote endpoint. Use + * `nghttp2_session_get_local_window_size()` to know the amount of + * data the remote endpoint can send without receiving + * connection-level WINDOW_UPDATE frame. Note that each stream is + * still subject to the stream level flow control. + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_effective_local_window_size(nghttp2_session *session); + +/** + * @function + * + * Returns the amount of flow-controlled payload (e.g., DATA) that the + * remote endpoint can send without receiving connection level + * WINDOW_UPDATE frame. Note that each stream is still subject to the + * stream level flow control (see + * `nghttp2_session_get_stream_local_window_size()`). + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_local_window_size(nghttp2_session *session); + +/** + * @function + * + * Returns the remote window size for a given stream |stream_id|. + * + * This is the amount of flow-controlled payload (e.g., DATA) that the + * local endpoint can send without stream level WINDOW_UPDATE. There + * is also connection level flow control, so the effective size of + * payload that the local endpoint can actually send is + * min(`nghttp2_session_get_stream_remote_window_size()`, + * `nghttp2_session_get_remote_window_size()`). + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_remote_window_size( + nghttp2_session *session, int32_t stream_id); + +/** + * @function + * + * Returns the remote window size for a connection. + * + * This function always succeeds. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_remote_window_size(nghttp2_session *session); + +/** + * @function + * + * Returns 1 if local peer half closed the given stream |stream_id|. + * Returns 0 if it did not. Returns -1 if no such stream exists. + */ +NGHTTP2_EXTERN int +nghttp2_session_get_stream_local_close(nghttp2_session *session, + int32_t stream_id); + +/** + * @function + * + * Returns 1 if remote peer half closed the given stream |stream_id|. + * Returns 0 if it did not. Returns -1 if no such stream exists. + */ +NGHTTP2_EXTERN int +nghttp2_session_get_stream_remote_close(nghttp2_session *session, + int32_t stream_id); + +/** + * @function + * + * Returns the current dynamic table size of HPACK inflater, including + * the overhead 32 bytes per entry described in RFC 7541. + */ +NGHTTP2_EXTERN size_t +nghttp2_session_get_hd_inflate_dynamic_table_size(nghttp2_session *session); + +/** + * @function + * + * Returns the current dynamic table size of HPACK deflater including + * the overhead 32 bytes per entry described in RFC 7541. + */ +NGHTTP2_EXTERN size_t +nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session); + +/** + * @function + * + * Signals the session so that the connection should be terminated. + * + * The last stream ID is the minimum value between the stream ID of a + * stream for which :type:`nghttp2_on_frame_recv_callback` was called + * most recently and the last stream ID we have sent to the peer + * previously. + * + * The |error_code| is the error code of this GOAWAY frame. The + * pre-defined error code is one of :enum:`nghttp2_error_code`. + * + * After the transmission, both `nghttp2_session_want_read()` and + * `nghttp2_session_want_write()` return 0. + * + * This function should be called when the connection should be + * terminated after sending GOAWAY. If the remaining streams should + * be processed after GOAWAY, use `nghttp2_submit_goaway()` instead. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_session_terminate_session(nghttp2_session *session, + uint32_t error_code); + +/** + * @function + * + * Signals the session so that the connection should be terminated. + * + * This function behaves like `nghttp2_session_terminate_session()`, + * but the last stream ID can be specified by the application for fine + * grained control of stream. The HTTP/2 specification does not allow + * last_stream_id to be increased. So the actual value sent as + * last_stream_id is the minimum value between the given + * |last_stream_id| and the last_stream_id we have previously sent to + * the peer. + * + * The |last_stream_id| is peer's stream ID or 0. So if |session| is + * initialized as client, |last_stream_id| must be even or 0. If + * |session| is initialized as server, |last_stream_id| must be odd or + * 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |last_stream_id| is invalid. + */ +NGHTTP2_EXTERN int nghttp2_session_terminate_session2(nghttp2_session *session, + int32_t last_stream_id, + uint32_t error_code); + +/** + * @function + * + * Signals to the client that the server started graceful shutdown + * procedure. + * + * This function is only usable for server. If this function is + * called with client side session, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. + * + * To gracefully shutdown HTTP/2 session, server should call this + * function to send GOAWAY with last_stream_id (1u << 31) - 1. And + * after some delay (e.g., 1 RTT), send another GOAWAY with the stream + * ID that the server has some processing using + * `nghttp2_submit_goaway()`. See also + * `nghttp2_session_get_last_proc_stream_id()`. + * + * Unlike `nghttp2_submit_goaway()`, this function just sends GOAWAY + * and does nothing more. This is a mere indication to the client + * that session shutdown is imminent. The application should call + * `nghttp2_submit_goaway()` with appropriate last_stream_id after + * this call. + * + * If one or more GOAWAY frame have been already sent by either + * `nghttp2_submit_goaway()` or `nghttp2_session_terminate_session()`, + * this function has no effect. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The |session| is initialized as client. + */ +NGHTTP2_EXTERN int nghttp2_submit_shutdown_notice(nghttp2_session *session); + +/** + * @function + * + * Returns the value of SETTINGS |id| notified by a remote endpoint. + * The |id| must be one of values defined in + * :enum:`nghttp2_settings_id`. + */ +NGHTTP2_EXTERN uint32_t nghttp2_session_get_remote_settings( + nghttp2_session *session, nghttp2_settings_id id); + +/** + * @function + * + * Returns the value of SETTINGS |id| of local endpoint acknowledged + * by the remote endpoint. The |id| must be one of the values defined + * in :enum:`nghttp2_settings_id`. + */ +NGHTTP2_EXTERN uint32_t nghttp2_session_get_local_settings( + nghttp2_session *session, nghttp2_settings_id id); + +/** + * @function + * + * Tells the |session| that next stream ID is |next_stream_id|. The + * |next_stream_id| must be equal or greater than the value returned + * by `nghttp2_session_get_next_stream_id()`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |next_stream_id| is strictly less than the value + * `nghttp2_session_get_next_stream_id()` returns; or + * |next_stream_id| is invalid (e.g., even integer for client, or + * odd integer for server). + */ +NGHTTP2_EXTERN int nghttp2_session_set_next_stream_id(nghttp2_session *session, + int32_t next_stream_id); + +/** + * @function + * + * Returns the next outgoing stream ID. Notice that return type is + * uint32_t. If we run out of stream ID for this session, this + * function returns 1 << 31. + */ +NGHTTP2_EXTERN uint32_t +nghttp2_session_get_next_stream_id(nghttp2_session *session); + +/** + * @function + * + * Tells the |session| that |size| bytes for a stream denoted by + * |stream_id| were consumed by application and are ready to + * WINDOW_UPDATE. The consumed bytes are counted towards both + * connection and stream level WINDOW_UPDATE (see + * `nghttp2_session_consume_connection()` and + * `nghttp2_session_consume_stream()` to update consumption + * independently). This function is intended to be used without + * automatic window update (see + * `nghttp2_option_set_no_auto_window_update()`). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +NGHTTP2_EXTERN int nghttp2_session_consume(nghttp2_session *session, + int32_t stream_id, size_t size); + +/** + * @function + * + * Like `nghttp2_session_consume()`, but this only tells library that + * |size| bytes were consumed only for connection level. Note that + * HTTP/2 maintains connection and stream level flow control windows + * independently. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +NGHTTP2_EXTERN int nghttp2_session_consume_connection(nghttp2_session *session, + size_t size); + +/** + * @function + * + * Like `nghttp2_session_consume()`, but this only tells library that + * |size| bytes were consumed only for stream denoted by |stream_id|. + * Note that HTTP/2 maintains connection and stream level flow control + * windows independently. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session, + int32_t stream_id, + size_t size); + +/** + * @function + * + * Changes priority of existing stream denoted by |stream_id|. The + * new priority specification is |pri_spec|. + * + * The priority is changed silently and instantly, and no PRIORITY + * frame will be sent to notify the peer of this change. This + * function may be useful for server to change the priority of pushed + * stream. + * + * If |session| is initialized as server, and ``pri_spec->stream_id`` + * points to the idle stream, the idle stream is created if it does + * not exist. The created idle stream will depend on root stream + * (stream 0) with weight 16. + * + * Otherwise, if stream denoted by ``pri_spec->stream_id`` is not + * found, we use default priority instead of given |pri_spec|. That + * is make stream depend on root stream with weight 16. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is submitted via `nghttp2_submit_settings()`, this + * function does nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Attempted to depend on itself; or no stream exist for the given + * |stream_id|; or |stream_id| is 0 + */ +NGHTTP2_EXTERN int +nghttp2_session_change_stream_priority(nghttp2_session *session, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec); + +/** + * @function + * + * Creates idle stream with the given |stream_id|, and priority + * |pri_spec|. + * + * The stream creation is done without sending PRIORITY frame, which + * means that peer does not know about the existence of this idle + * stream in the local endpoint. + * + * RFC 7540 does not disallow the use of creation of idle stream with + * odd or even stream ID regardless of client or server. So this + * function can create odd or even stream ID regardless of client or + * server. But probably it is a bit safer to use the stream ID the + * local endpoint can initiate (in other words, use odd stream ID for + * client, and even stream ID for server), to avoid potential + * collision from peer's instruction. Also we can use + * `nghttp2_session_set_next_stream_id()` to avoid to open created + * idle streams accidentally if we follow this recommendation. + * + * If |session| is initialized as server, and ``pri_spec->stream_id`` + * points to the idle stream, the idle stream is created if it does + * not exist. The created idle stream will depend on root stream + * (stream 0) with weight 16. + * + * Otherwise, if stream denoted by ``pri_spec->stream_id`` is not + * found, we use default priority instead of given |pri_spec|. That + * is make stream depend on root stream with weight 16. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is submitted via `nghttp2_submit_settings()`, this + * function does nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Attempted to depend on itself; or stream denoted by |stream_id| + * already exists; or |stream_id| cannot be used to create idle + * stream (in other words, local endpoint has already opened + * stream ID greater than or equal to the given stream ID; or + * |stream_id| is 0 + */ +NGHTTP2_EXTERN int +nghttp2_session_create_idle_stream(nghttp2_session *session, int32_t stream_id, + const nghttp2_priority_spec *pri_spec); + +/** + * @function + * + * Performs post-process of HTTP Upgrade request. This function can + * be called from both client and server, but the behavior is very + * different in each other. + * + * .. warning:: + * + * This function is deprecated in favor of + * `nghttp2_session_upgrade2()`, because this function lacks the + * parameter to tell the library the request method used in the + * original HTTP request. This information is required for client + * to validate actual response body length against content-length + * header field (see `nghttp2_option_set_no_http_messaging()`). If + * HEAD is used in request, the length of response body must be 0 + * regardless of value included in content-length header field. + * + * If called from client side, the |settings_payload| must be the + * value sent in ``HTTP2-Settings`` header field and must be decoded + * by base64url decoder. The |settings_payloadlen| is the length of + * |settings_payload|. The |settings_payload| is unpacked and its + * setting values will be submitted using `nghttp2_submit_settings()`. + * This means that the client application code does not need to submit + * SETTINGS by itself. The stream with stream ID=1 is opened and the + * |stream_user_data| is used for its stream_user_data. The opened + * stream becomes half-closed (local) state. + * + * If called from server side, the |settings_payload| must be the + * value received in ``HTTP2-Settings`` header field and must be + * decoded by base64url decoder. The |settings_payloadlen| is the + * length of |settings_payload|. It is treated as if the SETTINGS + * frame with that payload is received. Thus, callback functions for + * the reception of SETTINGS frame will be invoked. The stream with + * stream ID=1 is opened. The |stream_user_data| is ignored. The + * opened stream becomes half-closed (remote). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |settings_payload| is badly formed. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The stream ID 1 is already used or closed; or is not available. + */ +NGHTTP2_EXTERN int nghttp2_session_upgrade(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, + void *stream_user_data); + +/** + * @function + * + * Performs post-process of HTTP Upgrade request. This function can + * be called from both client and server, but the behavior is very + * different in each other. + * + * If called from client side, the |settings_payload| must be the + * value sent in ``HTTP2-Settings`` header field and must be decoded + * by base64url decoder. The |settings_payloadlen| is the length of + * |settings_payload|. The |settings_payload| is unpacked and its + * setting values will be submitted using `nghttp2_submit_settings()`. + * This means that the client application code does not need to submit + * SETTINGS by itself. The stream with stream ID=1 is opened and the + * |stream_user_data| is used for its stream_user_data. The opened + * stream becomes half-closed (local) state. + * + * If called from server side, the |settings_payload| must be the + * value received in ``HTTP2-Settings`` header field and must be + * decoded by base64url decoder. The |settings_payloadlen| is the + * length of |settings_payload|. It is treated as if the SETTINGS + * frame with that payload is received. Thus, callback functions for + * the reception of SETTINGS frame will be invoked. The stream with + * stream ID=1 is opened. The |stream_user_data| is ignored. The + * opened stream becomes half-closed (remote). + * + * If the request method is HEAD, pass nonzero value to + * |head_request|. Otherwise, pass 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |settings_payload| is badly formed. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The stream ID 1 is already used or closed; or is not available. + */ +NGHTTP2_EXTERN int nghttp2_session_upgrade2(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, + int head_request, + void *stream_user_data); + +/** + * @function + * + * Serializes the SETTINGS values |iv| in the |buf|. The size of the + * |buf| is specified by |buflen|. The number of entries in the |iv| + * array is given by |niv|. The required space in |buf| for the |niv| + * entries is ``6*niv`` bytes and if the given buffer is too small, an + * error is returned. This function is used mainly for creating a + * SETTINGS payload to be sent with the ``HTTP2-Settings`` header + * field in an HTTP Upgrade request. The data written in |buf| is NOT + * base64url encoded and the application is responsible for encoding. + * + * This function returns the number of bytes written in |buf|, or one + * of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |iv| contains duplicate settings ID or invalid value. + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN ssize_t nghttp2_pack_settings_payload( + uint8_t *buf, size_t buflen, const nghttp2_settings_entry *iv, size_t niv); + +/** + * @function + * + * Returns string describing the |lib_error_code|. The + * |lib_error_code| must be one of the :enum:`nghttp2_error`. + */ +NGHTTP2_EXTERN const char *nghttp2_strerror(int lib_error_code); + +/** + * @function + * + * Returns string representation of HTTP/2 error code |error_code| + * (e.g., ``PROTOCOL_ERROR`` is returned if ``error_code == + * NGHTTP2_PROTOCOL_ERROR``). If string representation is unknown for + * given |error_code|, this function returns string ``unknown``. + */ +NGHTTP2_EXTERN const char *nghttp2_http2_strerror(uint32_t error_code); + +/** + * @function + * + * Initializes |pri_spec| with the |stream_id| of the stream to depend + * on with |weight| and its exclusive flag. If |exclusive| is + * nonzero, exclusive flag is set. + * + * The |weight| must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. + */ +NGHTTP2_EXTERN void nghttp2_priority_spec_init(nghttp2_priority_spec *pri_spec, + int32_t stream_id, + int32_t weight, int exclusive); + +/** + * @function + * + * Initializes |pri_spec| with the default values. The default values + * are: stream_id = 0, weight = :macro:`NGHTTP2_DEFAULT_WEIGHT` and + * exclusive = 0. + */ +NGHTTP2_EXTERN void +nghttp2_priority_spec_default_init(nghttp2_priority_spec *pri_spec); + +/** + * @function + * + * Returns nonzero if the |pri_spec| is filled with default values. + */ +NGHTTP2_EXTERN int +nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec); + +/** + * @function + * + * Submits HEADERS frame and optionally one or more DATA frames. + * + * The |pri_spec| is priority specification of this request. ``NULL`` + * means the default priority (see + * `nghttp2_priority_spec_default_init()`). To specify the priority, + * use `nghttp2_priority_spec_init()`. If |pri_spec| is not ``NULL``, + * this function will copy its data members. + * + * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` + * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes + * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than + * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes + * :macro:`NGHTTP2_MAX_WEIGHT`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is received by a remote endpoint, |pri_spec| is + * ignored, and treated as if ``NULL`` is specified. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * HTTP/2 specification has requirement about header fields in the + * request HEADERS. See the specification for more details. + * + * If |data_prd| is not ``NULL``, it provides data which will be sent + * in subsequent DATA frames. In this case, a method that allows + * request message bodies + * (https://tools.ietf.org/html/rfc7231#section-4) must be specified + * with ``:method`` key in |nva| (e.g. ``POST``). This function does + * not take ownership of the |data_prd|. The function copies the + * members of the |data_prd|. If |data_prd| is ``NULL``, HEADERS have + * END_STREAM set. The |stream_user_data| is data associated to the + * stream opened by this request and can be an arbitrary pointer, + * which can be retrieved later by + * `nghttp2_session_get_stream_user_data()`. + * + * This function returns assigned stream ID if it succeeds, or one of + * the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` + * No stream ID is available because maximum stream ID was + * reached. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Trying to depend on itself (new stream ID equals + * ``pri_spec->stream_id``). + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The |session| is server session. + * + * .. warning:: + * + * This function returns assigned stream ID if it succeeds. But + * that stream is not created yet. The application must not submit + * frame to that stream ID before + * :type:`nghttp2_before_frame_send_callback` is called for this + * frame. This means `nghttp2_session_get_stream_user_data()` does + * not work before the callback. But + * `nghttp2_session_set_stream_user_data()` handles this situation + * specially, and it can set data to a stream during this period. + * + */ +NGHTTP2_EXTERN int32_t nghttp2_submit_request( + nghttp2_session *session, const nghttp2_priority_spec *pri_spec, + const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider *data_prd, + void *stream_user_data); + +/** + * @function + * + * Submits response HEADERS frame and optionally one or more DATA + * frames against the stream |stream_id|. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * HTTP/2 specification has requirement about header fields in the + * response HEADERS. See the specification for more details. + * + * If |data_prd| is not ``NULL``, it provides data which will be sent + * in subsequent DATA frames. This function does not take ownership + * of the |data_prd|. The function copies the members of the + * |data_prd|. If |data_prd| is ``NULL``, HEADERS will have + * END_STREAM flag set. + * + * This method can be used as normal HTTP response and push response. + * When pushing a resource using this function, the |session| must be + * configured using `nghttp2_session_server_new()` or its variants and + * the target stream denoted by the |stream_id| must be reserved using + * `nghttp2_submit_push_promise()`. + * + * To send non-final response headers (e.g., HTTP status 101), don't + * use this function because this function half-closes the outbound + * stream. Instead, use `nghttp2_submit_headers()` for this purpose. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` + * DATA or HEADERS has been already submitted and not fully + * processed yet. Normally, this does not happen, but when + * application wrongly calls `nghttp2_submit_response()` twice, + * this may happen. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The |session| is client session. + * + * .. warning:: + * + * Calling this function twice for the same stream ID may lead to + * program crash. It is generally considered to a programming error + * to commit response twice. + */ +NGHTTP2_EXTERN int +nghttp2_submit_response(nghttp2_session *session, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen, + const nghttp2_data_provider *data_prd); + +/** + * @function + * + * Submits trailer fields HEADERS against the stream |stream_id|. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application must not include pseudo-header + * fields (headers whose names starts with ":") in |nva|. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * For server, trailer fields must follow response HEADERS or response + * DATA without END_STREAM flat set. The library does not enforce + * this requirement, and applications should do this for themselves. + * If `nghttp2_submit_trailer()` is called before any response HEADERS + * submission (usually by `nghttp2_submit_response()`), the content of + * |nva| will be sent as response headers, which will result in error. + * + * This function has the same effect with `nghttp2_submit_headers()`, + * with flags = :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` and both + * pri_spec and stream_user_data to NULL. + * + * To submit trailer fields after `nghttp2_submit_response()` is + * called, the application has to specify + * :type:`nghttp2_data_provider` to `nghttp2_submit_response()`. + * Inside of :type:`nghttp2_data_source_read_callback`, when setting + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF`, also set + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM`. After + * that, the application can send trailer fields using + * `nghttp2_submit_trailer()`. `nghttp2_submit_trailer()` can be used + * inside :type:`nghttp2_data_source_read_callback`. + * + * This function returns 0 if it succeeds and |stream_id| is -1. + * Otherwise, this function returns 0 if it succeeds, or one of the + * following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + */ +NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session, + int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen); + +/** + * @function + * + * Submits HEADERS frame. The |flags| is bitwise OR of the + * following values: + * + * * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` + * + * If |flags| includes :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`, + * this frame has END_STREAM flag set. + * + * The library handles the CONTINUATION frame internally and it + * correctly sets END_HEADERS to the last sequence of the PUSH_PROMISE + * or CONTINUATION frame. + * + * If the |stream_id| is -1, this frame is assumed as request (i.e., + * request HEADERS frame which opens new stream). In this case, the + * assigned stream ID will be returned. Otherwise, specify stream ID + * in |stream_id|. + * + * The |pri_spec| is priority specification of this request. ``NULL`` + * means the default priority (see + * `nghttp2_priority_spec_default_init()`). To specify the priority, + * use `nghttp2_priority_spec_init()`. If |pri_spec| is not ``NULL``, + * this function will copy its data members. + * + * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` + * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes + * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than + * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes :macro:`NGHTTP2_MAX_WEIGHT`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is received by a remote endpoint, |pri_spec| is + * ignored, and treated as if ``NULL`` is specified. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * The |stream_user_data| is a pointer to an arbitrary data which is + * associated to the stream this frame will open. Therefore it is + * only used if this frame opens streams, in other words, it changes + * stream state from idle or reserved to open. + * + * This function is low-level in a sense that the application code can + * specify flags directly. For usual HTTP request, + * `nghttp2_submit_request()` is useful. Likewise, for HTTP response, + * prefer `nghttp2_submit_response()`. + * + * This function returns newly assigned stream ID if it succeeds and + * |stream_id| is -1. Otherwise, this function returns 0 if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` + * No stream ID is available because maximum stream ID was + * reached. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0; or trying to depend on itself (stream ID + * equals ``pri_spec->stream_id``). + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` + * DATA or HEADERS has been already submitted and not fully + * processed yet. This happens if stream denoted by |stream_id| + * is in reserved state. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The |stream_id| is -1, and |session| is server session. + * + * .. warning:: + * + * This function returns assigned stream ID if it succeeds and + * |stream_id| is -1. But that stream is not opened yet. The + * application must not submit frame to that stream ID before + * :type:`nghttp2_before_frame_send_callback` is called for this + * frame. + * + */ +NGHTTP2_EXTERN int32_t nghttp2_submit_headers( + nghttp2_session *session, uint8_t flags, int32_t stream_id, + const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, + void *stream_user_data); + +/** + * @function + * + * Submits one or more DATA frames to the stream |stream_id|. The + * data to be sent are provided by |data_prd|. If |flags| contains + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`, the last DATA frame + * has END_STREAM flag set. + * + * This function does not take ownership of the |data_prd|. The + * function copies the members of the |data_prd|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` + * DATA or HEADERS has been already submitted and not fully + * processed yet. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED` + * The stream was already closed; or the |stream_id| is invalid. + * + * .. note:: + * + * Currently, only one DATA or HEADERS is allowed for a stream at a + * time. Submitting these frames more than once before first DATA + * or HEADERS is finished results in + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` error code. The + * earliest callback which tells that previous frame is done is + * :type:`nghttp2_on_frame_send_callback`. In side that callback, + * new data can be submitted using `nghttp2_submit_data()`. Of + * course, all data except for last one must not have + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` flag set in |flags|. + * This sounds a bit complicated, and we recommend to use + * `nghttp2_submit_request()` and `nghttp2_submit_response()` to + * avoid this cascading issue. The experience shows that for HTTP + * use, these two functions are enough to implement both client and + * server. + */ +NGHTTP2_EXTERN int nghttp2_submit_data(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_data_provider *data_prd); + +/** + * @function + * + * Submits PRIORITY frame to change the priority of stream |stream_id| + * to the priority specification |pri_spec|. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |pri_spec| is priority specification of this request. ``NULL`` + * is not allowed for this function. To specify the priority, use + * `nghttp2_priority_spec_init()`. This function will copy its data + * members. + * + * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` + * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes + * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than + * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes + * :macro:`NGHTTP2_MAX_WEIGHT`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is received by a remote endpoint, this function does + * nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0; or the |pri_spec| is NULL; or trying to + * depend on itself. + */ +NGHTTP2_EXTERN int +nghttp2_submit_priority(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec); + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_DEFAULT_URGENCY` is the default urgency + * level for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_DEFAULT_URGENCY 3 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_HIGH` is the highest urgency level + * for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_HIGH 0 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW` is the lowest urgency level for + * :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_LOW 7 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_LEVELS` is the number of urgency + * levels for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_LEVELS (NGHTTP2_EXTPRI_URGENCY_LOW + 1) + +/** + * @struct + * + * :type:`nghttp2_extpri` is :rfc:`9218` extensible priorities + * specification for a stream. + */ +typedef struct nghttp2_extpri { + /** + * :member:`urgency` is the urgency of a stream, it must be in + * [:macro:`NGHTTP2_EXTPRI_URGENCY_HIGH`, + * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`], inclusive, and 0 is the + * highest urgency. + */ + uint32_t urgency; + /** + * :member:`inc` indicates that a content can be processed + * incrementally or not. If inc is 0, it cannot be processed + * incrementally. If inc is 1, it can be processed incrementally. + * Other value is not permitted. + */ + int inc; +} nghttp2_extpri; + +/** + * @function + * + * Submits RST_STREAM frame to cancel/reject the stream |stream_id| + * with the error code |error_code|. + * + * The pre-defined error code is one of :enum:`nghttp2_error_code`. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + */ +NGHTTP2_EXTERN int nghttp2_submit_rst_stream(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + uint32_t error_code); + +/** + * @function + * + * Stores local settings and submits SETTINGS frame. The |iv| is the + * pointer to the array of :type:`nghttp2_settings_entry`. The |niv| + * indicates the number of :type:`nghttp2_settings_entry`. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * This function does not take ownership of the |iv|. This function + * copies all the elements in the |iv|. + * + * While updating individual stream's local window size, if the window + * size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE, + * RST_STREAM is issued against such a stream. + * + * SETTINGS with :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` is + * automatically submitted by the library and application could not + * send it at its will. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |iv| contains invalid value (e.g., initial window size + * strictly greater than (1 << 31) - 1. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session, + uint8_t flags, + const nghttp2_settings_entry *iv, + size_t niv); + +/** + * @function + * + * Submits PUSH_PROMISE frame. + * + * The |flags| is currently ignored. The library handles the + * CONTINUATION frame internally and it correctly sets END_HEADERS to + * the last sequence of the PUSH_PROMISE or CONTINUATION frame. + * + * The |stream_id| must be client initiated stream ID. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * The |promised_stream_user_data| is a pointer to an arbitrary data + * which is associated to the promised stream this frame will open and + * make it in reserved state. It is available using + * `nghttp2_session_get_stream_user_data()`. The application can + * access it in :type:`nghttp2_before_frame_send_callback` and + * :type:`nghttp2_on_frame_send_callback` of this frame. + * + * The client side is not allowed to use this function. + * + * To submit response headers and data, use + * `nghttp2_submit_response()`. + * + * This function returns assigned promised stream ID if it succeeds, + * or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * This function was invoked when |session| is initialized as + * client. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` + * No stream ID is available because maximum stream ID was + * reached. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0; The |stream_id| does not designate stream + * that peer initiated. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED` + * The stream was already closed; or the |stream_id| is invalid. + * + * .. warning:: + * + * This function returns assigned promised stream ID if it succeeds. + * As of 1.16.0, stream object for pushed resource is created when + * this function succeeds. In that case, the application can submit + * push response for the promised frame. + * + * In 1.15.0 or prior versions, pushed stream is not opened yet when + * this function succeeds. The application must not submit frame to + * that stream ID before :type:`nghttp2_before_frame_send_callback` + * is called for this frame. + * + */ +NGHTTP2_EXTERN int32_t nghttp2_submit_push_promise( + nghttp2_session *session, uint8_t flags, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen, void *promised_stream_user_data); + +/** + * @function + * + * Submits PING frame. You don't have to send PING back when you + * received PING frame. The library automatically submits PING frame + * in this case. + * + * The |flags| is bitwise OR of 0 or more of the following value. + * + * * :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` + * + * Unless `nghttp2_option_set_no_auto_ping_ack()` is used, the |flags| + * should be :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * If the |opaque_data| is non ``NULL``, then it should point to the 8 + * bytes array of memory to specify opaque data to send with PING + * frame. If the |opaque_data| is ``NULL``, zero-cleared 8 bytes will + * be sent as opaque data. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags, + const uint8_t *opaque_data); + +/** + * @function + * + * Submits GOAWAY frame with the last stream ID |last_stream_id| and + * the error code |error_code|. + * + * The pre-defined error code is one of :enum:`nghttp2_error_code`. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |last_stream_id| is peer's stream ID or 0. So if |session| is + * initialized as client, |last_stream_id| must be even or 0. If + * |session| is initialized as server, |last_stream_id| must be odd or + * 0. + * + * The HTTP/2 specification says last_stream_id must not be increased + * from the value previously sent. So the actual value sent as + * last_stream_id is the minimum value between the given + * |last_stream_id| and the last_stream_id previously sent to the + * peer. + * + * If the |opaque_data| is not ``NULL`` and |opaque_data_len| is not + * zero, those data will be sent as additional debug data. The + * library makes a copy of the memory region pointed by |opaque_data| + * with the length |opaque_data_len|, so the caller does not need to + * keep this memory after the return of this function. If the + * |opaque_data_len| is 0, the |opaque_data| could be ``NULL``. + * + * After successful transmission of GOAWAY, following things happen. + * All incoming streams having strictly more than |last_stream_id| are + * closed. All incoming HEADERS which starts new stream are simply + * ignored. After all active streams are handled, both + * `nghttp2_session_want_read()` and `nghttp2_session_want_write()` + * return 0 and the application can close session. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |opaque_data_len| is too large; the |last_stream_id| is + * invalid. + */ +NGHTTP2_EXTERN int nghttp2_submit_goaway(nghttp2_session *session, + uint8_t flags, int32_t last_stream_id, + uint32_t error_code, + const uint8_t *opaque_data, + size_t opaque_data_len); + +/** + * @function + * + * Returns the last stream ID of a stream for which + * :type:`nghttp2_on_frame_recv_callback` was invoked most recently. + * The returned value can be used as last_stream_id parameter for + * `nghttp2_submit_goaway()` and + * `nghttp2_session_terminate_session2()`. + * + * This function always succeeds. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_last_proc_stream_id(nghttp2_session *session); + +/** + * @function + * + * Returns nonzero if new request can be sent from local endpoint. + * + * This function return 0 if request is not allowed for this session. + * There are several reasons why request is not allowed. Some of the + * reasons are: session is server; stream ID has been spent; GOAWAY + * has been sent or received. + * + * The application can call `nghttp2_submit_request()` without + * consulting this function. In that case, `nghttp2_submit_request()` + * may return error. Or, request is failed to sent, and + * :type:`nghttp2_on_stream_close_callback` is called. + */ +NGHTTP2_EXTERN int +nghttp2_session_check_request_allowed(nghttp2_session *session); + +/** + * @function + * + * Returns nonzero if |session| is initialized as server side session. + */ +NGHTTP2_EXTERN int +nghttp2_session_check_server_session(nghttp2_session *session); + +/** + * @function + * + * Submits WINDOW_UPDATE frame. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |stream_id| is the stream ID to send this WINDOW_UPDATE. To + * send connection level WINDOW_UPDATE, specify 0 to |stream_id|. + * + * If the |window_size_increment| is positive, the WINDOW_UPDATE with + * that value as window_size_increment is queued. If the + * |window_size_increment| is larger than the received bytes from the + * remote endpoint, the local window size is increased by that + * difference. If the sole purpose is to increase the local window + * size, consider to use `nghttp2_session_set_local_window_size()`. + * + * If the |window_size_increment| is negative, the local window size + * is decreased by -|window_size_increment|. If automatic + * WINDOW_UPDATE is enabled + * (`nghttp2_option_set_no_auto_window_update()`), and the library + * decided that the WINDOW_UPDATE should be submitted, then + * WINDOW_UPDATE is queued with the current received bytes count. If + * the sole purpose is to decrease the local window size, consider to + * use `nghttp2_session_set_local_window_size()`. + * + * If the |window_size_increment| is 0, the function does nothing and + * returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_FLOW_CONTROL` + * The local window size overflow or gets negative. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session, + uint8_t flags, + int32_t stream_id, + int32_t window_size_increment); + +/** + * @function + * + * Set local window size (local endpoints's window size) to the given + * |window_size| for the given stream denoted by |stream_id|. To + * change connection level window size, specify 0 to |stream_id|. To + * increase window size, this function may submit WINDOW_UPDATE frame + * to transmission queue. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * This sounds similar to `nghttp2_submit_window_update()`, but there + * are 2 differences. The first difference is that this function + * takes the absolute value of window size to set, rather than the + * delta. To change the window size, this may be easier to use since + * the application just declares the intended window size, rather than + * calculating delta. The second difference is that + * `nghttp2_submit_window_update()` affects the received bytes count + * which has not acked yet. By the specification of + * `nghttp2_submit_window_update()`, to strictly increase the local + * window size, we have to submit delta including all received bytes + * count, which might not be desirable in some cases. On the other + * hand, this function does not affect the received bytes count. It + * just sets the local window size to the given value. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is negative. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_set_local_window_size(nghttp2_session *session, uint8_t flags, + int32_t stream_id, int32_t window_size); + +/** + * @function + * + * Submits extension frame. + * + * Application can pass arbitrary frame flags and stream ID in |flags| + * and |stream_id| respectively. The |payload| is opaque pointer, and + * it can be accessible though ``frame->ext.payload`` in + * :type:`nghttp2_pack_extension_callback`. The library will not own + * passed |payload| pointer. + * + * The application must set :type:`nghttp2_pack_extension_callback` + * using `nghttp2_session_callbacks_set_pack_extension_callback()`. + * + * The application should retain the memory pointed by |payload| until + * the transmission of extension frame is done (which is indicated by + * :type:`nghttp2_on_frame_send_callback`), or transmission fails + * (which is indicated by :type:`nghttp2_on_frame_not_send_callback`). + * If application does not touch this memory region after packing it + * into a wire format, application can free it inside + * :type:`nghttp2_pack_extension_callback`. + * + * The standard HTTP/2 frame cannot be sent with this function, so + * |type| must be strictly grater than 0x9. Otherwise, this function + * will fail with error code + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * If :type:`nghttp2_pack_extension_callback` is not set. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * If |type| specifies standard HTTP/2 frame type. The frame + * types in the rage [0x0, 0x9], both inclusive, are standard + * HTTP/2 frame type, and cannot be sent using this function. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory + */ +NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session, + uint8_t type, uint8_t flags, + int32_t stream_id, void *payload); + +/** + * @struct + * + * The payload of ALTSVC frame. ALTSVC frame is a non-critical + * extension to HTTP/2. If this frame is received, and + * `nghttp2_option_set_user_recv_extension_type()` is not set, and + * `nghttp2_option_set_builtin_recv_extension_type()` is set for + * :enum:`nghttp2_frame_type.NGHTTP2_ALTSVC`, + * ``nghttp2_extension.payload`` will point to this struct. + * + * It has the following members: + */ +typedef struct { + /** + * The pointer to origin which this alternative service is + * associated with. This is not necessarily NULL-terminated. + */ + uint8_t *origin; + /** + * The length of the |origin|. + */ + size_t origin_len; + /** + * The pointer to Alt-Svc field value contained in ALTSVC frame. + * This is not necessarily NULL-terminated. + */ + uint8_t *field_value; + /** + * The length of the |field_value|. + */ + size_t field_value_len; +} nghttp2_ext_altsvc; + +/** + * @function + * + * Submits ALTSVC frame. + * + * ALTSVC frame is a non-critical extension to HTTP/2, and defined in + * `RFC 7383 `_. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |origin| points to the origin this alternative service is + * associated with. The |origin_len| is the length of the origin. If + * |stream_id| is 0, the origin must be specified. If |stream_id| is + * not zero, the origin must be empty (in other words, |origin_len| + * must be 0). + * + * The ALTSVC frame is only usable from server side. If this function + * is invoked with client side session, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The function is called from client side session + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The sum of |origin_len| and |field_value_len| is larger than + * 16382; or |origin_len| is 0 while |stream_id| is 0; or + * |origin_len| is not 0 while |stream_id| is not 0. + */ +NGHTTP2_EXTERN int nghttp2_submit_altsvc(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + const uint8_t *origin, + size_t origin_len, + const uint8_t *field_value, + size_t field_value_len); + +/** + * @struct + * + * The single entry of an origin. + */ +typedef struct { + /** + * The pointer to origin. No validation is made against this field + * by the library. This is not necessarily NULL-terminated. + */ + uint8_t *origin; + /** + * The length of the |origin|. + */ + size_t origin_len; +} nghttp2_origin_entry; + +/** + * @struct + * + * The payload of ORIGIN frame. ORIGIN frame is a non-critical + * extension to HTTP/2 and defined by `RFC 8336 + * `_. + * + * If this frame is received, and + * `nghttp2_option_set_user_recv_extension_type()` is not set, and + * `nghttp2_option_set_builtin_recv_extension_type()` is set for + * :enum:`nghttp2_frame_type.NGHTTP2_ORIGIN`, + * ``nghttp2_extension.payload`` will point to this struct. + * + * It has the following members: + */ +typedef struct { + /** + * The number of origins contained in |ov|. + */ + size_t nov; + /** + * The pointer to the array of origins contained in ORIGIN frame. + */ + nghttp2_origin_entry *ov; +} nghttp2_ext_origin; + +/** + * @function + * + * Submits ORIGIN frame. + * + * ORIGIN frame is a non-critical extension to HTTP/2 and defined by + * `RFC 8336 `_. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |ov| points to the array of origins. The |nov| specifies the + * number of origins included in |ov|. This function creates copies + * of all elements in |ov|. + * + * The ORIGIN frame is only usable by a server. If this function is + * invoked with client side session, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The function is called from client side session. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * There are too many origins, or an origin is too large to fit + * into a default frame payload. + */ +NGHTTP2_EXTERN int nghttp2_submit_origin(nghttp2_session *session, + uint8_t flags, + const nghttp2_origin_entry *ov, + size_t nov); + +/** + * @struct + * + * The payload of PRIORITY_UPDATE frame. PRIORITY_UPDATE frame is a + * non-critical extension to HTTP/2. If this frame is received, and + * `nghttp2_option_set_user_recv_extension_type()` is not set, and + * `nghttp2_option_set_builtin_recv_extension_type()` is set for + * :enum:`nghttp2_frame_type.NGHTTP2_PRIORITY_UPDATE`, + * ``nghttp2_extension.payload`` will point to this struct. + * + * It has the following members: + */ +typedef struct { + /** + * The stream ID of the stream whose priority is updated. + */ + int32_t stream_id; + /** + * The pointer to Priority field value. It is not necessarily + * NULL-terminated. + */ + uint8_t *field_value; + /** + * The length of the :member:`field_value`. + */ + size_t field_value_len; +} nghttp2_ext_priority_update; + +/** + * @function + * + * Submits PRIORITY_UPDATE frame. + * + * PRIORITY_UPDATE frame is a non-critical extension to HTTP/2, and + * defined in :rfc:`9218#section-7.1`. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |stream_id| is the ID of stream which is prioritized. The + * |field_value| points to the Priority field value. The + * |field_value_len| is the length of the Priority field value. + * + * If this function is called by server, + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` is returned. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 0 is received by a remote endpoint (or it is omitted), + * this function does nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The function is called from server side session + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |field_value_len| is larger than 16380; or |stream_id| is + * 0. + */ +NGHTTP2_EXTERN int nghttp2_submit_priority_update(nghttp2_session *session, + uint8_t flags, + int32_t stream_id, + const uint8_t *field_value, + size_t field_value_len); + +/** + * @function + * + * Changes the priority of the existing stream denoted by |stream_id|. + * The new priority is |extpri|. This function is meant to be used by + * server for :rfc:`9218` extensible prioritization scheme. + * + * If |session| is initialized as client, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. For client, use + * `nghttp2_submit_priority_update()` instead. + * + * If :member:`extpri->urgency ` is out of + * bound, it is set to :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`. + * + * If |ignore_client_signal| is nonzero, server starts to ignore + * client priority signals for this stream. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is not submitted via `nghttp2_submit_settings()`, + * this function does nothing and returns 0. + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The |session| is initialized as client. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * |stream_id| is zero; or a stream denoted by |stream_id| is not + * found. + */ +NGHTTP2_EXTERN int nghttp2_session_change_extpri_stream_priority( + nghttp2_session *session, int32_t stream_id, const nghttp2_extpri *extpri, + int ignore_client_signal); + +/** + * @function + * + * Stores the stream priority of the existing stream denoted by + * |stream_id| in the object pointed by |extpri|. This function is + * meant to be used by server for :rfc:`9218` extensible + * prioritization scheme. + * + * If |session| is initialized as client, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is not submitted via `nghttp2_submit_settings()`, + * this function does nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The |session| is initialized as client. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * |stream_id| is zero; or a stream denoted by |stream_id| is not + * found. + */ +NGHTTP2_EXTERN int nghttp2_session_get_extpri_stream_priority( + nghttp2_session *session, nghttp2_extpri *extpri, int32_t stream_id); + +/** + * @function + * + * Parses Priority header field value pointed by |value| of length + * |len|, and stores the result in the object pointed by |extpri|. + * Priority header field is defined in :rfc:`9218`. + * + * This function does not initialize the object pointed by |extpri| + * before storing the result. It only assigns the values that the + * parser correctly extracted to fields. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Failed to parse the header field value. + */ +NGHTTP2_EXTERN int nghttp2_extpri_parse_priority(nghttp2_extpri *extpri, + const uint8_t *value, + size_t len); + +/** + * @function + * + * Compares ``lhs->name`` of length ``lhs->namelen`` bytes and + * ``rhs->name`` of length ``rhs->namelen`` bytes. Returns negative + * integer if ``lhs->name`` is found to be less than ``rhs->name``; or + * returns positive integer if ``lhs->name`` is found to be greater + * than ``rhs->name``; or returns 0 otherwise. + */ +NGHTTP2_EXTERN int nghttp2_nv_compare_name(const nghttp2_nv *lhs, + const nghttp2_nv *rhs); + +/** + * @function + * + * A helper function for dealing with NPN in client side or ALPN in + * server side. The |in| contains peer's protocol list in preferable + * order. The format of |in| is length-prefixed and not + * null-terminated. For example, ``h2`` and + * ``http/1.1`` stored in |in| like this:: + * + * in[0] = 2 + * in[1..2] = "h2" + * in[3] = 8 + * in[4..11] = "http/1.1" + * inlen = 12 + * + * The selection algorithm is as follows: + * + * 1. If peer's list contains HTTP/2 protocol the library supports, + * it is selected and returns 1. The following step is not taken. + * + * 2. If peer's list contains ``http/1.1``, this function selects + * ``http/1.1`` and returns 0. The following step is not taken. + * + * 3. This function selects nothing and returns -1 (So called + * non-overlap case). In this case, |out| and |outlen| are left + * untouched. + * + * Selecting ``h2`` means that ``h2`` is written into |*out| and its + * length (which is 2) is assigned to |*outlen|. + * + * For ALPN, refer to https://tools.ietf.org/html/rfc7301 + * + * See http://technotes.googlecode.com/git/nextprotoneg.html for more + * details about NPN. + * + * For NPN, to use this method you should do something like:: + * + * static int select_next_proto_cb(SSL* ssl, + * unsigned char **out, + * unsigned char *outlen, + * const unsigned char *in, + * unsigned int inlen, + * void *arg) + * { + * int rv; + * rv = nghttp2_select_next_protocol(out, outlen, in, inlen); + * if (rv == -1) { + * return SSL_TLSEXT_ERR_NOACK; + * } + * if (rv == 1) { + * ((MyType*)arg)->http2_selected = 1; + * } + * return SSL_TLSEXT_ERR_OK; + * } + * ... + * SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, my_obj); + * + */ +NGHTTP2_EXTERN int nghttp2_select_next_protocol(unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen); + +/** + * @function + * + * Returns a pointer to a nghttp2_info struct with version information + * about the run-time library in use. The |least_version| argument + * can be set to a 24 bit numerical value for the least accepted + * version number and if the condition is not met, this function will + * return a ``NULL``. Pass in 0 to skip the version checking. + */ +NGHTTP2_EXTERN nghttp2_info *nghttp2_version(int least_version); + +/** + * @function + * + * Returns nonzero if the :type:`nghttp2_error` library error code + * |lib_error| is fatal. + */ +NGHTTP2_EXTERN int nghttp2_is_fatal(int lib_error_code); + +/** + * @function + * + * Returns nonzero if HTTP header field name |name| of length |len| is + * valid according to http://tools.ietf.org/html/rfc7230#section-3.2 + * + * Because this is a header field name in HTTP2, the upper cased alphabet + * is treated as error. + */ +NGHTTP2_EXTERN int nghttp2_check_header_name(const uint8_t *name, size_t len); + +/** + * @function + * + * Returns nonzero if HTTP header field value |value| of length |len| + * is valid according to + * http://tools.ietf.org/html/rfc7230#section-3.2 + * + * This function is considered obsolete, and application should + * consider to use `nghttp2_check_header_value_rfc9113()` instead. + */ +NGHTTP2_EXTERN int nghttp2_check_header_value(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if HTTP header field value |value| of length |len| + * is valid according to + * http://tools.ietf.org/html/rfc7230#section-3.2, plus + * https://datatracker.ietf.org/doc/html/rfc9113#section-8.2.1 + */ +NGHTTP2_EXTERN int nghttp2_check_header_value_rfc9113(const uint8_t *value, + size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of + * the :method header field is valid according to + * https://datatracker.ietf.org/doc/html/rfc7231#section-4 and + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 + */ +NGHTTP2_EXTERN int nghttp2_check_method(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of + * the :path header field is valid according to + * https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3 + * + * |value| is valid if it merely consists of the allowed characters. + * In particular, it does not check whether |value| follows the syntax + * of path. The allowed characters are all characters valid by + * `nghttp2_check_header_value` minus SPC and HT. + */ +NGHTTP2_EXTERN int nghttp2_check_path(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of the + * :authority or host header field is valid according to + * https://tools.ietf.org/html/rfc3986#section-3.2 + * + * |value| is valid if it merely consists of the allowed characters. + * In particular, it does not check whether |value| follows the syntax + * of authority. + */ +NGHTTP2_EXTERN int nghttp2_check_authority(const uint8_t *value, size_t len); + +/* HPACK API */ + +struct nghttp2_hd_deflater; + +/** + * @struct + * + * HPACK deflater object. + */ +typedef struct nghttp2_hd_deflater nghttp2_hd_deflater; + +/** + * @function + * + * Initializes |*deflater_ptr| for deflating name/values pairs. + * + * The |max_deflate_dynamic_table_size| is the upper bound of header + * table size the deflater will use. + * + * If this function fails, |*deflater_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, + size_t max_deflate_dynamic_table_size); + +/** + * @function + * + * Like `nghttp2_hd_deflate_new()`, but with additional custom memory + * allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_hd_deflate_new()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, + size_t max_deflate_dynamic_table_size, + nghttp2_mem *mem); + +/** + * @function + * + * Deallocates any resources allocated for |deflater|. + */ +NGHTTP2_EXTERN void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater); + +/** + * @function + * + * Changes header table size of the |deflater| to + * |settings_max_dynamic_table_size| bytes. This may trigger eviction + * in the dynamic table. + * + * The |settings_max_dynamic_table_size| should be the value received + * in SETTINGS_HEADER_TABLE_SIZE. + * + * The deflater never uses more memory than + * ``max_deflate_dynamic_table_size`` bytes specified in + * `nghttp2_hd_deflate_new()`. Therefore, if + * |settings_max_dynamic_table_size| > + * ``max_deflate_dynamic_table_size``, resulting maximum table size + * becomes ``max_deflate_dynamic_table_size``. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, + size_t settings_max_dynamic_table_size); + +/** + * @function + * + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |buf| of length |buflen|. + * + * If |buf| is not large enough to store the deflated header block, + * this function fails with + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller + * should use `nghttp2_hd_deflate_bound()` to know the upper bound of + * buffer size required to deflate given header name/value pairs. + * + * Once this function fails, subsequent call of this function always + * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns the number of bytes written to |buf| if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Deflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, + uint8_t *buf, size_t buflen, + const nghttp2_nv *nva, + size_t nvlen); + +/** + * @function + * + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |veclen| size of buf vector |vec|. The each size of buffer + * must be set in len field of :type:`nghttp2_vec`. If and only if + * one chunk is filled up completely, next chunk will be used. If + * |vec| is not large enough to store the deflated header block, this + * function fails with + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller + * should use `nghttp2_hd_deflate_bound()` to know the upper bound of + * buffer size required to deflate given header name/value pairs. + * + * Once this function fails, subsequent call of this function always + * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns the number of bytes written to |vec| if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Deflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, + const nghttp2_vec *vec, + size_t veclen, + const nghttp2_nv *nva, + size_t nvlen); + +/** + * @function + * + * Returns an upper bound on the compressed size after deflation of + * |nva| of length |nvlen|. + */ +NGHTTP2_EXTERN size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, + const nghttp2_nv *nva, + size_t nvlen); + +/** + * @function + * + * Returns the number of entries that header table of |deflater| + * contains. This is the sum of the number of static table and + * dynamic table, so the return value is at least 61. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater); + +/** + * @function + * + * Returns the table entry denoted by |idx| from header table of + * |deflater|. The |idx| is 1-based, and idx=1 returns first entry of + * static table. idx=62 returns first entry of dynamic table if it + * exists. Specifying idx=0 is error, and this function returns NULL. + * If |idx| is strictly greater than the number of entries the tables + * contain, this function returns NULL. + */ +NGHTTP2_EXTERN +const nghttp2_nv * +nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx); + +/** + * @function + * + * Returns the used dynamic table size, including the overhead 32 + * bytes per entry described in RFC 7541. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_deflate_get_dynamic_table_size(nghttp2_hd_deflater *deflater); + +/** + * @function + * + * Returns the maximum dynamic table size. + */ +NGHTTP2_EXTERN +size_t +nghttp2_hd_deflate_get_max_dynamic_table_size(nghttp2_hd_deflater *deflater); + +struct nghttp2_hd_inflater; + +/** + * @struct + * + * HPACK inflater object. + */ +typedef struct nghttp2_hd_inflater nghttp2_hd_inflater; + +/** + * @function + * + * Initializes |*inflater_ptr| for inflating name/values pairs. + * + * If this function fails, |*inflater_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr); + +/** + * @function + * + * Like `nghttp2_hd_inflate_new()`, but with additional custom memory + * allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_hd_inflate_new()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + */ +NGHTTP2_EXTERN int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr, + nghttp2_mem *mem); + +/** + * @function + * + * Deallocates any resources allocated for |inflater|. + */ +NGHTTP2_EXTERN void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater); + +/** + * @function + * + * Changes header table size in the |inflater|. This may trigger + * eviction in the dynamic table. + * + * The |settings_max_dynamic_table_size| should be the value + * transmitted in SETTINGS_HEADER_TABLE_SIZE. + * + * This function must not be called while header block is being + * inflated. In other words, this function must be called after + * initialization of |inflater|, but before calling + * `nghttp2_hd_inflate_hd2()`, or after + * `nghttp2_hd_inflate_end_headers()`. Otherwise, + * `NGHTTP2_ERR_INVALID_STATE` was returned. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The function is called while header block is being inflated. + * Probably, application missed to call + * `nghttp2_hd_inflate_end_headers()`. + */ +NGHTTP2_EXTERN int +nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater, + size_t settings_max_dynamic_table_size); + +/** + * @enum + * + * The flags for header inflation. + */ +typedef enum { + /** + * No flag set. + */ + NGHTTP2_HD_INFLATE_NONE = 0, + /** + * Indicates all headers were inflated. + */ + NGHTTP2_HD_INFLATE_FINAL = 0x01, + /** + * Indicates a header was emitted. + */ + NGHTTP2_HD_INFLATE_EMIT = 0x02 +} nghttp2_hd_inflate_flag; + +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_hd_inflate_hd2()` instead. + * + * Inflates name/value block stored in |in| with length |inlen|. This + * function performs decompression. For each successful emission of + * header name/value pair, + * :enum:`nghttp2_hd_inflate_flag.NGHTTP2_HD_INFLATE_EMIT` is set in + * |*inflate_flags| and name/value pair is assigned to the |nv_out| + * and the function returns. The caller must not free the members of + * |nv_out|. + * + * The |nv_out| may include pointers to the memory region in the |in|. + * The caller must retain the |in| while the |nv_out| is used. + * + * The application should call this function repeatedly until the + * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and + * return value is non-negative. This means the all input values are + * processed successfully. Then the application must call + * `nghttp2_hd_inflate_end_headers()` to prepare for the next header + * block input. + * + * The caller can feed complete compressed header block. It also can + * feed it in several chunks. The caller must set |in_final| to + * nonzero if the given input is the last block of the compressed + * header. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Inflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR` + * The header field name or value is too large. + * + * Example follows:: + * + * int inflate_header_block(nghttp2_hd_inflater *hd_inflater, + * uint8_t *in, size_t inlen, int final) + * { + * ssize_t rv; + * + * for(;;) { + * nghttp2_nv nv; + * int inflate_flags = 0; + * + * rv = nghttp2_hd_inflate_hd(hd_inflater, &nv, &inflate_flags, + * in, inlen, final); + * + * if(rv < 0) { + * fprintf(stderr, "inflate failed with error code %zd", rv); + * return -1; + * } + * + * in += rv; + * inlen -= rv; + * + * if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + * fwrite(nv.name, nv.namelen, 1, stderr); + * fprintf(stderr, ": "); + * fwrite(nv.value, nv.valuelen, 1, stderr); + * fprintf(stderr, "\n"); + * } + * if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + * nghttp2_hd_inflate_end_headers(hd_inflater); + * break; + * } + * if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && + * inlen == 0) { + * break; + * } + * } + * + * return 0; + * } + * + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, + nghttp2_nv *nv_out, + int *inflate_flags, uint8_t *in, + size_t inlen, int in_final); + +/** + * @function + * + * Inflates name/value block stored in |in| with length |inlen|. This + * function performs decompression. For each successful emission of + * header name/value pair, + * :enum:`nghttp2_hd_inflate_flag.NGHTTP2_HD_INFLATE_EMIT` is set in + * |*inflate_flags| and name/value pair is assigned to the |nv_out| + * and the function returns. The caller must not free the members of + * |nv_out|. + * + * The |nv_out| may include pointers to the memory region in the |in|. + * The caller must retain the |in| while the |nv_out| is used. + * + * The application should call this function repeatedly until the + * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and + * return value is non-negative. If that happens, all given input + * data (|inlen| bytes) are processed successfully. Then the + * application must call `nghttp2_hd_inflate_end_headers()` to prepare + * for the next header block input. + * + * In other words, if |in_final| is nonzero, and this function returns + * |inlen|, you can assert that + * :enum:`nghttp2_hd_inflate_final.NGHTTP2_HD_INFLATE_FINAL` is set in + * |*inflate_flags|. + * + * The caller can feed complete compressed header block. It also can + * feed it in several chunks. The caller must set |in_final| to + * nonzero if the given input is the last block of the compressed + * header. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Inflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR` + * The header field name or value is too large. + * + * Example follows:: + * + * int inflate_header_block(nghttp2_hd_inflater *hd_inflater, + * uint8_t *in, size_t inlen, int final) + * { + * ssize_t rv; + * + * for(;;) { + * nghttp2_nv nv; + * int inflate_flags = 0; + * + * rv = nghttp2_hd_inflate_hd2(hd_inflater, &nv, &inflate_flags, + * in, inlen, final); + * + * if(rv < 0) { + * fprintf(stderr, "inflate failed with error code %zd", rv); + * return -1; + * } + * + * in += rv; + * inlen -= rv; + * + * if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + * fwrite(nv.name, nv.namelen, 1, stderr); + * fprintf(stderr, ": "); + * fwrite(nv.value, nv.valuelen, 1, stderr); + * fprintf(stderr, "\n"); + * } + * if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + * nghttp2_hd_inflate_end_headers(hd_inflater); + * break; + * } + * if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && + * inlen == 0) { + * break; + * } + * } + * + * return 0; + * } + * + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, + nghttp2_nv *nv_out, + int *inflate_flags, + const uint8_t *in, size_t inlen, + int in_final); + +/** + * @function + * + * Signals the end of decompression for one header block. + * + * This function returns 0 if it succeeds. Currently this function + * always succeeds. + */ +NGHTTP2_EXTERN int +nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater); + +/** + * @function + * + * Returns the number of entries that header table of |inflater| + * contains. This is the sum of the number of static table and + * dynamic table, so the return value is at least 61. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater); + +/** + * @function + * + * Returns the table entry denoted by |idx| from header table of + * |inflater|. The |idx| is 1-based, and idx=1 returns first entry of + * static table. idx=62 returns first entry of dynamic table if it + * exists. Specifying idx=0 is error, and this function returns NULL. + * If |idx| is strictly greater than the number of entries the tables + * contain, this function returns NULL. + */ +NGHTTP2_EXTERN +const nghttp2_nv * +nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx); + +/** + * @function + * + * Returns the used dynamic table size, including the overhead 32 + * bytes per entry described in RFC 7541. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_inflate_get_dynamic_table_size(nghttp2_hd_inflater *inflater); + +/** + * @function + * + * Returns the maximum dynamic table size. + */ +NGHTTP2_EXTERN +size_t +nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater); + +struct nghttp2_stream; + +/** + * @struct + * + * The structure to represent HTTP/2 stream. The details of this + * structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_stream nghttp2_stream; + +/** + * @function + * + * Returns pointer to :type:`nghttp2_stream` object denoted by + * |stream_id|. If stream was not found, returns NULL. + * + * Returns imaginary root stream (see + * `nghttp2_session_get_root_stream()`) if 0 is given in |stream_id|. + * + * Unless |stream_id| == 0, the returned pointer is valid until next + * call of `nghttp2_session_send()`, `nghttp2_session_mem_send()`, + * `nghttp2_session_recv()`, and `nghttp2_session_mem_recv()`. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_session_find_stream(nghttp2_session *session, int32_t stream_id); + +/** + * @enum + * + * State of stream as described in RFC 7540. + */ +typedef enum { + /** + * idle state. + */ + NGHTTP2_STREAM_STATE_IDLE = 1, + /** + * open state. + */ + NGHTTP2_STREAM_STATE_OPEN, + /** + * reserved (local) state. + */ + NGHTTP2_STREAM_STATE_RESERVED_LOCAL, + /** + * reserved (remote) state. + */ + NGHTTP2_STREAM_STATE_RESERVED_REMOTE, + /** + * half closed (local) state. + */ + NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL, + /** + * half closed (remote) state. + */ + NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE, + /** + * closed state. + */ + NGHTTP2_STREAM_STATE_CLOSED +} nghttp2_stream_proto_state; + +/** + * @function + * + * Returns state of |stream|. The root stream retrieved by + * `nghttp2_session_get_root_stream()` will have stream state + * :enum:`nghttp2_stream_proto_state.NGHTTP2_STREAM_STATE_IDLE`. + */ +NGHTTP2_EXTERN nghttp2_stream_proto_state +nghttp2_stream_get_state(nghttp2_stream *stream); + +/** + * @function + * + * Returns root of dependency tree, which is imaginary stream with + * stream ID 0. The returned pointer is valid until |session| is + * freed by `nghttp2_session_del()`. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_session_get_root_stream(nghttp2_session *session); + +/** + * @function + * + * Returns the parent stream of |stream| in dependency tree. Returns + * NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_parent(nghttp2_stream *stream); + +NGHTTP2_EXTERN int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream); + +/** + * @function + * + * Returns the next sibling stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_next_sibling(nghttp2_stream *stream); + +/** + * @function + * + * Returns the previous sibling stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_previous_sibling(nghttp2_stream *stream); + +/** + * @function + * + * Returns the first child stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_first_child(nghttp2_stream *stream); + +/** + * @function + * + * Returns dependency weight to the parent stream of |stream|. + */ +NGHTTP2_EXTERN int32_t nghttp2_stream_get_weight(nghttp2_stream *stream); + +/** + * @function + * + * Returns the sum of the weight for |stream|'s children. + */ +NGHTTP2_EXTERN int32_t +nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream); + +/** + * @functypedef + * + * Callback function invoked when the library outputs debug logging. + * The function is called with arguments suitable for ``vfprintf(3)`` + * + * The debug output is only enabled if the library is built with + * ``DEBUGBUILD`` macro defined. + */ +typedef void (*nghttp2_debug_vprintf_callback)(const char *format, + va_list args); + +/** + * @function + * + * Sets a debug output callback called by the library when built with + * ``DEBUGBUILD`` macro defined. If this option is not used, debug + * log is written into standard error output. + * + * For builds without ``DEBUGBUILD`` macro defined, this function is + * noop. + * + * Note that building with ``DEBUGBUILD`` may cause significant + * performance penalty to libnghttp2 because of extra processing. It + * should be used for debugging purpose only. + * + * .. Warning:: + * + * Building with ``DEBUGBUILD`` may cause significant performance + * penalty to libnghttp2 because of extra processing. It should be + * used for debugging purpose only. We write this two times because + * this is important. + */ +NGHTTP2_EXTERN void nghttp2_set_debug_vprintf_callback( + nghttp2_debug_vprintf_callback debug_vprintf_callback); + +#ifdef __cplusplus +} +#endif + +#endif /* NGHTTP2_H */ diff --git a/lib/nghttp2/lib/includes/nghttp2/nghttp2ver.h.in b/lib/nghttp2/lib/includes/nghttp2/nghttp2ver.h.in new file mode 100644 index 00000000000..7717a647f75 --- /dev/null +++ b/lib/nghttp2/lib/includes/nghttp2/nghttp2ver.h.in @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2VER_H +#define NGHTTP2VER_H + +/** + * @macro + * Version number of the nghttp2 library release + */ +#define NGHTTP2_VERSION "@PACKAGE_VERSION@" + +/** + * @macro + * Numerical representation of the version number of the nghttp2 library + * release. This is a 24 bit number with 8 bits for major number, 8 bits + * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. + */ +#define NGHTTP2_VERSION_NUM @PACKAGE_VERSION_NUM@ + +#endif /* NGHTTP2VER_H */ diff --git a/lib/nghttp2/lib/libnghttp2.pc.in b/lib/nghttp2/lib/libnghttp2.pc.in new file mode 100644 index 00000000000..da6938f2847 --- /dev/null +++ b/lib/nghttp2/lib/libnghttp2.pc.in @@ -0,0 +1,33 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libnghttp2 +Description: HTTP/2 C library +URL: https://github.com/tatsuhiro-t/nghttp2 +Version: @VERSION@ +Libs: -L${libdir} -lnghttp2 +Cflags: -I${includedir} diff --git a/lib/nghttp2/lib/nghttp2_buf.c b/lib/nghttp2/lib/nghttp2_buf.c new file mode 100644 index 00000000000..a32844712e4 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_buf.c @@ -0,0 +1,527 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_buf.h" + +#include + +#include "nghttp2_helper.h" +#include "nghttp2_debug.h" + +void nghttp2_buf_init(nghttp2_buf *buf) { + buf->begin = NULL; + buf->end = NULL; + buf->pos = NULL; + buf->last = NULL; + buf->mark = NULL; +} + +int nghttp2_buf_init2(nghttp2_buf *buf, size_t initial, nghttp2_mem *mem) { + nghttp2_buf_init(buf); + return nghttp2_buf_reserve(buf, initial, mem); +} + +void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem) { + if (buf == NULL) { + return; + } + + nghttp2_mem_free(mem, buf->begin); + buf->begin = NULL; +} + +int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem) { + uint8_t *ptr; + size_t cap; + + cap = nghttp2_buf_cap(buf); + + if (cap >= new_cap) { + return 0; + } + + new_cap = nghttp2_max(new_cap, cap * 2); + + ptr = nghttp2_mem_realloc(mem, buf->begin, new_cap); + if (ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + buf->pos = ptr + (buf->pos - buf->begin); + buf->last = ptr + (buf->last - buf->begin); + buf->mark = ptr + (buf->mark - buf->begin); + buf->begin = ptr; + buf->end = ptr + new_cap; + + return 0; +} + +void nghttp2_buf_reset(nghttp2_buf *buf) { + buf->pos = buf->last = buf->mark = buf->begin; +} + +void nghttp2_buf_wrap_init(nghttp2_buf *buf, uint8_t *begin, size_t len) { + buf->begin = buf->pos = buf->last = buf->mark = buf->end = begin; + if (len) { + buf->end += len; + } +} + +static int buf_chain_new(nghttp2_buf_chain **chain, size_t chunk_length, + nghttp2_mem *mem) { + int rv; + + *chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain)); + if (*chain == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + (*chain)->next = NULL; + + rv = nghttp2_buf_init2(&(*chain)->buf, chunk_length, mem); + if (rv != 0) { + nghttp2_mem_free(mem, *chain); + return NGHTTP2_ERR_NOMEM; + } + + return 0; +} + +static void buf_chain_del(nghttp2_buf_chain *chain, nghttp2_mem *mem) { + nghttp2_buf_free(&chain->buf, mem); + nghttp2_mem_free(mem, chain); +} + +int nghttp2_bufs_init(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, + nghttp2_mem *mem) { + return nghttp2_bufs_init2(bufs, chunk_length, max_chunk, 0, mem); +} + +int nghttp2_bufs_init2(nghttp2_bufs *bufs, size_t chunk_length, + size_t max_chunk, size_t offset, nghttp2_mem *mem) { + return nghttp2_bufs_init3(bufs, chunk_length, max_chunk, max_chunk, offset, + mem); +} + +int nghttp2_bufs_init3(nghttp2_bufs *bufs, size_t chunk_length, + size_t max_chunk, size_t chunk_keep, size_t offset, + nghttp2_mem *mem) { + int rv; + nghttp2_buf_chain *chain; + + if (chunk_keep == 0 || max_chunk < chunk_keep || chunk_length < offset) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + rv = buf_chain_new(&chain, chunk_length, mem); + if (rv != 0) { + return rv; + } + + bufs->mem = mem; + bufs->offset = offset; + + bufs->head = chain; + bufs->cur = bufs->head; + + nghttp2_buf_shift_right(&bufs->cur->buf, offset); + + bufs->chunk_length = chunk_length; + bufs->chunk_used = 1; + bufs->max_chunk = max_chunk; + bufs->chunk_keep = chunk_keep; + + return 0; +} + +int nghttp2_bufs_realloc(nghttp2_bufs *bufs, size_t chunk_length) { + int rv; + nghttp2_buf_chain *chain; + + if (chunk_length < bufs->offset) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + rv = buf_chain_new(&chain, chunk_length, bufs->mem); + if (rv != 0) { + return rv; + } + + nghttp2_bufs_free(bufs); + + bufs->head = chain; + bufs->cur = bufs->head; + + nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset); + + bufs->chunk_length = chunk_length; + bufs->chunk_used = 1; + + return 0; +} + +void nghttp2_bufs_free(nghttp2_bufs *bufs) { + nghttp2_buf_chain *chain, *next_chain; + + if (bufs == NULL) { + return; + } + + for (chain = bufs->head; chain;) { + next_chain = chain->next; + + buf_chain_del(chain, bufs->mem); + + chain = next_chain; + } + + bufs->head = NULL; +} + +int nghttp2_bufs_wrap_init(nghttp2_bufs *bufs, uint8_t *begin, size_t len, + nghttp2_mem *mem) { + nghttp2_buf_chain *chain; + + chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain)); + if (chain == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + chain->next = NULL; + + nghttp2_buf_wrap_init(&chain->buf, begin, len); + + bufs->mem = mem; + bufs->offset = 0; + + bufs->head = chain; + bufs->cur = bufs->head; + + bufs->chunk_length = len; + bufs->chunk_used = 1; + bufs->max_chunk = 1; + bufs->chunk_keep = 1; + + return 0; +} + +int nghttp2_bufs_wrap_init2(nghttp2_bufs *bufs, const nghttp2_vec *vec, + size_t veclen, nghttp2_mem *mem) { + size_t i = 0; + nghttp2_buf_chain *cur_chain; + nghttp2_buf_chain *head_chain; + nghttp2_buf_chain **dst_chain = &head_chain; + + if (veclen == 0) { + return nghttp2_bufs_wrap_init(bufs, NULL, 0, mem); + } + + head_chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain) * veclen); + if (head_chain == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + for (i = 0; i < veclen; ++i) { + cur_chain = &head_chain[i]; + cur_chain->next = NULL; + nghttp2_buf_wrap_init(&cur_chain->buf, vec[i].base, vec[i].len); + + *dst_chain = cur_chain; + dst_chain = &cur_chain->next; + } + + bufs->mem = mem; + bufs->offset = 0; + + bufs->head = head_chain; + bufs->cur = bufs->head; + + /* We don't use chunk_length since no allocation is expected. */ + bufs->chunk_length = 0; + bufs->chunk_used = veclen; + bufs->max_chunk = veclen; + bufs->chunk_keep = veclen; + + return 0; +} + +void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs) { + if (bufs == NULL) { + return; + } + + if (bufs->head) { + nghttp2_mem_free(bufs->mem, bufs->head); + } +} + +void nghttp2_bufs_seek_last_present(nghttp2_bufs *bufs) { + nghttp2_buf_chain *ci; + + for (ci = bufs->cur; ci; ci = ci->next) { + if (nghttp2_buf_len(&ci->buf) == 0) { + return; + } else { + bufs->cur = ci; + } + } +} + +size_t nghttp2_bufs_len(nghttp2_bufs *bufs) { + nghttp2_buf_chain *ci; + size_t len; + + len = 0; + for (ci = bufs->head; ci; ci = ci->next) { + len += nghttp2_buf_len(&ci->buf); + } + + return len; +} + +static int bufs_alloc_chain(nghttp2_bufs *bufs) { + int rv; + nghttp2_buf_chain *chain; + + if (bufs->cur->next) { + bufs->cur = bufs->cur->next; + + return 0; + } + + if (bufs->max_chunk == bufs->chunk_used) { + return NGHTTP2_ERR_BUFFER_ERROR; + } + + rv = buf_chain_new(&chain, bufs->chunk_length, bufs->mem); + if (rv != 0) { + return rv; + } + + DEBUGF("new buffer %zu bytes allocated for bufs %p, used %zu\n", + bufs->chunk_length, bufs, bufs->chunk_used); + + ++bufs->chunk_used; + + bufs->cur->next = chain; + bufs->cur = chain; + + nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset); + + return 0; +} + +int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len) { + int rv; + size_t nwrite; + nghttp2_buf *buf; + const uint8_t *p; + + p = data; + + while (len) { + buf = &bufs->cur->buf; + + nwrite = nghttp2_min(nghttp2_buf_avail(buf), len); + if (nwrite == 0) { + rv = bufs_alloc_chain(bufs); + if (rv != 0) { + return rv; + } + continue; + } + + buf->last = nghttp2_cpymem(buf->last, p, nwrite); + p += nwrite; + len -= nwrite; + } + + return 0; +} + +static int bufs_ensure_addb(nghttp2_bufs *bufs) { + int rv; + nghttp2_buf *buf; + + buf = &bufs->cur->buf; + + if (nghttp2_buf_avail(buf) > 0) { + return 0; + } + + rv = bufs_alloc_chain(bufs); + if (rv != 0) { + return rv; + } + + return 0; +} + +int nghttp2_bufs_addb(nghttp2_bufs *bufs, uint8_t b) { + int rv; + + rv = bufs_ensure_addb(bufs); + if (rv != 0) { + return rv; + } + + *bufs->cur->buf.last++ = b; + + return 0; +} + +int nghttp2_bufs_addb_hold(nghttp2_bufs *bufs, uint8_t b) { + int rv; + + rv = bufs_ensure_addb(bufs); + if (rv != 0) { + return rv; + } + + *bufs->cur->buf.last = b; + + return 0; +} + +int nghttp2_bufs_orb(nghttp2_bufs *bufs, uint8_t b) { + int rv; + + rv = bufs_ensure_addb(bufs); + if (rv != 0) { + return rv; + } + + *bufs->cur->buf.last++ |= b; + + return 0; +} + +int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b) { + int rv; + + rv = bufs_ensure_addb(bufs); + if (rv != 0) { + return rv; + } + + *bufs->cur->buf.last |= b; + + return 0; +} + +ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out) { + size_t len; + nghttp2_buf_chain *chain; + nghttp2_buf *buf; + uint8_t *res; + nghttp2_buf resbuf; + + len = 0; + + for (chain = bufs->head; chain; chain = chain->next) { + len += nghttp2_buf_len(&chain->buf); + } + + if (len == 0) { + res = NULL; + return 0; + } + + res = nghttp2_mem_malloc(bufs->mem, len); + if (res == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&resbuf, res, len); + + for (chain = bufs->head; chain; chain = chain->next) { + buf = &chain->buf; + resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf)); + } + + *out = res; + + return (ssize_t)len; +} + +size_t nghttp2_bufs_remove_copy(nghttp2_bufs *bufs, uint8_t *out) { + size_t len; + nghttp2_buf_chain *chain; + nghttp2_buf *buf; + nghttp2_buf resbuf; + + len = nghttp2_bufs_len(bufs); + + nghttp2_buf_wrap_init(&resbuf, out, len); + + for (chain = bufs->head; chain; chain = chain->next) { + buf = &chain->buf; + resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf)); + } + + return len; +} + +void nghttp2_bufs_reset(nghttp2_bufs *bufs) { + nghttp2_buf_chain *chain, *ci; + size_t k; + + k = bufs->chunk_keep; + + for (ci = bufs->head; ci; ci = ci->next) { + nghttp2_buf_reset(&ci->buf); + nghttp2_buf_shift_right(&ci->buf, bufs->offset); + + if (--k == 0) { + break; + } + } + + if (ci) { + chain = ci->next; + ci->next = NULL; + + for (ci = chain; ci;) { + chain = ci->next; + + buf_chain_del(ci, bufs->mem); + + ci = chain; + } + + bufs->chunk_used = bufs->chunk_keep; + } + + bufs->cur = bufs->head; +} + +int nghttp2_bufs_advance(nghttp2_bufs *bufs) { return bufs_alloc_chain(bufs); } + +int nghttp2_bufs_next_present(nghttp2_bufs *bufs) { + nghttp2_buf_chain *chain; + + chain = bufs->cur->next; + + return chain && nghttp2_buf_len(&chain->buf); +} diff --git a/lib/nghttp2/lib/nghttp2_buf.h b/lib/nghttp2/lib/nghttp2_buf.h new file mode 100644 index 00000000000..45f62f16e27 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_buf.h @@ -0,0 +1,412 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_BUF_H +#define NGHTTP2_BUF_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#include "nghttp2_int.h" +#include "nghttp2_mem.h" + +typedef struct { + /* This points to the beginning of the buffer. The effective range + of buffer is [begin, end). */ + uint8_t *begin; + /* This points to the memory one byte beyond the end of the + buffer. */ + uint8_t *end; + /* The position indicator for effective start of the buffer. pos <= + last must be hold. */ + uint8_t *pos; + /* The position indicator for effective one beyond of the end of the + buffer. last <= end must be hold. */ + uint8_t *last; + /* Mark arbitrary position in buffer [begin, end) */ + uint8_t *mark; +} nghttp2_buf; + +#define nghttp2_buf_len(BUF) ((size_t)((BUF)->last - (BUF)->pos)) +#define nghttp2_buf_avail(BUF) ((size_t)((BUF)->end - (BUF)->last)) +#define nghttp2_buf_mark_avail(BUF) ((size_t)((BUF)->mark - (BUF)->last)) +#define nghttp2_buf_cap(BUF) ((size_t)((BUF)->end - (BUF)->begin)) + +#define nghttp2_buf_pos_offset(BUF) ((size_t)((BUF)->pos - (BUF)->begin)) +#define nghttp2_buf_last_offset(BUF) ((size_t)((BUF)->last - (BUF)->begin)) + +#define nghttp2_buf_shift_right(BUF, AMT) \ + do { \ + (BUF)->pos += AMT; \ + (BUF)->last += AMT; \ + } while (0) + +#define nghttp2_buf_shift_left(BUF, AMT) \ + do { \ + (BUF)->pos -= AMT; \ + (BUF)->last -= AMT; \ + } while (0) + +/* + * Initializes the |buf|. No memory is allocated in this function. Use + * nghttp2_buf_reserve() to allocate memory. + */ +void nghttp2_buf_init(nghttp2_buf *buf); + +/* + * Initializes the |buf| and allocates at least |initial| bytes of + * memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_buf_init2(nghttp2_buf *buf, size_t initial, nghttp2_mem *mem); + +/* + * Frees buffer in |buf|. + */ +void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem); + +/* + * Extends buffer so that nghttp2_buf_cap() returns at least + * |new_cap|. If extensions took place, buffer pointers in |buf| will + * change. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem); + +/* + * Resets pos, last, mark member of |buf| to buf->begin. + */ +void nghttp2_buf_reset(nghttp2_buf *buf); + +/* + * Initializes |buf| using supplied buffer |begin| of length + * |len|. Semantically, the application should not call *_reserve() or + * nghttp2_free() functions for |buf|. + */ +void nghttp2_buf_wrap_init(nghttp2_buf *buf, uint8_t *begin, size_t len); + +struct nghttp2_buf_chain; + +typedef struct nghttp2_buf_chain nghttp2_buf_chain; + +/* Chains 2 buffers */ +struct nghttp2_buf_chain { + /* Points to the subsequent buffer. NULL if there is no such + buffer. */ + nghttp2_buf_chain *next; + nghttp2_buf buf; +}; + +typedef struct { + /* Points to the first buffer */ + nghttp2_buf_chain *head; + /* Buffer pointer where write occurs. */ + nghttp2_buf_chain *cur; + /* Memory allocator */ + nghttp2_mem *mem; + /* The buffer capacity of each buf. This field may be 0 if + nghttp2_bufs is initialized by nghttp2_bufs_wrap_init* family + functions. */ + size_t chunk_length; + /* The maximum number of nghttp2_buf_chain */ + size_t max_chunk; + /* The number of nghttp2_buf_chain allocated */ + size_t chunk_used; + /* The number of nghttp2_buf_chain to keep on reset */ + size_t chunk_keep; + /* pos offset from begin in each buffers. On initialization and + reset, buf->pos and buf->last are positioned at buf->begin + + offset. */ + size_t offset; +} nghttp2_bufs; + +/* + * This is the same as calling nghttp2_bufs_init2 with the given + * arguments and offset = 0. + */ +int nghttp2_bufs_init(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk, + nghttp2_mem *mem); + +/* + * This is the same as calling nghttp2_bufs_init3 with the given + * arguments and chunk_keep = max_chunk. + */ +int nghttp2_bufs_init2(nghttp2_bufs *bufs, size_t chunk_length, + size_t max_chunk, size_t offset, nghttp2_mem *mem); + +/* + * Initializes |bufs|. Each buffer size is given in the + * |chunk_length|. The maximum number of buffers is given in the + * |max_chunk|. On reset, first |chunk_keep| buffers are kept and + * remaining buffers are deleted. Each buffer will have bufs->pos and + * bufs->last shifted to left by |offset| bytes on creation and reset. + * + * This function allocates first buffer. bufs->head and bufs->cur + * will point to the first buffer after this call. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_INVALID_ARGUMENT + * chunk_keep is 0; or max_chunk < chunk_keep; or offset is too + * long. + */ +int nghttp2_bufs_init3(nghttp2_bufs *bufs, size_t chunk_length, + size_t max_chunk, size_t chunk_keep, size_t offset, + nghttp2_mem *mem); + +/* + * Frees any related resources to the |bufs|. + */ +void nghttp2_bufs_free(nghttp2_bufs *bufs); + +/* + * Initializes |bufs| using supplied buffer |begin| of length |len|. + * The first buffer bufs->head uses buffer |begin|. The buffer size + * is fixed and no extra chunk buffer is allocated. In other + * words, max_chunk = chunk_keep = 1. To free the resource allocated + * for |bufs|, use nghttp2_bufs_wrap_free(). + * + * Don't use the function which performs allocation, such as + * nghttp2_bufs_realloc(). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_bufs_wrap_init(nghttp2_bufs *bufs, uint8_t *begin, size_t len, + nghttp2_mem *mem); + +/* + * Initializes |bufs| using supplied |veclen| size of buf vector + * |vec|. The number of buffers is fixed and no extra chunk buffer is + * allocated. In other words, max_chunk = chunk_keep = |in_len|. To + * free the resource allocated for |bufs|, use + * nghttp2_bufs_wrap_free(). + * + * Don't use the function which performs allocation, such as + * nghttp2_bufs_realloc(). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_bufs_wrap_init2(nghttp2_bufs *bufs, const nghttp2_vec *vec, + size_t veclen, nghttp2_mem *mem); + +/* + * Frees any related resource to the |bufs|. This function does not + * free supplied buffer provided in nghttp2_bufs_wrap_init(). + */ +void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs); + +/* + * Reallocates internal buffer using |chunk_length|. The max_chunk, + * chunk_keep and offset do not change. After successful allocation + * of new buffer, previous buffers are deallocated without copying + * anything into new buffers. chunk_used is reset to 1. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_INVALID_ARGUMENT + * chunk_length < offset + */ +int nghttp2_bufs_realloc(nghttp2_bufs *bufs, size_t chunk_length); + +/* + * Appends the |data| of length |len| to the |bufs|. The write starts + * at bufs->cur->buf.last. A new buffers will be allocated to store + * all data. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_BUFFER_ERROR + * Out of buffer space. + */ +int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len); + +/* + * Appends a single byte |b| to the |bufs|. The write starts at + * bufs->cur->buf.last. A new buffers will be allocated to store all + * data. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_BUFFER_ERROR + * Out of buffer space. + */ +int nghttp2_bufs_addb(nghttp2_bufs *bufs, uint8_t b); + +/* + * Behaves like nghttp2_bufs_addb(), but this does not update + * buf->last pointer. + */ +int nghttp2_bufs_addb_hold(nghttp2_bufs *bufs, uint8_t b); + +#define nghttp2_bufs_fast_addb(BUFS, B) \ + do { \ + *(BUFS)->cur->buf.last++ = B; \ + } while (0) + +#define nghttp2_bufs_fast_addb_hold(BUFS, B) \ + do { \ + *(BUFS)->cur->buf.last = B; \ + } while (0) + +/* + * Performs bitwise-OR of |b| at bufs->cur->buf.last. A new buffers + * will be allocated if necessary. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_BUFFER_ERROR + * Out of buffer space. + */ +int nghttp2_bufs_orb(nghttp2_bufs *bufs, uint8_t b); + +/* + * Behaves like nghttp2_bufs_orb(), but does not update buf->last + * pointer. + */ +int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b); + +#define nghttp2_bufs_fast_orb(BUFS, B) \ + do { \ + uint8_t **p = &(BUFS)->cur->buf.last; \ + **p = (uint8_t)(**p | (B)); \ + ++(*p); \ + } while (0) + +#define nghttp2_bufs_fast_orb_hold(BUFS, B) \ + do { \ + uint8_t *p = (BUFS)->cur->buf.last; \ + *p = (uint8_t)(*p | (B)); \ + } while (0) + +/* + * Copies all data stored in |bufs| to the contiguous buffer. This + * function allocates the contiguous memory to store all data in + * |bufs| and assigns it to |*out|. + * + * The contents of |bufs| is left unchanged. + * + * This function returns the length of copied data and assigns the + * pointer to copied data to |*out| if it succeeds, or one of the + * following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out); + +/* + * Copies all data stored in |bufs| to |out|. This function assumes + * that the buffer space pointed by |out| has at least + * nghttp2_bufs(bufs) bytes. + * + * The contents of |bufs| is left unchanged. + * + * This function returns the length of copied data. + */ +size_t nghttp2_bufs_remove_copy(nghttp2_bufs *bufs, uint8_t *out); + +/* + * Resets |bufs| and makes the buffers empty. + */ +void nghttp2_bufs_reset(nghttp2_bufs *bufs); + +/* + * Moves bufs->cur to bufs->cur->next. If resulting bufs->cur is + * NULL, this function allocates new buffers and bufs->cur points to + * it. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_BUFFER_ERROR + * Out of buffer space. + */ +int nghttp2_bufs_advance(nghttp2_bufs *bufs); + +/* Sets bufs->cur to bufs->head */ +#define nghttp2_bufs_rewind(BUFS) \ + do { \ + (BUFS)->cur = (BUFS)->head; \ + } while (0) + +/* + * Move bufs->cur, from the current position, using next member, to + * the last buf which has nghttp2_buf_len(buf) > 0 without seeing buf + * which satisfies nghttp2_buf_len(buf) == 0. If + * nghttp2_buf_len(&bufs->cur->buf) == 0 or bufs->cur->next is NULL, + * bufs->cur is unchanged. + */ +void nghttp2_bufs_seek_last_present(nghttp2_bufs *bufs); + +/* + * Returns nonzero if bufs->cur->next is not empty. + */ +int nghttp2_bufs_next_present(nghttp2_bufs *bufs); + +#define nghttp2_bufs_cur_avail(BUFS) nghttp2_buf_avail(&(BUFS)->cur->buf) + +/* + * Returns the total buffer length of |bufs|. + */ +size_t nghttp2_bufs_len(nghttp2_bufs *bufs); + +#endif /* NGHTTP2_BUF_H */ diff --git a/lib/nghttp2/lib/nghttp2_callbacks.c b/lib/nghttp2/lib/nghttp2_callbacks.c new file mode 100644 index 00000000000..3c38214859b --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_callbacks.c @@ -0,0 +1,175 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_callbacks.h" + +#include + +int nghttp2_session_callbacks_new(nghttp2_session_callbacks **callbacks_ptr) { + *callbacks_ptr = calloc(1, sizeof(nghttp2_session_callbacks)); + + if (*callbacks_ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + return 0; +} + +void nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks) { + free(callbacks); +} + +void nghttp2_session_callbacks_set_send_callback( + nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback) { + cbs->send_callback = send_callback; +} + +void nghttp2_session_callbacks_set_recv_callback( + nghttp2_session_callbacks *cbs, nghttp2_recv_callback recv_callback) { + cbs->recv_callback = recv_callback; +} + +void nghttp2_session_callbacks_set_on_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_recv_callback on_frame_recv_callback) { + cbs->on_frame_recv_callback = on_frame_recv_callback; +} + +void nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback) { + cbs->on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; +} + +void nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback) { + cbs->on_data_chunk_recv_callback = on_data_chunk_recv_callback; +} + +void nghttp2_session_callbacks_set_before_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_before_frame_send_callback before_frame_send_callback) { + cbs->before_frame_send_callback = before_frame_send_callback; +} + +void nghttp2_session_callbacks_set_on_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_send_callback on_frame_send_callback) { + cbs->on_frame_send_callback = on_frame_send_callback; +} + +void nghttp2_session_callbacks_set_on_frame_not_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_not_send_callback on_frame_not_send_callback) { + cbs->on_frame_not_send_callback = on_frame_not_send_callback; +} + +void nghttp2_session_callbacks_set_on_stream_close_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_stream_close_callback on_stream_close_callback) { + cbs->on_stream_close_callback = on_stream_close_callback; +} + +void nghttp2_session_callbacks_set_on_begin_headers_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_headers_callback on_begin_headers_callback) { + cbs->on_begin_headers_callback = on_begin_headers_callback; +} + +void nghttp2_session_callbacks_set_on_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback on_header_callback) { + cbs->on_header_callback = on_header_callback; +} + +void nghttp2_session_callbacks_set_on_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback2 on_header_callback2) { + cbs->on_header_callback2 = on_header_callback2; +} + +void nghttp2_session_callbacks_set_on_invalid_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback on_invalid_header_callback) { + cbs->on_invalid_header_callback = on_invalid_header_callback; +} + +void nghttp2_session_callbacks_set_on_invalid_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback2 on_invalid_header_callback2) { + cbs->on_invalid_header_callback2 = on_invalid_header_callback2; +} + +void nghttp2_session_callbacks_set_select_padding_callback( + nghttp2_session_callbacks *cbs, + nghttp2_select_padding_callback select_padding_callback) { + cbs->select_padding_callback = select_padding_callback; +} + +void nghttp2_session_callbacks_set_data_source_read_length_callback( + nghttp2_session_callbacks *cbs, + nghttp2_data_source_read_length_callback data_source_read_length_callback) { + cbs->read_length_callback = data_source_read_length_callback; +} + +void nghttp2_session_callbacks_set_on_begin_frame_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_frame_callback on_begin_frame_callback) { + cbs->on_begin_frame_callback = on_begin_frame_callback; +} + +void nghttp2_session_callbacks_set_send_data_callback( + nghttp2_session_callbacks *cbs, + nghttp2_send_data_callback send_data_callback) { + cbs->send_data_callback = send_data_callback; +} + +void nghttp2_session_callbacks_set_pack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_pack_extension_callback pack_extension_callback) { + cbs->pack_extension_callback = pack_extension_callback; +} + +void nghttp2_session_callbacks_set_unpack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_unpack_extension_callback unpack_extension_callback) { + cbs->unpack_extension_callback = unpack_extension_callback; +} + +void nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback) { + cbs->on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; +} + +void nghttp2_session_callbacks_set_error_callback( + nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback) { + cbs->error_callback = error_callback; +} + +void nghttp2_session_callbacks_set_error_callback2( + nghttp2_session_callbacks *cbs, nghttp2_error_callback2 error_callback2) { + cbs->error_callback2 = error_callback2; +} diff --git a/lib/nghttp2/lib/nghttp2_callbacks.h b/lib/nghttp2/lib/nghttp2_callbacks.h new file mode 100644 index 00000000000..61e51fa5363 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_callbacks.h @@ -0,0 +1,125 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_CALLBACKS_H +#define NGHTTP2_CALLBACKS_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +/* + * Callback functions. + */ +struct nghttp2_session_callbacks { + /** + * Callback function invoked when the session wants to send data to + * the remote peer. This callback is not necessary if the + * application uses solely `nghttp2_session_mem_send()` to serialize + * data to transmit. + */ + nghttp2_send_callback send_callback; + /** + * Callback function invoked when the session wants to receive data + * from the remote peer. This callback is not necessary if the + * application uses solely `nghttp2_session_mem_recv()` to process + * received data. + */ + nghttp2_recv_callback recv_callback; + /** + * Callback function invoked by `nghttp2_session_recv()` when a + * frame is received. + */ + nghttp2_on_frame_recv_callback on_frame_recv_callback; + /** + * Callback function invoked by `nghttp2_session_recv()` when an + * invalid non-DATA frame is received. + */ + nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback; + /** + * Callback function invoked when a chunk of data in DATA frame is + * received. + */ + nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback; + /** + * Callback function invoked before a non-DATA frame is sent. + */ + nghttp2_before_frame_send_callback before_frame_send_callback; + /** + * Callback function invoked after a frame is sent. + */ + nghttp2_on_frame_send_callback on_frame_send_callback; + /** + * The callback function invoked when a non-DATA frame is not sent + * because of an error. + */ + nghttp2_on_frame_not_send_callback on_frame_not_send_callback; + /** + * Callback function invoked when the stream is closed. + */ + nghttp2_on_stream_close_callback on_stream_close_callback; + /** + * Callback function invoked when the reception of header block in + * HEADERS or PUSH_PROMISE is started. + */ + nghttp2_on_begin_headers_callback on_begin_headers_callback; + /** + * Callback function invoked when a header name/value pair is + * received. + */ + nghttp2_on_header_callback on_header_callback; + nghttp2_on_header_callback2 on_header_callback2; + /** + * Callback function invoked when a invalid header name/value pair + * is received which is silently ignored if these callbacks are not + * set. + */ + nghttp2_on_invalid_header_callback on_invalid_header_callback; + nghttp2_on_invalid_header_callback2 on_invalid_header_callback2; + /** + * Callback function invoked when the library asks application how + * many padding bytes are required for the transmission of the given + * frame. + */ + nghttp2_select_padding_callback select_padding_callback; + /** + * The callback function used to determine the length allowed in + * `nghttp2_data_source_read_callback()` + */ + nghttp2_data_source_read_length_callback read_length_callback; + /** + * Sets callback function invoked when a frame header is received. + */ + nghttp2_on_begin_frame_callback on_begin_frame_callback; + nghttp2_send_data_callback send_data_callback; + nghttp2_pack_extension_callback pack_extension_callback; + nghttp2_unpack_extension_callback unpack_extension_callback; + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback; + nghttp2_error_callback error_callback; + nghttp2_error_callback2 error_callback2; +}; + +#endif /* NGHTTP2_CALLBACKS_H */ diff --git a/lib/nghttp2/lib/nghttp2_debug.c b/lib/nghttp2/lib/nghttp2_debug.c new file mode 100644 index 00000000000..cb2779700bd --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_debug.c @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_debug.h" + +#include + +#ifdef DEBUGBUILD + +static void nghttp2_default_debug_vfprintf_callback(const char *fmt, + va_list args) { + vfprintf(stderr, fmt, args); +} + +static nghttp2_debug_vprintf_callback static_debug_vprintf_callback = + nghttp2_default_debug_vfprintf_callback; + +void nghttp2_debug_vprintf(const char *format, ...) { + if (static_debug_vprintf_callback) { + va_list args; + va_start(args, format); + static_debug_vprintf_callback(format, args); + va_end(args); + } +} + +void nghttp2_set_debug_vprintf_callback( + nghttp2_debug_vprintf_callback debug_vprintf_callback) { + static_debug_vprintf_callback = debug_vprintf_callback; +} + +#else /* !DEBUGBUILD */ + +void nghttp2_set_debug_vprintf_callback( + nghttp2_debug_vprintf_callback debug_vprintf_callback) { + (void)debug_vprintf_callback; +} + +#endif /* !DEBUGBUILD */ diff --git a/lib/nghttp2/lib/nghttp2_debug.h b/lib/nghttp2/lib/nghttp2_debug.h new file mode 100644 index 00000000000..cbb4dd57547 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_debug.h @@ -0,0 +1,43 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_DEBUG_H +#define NGHTTP2_DEBUG_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#ifdef DEBUGBUILD +# define DEBUGF(...) nghttp2_debug_vprintf(__VA_ARGS__) +void nghttp2_debug_vprintf(const char *format, ...); +#else +# define DEBUGF(...) \ + do { \ + } while (0) +#endif + +#endif /* NGHTTP2_DEBUG_H */ diff --git a/lib/nghttp2/lib/nghttp2_extpri.c b/lib/nghttp2/lib/nghttp2_extpri.c new file mode 100644 index 00000000000..ba0263e7c8e --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_extpri.c @@ -0,0 +1,41 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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 "nghttp2_extpri.h" +#include "nghttp2_http.h" + +uint8_t nghttp2_extpri_to_uint8(const nghttp2_extpri *extpri) { + return (uint8_t)((uint32_t)extpri->inc << 7 | extpri->urgency); +} + +void nghttp2_extpri_from_uint8(nghttp2_extpri *extpri, uint8_t u8extpri) { + extpri->urgency = nghttp2_extpri_uint8_urgency(u8extpri); + extpri->inc = nghttp2_extpri_uint8_inc(u8extpri); +} + +int nghttp2_extpri_parse_priority(nghttp2_extpri *extpri, const uint8_t *value, + size_t len) { + return nghttp2_http_parse_priority(extpri, value, len); +} diff --git a/lib/nghttp2/lib/nghttp2_extpri.h b/lib/nghttp2/lib/nghttp2_extpri.h new file mode 100644 index 00000000000..23c6ddc0c05 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_extpri.h @@ -0,0 +1,65 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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 NGHTTP2_EXTPRI_H +#define NGHTTP2_EXTPRI_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +/* + * NGHTTP2_EXTPRI_INC_MASK is a bit mask to retrieve incremental bit + * from a value produced by nghttp2_extpri_to_uint8. + */ +#define NGHTTP2_EXTPRI_INC_MASK (1 << 7) + +/* + * nghttp2_extpri_to_uint8 encodes |pri| into uint8_t variable. + */ +uint8_t nghttp2_extpri_to_uint8(const nghttp2_extpri *extpri); + +/* + * nghttp2_extpri_from_uint8 decodes |u8extpri|, which is produced by + * nghttp2_extpri_to_uint8, intto |extpri|. + */ +void nghttp2_extpri_from_uint8(nghttp2_extpri *extpri, uint8_t u8extpri); + +/* + * nghttp2_extpri_uint8_urgency extracts urgency from |PRI| which is + * supposed to be constructed by nghttp2_extpri_to_uint8. + */ +#define nghttp2_extpri_uint8_urgency(PRI) \ + ((uint32_t)((PRI) & ~NGHTTP2_EXTPRI_INC_MASK)) + +/* + * nghttp2_extpri_uint8_inc extracts inc from |PRI| which is supposed to + * be constructed by nghttp2_extpri_to_uint8. + */ +#define nghttp2_extpri_uint8_inc(PRI) (((PRI)&NGHTTP2_EXTPRI_INC_MASK) != 0) + +#endif /* NGHTTP2_EXTPRI_H */ diff --git a/lib/nghttp2/lib/nghttp2_frame.c b/lib/nghttp2/lib/nghttp2_frame.c new file mode 100644 index 00000000000..77cb463df54 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_frame.c @@ -0,0 +1,1214 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_frame.h" + +#include +#include +#include +#include + +#include "nghttp2_helper.h" +#include "nghttp2_net.h" +#include "nghttp2_priority_spec.h" +#include "nghttp2_debug.h" + +void nghttp2_frame_pack_frame_hd(uint8_t *buf, const nghttp2_frame_hd *hd) { + nghttp2_put_uint32be(&buf[0], (uint32_t)(hd->length << 8)); + buf[3] = hd->type; + buf[4] = hd->flags; + nghttp2_put_uint32be(&buf[5], (uint32_t)hd->stream_id); + /* ignore hd->reserved for now */ +} + +void nghttp2_frame_unpack_frame_hd(nghttp2_frame_hd *hd, const uint8_t *buf) { + hd->length = nghttp2_get_uint32(&buf[0]) >> 8; + hd->type = buf[3]; + hd->flags = buf[4]; + hd->stream_id = nghttp2_get_uint32(&buf[5]) & NGHTTP2_STREAM_ID_MASK; + hd->reserved = 0; +} + +void nghttp2_frame_hd_init(nghttp2_frame_hd *hd, size_t length, uint8_t type, + uint8_t flags, int32_t stream_id) { + hd->length = length; + hd->type = type; + hd->flags = flags; + hd->stream_id = stream_id; + hd->reserved = 0; +} + +void nghttp2_frame_headers_init(nghttp2_headers *frame, uint8_t flags, + int32_t stream_id, nghttp2_headers_category cat, + const nghttp2_priority_spec *pri_spec, + nghttp2_nv *nva, size_t nvlen) { + nghttp2_frame_hd_init(&frame->hd, 0, NGHTTP2_HEADERS, flags, stream_id); + frame->padlen = 0; + frame->nva = nva; + frame->nvlen = nvlen; + frame->cat = cat; + + if (pri_spec) { + frame->pri_spec = *pri_spec; + } else { + nghttp2_priority_spec_default_init(&frame->pri_spec); + } +} + +void nghttp2_frame_headers_free(nghttp2_headers *frame, nghttp2_mem *mem) { + nghttp2_nv_array_del(frame->nva, mem); +} + +void nghttp2_frame_priority_init(nghttp2_priority *frame, int32_t stream_id, + const nghttp2_priority_spec *pri_spec) { + nghttp2_frame_hd_init(&frame->hd, NGHTTP2_PRIORITY_SPECLEN, NGHTTP2_PRIORITY, + NGHTTP2_FLAG_NONE, stream_id); + frame->pri_spec = *pri_spec; +} + +void nghttp2_frame_priority_free(nghttp2_priority *frame) { (void)frame; } + +void nghttp2_frame_rst_stream_init(nghttp2_rst_stream *frame, int32_t stream_id, + uint32_t error_code) { + nghttp2_frame_hd_init(&frame->hd, 4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, + stream_id); + frame->error_code = error_code; +} + +void nghttp2_frame_rst_stream_free(nghttp2_rst_stream *frame) { (void)frame; } + +void nghttp2_frame_settings_init(nghttp2_settings *frame, uint8_t flags, + nghttp2_settings_entry *iv, size_t niv) { + nghttp2_frame_hd_init(&frame->hd, niv * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH, + NGHTTP2_SETTINGS, flags, 0); + frame->niv = niv; + frame->iv = iv; +} + +void nghttp2_frame_settings_free(nghttp2_settings *frame, nghttp2_mem *mem) { + nghttp2_mem_free(mem, frame->iv); +} + +void nghttp2_frame_push_promise_init(nghttp2_push_promise *frame, uint8_t flags, + int32_t stream_id, + int32_t promised_stream_id, + nghttp2_nv *nva, size_t nvlen) { + nghttp2_frame_hd_init(&frame->hd, 0, NGHTTP2_PUSH_PROMISE, flags, stream_id); + frame->padlen = 0; + frame->nva = nva; + frame->nvlen = nvlen; + frame->promised_stream_id = promised_stream_id; + frame->reserved = 0; +} + +void nghttp2_frame_push_promise_free(nghttp2_push_promise *frame, + nghttp2_mem *mem) { + nghttp2_nv_array_del(frame->nva, mem); +} + +void nghttp2_frame_ping_init(nghttp2_ping *frame, uint8_t flags, + const uint8_t *opaque_data) { + nghttp2_frame_hd_init(&frame->hd, 8, NGHTTP2_PING, flags, 0); + if (opaque_data) { + memcpy(frame->opaque_data, opaque_data, sizeof(frame->opaque_data)); + } else { + memset(frame->opaque_data, 0, sizeof(frame->opaque_data)); + } +} + +void nghttp2_frame_ping_free(nghttp2_ping *frame) { (void)frame; } + +void nghttp2_frame_goaway_init(nghttp2_goaway *frame, int32_t last_stream_id, + uint32_t error_code, uint8_t *opaque_data, + size_t opaque_data_len) { + nghttp2_frame_hd_init(&frame->hd, 8 + opaque_data_len, NGHTTP2_GOAWAY, + NGHTTP2_FLAG_NONE, 0); + frame->last_stream_id = last_stream_id; + frame->error_code = error_code; + frame->opaque_data = opaque_data; + frame->opaque_data_len = opaque_data_len; + frame->reserved = 0; +} + +void nghttp2_frame_goaway_free(nghttp2_goaway *frame, nghttp2_mem *mem) { + nghttp2_mem_free(mem, frame->opaque_data); +} + +void nghttp2_frame_window_update_init(nghttp2_window_update *frame, + uint8_t flags, int32_t stream_id, + int32_t window_size_increment) { + nghttp2_frame_hd_init(&frame->hd, 4, NGHTTP2_WINDOW_UPDATE, flags, stream_id); + frame->window_size_increment = window_size_increment; + frame->reserved = 0; +} + +void nghttp2_frame_window_update_free(nghttp2_window_update *frame) { + (void)frame; +} + +size_t nghttp2_frame_trail_padlen(nghttp2_frame *frame, size_t padlen) { + /* We have iframe->padlen == 0, but iframe->frame.hd.flags may have + NGHTTP2_FLAG_PADDED set. This happens when receiving + CONTINUATION frame, since we don't reset flags after HEADERS was + received. */ + if (padlen == 0) { + return 0; + } + return padlen - ((frame->hd.flags & NGHTTP2_FLAG_PADDED) > 0); +} + +void nghttp2_frame_data_init(nghttp2_data *frame, uint8_t flags, + int32_t stream_id) { + /* At this moment, the length of DATA frame is unknown */ + nghttp2_frame_hd_init(&frame->hd, 0, NGHTTP2_DATA, flags, stream_id); + frame->padlen = 0; +} + +void nghttp2_frame_data_free(nghttp2_data *frame) { (void)frame; } + +void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type, + uint8_t flags, int32_t stream_id, + void *payload) { + nghttp2_frame_hd_init(&frame->hd, 0, type, flags, stream_id); + frame->payload = payload; +} + +void nghttp2_frame_extension_free(nghttp2_extension *frame) { (void)frame; } + +void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id, + uint8_t *origin, size_t origin_len, + uint8_t *field_value, size_t field_value_len) { + nghttp2_ext_altsvc *altsvc; + + nghttp2_frame_hd_init(&frame->hd, 2 + origin_len + field_value_len, + NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, stream_id); + + altsvc = frame->payload; + altsvc->origin = origin; + altsvc->origin_len = origin_len; + altsvc->field_value = field_value; + altsvc->field_value_len = field_value_len; +} + +void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem) { + nghttp2_ext_altsvc *altsvc; + + altsvc = frame->payload; + if (altsvc == NULL) { + return; + } + /* We use the same buffer for altsvc->origin and + altsvc->field_value. */ + nghttp2_mem_free(mem, altsvc->origin); +} + +void nghttp2_frame_origin_init(nghttp2_extension *frame, + nghttp2_origin_entry *ov, size_t nov) { + nghttp2_ext_origin *origin; + size_t payloadlen = 0; + size_t i; + + for (i = 0; i < nov; ++i) { + payloadlen += 2 + ov[i].origin_len; + } + + nghttp2_frame_hd_init(&frame->hd, payloadlen, NGHTTP2_ORIGIN, + NGHTTP2_FLAG_NONE, 0); + + origin = frame->payload; + origin->ov = ov; + origin->nov = nov; +} + +void nghttp2_frame_origin_free(nghttp2_extension *frame, nghttp2_mem *mem) { + nghttp2_ext_origin *origin; + + origin = frame->payload; + if (origin == NULL) { + return; + } + /* We use the same buffer for all resources pointed by the field of + origin directly or indirectly. */ + nghttp2_mem_free(mem, origin->ov); +} + +void nghttp2_frame_priority_update_init(nghttp2_extension *frame, + int32_t stream_id, uint8_t *field_value, + size_t field_value_len) { + nghttp2_ext_priority_update *priority_update; + + nghttp2_frame_hd_init(&frame->hd, 4 + field_value_len, + NGHTTP2_PRIORITY_UPDATE, NGHTTP2_FLAG_NONE, 0); + + priority_update = frame->payload; + priority_update->stream_id = stream_id; + priority_update->field_value = field_value; + priority_update->field_value_len = field_value_len; +} + +void nghttp2_frame_priority_update_free(nghttp2_extension *frame, + nghttp2_mem *mem) { + nghttp2_ext_priority_update *priority_update; + + priority_update = frame->payload; + if (priority_update == NULL) { + return; + } + nghttp2_mem_free(mem, priority_update->field_value); +} + +size_t nghttp2_frame_priority_len(uint8_t flags) { + if (flags & NGHTTP2_FLAG_PRIORITY) { + return NGHTTP2_PRIORITY_SPECLEN; + } + + return 0; +} + +size_t nghttp2_frame_headers_payload_nv_offset(nghttp2_headers *frame) { + return nghttp2_frame_priority_len(frame->hd.flags); +} + +/* + * Call this function after payload was serialized, but not before + * changing buf->pos and serializing frame header. + * + * This function assumes bufs->cur points to the last buf chain of the + * frame(s). + * + * This function serializes frame header for HEADERS/PUSH_PROMISE and + * handles their successive CONTINUATION frames. + * + * We don't process any padding here. + */ +static int frame_pack_headers_shared(nghttp2_bufs *bufs, + nghttp2_frame_hd *frame_hd) { + nghttp2_buf *buf; + nghttp2_buf_chain *ci, *ce; + nghttp2_frame_hd hd; + + buf = &bufs->head->buf; + + hd = *frame_hd; + hd.length = nghttp2_buf_len(buf); + + DEBUGF("send: HEADERS/PUSH_PROMISE, payloadlen=%zu\n", hd.length); + + /* We have multiple frame buffers, which means one or more + CONTINUATION frame is involved. Remove END_HEADERS flag from the + first frame. */ + if (bufs->head != bufs->cur) { + hd.flags = (uint8_t)(hd.flags & ~NGHTTP2_FLAG_END_HEADERS); + } + + buf->pos -= NGHTTP2_FRAME_HDLEN; + nghttp2_frame_pack_frame_hd(buf->pos, &hd); + + if (bufs->head != bufs->cur) { + /* 2nd and later frames are CONTINUATION frames. */ + hd.type = NGHTTP2_CONTINUATION; + /* We don't have no flags except for last CONTINUATION */ + hd.flags = NGHTTP2_FLAG_NONE; + + ce = bufs->cur; + + for (ci = bufs->head->next; ci != ce; ci = ci->next) { + buf = &ci->buf; + + hd.length = nghttp2_buf_len(buf); + + DEBUGF("send: int CONTINUATION, payloadlen=%zu\n", hd.length); + + buf->pos -= NGHTTP2_FRAME_HDLEN; + nghttp2_frame_pack_frame_hd(buf->pos, &hd); + } + + buf = &ci->buf; + hd.length = nghttp2_buf_len(buf); + /* Set END_HEADERS flag for last CONTINUATION */ + hd.flags = NGHTTP2_FLAG_END_HEADERS; + + DEBUGF("send: last CONTINUATION, payloadlen=%zu\n", hd.length); + + buf->pos -= NGHTTP2_FRAME_HDLEN; + nghttp2_frame_pack_frame_hd(buf->pos, &hd); + } + + return 0; +} + +int nghttp2_frame_pack_headers(nghttp2_bufs *bufs, nghttp2_headers *frame, + nghttp2_hd_deflater *deflater) { + size_t nv_offset; + int rv; + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + nv_offset = nghttp2_frame_headers_payload_nv_offset(frame); + + buf = &bufs->cur->buf; + + buf->pos += nv_offset; + buf->last = buf->pos; + + /* This call will adjust buf->last to the correct position */ + rv = nghttp2_hd_deflate_hd_bufs(deflater, bufs, frame->nva, frame->nvlen); + + if (rv == NGHTTP2_ERR_BUFFER_ERROR) { + rv = NGHTTP2_ERR_HEADER_COMP; + } + + buf->pos -= nv_offset; + + if (rv != 0) { + return rv; + } + + if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { + nghttp2_frame_pack_priority_spec(buf->pos, &frame->pri_spec); + } + + frame->padlen = 0; + frame->hd.length = nghttp2_bufs_len(bufs); + + return frame_pack_headers_shared(bufs, &frame->hd); +} + +void nghttp2_frame_pack_priority_spec(uint8_t *buf, + const nghttp2_priority_spec *pri_spec) { + nghttp2_put_uint32be(buf, (uint32_t)pri_spec->stream_id); + if (pri_spec->exclusive) { + buf[0] |= 0x80; + } + buf[4] = (uint8_t)(pri_spec->weight - 1); +} + +void nghttp2_frame_unpack_priority_spec(nghttp2_priority_spec *pri_spec, + const uint8_t *payload) { + int32_t dep_stream_id; + uint8_t exclusive; + int32_t weight; + + dep_stream_id = nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK; + exclusive = (payload[0] & 0x80) > 0; + weight = payload[4] + 1; + + nghttp2_priority_spec_init(pri_spec, dep_stream_id, weight, exclusive); +} + +void nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame, + const uint8_t *payload) { + if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { + nghttp2_frame_unpack_priority_spec(&frame->pri_spec, payload); + } else { + nghttp2_priority_spec_default_init(&frame->pri_spec); + } + + frame->nva = NULL; + frame->nvlen = 0; +} + +void nghttp2_frame_pack_priority(nghttp2_bufs *bufs, nghttp2_priority *frame) { + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + buf = &bufs->head->buf; + + assert(nghttp2_buf_avail(buf) >= NGHTTP2_PRIORITY_SPECLEN); + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_frame_pack_priority_spec(buf->last, &frame->pri_spec); + + buf->last += NGHTTP2_PRIORITY_SPECLEN; +} + +void nghttp2_frame_unpack_priority_payload(nghttp2_priority *frame, + const uint8_t *payload) { + nghttp2_frame_unpack_priority_spec(&frame->pri_spec, payload); +} + +void nghttp2_frame_pack_rst_stream(nghttp2_bufs *bufs, + nghttp2_rst_stream *frame) { + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + buf = &bufs->head->buf; + + assert(nghttp2_buf_avail(buf) >= 4); + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_put_uint32be(buf->last, frame->error_code); + buf->last += 4; +} + +void nghttp2_frame_unpack_rst_stream_payload(nghttp2_rst_stream *frame, + const uint8_t *payload) { + frame->error_code = nghttp2_get_uint32(payload); +} + +int nghttp2_frame_pack_settings(nghttp2_bufs *bufs, nghttp2_settings *frame) { + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + buf = &bufs->head->buf; + + if (nghttp2_buf_avail(buf) < frame->hd.length) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + buf->last += + nghttp2_frame_pack_settings_payload(buf->last, frame->iv, frame->niv); + + return 0; +} + +size_t nghttp2_frame_pack_settings_payload(uint8_t *buf, + const nghttp2_settings_entry *iv, + size_t niv) { + size_t i; + for (i = 0; i < niv; ++i, buf += NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) { + nghttp2_put_uint16be(buf, (uint16_t)iv[i].settings_id); + nghttp2_put_uint32be(buf + 2, iv[i].value); + } + return NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH * niv; +} + +void nghttp2_frame_unpack_settings_payload(nghttp2_settings *frame, + nghttp2_settings_entry *iv, + size_t niv) { + frame->iv = iv; + frame->niv = niv; +} + +void nghttp2_frame_unpack_settings_entry(nghttp2_settings_entry *iv, + const uint8_t *payload) { + iv->settings_id = nghttp2_get_uint16(&payload[0]); + iv->value = nghttp2_get_uint32(&payload[2]); +} + +int nghttp2_frame_unpack_settings_payload2(nghttp2_settings_entry **iv_ptr, + size_t *niv_ptr, + const uint8_t *payload, + size_t payloadlen, + nghttp2_mem *mem) { + size_t i; + + *niv_ptr = payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH; + + if (*niv_ptr == 0) { + *iv_ptr = NULL; + + return 0; + } + + *iv_ptr = + nghttp2_mem_malloc(mem, (*niv_ptr) * sizeof(nghttp2_settings_entry)); + + if (*iv_ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + for (i = 0; i < *niv_ptr; ++i) { + size_t off = i * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH; + nghttp2_frame_unpack_settings_entry(&(*iv_ptr)[i], &payload[off]); + } + + return 0; +} + +int nghttp2_frame_pack_push_promise(nghttp2_bufs *bufs, + nghttp2_push_promise *frame, + nghttp2_hd_deflater *deflater) { + size_t nv_offset = 4; + int rv; + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + buf = &bufs->cur->buf; + + buf->pos += nv_offset; + buf->last = buf->pos; + + /* This call will adjust buf->last to the correct position */ + rv = nghttp2_hd_deflate_hd_bufs(deflater, bufs, frame->nva, frame->nvlen); + + if (rv == NGHTTP2_ERR_BUFFER_ERROR) { + rv = NGHTTP2_ERR_HEADER_COMP; + } + + buf->pos -= nv_offset; + + if (rv != 0) { + return rv; + } + + nghttp2_put_uint32be(buf->pos, (uint32_t)frame->promised_stream_id); + + frame->padlen = 0; + frame->hd.length = nghttp2_bufs_len(bufs); + + return frame_pack_headers_shared(bufs, &frame->hd); +} + +void nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame, + const uint8_t *payload) { + frame->promised_stream_id = + nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK; + frame->nva = NULL; + frame->nvlen = 0; +} + +void nghttp2_frame_pack_ping(nghttp2_bufs *bufs, nghttp2_ping *frame) { + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + buf = &bufs->head->buf; + + assert(nghttp2_buf_avail(buf) >= 8); + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + buf->last = + nghttp2_cpymem(buf->last, frame->opaque_data, sizeof(frame->opaque_data)); +} + +void nghttp2_frame_unpack_ping_payload(nghttp2_ping *frame, + const uint8_t *payload) { + memcpy(frame->opaque_data, payload, sizeof(frame->opaque_data)); +} + +int nghttp2_frame_pack_goaway(nghttp2_bufs *bufs, nghttp2_goaway *frame) { + int rv; + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + buf = &bufs->head->buf; + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_put_uint32be(buf->last, (uint32_t)frame->last_stream_id); + buf->last += 4; + + nghttp2_put_uint32be(buf->last, frame->error_code); + buf->last += 4; + + rv = nghttp2_bufs_add(bufs, frame->opaque_data, frame->opaque_data_len); + + if (rv == NGHTTP2_ERR_BUFFER_ERROR) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + + if (rv != 0) { + return rv; + } + + return 0; +} + +void nghttp2_frame_unpack_goaway_payload(nghttp2_goaway *frame, + const uint8_t *payload, + uint8_t *var_gift_payload, + size_t var_gift_payloadlen) { + frame->last_stream_id = nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK; + frame->error_code = nghttp2_get_uint32(payload + 4); + + frame->opaque_data = var_gift_payload; + frame->opaque_data_len = var_gift_payloadlen; +} + +int nghttp2_frame_unpack_goaway_payload2(nghttp2_goaway *frame, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem) { + uint8_t *var_gift_payload; + size_t var_gift_payloadlen; + + if (payloadlen > 8) { + var_gift_payloadlen = payloadlen - 8; + } else { + var_gift_payloadlen = 0; + } + + if (!var_gift_payloadlen) { + var_gift_payload = NULL; + } else { + var_gift_payload = nghttp2_mem_malloc(mem, var_gift_payloadlen); + + if (var_gift_payload == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + memcpy(var_gift_payload, payload + 8, var_gift_payloadlen); + } + + nghttp2_frame_unpack_goaway_payload(frame, payload, var_gift_payload, + var_gift_payloadlen); + + return 0; +} + +void nghttp2_frame_pack_window_update(nghttp2_bufs *bufs, + nghttp2_window_update *frame) { + nghttp2_buf *buf; + + assert(bufs->head == bufs->cur); + + buf = &bufs->head->buf; + + assert(nghttp2_buf_avail(buf) >= 4); + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_put_uint32be(buf->last, (uint32_t)frame->window_size_increment); + buf->last += 4; +} + +void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame, + const uint8_t *payload) { + frame->window_size_increment = + nghttp2_get_uint32(payload) & NGHTTP2_WINDOW_SIZE_INCREMENT_MASK; +} + +void nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *frame) { + int rv; + nghttp2_buf *buf; + nghttp2_ext_altsvc *altsvc; + + /* This is required with --disable-assert. */ + (void)rv; + + altsvc = frame->payload; + + buf = &bufs->head->buf; + + assert(nghttp2_buf_avail(buf) >= + 2 + altsvc->origin_len + altsvc->field_value_len); + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_put_uint16be(buf->last, (uint16_t)altsvc->origin_len); + buf->last += 2; + + rv = nghttp2_bufs_add(bufs, altsvc->origin, altsvc->origin_len); + + assert(rv == 0); + + rv = nghttp2_bufs_add(bufs, altsvc->field_value, altsvc->field_value_len); + + assert(rv == 0); +} + +void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame, + size_t origin_len, uint8_t *payload, + size_t payloadlen) { + nghttp2_ext_altsvc *altsvc; + uint8_t *p; + + altsvc = frame->payload; + p = payload; + + altsvc->origin = p; + + p += origin_len; + + altsvc->origin_len = origin_len; + + altsvc->field_value = p; + altsvc->field_value_len = (size_t)(payload + payloadlen - p); +} + +int nghttp2_frame_unpack_altsvc_payload2(nghttp2_extension *frame, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem) { + uint8_t *buf; + size_t origin_len; + + if (payloadlen < 2) { + return NGHTTP2_FRAME_SIZE_ERROR; + } + + origin_len = nghttp2_get_uint16(payload); + + buf = nghttp2_mem_malloc(mem, payloadlen - 2); + if (!buf) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_cpymem(buf, payload + 2, payloadlen - 2); + + nghttp2_frame_unpack_altsvc_payload(frame, origin_len, buf, payloadlen - 2); + + return 0; +} + +int nghttp2_frame_pack_origin(nghttp2_bufs *bufs, nghttp2_extension *frame) { + nghttp2_buf *buf; + nghttp2_ext_origin *origin; + nghttp2_origin_entry *orig; + size_t i; + + origin = frame->payload; + + buf = &bufs->head->buf; + + if (nghttp2_buf_avail(buf) < frame->hd.length) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + for (i = 0; i < origin->nov; ++i) { + orig = &origin->ov[i]; + nghttp2_put_uint16be(buf->last, (uint16_t)orig->origin_len); + buf->last += 2; + buf->last = nghttp2_cpymem(buf->last, orig->origin, orig->origin_len); + } + + assert(nghttp2_buf_len(buf) == NGHTTP2_FRAME_HDLEN + frame->hd.length); + + return 0; +} + +int nghttp2_frame_unpack_origin_payload(nghttp2_extension *frame, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem) { + nghttp2_ext_origin *origin; + const uint8_t *p, *end; + uint8_t *dst; + size_t originlen; + nghttp2_origin_entry *ov; + size_t nov = 0; + size_t len = 0; + + origin = frame->payload; + p = end = payload; + if (payloadlen) { + end += payloadlen; + } + + for (; p != end;) { + if (end - p < 2) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + originlen = nghttp2_get_uint16(p); + p += 2; + if (originlen == 0) { + continue; + } + if (originlen > (size_t)(end - p)) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + p += originlen; + /* 1 for terminal NULL */ + len += originlen + 1; + ++nov; + } + + if (nov == 0) { + origin->ov = NULL; + origin->nov = 0; + + return 0; + } + + len += nov * sizeof(nghttp2_origin_entry); + + ov = nghttp2_mem_malloc(mem, len); + if (ov == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + origin->ov = ov; + origin->nov = nov; + + dst = (uint8_t *)ov + nov * sizeof(nghttp2_origin_entry); + p = payload; + + for (; p != end;) { + originlen = nghttp2_get_uint16(p); + p += 2; + if (originlen == 0) { + continue; + } + ov->origin = dst; + ov->origin_len = originlen; + dst = nghttp2_cpymem(dst, p, originlen); + *dst++ = '\0'; + p += originlen; + ++ov; + } + + return 0; +} + +void nghttp2_frame_pack_priority_update(nghttp2_bufs *bufs, + nghttp2_extension *frame) { + int rv; + nghttp2_buf *buf; + nghttp2_ext_priority_update *priority_update; + + /* This is required with --disable-assert. */ + (void)rv; + + priority_update = frame->payload; + + buf = &bufs->head->buf; + + assert(nghttp2_buf_avail(buf) >= 4 + priority_update->field_value_len); + + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_put_uint32be(buf->last, (uint32_t)priority_update->stream_id); + buf->last += 4; + + rv = nghttp2_bufs_add(bufs, priority_update->field_value, + priority_update->field_value_len); + + assert(rv == 0); +} + +void nghttp2_frame_unpack_priority_update_payload(nghttp2_extension *frame, + uint8_t *payload, + size_t payloadlen) { + nghttp2_ext_priority_update *priority_update; + + assert(payloadlen >= 4); + + priority_update = frame->payload; + + priority_update->stream_id = + nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK; + + if (payloadlen > 4) { + priority_update->field_value = payload + 4; + priority_update->field_value_len = payloadlen - 4; + } else { + priority_update->field_value = NULL; + priority_update->field_value_len = 0; + } +} + +nghttp2_settings_entry *nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv, + size_t niv, nghttp2_mem *mem) { + nghttp2_settings_entry *iv_copy; + size_t len = niv * sizeof(nghttp2_settings_entry); + + if (len == 0) { + return NULL; + } + + iv_copy = nghttp2_mem_malloc(mem, len); + + if (iv_copy == NULL) { + return NULL; + } + + memcpy(iv_copy, iv, len); + + return iv_copy; +} + +int nghttp2_nv_equal(const nghttp2_nv *a, const nghttp2_nv *b) { + if (a->namelen != b->namelen || a->valuelen != b->valuelen) { + return 0; + } + + if (a->name == NULL || b->name == NULL) { + assert(a->namelen == 0); + assert(b->namelen == 0); + } else if (memcmp(a->name, b->name, a->namelen) != 0) { + return 0; + } + + if (a->value == NULL || b->value == NULL) { + assert(a->valuelen == 0); + assert(b->valuelen == 0); + } else if (memcmp(a->value, b->value, a->valuelen) != 0) { + return 0; + } + + return 1; +} + +void nghttp2_nv_array_del(nghttp2_nv *nva, nghttp2_mem *mem) { + nghttp2_mem_free(mem, nva); +} + +static int bytes_compar(const uint8_t *a, size_t alen, const uint8_t *b, + size_t blen) { + int rv; + + if (alen == blen) { + return memcmp(a, b, alen); + } + + if (alen < blen) { + rv = memcmp(a, b, alen); + + if (rv == 0) { + return -1; + } + + return rv; + } + + rv = memcmp(a, b, blen); + + if (rv == 0) { + return 1; + } + + return rv; +} + +int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs) { + return bytes_compar(lhs->name, lhs->namelen, rhs->name, rhs->namelen); +} + +static int nv_compar(const void *lhs, const void *rhs) { + const nghttp2_nv *a = (const nghttp2_nv *)lhs; + const nghttp2_nv *b = (const nghttp2_nv *)rhs; + int rv; + + rv = bytes_compar(a->name, a->namelen, b->name, b->namelen); + + if (rv == 0) { + return bytes_compar(a->value, a->valuelen, b->value, b->valuelen); + } + + return rv; +} + +void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen) { + qsort(nva, nvlen, sizeof(nghttp2_nv), nv_compar); +} + +int nghttp2_nv_array_copy(nghttp2_nv **nva_ptr, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem) { + size_t i; + uint8_t *data = NULL; + size_t buflen = 0; + nghttp2_nv *p; + + if (nvlen == 0) { + *nva_ptr = NULL; + + return 0; + } + + for (i = 0; i < nvlen; ++i) { + /* + 1 for null-termination */ + if ((nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_NAME) == 0) { + buflen += nva[i].namelen + 1; + } + if ((nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_VALUE) == 0) { + buflen += nva[i].valuelen + 1; + } + } + + buflen += sizeof(nghttp2_nv) * nvlen; + + *nva_ptr = nghttp2_mem_malloc(mem, buflen); + + if (*nva_ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + p = *nva_ptr; + data = (uint8_t *)(*nva_ptr) + sizeof(nghttp2_nv) * nvlen; + + for (i = 0; i < nvlen; ++i) { + p->flags = nva[i].flags; + + if (nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_NAME) { + p->name = nva[i].name; + p->namelen = nva[i].namelen; + } else { + if (nva[i].namelen) { + memcpy(data, nva[i].name, nva[i].namelen); + } + p->name = data; + p->namelen = nva[i].namelen; + data[p->namelen] = '\0'; + nghttp2_downcase(p->name, p->namelen); + data += nva[i].namelen + 1; + } + + if (nva[i].flags & NGHTTP2_NV_FLAG_NO_COPY_VALUE) { + p->value = nva[i].value; + p->valuelen = nva[i].valuelen; + } else { + if (nva[i].valuelen) { + memcpy(data, nva[i].value, nva[i].valuelen); + } + p->value = data; + p->valuelen = nva[i].valuelen; + data[p->valuelen] = '\0'; + data += nva[i].valuelen + 1; + } + + ++p; + } + return 0; +} + +int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv) { + size_t i; + for (i = 0; i < niv; ++i) { + switch (iv[i].settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + break; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + break; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + if (iv[i].value != 0 && iv[i].value != 1) { + return 0; + } + break; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + if (iv[i].value > (uint32_t)NGHTTP2_MAX_WINDOW_SIZE) { + return 0; + } + break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + if (iv[i].value < NGHTTP2_MAX_FRAME_SIZE_MIN || + iv[i].value > NGHTTP2_MAX_FRAME_SIZE_MAX) { + return 0; + } + break; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + break; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + if (iv[i].value != 0 && iv[i].value != 1) { + return 0; + } + break; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + if (iv[i].value != 0 && iv[i].value != 1) { + return 0; + } + break; + } + } + return 1; +} + +static void frame_set_pad(nghttp2_buf *buf, size_t padlen, int framehd_only) { + size_t trail_padlen; + size_t newlen; + + DEBUGF("send: padlen=%zu, shift left 1 bytes\n", padlen); + + memmove(buf->pos - 1, buf->pos, NGHTTP2_FRAME_HDLEN); + + --buf->pos; + + buf->pos[4] |= NGHTTP2_FLAG_PADDED; + + newlen = (nghttp2_get_uint32(buf->pos) >> 8) + padlen; + nghttp2_put_uint32be(buf->pos, (uint32_t)((newlen << 8) + buf->pos[3])); + + if (framehd_only) { + return; + } + + trail_padlen = padlen - 1; + buf->pos[NGHTTP2_FRAME_HDLEN] = (uint8_t)trail_padlen; + + /* zero out padding */ + memset(buf->last, 0, trail_padlen); + /* extend buffers trail_padlen bytes, since we ate previous padlen - + trail_padlen byte(s) */ + buf->last += trail_padlen; +} + +void nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd, + size_t padlen, int framehd_only) { + nghttp2_buf *buf; + + if (padlen == 0) { + DEBUGF("send: padlen = 0, nothing to do\n"); + + return; + } + + /* + * We have arranged bufs like this: + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | |Frame header | Frame payload... : + * +-+-----------------+-------------------------------------------+ + * | |Frame header | Frame payload... : + * +-+-----------------+-------------------------------------------+ + * | |Frame header | Frame payload... : + * +-+-----------------+-------------------------------------------+ + * + * We arranged padding so that it is included in the first frame + * completely. For padded frame, we are going to adjust buf->pos of + * frame which includes padding and serialize (memmove) frame header + * in the correct position. Also extends buf->last to include + * padding. + */ + + buf = &bufs->head->buf; + + assert(nghttp2_buf_avail(buf) >= padlen - 1); + + frame_set_pad(buf, padlen, framehd_only); + + hd->length += padlen; + hd->flags |= NGHTTP2_FLAG_PADDED; + + DEBUGF("send: final payloadlen=%zu, padlen=%zu\n", hd->length, padlen); +} diff --git a/lib/nghttp2/lib/nghttp2_frame.h b/lib/nghttp2/lib/nghttp2_frame.h new file mode 100644 index 00000000000..d58668806c4 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_frame.h @@ -0,0 +1,637 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_FRAME_H +#define NGHTTP2_FRAME_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include "nghttp2_hd.h" +#include "nghttp2_buf.h" + +#define NGHTTP2_STREAM_ID_MASK ((1u << 31) - 1) +#define NGHTTP2_PRI_GROUP_ID_MASK ((1u << 31) - 1) +#define NGHTTP2_PRIORITY_MASK ((1u << 31) - 1) +#define NGHTTP2_WINDOW_SIZE_INCREMENT_MASK ((1u << 31) - 1) +#define NGHTTP2_SETTINGS_ID_MASK ((1 << 24) - 1) + +/* The number of bytes of frame header. */ +#define NGHTTP2_FRAME_HDLEN 9 + +#define NGHTTP2_MAX_FRAME_SIZE_MAX ((1 << 24) - 1) +#define NGHTTP2_MAX_FRAME_SIZE_MIN (1 << 14) + +#define NGHTTP2_MAX_PAYLOADLEN 16384 +/* The one frame buffer length for transmission. We may use several of + them to support CONTINUATION. To account for Pad Length field, we + allocate extra 1 byte, which saves extra large memcopying. */ +#define NGHTTP2_FRAMEBUF_CHUNKLEN \ + (NGHTTP2_FRAME_HDLEN + 1 + NGHTTP2_MAX_PAYLOADLEN) + +/* The default length of DATA frame payload. */ +#define NGHTTP2_DATA_PAYLOADLEN NGHTTP2_MAX_FRAME_SIZE_MIN + +/* Maximum headers block size to send, calculated using + nghttp2_hd_deflate_bound(). This is the default value, and can be + overridden by nghttp2_option_set_max_send_header_block_length(). */ +#define NGHTTP2_MAX_HEADERSLEN 65536 + +/* The number of bytes for each SETTINGS entry */ +#define NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH 6 + +/* Length of priority related fields in HEADERS/PRIORITY frames */ +#define NGHTTP2_PRIORITY_SPECLEN 5 + +/* Maximum length of padding in bytes. */ +#define NGHTTP2_MAX_PADLEN 256 + +/* Union of extension frame payload */ +typedef union { + nghttp2_ext_altsvc altsvc; + nghttp2_ext_origin origin; + nghttp2_ext_priority_update priority_update; +} nghttp2_ext_frame_payload; + +void nghttp2_frame_pack_frame_hd(uint8_t *buf, const nghttp2_frame_hd *hd); + +void nghttp2_frame_unpack_frame_hd(nghttp2_frame_hd *hd, const uint8_t *buf); + +/** + * Initializes frame header |hd| with given parameters. Reserved bit + * is set to 0. + */ +void nghttp2_frame_hd_init(nghttp2_frame_hd *hd, size_t length, uint8_t type, + uint8_t flags, int32_t stream_id); + +/** + * Returns the number of priority field depending on the |flags|. If + * |flags| has neither NGHTTP2_FLAG_PRIORITY_GROUP nor + * NGHTTP2_FLAG_PRIORITY_DEPENDENCY set, return 0. + */ +size_t nghttp2_frame_priority_len(uint8_t flags); + +/** + * Packs the |pri_spec| in |buf|. This function assumes |buf| has + * enough space for serialization. + */ +void nghttp2_frame_pack_priority_spec(uint8_t *buf, + const nghttp2_priority_spec *pri_spec); + +/** + * Unpacks the priority specification from payload |payload| of length + * |payloadlen| to |pri_spec|. The |flags| is used to determine what + * kind of priority specification is in |payload|. This function + * assumes the |payload| contains whole priority specification. + */ +void nghttp2_frame_unpack_priority_spec(nghttp2_priority_spec *pri_spec, + const uint8_t *payload); + +/* + * Returns the offset from the HEADERS frame payload where the + * compressed header block starts. The frame payload does not include + * frame header. + */ +size_t nghttp2_frame_headers_payload_nv_offset(nghttp2_headers *frame); + +/* + * Packs HEADERS frame |frame| in wire format and store it in |bufs|. + * This function expands |bufs| as necessary to store frame. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + * + * frame->hd.length is assigned after length is determined during + * packing process. CONTINUATION frames are also serialized in this + * function. This function does not handle padding. + * + * This function returns 0 if it succeeds, or returns one of the + * following negative error codes: + * + * NGHTTP2_ERR_HEADER_COMP + * The deflate operation failed. + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_frame_pack_headers(nghttp2_bufs *bufs, nghttp2_headers *frame, + nghttp2_hd_deflater *deflater); + +/* + * Unpacks HEADERS frame byte sequence into |frame|. This function + * only unapcks bytes that come before name/value header block and + * after possible Pad Length field. + */ +void nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame, + const uint8_t *payload); + +/* + * Packs PRIORITY frame |frame| in wire format and store it in + * |bufs|. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + */ +void nghttp2_frame_pack_priority(nghttp2_bufs *bufs, nghttp2_priority *frame); + +/* + * Unpacks PRIORITY wire format into |frame|. + */ +void nghttp2_frame_unpack_priority_payload(nghttp2_priority *frame, + const uint8_t *payload); + +/* + * Packs RST_STREAM frame |frame| in wire frame format and store it in + * |bufs|. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + */ +void nghttp2_frame_pack_rst_stream(nghttp2_bufs *bufs, + nghttp2_rst_stream *frame); + +/* + * Unpacks RST_STREAM frame byte sequence into |frame|. + */ +void nghttp2_frame_unpack_rst_stream_payload(nghttp2_rst_stream *frame, + const uint8_t *payload); + +/* + * Packs SETTINGS frame |frame| in wire format and store it in + * |bufs|. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + * + * This function returns 0 if it succeeds, or returns one of the + * following negative error codes: + * + * NGHTTP2_ERR_FRAME_SIZE_ERROR + * The length of the frame is too large. + */ +int nghttp2_frame_pack_settings(nghttp2_bufs *bufs, nghttp2_settings *frame); + +/* + * Packs the |iv|, which includes |niv| entries, in the |buf|, + * assuming the |buf| has at least 8 * |niv| bytes. + * + * Returns the number of bytes written into the |buf|. + */ +size_t nghttp2_frame_pack_settings_payload(uint8_t *buf, + const nghttp2_settings_entry *iv, + size_t niv); + +void nghttp2_frame_unpack_settings_entry(nghttp2_settings_entry *iv, + const uint8_t *payload); + +/* + * Initializes payload of frame->settings. The |frame| takes + * ownership of |iv|. + */ +void nghttp2_frame_unpack_settings_payload(nghttp2_settings *frame, + nghttp2_settings_entry *iv, + size_t niv); + +/* + * Unpacks SETTINGS payload into |*iv_ptr|. The number of entries are + * assigned to the |*niv_ptr|. This function allocates enough memory + * to store the result in |*iv_ptr|. The caller is responsible to free + * |*iv_ptr| after its use. + * + * This function returns 0 if it succeeds or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_frame_unpack_settings_payload2(nghttp2_settings_entry **iv_ptr, + size_t *niv_ptr, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem); + +/* + * Packs PUSH_PROMISE frame |frame| in wire format and store it in + * |bufs|. This function expands |bufs| as necessary to store + * frame. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + * + * frame->hd.length is assigned after length is determined during + * packing process. CONTINUATION frames are also serialized in this + * function. This function does not handle padding. + * + * This function returns 0 if it succeeds, or returns one of the + * following negative error codes: + * + * NGHTTP2_ERR_HEADER_COMP + * The deflate operation failed. + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_frame_pack_push_promise(nghttp2_bufs *bufs, + nghttp2_push_promise *frame, + nghttp2_hd_deflater *deflater); + +/* + * Unpacks PUSH_PROMISE frame byte sequence into |frame|. This + * function only unapcks bytes that come before name/value header + * block and after possible Pad Length field. + */ +void nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame, + const uint8_t *payload); + +/* + * Packs PING frame |frame| in wire format and store it in + * |bufs|. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + */ +void nghttp2_frame_pack_ping(nghttp2_bufs *bufs, nghttp2_ping *frame); + +/* + * Unpacks PING wire format into |frame|. + */ +void nghttp2_frame_unpack_ping_payload(nghttp2_ping *frame, + const uint8_t *payload); + +/* + * Packs GOAWAY frame |frame| in wire format and store it in |bufs|. + * This function expands |bufs| as necessary to store frame. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + * + * This function returns 0 if it succeeds or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_FRAME_SIZE_ERROR + * The length of the frame is too large. + */ +int nghttp2_frame_pack_goaway(nghttp2_bufs *bufs, nghttp2_goaway *frame); + +/* + * Unpacks GOAWAY wire format into |frame|. The |payload| of length + * |payloadlen| contains first 8 bytes of payload. The + * |var_gift_payload| of length |var_gift_payloadlen| contains + * remaining payload and its buffer is gifted to the function and then + * |frame|. The |var_gift_payloadlen| must be freed by + * nghttp2_frame_goaway_free(). + */ +void nghttp2_frame_unpack_goaway_payload(nghttp2_goaway *frame, + const uint8_t *payload, + uint8_t *var_gift_payload, + size_t var_gift_payloadlen); + +/* + * Unpacks GOAWAY wire format into |frame|. This function only exists + * for unit test. After allocating buffer for debug data, this + * function internally calls nghttp2_frame_unpack_goaway_payload(). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_frame_unpack_goaway_payload2(nghttp2_goaway *frame, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem); + +/* + * Packs WINDOW_UPDATE frame |frame| in wire frame format and store it + * in |bufs|. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + */ +void nghttp2_frame_pack_window_update(nghttp2_bufs *bufs, + nghttp2_window_update *frame); + +/* + * Unpacks WINDOW_UPDATE frame byte sequence into |frame|. + */ +void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame, + const uint8_t *payload); + +/* + * Packs ALTSVC frame |frame| in wire frame format and store it in + * |bufs|. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + */ +void nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *ext); + +/* + * Unpacks ALTSVC wire format into |frame|. The |payload| of + * |payloadlen| bytes contains frame payload. This function assumes + * that frame->payload points to the nghttp2_ext_altsvc object. + */ +void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame, + size_t origin_len, uint8_t *payload, + size_t payloadlen); + +/* + * Unpacks ALTSVC wire format into |frame|. This function only exists + * for unit test. After allocating buffer for fields, this function + * internally calls nghttp2_frame_unpack_altsvc_payload(). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_FRAME_SIZE_ERROR + * The payload is too small. + */ +int nghttp2_frame_unpack_altsvc_payload2(nghttp2_extension *frame, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem); + +/* + * Packs ORIGIN frame |frame| in wire frame format and store it in + * |bufs|. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_FRAME_SIZE_ERROR + * The length of the frame is too large. + */ +int nghttp2_frame_pack_origin(nghttp2_bufs *bufs, nghttp2_extension *ext); + +/* + * Unpacks ORIGIN wire format into |frame|. The |payload| of length + * |payloadlen| contains the frame payload. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_FRAME_SIZE_ERROR + * The payload is too small. + */ +int nghttp2_frame_unpack_origin_payload(nghttp2_extension *frame, + const uint8_t *payload, + size_t payloadlen, nghttp2_mem *mem); + +/* + * Packs PRIORITY_UPDATE frame |frame| in wire frame format and store + * it in |bufs|. + * + * The caller must make sure that nghttp2_bufs_reset(bufs) is called + * before calling this function. + */ +void nghttp2_frame_pack_priority_update(nghttp2_bufs *bufs, + nghttp2_extension *ext); + +/* + * Unpacks PRIORITY_UPDATE wire format into |frame|. The |payload| of + * |payloadlen| bytes contains frame payload. This function assumes + * that frame->payload points to the nghttp2_ext_priority_update + * object. + */ +void nghttp2_frame_unpack_priority_update_payload(nghttp2_extension *frame, + uint8_t *payload, + size_t payloadlen); + +/* + * Initializes HEADERS frame |frame| with given values. |frame| takes + * ownership of |nva|, so caller must not free it. If |stream_id| is + * not assigned yet, it must be -1. + */ +void nghttp2_frame_headers_init(nghttp2_headers *frame, uint8_t flags, + int32_t stream_id, nghttp2_headers_category cat, + const nghttp2_priority_spec *pri_spec, + nghttp2_nv *nva, size_t nvlen); + +void nghttp2_frame_headers_free(nghttp2_headers *frame, nghttp2_mem *mem); + +void nghttp2_frame_priority_init(nghttp2_priority *frame, int32_t stream_id, + const nghttp2_priority_spec *pri_spec); + +void nghttp2_frame_priority_free(nghttp2_priority *frame); + +void nghttp2_frame_rst_stream_init(nghttp2_rst_stream *frame, int32_t stream_id, + uint32_t error_code); + +void nghttp2_frame_rst_stream_free(nghttp2_rst_stream *frame); + +/* + * Initializes PUSH_PROMISE frame |frame| with given values. |frame| + * takes ownership of |nva|, so caller must not free it. + */ +void nghttp2_frame_push_promise_init(nghttp2_push_promise *frame, uint8_t flags, + int32_t stream_id, + int32_t promised_stream_id, + nghttp2_nv *nva, size_t nvlen); + +void nghttp2_frame_push_promise_free(nghttp2_push_promise *frame, + nghttp2_mem *mem); + +/* + * Initializes SETTINGS frame |frame| with given values. |frame| takes + * ownership of |iv|, so caller must not free it. The |flags| are + * bitwise-OR of one or more of nghttp2_settings_flag. + */ +void nghttp2_frame_settings_init(nghttp2_settings *frame, uint8_t flags, + nghttp2_settings_entry *iv, size_t niv); + +void nghttp2_frame_settings_free(nghttp2_settings *frame, nghttp2_mem *mem); + +/* + * Initializes PING frame |frame| with given values. If the + * |opqeue_data| is not NULL, it must point to 8 bytes memory region + * of data. The data pointed by |opaque_data| is copied. It can be + * NULL. In this case, 8 bytes NULL is used. + */ +void nghttp2_frame_ping_init(nghttp2_ping *frame, uint8_t flags, + const uint8_t *opque_data); + +void nghttp2_frame_ping_free(nghttp2_ping *frame); + +/* + * Initializes GOAWAY frame |frame| with given values. On success, + * this function takes ownership of |opaque_data|, so caller must not + * free it. If the |opaque_data_len| is 0, opaque_data could be NULL. + */ +void nghttp2_frame_goaway_init(nghttp2_goaway *frame, int32_t last_stream_id, + uint32_t error_code, uint8_t *opaque_data, + size_t opaque_data_len); + +void nghttp2_frame_goaway_free(nghttp2_goaway *frame, nghttp2_mem *mem); + +void nghttp2_frame_window_update_init(nghttp2_window_update *frame, + uint8_t flags, int32_t stream_id, + int32_t window_size_increment); + +void nghttp2_frame_window_update_free(nghttp2_window_update *frame); + +void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type, + uint8_t flags, int32_t stream_id, + void *payload); + +void nghttp2_frame_extension_free(nghttp2_extension *frame); + +/* + * Initializes ALTSVC frame |frame| with given values. This function + * assumes that frame->payload points to nghttp2_ext_altsvc object. + * Also |origin| and |field_value| are allocated in single buffer, + * starting |origin|. On success, this function takes ownership of + * |origin|, so caller must not free it. + */ +void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id, + uint8_t *origin, size_t origin_len, + uint8_t *field_value, size_t field_value_len); + +/* + * Frees up resources under |frame|. This function does not free + * nghttp2_ext_altsvc object pointed by frame->payload. This function + * only frees origin pointed by nghttp2_ext_altsvc.origin. Therefore, + * other fields must be allocated in the same buffer with origin. + */ +void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem); + +/* + * Initializes ORIGIN frame |frame| with given values. This function + * assumes that frame->payload points to nghttp2_ext_origin object. + * Also |ov| and the memory pointed by the field of its elements are + * allocated in single buffer, starting with |ov|. On success, this + * function takes ownership of |ov|, so caller must not free it. + */ +void nghttp2_frame_origin_init(nghttp2_extension *frame, + nghttp2_origin_entry *ov, size_t nov); + +/* + * Frees up resources under |frame|. This function does not free + * nghttp2_ext_origin object pointed by frame->payload. This function + * only frees nghttp2_ext_origin.ov. Therefore, other fields must be + * allocated in the same buffer with ov. + */ +void nghttp2_frame_origin_free(nghttp2_extension *frame, nghttp2_mem *mem); + +/* + * Initializes PRIORITY_UPDATE frame |frame| with given values. This + * function assumes that frame->payload points to + * nghttp2_ext_priority_update object. On success, this function + * takes ownership of |field_value|, so caller must not free it. + */ +void nghttp2_frame_priority_update_init(nghttp2_extension *frame, + int32_t stream_id, uint8_t *field_value, + size_t field_value_len); + +/* + * Frees up resources under |frame|. This function does not free + * nghttp2_ext_priority_update object pointed by frame->payload. This + * function only frees field_value pointed by + * nghttp2_ext_priority_update.field_value. + */ +void nghttp2_frame_priority_update_free(nghttp2_extension *frame, + nghttp2_mem *mem); + +/* + * Returns the number of padding bytes after payload. The total + * padding length is given in the |padlen|. The returned value does + * not include the Pad Length field. If |padlen| is 0, this function + * returns 0, regardless of frame->hd.flags. + */ +size_t nghttp2_frame_trail_padlen(nghttp2_frame *frame, size_t padlen); + +void nghttp2_frame_data_init(nghttp2_data *frame, uint8_t flags, + int32_t stream_id); + +void nghttp2_frame_data_free(nghttp2_data *frame); + +/* + * Makes copy of |iv| and return the copy. The |niv| is the number of + * entries in |iv|. This function returns the pointer to the copy if + * it succeeds, or NULL. + */ +nghttp2_settings_entry *nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv, + size_t niv, nghttp2_mem *mem); + +/* + * Sorts the |nva| in ascending order of name and value. If names are + * equivalent, sort them by value. + */ +void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen); + +/* + * Copies name/value pairs from |nva|, which contains |nvlen| pairs, + * to |*nva_ptr|, which is dynamically allocated so that all items can + * be stored. The resultant name and value in nghttp2_nv are + * guaranteed to be NULL-terminated even if the input is not + * null-terminated. + * + * The |*nva_ptr| must be freed using nghttp2_nv_array_del(). + * + * This function returns 0 if it succeeds or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_nv_array_copy(nghttp2_nv **nva_ptr, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem); + +/* + * Returns nonzero if the name/value pair |a| equals to |b|. The name + * is compared in case-sensitive, because we ensure that this function + * is called after the name is lower-cased. + */ +int nghttp2_nv_equal(const nghttp2_nv *a, const nghttp2_nv *b); + +/* + * Frees |nva|. + */ +void nghttp2_nv_array_del(nghttp2_nv *nva, nghttp2_mem *mem); + +/* + * Checks that the |iv|, which includes |niv| entries, does not have + * invalid values. + * + * This function returns nonzero if it succeeds, or 0. + */ +int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv); + +/* + * Sets Pad Length field and flags and adjusts frame header position + * of each buffers in |bufs|. The number of padding is given in the + * |padlen| including Pad Length field. The |hd| is the frame header + * for the serialized data. This function fills zeros padding region + * unless framehd_only is nonzero. + */ +void nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd, + size_t padlen, int framehd_only); + +#endif /* NGHTTP2_FRAME_H */ diff --git a/lib/nghttp2/lib/nghttp2_hd.c b/lib/nghttp2/lib/nghttp2_hd.c new file mode 100644 index 00000000000..8a2bda64c1f --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_hd.c @@ -0,0 +1,2357 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_hd.h" + +#include +#include +#include + +#include "nghttp2_helper.h" +#include "nghttp2_int.h" +#include "nghttp2_debug.h" + +/* Make scalar initialization form of nghttp2_hd_entry */ +#define MAKE_STATIC_ENT(N, V, T, H) \ + { \ + {NULL, NULL, (uint8_t *)(N), sizeof((N)) - 1, -1}, \ + {NULL, NULL, (uint8_t *)(V), sizeof((V)) - 1, -1}, \ + {(uint8_t *)(N), (uint8_t *)(V), sizeof((N)) - 1, sizeof((V)) - 1, 0}, \ + T, H \ + } + +/* Generated by mkstatictbl.py */ +/* 3rd parameter is nghttp2_token value for header field name. We use + first enum value if same header names are repeated (e.g., + :status). */ +static const nghttp2_hd_static_entry static_table[] = { + MAKE_STATIC_ENT(":authority", "", 0, 3153725150u), + MAKE_STATIC_ENT(":method", "GET", 1, 695666056u), + MAKE_STATIC_ENT(":method", "POST", 1, 695666056u), + MAKE_STATIC_ENT(":path", "/", 3, 3292848686u), + MAKE_STATIC_ENT(":path", "/index.html", 3, 3292848686u), + MAKE_STATIC_ENT(":scheme", "http", 5, 2510477674u), + MAKE_STATIC_ENT(":scheme", "https", 5, 2510477674u), + MAKE_STATIC_ENT(":status", "200", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "204", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "206", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "304", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "400", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "404", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "500", 7, 4000288983u), + MAKE_STATIC_ENT("accept-charset", "", 14, 3664010344u), + MAKE_STATIC_ENT("accept-encoding", "gzip, deflate", 15, 3379649177u), + MAKE_STATIC_ENT("accept-language", "", 16, 1979086614u), + MAKE_STATIC_ENT("accept-ranges", "", 17, 1713753958u), + MAKE_STATIC_ENT("accept", "", 18, 136609321u), + MAKE_STATIC_ENT("access-control-allow-origin", "", 19, 2710797292u), + MAKE_STATIC_ENT("age", "", 20, 742476188u), + MAKE_STATIC_ENT("allow", "", 21, 2930878514u), + MAKE_STATIC_ENT("authorization", "", 22, 2436257726u), + MAKE_STATIC_ENT("cache-control", "", 23, 1355326669u), + MAKE_STATIC_ENT("content-disposition", "", 24, 3889184348u), + MAKE_STATIC_ENT("content-encoding", "", 25, 65203592u), + MAKE_STATIC_ENT("content-language", "", 26, 24973587u), + MAKE_STATIC_ENT("content-length", "", 27, 1308181789u), + MAKE_STATIC_ENT("content-location", "", 28, 2302364718u), + MAKE_STATIC_ENT("content-range", "", 29, 3555523146u), + MAKE_STATIC_ENT("content-type", "", 30, 4244048277u), + MAKE_STATIC_ENT("cookie", "", 31, 2007449791u), + MAKE_STATIC_ENT("date", "", 32, 3564297305u), + MAKE_STATIC_ENT("etag", "", 33, 113792960u), + MAKE_STATIC_ENT("expect", "", 34, 2530896728u), + MAKE_STATIC_ENT("expires", "", 35, 1049544579u), + MAKE_STATIC_ENT("from", "", 36, 2513272949u), + MAKE_STATIC_ENT("host", "", 37, 2952701295u), + MAKE_STATIC_ENT("if-match", "", 38, 3597694698u), + MAKE_STATIC_ENT("if-modified-since", "", 39, 2213050793u), + MAKE_STATIC_ENT("if-none-match", "", 40, 2536202615u), + MAKE_STATIC_ENT("if-range", "", 41, 2340978238u), + MAKE_STATIC_ENT("if-unmodified-since", "", 42, 3794814858u), + MAKE_STATIC_ENT("last-modified", "", 43, 3226950251u), + MAKE_STATIC_ENT("link", "", 44, 232457833u), + MAKE_STATIC_ENT("location", "", 45, 200649126u), + MAKE_STATIC_ENT("max-forwards", "", 46, 1826162134u), + MAKE_STATIC_ENT("proxy-authenticate", "", 47, 2709445359u), + MAKE_STATIC_ENT("proxy-authorization", "", 48, 2686392507u), + MAKE_STATIC_ENT("range", "", 49, 4208725202u), + MAKE_STATIC_ENT("referer", "", 50, 3969579366u), + MAKE_STATIC_ENT("refresh", "", 51, 3572655668u), + MAKE_STATIC_ENT("retry-after", "", 52, 3336180598u), + MAKE_STATIC_ENT("server", "", 53, 1085029842u), + MAKE_STATIC_ENT("set-cookie", "", 54, 1848371000u), + MAKE_STATIC_ENT("strict-transport-security", "", 55, 4138147361u), + MAKE_STATIC_ENT("transfer-encoding", "", 56, 3719590988u), + MAKE_STATIC_ENT("user-agent", "", 57, 606444526u), + MAKE_STATIC_ENT("vary", "", 58, 1085005381u), + MAKE_STATIC_ENT("via", "", 59, 1762798611u), + MAKE_STATIC_ENT("www-authenticate", "", 60, 779865858u), +}; + +static int memeq(const void *s1, const void *s2, size_t n) { + return memcmp(s1, s2, n) == 0; +} + +/* + * This function was generated by genlibtokenlookup.py. Inspired by + * h2o header lookup. https://github.com/h2o/h2o + */ +static int32_t lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) { + case 2: + switch (name[1]) { + case 'e': + if (memeq("t", name, 1)) { + return NGHTTP2_TOKEN_TE; + } + break; + } + break; + case 3: + switch (name[2]) { + case 'a': + if (memeq("vi", name, 2)) { + return NGHTTP2_TOKEN_VIA; + } + break; + case 'e': + if (memeq("ag", name, 2)) { + return NGHTTP2_TOKEN_AGE; + } + break; + } + break; + case 4: + switch (name[3]) { + case 'e': + if (memeq("dat", name, 3)) { + return NGHTTP2_TOKEN_DATE; + } + break; + case 'g': + if (memeq("eta", name, 3)) { + return NGHTTP2_TOKEN_ETAG; + } + break; + case 'k': + if (memeq("lin", name, 3)) { + return NGHTTP2_TOKEN_LINK; + } + break; + case 'm': + if (memeq("fro", name, 3)) { + return NGHTTP2_TOKEN_FROM; + } + break; + case 't': + if (memeq("hos", name, 3)) { + return NGHTTP2_TOKEN_HOST; + } + break; + case 'y': + if (memeq("var", name, 3)) { + return NGHTTP2_TOKEN_VARY; + } + break; + } + break; + case 5: + switch (name[4]) { + case 'e': + if (memeq("rang", name, 4)) { + return NGHTTP2_TOKEN_RANGE; + } + break; + case 'h': + if (memeq(":pat", name, 4)) { + return NGHTTP2_TOKEN__PATH; + } + break; + case 'w': + if (memeq("allo", name, 4)) { + return NGHTTP2_TOKEN_ALLOW; + } + break; + } + break; + case 6: + switch (name[5]) { + case 'e': + if (memeq("cooki", name, 5)) { + return NGHTTP2_TOKEN_COOKIE; + } + break; + case 'r': + if (memeq("serve", name, 5)) { + return NGHTTP2_TOKEN_SERVER; + } + break; + case 't': + if (memeq("accep", name, 5)) { + return NGHTTP2_TOKEN_ACCEPT; + } + if (memeq("expec", name, 5)) { + return NGHTTP2_TOKEN_EXPECT; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'd': + if (memeq(":metho", name, 6)) { + return NGHTTP2_TOKEN__METHOD; + } + break; + case 'e': + if (memeq(":schem", name, 6)) { + return NGHTTP2_TOKEN__SCHEME; + } + if (memeq("upgrad", name, 6)) { + return NGHTTP2_TOKEN_UPGRADE; + } + break; + case 'h': + if (memeq("refres", name, 6)) { + return NGHTTP2_TOKEN_REFRESH; + } + break; + case 'r': + if (memeq("refere", name, 6)) { + return NGHTTP2_TOKEN_REFERER; + } + break; + case 's': + if (memeq(":statu", name, 6)) { + return NGHTTP2_TOKEN__STATUS; + } + if (memeq("expire", name, 6)) { + return NGHTTP2_TOKEN_EXPIRES; + } + break; + } + break; + case 8: + switch (name[7]) { + case 'e': + if (memeq("if-rang", name, 7)) { + return NGHTTP2_TOKEN_IF_RANGE; + } + break; + case 'h': + if (memeq("if-matc", name, 7)) { + return NGHTTP2_TOKEN_IF_MATCH; + } + break; + case 'n': + if (memeq("locatio", name, 7)) { + return NGHTTP2_TOKEN_LOCATION; + } + break; + case 'y': + if (memeq("priorit", name, 7)) { + return NGHTTP2_TOKEN_PRIORITY; + } + break; + } + break; + case 9: + switch (name[8]) { + case 'l': + if (memeq(":protoco", name, 8)) { + return NGHTTP2_TOKEN__PROTOCOL; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'e': + if (memeq("keep-aliv", name, 9)) { + return NGHTTP2_TOKEN_KEEP_ALIVE; + } + if (memeq("set-cooki", name, 9)) { + return NGHTTP2_TOKEN_SET_COOKIE; + } + break; + case 'n': + if (memeq("connectio", name, 9)) { + return NGHTTP2_TOKEN_CONNECTION; + } + break; + case 't': + if (memeq("user-agen", name, 9)) { + return NGHTTP2_TOKEN_USER_AGENT; + } + break; + case 'y': + if (memeq(":authorit", name, 9)) { + return NGHTTP2_TOKEN__AUTHORITY; + } + break; + } + break; + case 11: + switch (name[10]) { + case 'r': + if (memeq("retry-afte", name, 10)) { + return NGHTTP2_TOKEN_RETRY_AFTER; + } + break; + } + break; + case 12: + switch (name[11]) { + case 'e': + if (memeq("content-typ", name, 11)) { + return NGHTTP2_TOKEN_CONTENT_TYPE; + } + break; + case 's': + if (memeq("max-forward", name, 11)) { + return NGHTTP2_TOKEN_MAX_FORWARDS; + } + break; + } + break; + case 13: + switch (name[12]) { + case 'd': + if (memeq("last-modifie", name, 12)) { + return NGHTTP2_TOKEN_LAST_MODIFIED; + } + break; + case 'e': + if (memeq("content-rang", name, 12)) { + return NGHTTP2_TOKEN_CONTENT_RANGE; + } + break; + case 'h': + if (memeq("if-none-matc", name, 12)) { + return NGHTTP2_TOKEN_IF_NONE_MATCH; + } + break; + case 'l': + if (memeq("cache-contro", name, 12)) { + return NGHTTP2_TOKEN_CACHE_CONTROL; + } + break; + case 'n': + if (memeq("authorizatio", name, 12)) { + return NGHTTP2_TOKEN_AUTHORIZATION; + } + break; + case 's': + if (memeq("accept-range", name, 12)) { + return NGHTTP2_TOKEN_ACCEPT_RANGES; + } + break; + } + break; + case 14: + switch (name[13]) { + case 'h': + if (memeq("content-lengt", name, 13)) { + return NGHTTP2_TOKEN_CONTENT_LENGTH; + } + break; + case 't': + if (memeq("accept-charse", name, 13)) { + return NGHTTP2_TOKEN_ACCEPT_CHARSET; + } + break; + } + break; + case 15: + switch (name[14]) { + case 'e': + if (memeq("accept-languag", name, 14)) { + return NGHTTP2_TOKEN_ACCEPT_LANGUAGE; + } + break; + case 'g': + if (memeq("accept-encodin", name, 14)) { + return NGHTTP2_TOKEN_ACCEPT_ENCODING; + } + break; + } + break; + case 16: + switch (name[15]) { + case 'e': + if (memeq("content-languag", name, 15)) { + return NGHTTP2_TOKEN_CONTENT_LANGUAGE; + } + if (memeq("www-authenticat", name, 15)) { + return NGHTTP2_TOKEN_WWW_AUTHENTICATE; + } + break; + case 'g': + if (memeq("content-encodin", name, 15)) { + return NGHTTP2_TOKEN_CONTENT_ENCODING; + } + break; + case 'n': + if (memeq("content-locatio", name, 15)) { + return NGHTTP2_TOKEN_CONTENT_LOCATION; + } + if (memeq("proxy-connectio", name, 15)) { + return NGHTTP2_TOKEN_PROXY_CONNECTION; + } + break; + } + break; + case 17: + switch (name[16]) { + case 'e': + if (memeq("if-modified-sinc", name, 16)) { + return NGHTTP2_TOKEN_IF_MODIFIED_SINCE; + } + break; + case 'g': + if (memeq("transfer-encodin", name, 16)) { + return NGHTTP2_TOKEN_TRANSFER_ENCODING; + } + break; + } + break; + case 18: + switch (name[17]) { + case 'e': + if (memeq("proxy-authenticat", name, 17)) { + return NGHTTP2_TOKEN_PROXY_AUTHENTICATE; + } + break; + } + break; + case 19: + switch (name[18]) { + case 'e': + if (memeq("if-unmodified-sinc", name, 18)) { + return NGHTTP2_TOKEN_IF_UNMODIFIED_SINCE; + } + break; + case 'n': + if (memeq("content-dispositio", name, 18)) { + return NGHTTP2_TOKEN_CONTENT_DISPOSITION; + } + if (memeq("proxy-authorizatio", name, 18)) { + return NGHTTP2_TOKEN_PROXY_AUTHORIZATION; + } + break; + } + break; + case 25: + switch (name[24]) { + case 'y': + if (memeq("strict-transport-securit", name, 24)) { + return NGHTTP2_TOKEN_STRICT_TRANSPORT_SECURITY; + } + break; + } + break; + case 27: + switch (name[26]) { + case 'n': + if (memeq("access-control-allow-origi", name, 26)) { + return NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN; + } + break; + } + break; + } + return -1; +} + +void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv) { + ent->nv = *nv; + ent->cnv.name = nv->name->base; + ent->cnv.namelen = nv->name->len; + ent->cnv.value = nv->value->base; + ent->cnv.valuelen = nv->value->len; + ent->cnv.flags = nv->flags; + ent->next = NULL; + ent->hash = 0; + + nghttp2_rcbuf_incref(ent->nv.name); + nghttp2_rcbuf_incref(ent->nv.value); +} + +void nghttp2_hd_entry_free(nghttp2_hd_entry *ent) { + nghttp2_rcbuf_decref(ent->nv.value); + nghttp2_rcbuf_decref(ent->nv.name); +} + +static int name_eq(const nghttp2_hd_nv *a, const nghttp2_nv *b) { + return a->name->len == b->namelen && + memeq(a->name->base, b->name, b->namelen); +} + +static int value_eq(const nghttp2_hd_nv *a, const nghttp2_nv *b) { + return a->value->len == b->valuelen && + memeq(a->value->base, b->value, b->valuelen); +} + +static uint32_t name_hash(const nghttp2_nv *nv) { + /* 32 bit FNV-1a: http://isthe.com/chongo/tech/comp/fnv/ */ + uint32_t h = 2166136261u; + size_t i; + + for (i = 0; i < nv->namelen; ++i) { + h ^= nv->name[i]; + h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); + } + + return h; +} + +static void hd_map_init(nghttp2_hd_map *map) { + memset(map, 0, sizeof(nghttp2_hd_map)); +} + +static void hd_map_insert(nghttp2_hd_map *map, nghttp2_hd_entry *ent) { + nghttp2_hd_entry **bucket; + + bucket = &map->table[ent->hash & (HD_MAP_SIZE - 1)]; + + if (*bucket == NULL) { + *bucket = ent; + return; + } + + /* lower index is linked near the root */ + ent->next = *bucket; + *bucket = ent; +} + +static nghttp2_hd_entry *hd_map_find(nghttp2_hd_map *map, int *exact_match, + const nghttp2_nv *nv, int32_t token, + uint32_t hash, int name_only) { + nghttp2_hd_entry *p; + nghttp2_hd_entry *res = NULL; + + *exact_match = 0; + + for (p = map->table[hash & (HD_MAP_SIZE - 1)]; p; p = p->next) { + if (token != p->nv.token || + (token == -1 && (hash != p->hash || !name_eq(&p->nv, nv)))) { + continue; + } + if (!res) { + res = p; + if (name_only) { + break; + } + } + if (value_eq(&p->nv, nv)) { + res = p; + *exact_match = 1; + break; + } + } + + return res; +} + +static void hd_map_remove(nghttp2_hd_map *map, nghttp2_hd_entry *ent) { + nghttp2_hd_entry **dst; + + dst = &map->table[ent->hash & (HD_MAP_SIZE - 1)]; + + for (; *dst; dst = &(*dst)->next) { + if (*dst != ent) { + continue; + } + + *dst = ent->next; + ent->next = NULL; + return; + } +} + +static int hd_ringbuf_init(nghttp2_hd_ringbuf *ringbuf, size_t bufsize, + nghttp2_mem *mem) { + size_t size; + for (size = 1; size < bufsize; size <<= 1) + ; + ringbuf->buffer = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry *) * size); + if (ringbuf->buffer == NULL) { + return NGHTTP2_ERR_NOMEM; + } + ringbuf->mask = size - 1; + ringbuf->first = 0; + ringbuf->len = 0; + return 0; +} + +static nghttp2_hd_entry *hd_ringbuf_get(nghttp2_hd_ringbuf *ringbuf, + size_t idx) { + assert(idx < ringbuf->len); + return ringbuf->buffer[(ringbuf->first + idx) & ringbuf->mask]; +} + +static int hd_ringbuf_reserve(nghttp2_hd_ringbuf *ringbuf, size_t bufsize, + nghttp2_mem *mem) { + size_t i; + size_t size; + nghttp2_hd_entry **buffer; + + if (ringbuf->mask + 1 >= bufsize) { + return 0; + } + for (size = 1; size < bufsize; size <<= 1) + ; + buffer = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry *) * size); + if (buffer == NULL) { + return NGHTTP2_ERR_NOMEM; + } + for (i = 0; i < ringbuf->len; ++i) { + buffer[i] = hd_ringbuf_get(ringbuf, i); + } + nghttp2_mem_free(mem, ringbuf->buffer); + ringbuf->buffer = buffer; + ringbuf->mask = size - 1; + ringbuf->first = 0; + return 0; +} + +static void hd_ringbuf_free(nghttp2_hd_ringbuf *ringbuf, nghttp2_mem *mem) { + size_t i; + if (ringbuf == NULL) { + return; + } + for (i = 0; i < ringbuf->len; ++i) { + nghttp2_hd_entry *ent = hd_ringbuf_get(ringbuf, i); + + nghttp2_hd_entry_free(ent); + nghttp2_mem_free(mem, ent); + } + nghttp2_mem_free(mem, ringbuf->buffer); +} + +static int hd_ringbuf_push_front(nghttp2_hd_ringbuf *ringbuf, + nghttp2_hd_entry *ent, nghttp2_mem *mem) { + int rv; + + rv = hd_ringbuf_reserve(ringbuf, ringbuf->len + 1, mem); + + if (rv != 0) { + return rv; + } + + ringbuf->buffer[--ringbuf->first & ringbuf->mask] = ent; + ++ringbuf->len; + + return 0; +} + +static void hd_ringbuf_pop_back(nghttp2_hd_ringbuf *ringbuf) { + assert(ringbuf->len > 0); + --ringbuf->len; +} + +static int hd_context_init(nghttp2_hd_context *context, nghttp2_mem *mem) { + int rv; + context->mem = mem; + context->bad = 0; + context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; + rv = hd_ringbuf_init( + &context->hd_table, + context->hd_table_bufsize_max / NGHTTP2_HD_ENTRY_OVERHEAD, mem); + if (rv != 0) { + return rv; + } + + context->hd_table_bufsize = 0; + context->next_seq = 0; + + return 0; +} + +static void hd_context_free(nghttp2_hd_context *context) { + hd_ringbuf_free(&context->hd_table, context->mem); +} + +int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem) { + return nghttp2_hd_deflate_init2( + deflater, NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE, mem); +} + +int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, + size_t max_deflate_dynamic_table_size, + nghttp2_mem *mem) { + int rv; + rv = hd_context_init(&deflater->ctx, mem); + if (rv != 0) { + return rv; + } + + hd_map_init(&deflater->map); + + if (max_deflate_dynamic_table_size < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) { + deflater->notify_table_size_change = 1; + deflater->ctx.hd_table_bufsize_max = max_deflate_dynamic_table_size; + } else { + deflater->notify_table_size_change = 0; + } + + deflater->deflate_hd_table_bufsize_max = max_deflate_dynamic_table_size; + deflater->min_hd_table_bufsize_max = UINT32_MAX; + + return 0; +} + +int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem) { + int rv; + + rv = hd_context_init(&inflater->ctx, mem); + if (rv != 0) { + goto fail; + } + + inflater->settings_hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; + inflater->min_hd_table_bufsize_max = UINT32_MAX; + + inflater->nv_name_keep = NULL; + inflater->nv_value_keep = NULL; + + inflater->opcode = NGHTTP2_HD_OPCODE_NONE; + inflater->state = NGHTTP2_HD_STATE_INFLATE_START; + + nghttp2_buf_init(&inflater->namebuf); + nghttp2_buf_init(&inflater->valuebuf); + + inflater->namercbuf = NULL; + inflater->valuercbuf = NULL; + + inflater->huffman_encoded = 0; + inflater->index = 0; + inflater->left = 0; + inflater->shift = 0; + inflater->index_required = 0; + inflater->no_index = 0; + + return 0; + +fail: + return rv; +} + +static void hd_inflate_keep_free(nghttp2_hd_inflater *inflater) { + nghttp2_rcbuf_decref(inflater->nv_value_keep); + nghttp2_rcbuf_decref(inflater->nv_name_keep); + + inflater->nv_value_keep = NULL; + inflater->nv_name_keep = NULL; +} + +void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater) { + hd_context_free(&deflater->ctx); +} + +void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater) { + hd_inflate_keep_free(inflater); + + nghttp2_rcbuf_decref(inflater->valuercbuf); + nghttp2_rcbuf_decref(inflater->namercbuf); + + hd_context_free(&inflater->ctx); +} + +static size_t entry_room(size_t namelen, size_t valuelen) { + return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen; +} + +static void emit_header(nghttp2_hd_nv *nv_out, nghttp2_hd_nv *nv) { + DEBUGF("inflatehd: header emission: %s: %s\n", nv->name->base, + nv->value->base); + /* ent->ref may be 0. This happens if the encoder emits literal + block larger than header table capacity with indexing. */ + *nv_out = *nv; +} + +static size_t count_encoded_length(size_t n, size_t prefix) { + size_t k = (size_t)((1 << prefix) - 1); + size_t len = 0; + + if (n < k) { + return 1; + } + + n -= k; + ++len; + + for (; n >= 128; n >>= 7, ++len) + ; + + return len + 1; +} + +static size_t encode_length(uint8_t *buf, size_t n, size_t prefix) { + size_t k = (size_t)((1 << prefix) - 1); + uint8_t *begin = buf; + + *buf = (uint8_t)(*buf & ~k); + + if (n < k) { + *buf = (uint8_t)(*buf | n); + return 1; + } + + *buf = (uint8_t)(*buf | k); + ++buf; + + n -= k; + + for (; n >= 128; n >>= 7) { + *buf++ = (uint8_t)((1 << 7) | (n & 0x7f)); + } + + *buf++ = (uint8_t)n; + + return (size_t)(buf - begin); +} + +/* + * Decodes |prefix| prefixed integer stored from |in|. The |last| + * represents the 1 beyond the last of the valid contiguous memory + * region from |in|. The decoded integer must be less than or equal + * to UINT32_MAX. + * + * If the |initial| is nonzero, it is used as a initial value, this + * function assumes the |in| starts with intermediate data. + * + * An entire integer is decoded successfully, decoded, the |*fin| is + * set to nonzero. + * + * This function stores the decoded integer in |*res| if it succeed, + * including partial decoding (in this case, number of shift to make + * in the next call will be stored in |*shift_ptr|) and returns number + * of bytes processed, or returns -1, indicating decoding error. + */ +static ssize_t decode_length(uint32_t *res, size_t *shift_ptr, int *fin, + uint32_t initial, size_t shift, const uint8_t *in, + const uint8_t *last, size_t prefix) { + uint32_t k = (uint8_t)((1 << prefix) - 1); + uint32_t n = initial; + const uint8_t *start = in; + + *shift_ptr = 0; + *fin = 0; + + if (n == 0) { + if ((*in & k) != k) { + *res = (*in) & k; + *fin = 1; + return 1; + } + + n = k; + + if (++in == last) { + *res = n; + return (ssize_t)(in - start); + } + } + + for (; in != last; ++in, shift += 7) { + uint32_t add = *in & 0x7f; + + if (shift >= 32) { + DEBUGF("inflate: shift exponent overflow\n"); + return -1; + } + + if ((UINT32_MAX >> shift) < add) { + DEBUGF("inflate: integer overflow on shift\n"); + return -1; + } + + add <<= shift; + + if (UINT32_MAX - add < n) { + DEBUGF("inflate: integer overflow on addition\n"); + return -1; + } + + n += add; + + if ((*in & (1 << 7)) == 0) { + break; + } + } + + *shift_ptr = shift; + + if (in == last) { + *res = n; + return (ssize_t)(in - start); + } + + *res = n; + *fin = 1; + return (ssize_t)(in + 1 - start); +} + +static int emit_table_size(nghttp2_bufs *bufs, size_t table_size) { + int rv; + uint8_t *bufp; + size_t blocklen; + uint8_t sb[16]; + + DEBUGF("deflatehd: emit table_size=%zu\n", table_size); + + blocklen = count_encoded_length(table_size, 5); + + if (sizeof(sb) < blocklen) { + return NGHTTP2_ERR_HEADER_COMP; + } + + bufp = sb; + + *bufp = 0x20u; + + encode_length(bufp, table_size, 5); + + rv = nghttp2_bufs_add(bufs, sb, blocklen); + if (rv != 0) { + return rv; + } + + return 0; +} + +static int emit_indexed_block(nghttp2_bufs *bufs, size_t idx) { + int rv; + size_t blocklen; + uint8_t sb[16]; + uint8_t *bufp; + + blocklen = count_encoded_length(idx + 1, 7); + + DEBUGF("deflatehd: emit indexed index=%zu, %zu bytes\n", idx, blocklen); + + if (sizeof(sb) < blocklen) { + return NGHTTP2_ERR_HEADER_COMP; + } + + bufp = sb; + *bufp = 0x80u; + encode_length(bufp, idx + 1, 7); + + rv = nghttp2_bufs_add(bufs, sb, blocklen); + if (rv != 0) { + return rv; + } + + return 0; +} + +static int emit_string(nghttp2_bufs *bufs, const uint8_t *str, size_t len) { + int rv; + uint8_t sb[16]; + uint8_t *bufp; + size_t blocklen; + size_t enclen; + int huffman = 0; + + enclen = nghttp2_hd_huff_encode_count(str, len); + + if (enclen < len) { + huffman = 1; + } else { + enclen = len; + } + + blocklen = count_encoded_length(enclen, 7); + + DEBUGF("deflatehd: emit string str=%.*s, length=%zu, huffman=%d, " + "encoded_length=%zu\n", + (int)len, (const char *)str, len, huffman, enclen); + + if (sizeof(sb) < blocklen) { + return NGHTTP2_ERR_HEADER_COMP; + } + + bufp = sb; + *bufp = huffman ? 1 << 7 : 0; + encode_length(bufp, enclen, 7); + + rv = nghttp2_bufs_add(bufs, sb, blocklen); + if (rv != 0) { + return rv; + } + + if (huffman) { + rv = nghttp2_hd_huff_encode(bufs, str, len); + } else { + assert(enclen == len); + rv = nghttp2_bufs_add(bufs, str, len); + } + + return rv; +} + +static uint8_t pack_first_byte(int indexing_mode) { + switch (indexing_mode) { + case NGHTTP2_HD_WITH_INDEXING: + return 0x40u; + case NGHTTP2_HD_WITHOUT_INDEXING: + return 0; + case NGHTTP2_HD_NEVER_INDEXING: + return 0x10u; + default: + assert(0); + } + /* This is required to compile with android NDK r10d + + --enable-werror */ + return 0; +} + +static int emit_indname_block(nghttp2_bufs *bufs, size_t idx, + const nghttp2_nv *nv, int indexing_mode) { + int rv; + uint8_t *bufp; + size_t blocklen; + uint8_t sb[16]; + size_t prefixlen; + + if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) { + prefixlen = 6; + } else { + prefixlen = 4; + } + + DEBUGF("deflatehd: emit indname index=%zu, valuelen=%zu, indexing_mode=%d\n", + idx, nv->valuelen, indexing_mode); + + blocklen = count_encoded_length(idx + 1, prefixlen); + + if (sizeof(sb) < blocklen) { + return NGHTTP2_ERR_HEADER_COMP; + } + + bufp = sb; + + *bufp = pack_first_byte(indexing_mode); + + encode_length(bufp, idx + 1, prefixlen); + + rv = nghttp2_bufs_add(bufs, sb, blocklen); + if (rv != 0) { + return rv; + } + + rv = emit_string(bufs, nv->value, nv->valuelen); + if (rv != 0) { + return rv; + } + + return 0; +} + +static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv, + int indexing_mode) { + int rv; + + DEBUGF( + "deflatehd: emit newname namelen=%zu, valuelen=%zu, indexing_mode=%d\n", + nv->namelen, nv->valuelen, indexing_mode); + + rv = nghttp2_bufs_addb(bufs, pack_first_byte(indexing_mode)); + if (rv != 0) { + return rv; + } + + rv = emit_string(bufs, nv->name, nv->namelen); + if (rv != 0) { + return rv; + } + + rv = emit_string(bufs, nv->value, nv->valuelen); + if (rv != 0) { + return rv; + } + + return 0; +} + +static int add_hd_table_incremental(nghttp2_hd_context *context, + nghttp2_hd_nv *nv, nghttp2_hd_map *map, + uint32_t hash) { + int rv; + nghttp2_hd_entry *new_ent; + size_t room; + nghttp2_mem *mem; + + mem = context->mem; + room = entry_room(nv->name->len, nv->value->len); + + while (context->hd_table_bufsize + room > context->hd_table_bufsize_max && + context->hd_table.len > 0) { + + size_t idx = context->hd_table.len - 1; + nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx); + + context->hd_table_bufsize -= + entry_room(ent->nv.name->len, ent->nv.value->len); + + DEBUGF("hpack: remove item from header table: %s: %s\n", + (char *)ent->nv.name->base, (char *)ent->nv.value->base); + + hd_ringbuf_pop_back(&context->hd_table); + if (map) { + hd_map_remove(map, ent); + } + + nghttp2_hd_entry_free(ent); + nghttp2_mem_free(mem, ent); + } + + if (room > context->hd_table_bufsize_max) { + /* The entry taking more than NGHTTP2_HD_MAX_BUFFER_SIZE is + immediately evicted. So we don't allocate memory for it. */ + return 0; + } + + new_ent = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry)); + if (new_ent == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_hd_entry_init(new_ent, nv); + + rv = hd_ringbuf_push_front(&context->hd_table, new_ent, mem); + + if (rv != 0) { + nghttp2_hd_entry_free(new_ent); + nghttp2_mem_free(mem, new_ent); + + return rv; + } + + new_ent->seq = context->next_seq++; + new_ent->hash = hash; + + if (map) { + hd_map_insert(map, new_ent); + } + + context->hd_table_bufsize += room; + + return 0; +} + +typedef struct { + ssize_t index; + /* Nonzero if both name and value are matched. */ + int name_value_match; +} search_result; + +static search_result search_static_table(const nghttp2_nv *nv, int32_t token, + int name_only) { + search_result res = {token, 0}; + int i; + const nghttp2_hd_static_entry *ent; + + if (name_only) { + return res; + } + + for (i = token; + i <= NGHTTP2_TOKEN_WWW_AUTHENTICATE && static_table[i].token == token; + ++i) { + ent = &static_table[i]; + if (ent->value.len == nv->valuelen && + memcmp(ent->value.base, nv->value, nv->valuelen) == 0) { + res.index = i; + res.name_value_match = 1; + return res; + } + } + return res; +} + +static search_result search_hd_table(nghttp2_hd_context *context, + const nghttp2_nv *nv, int32_t token, + int indexing_mode, nghttp2_hd_map *map, + uint32_t hash) { + search_result res = {-1, 0}; + const nghttp2_hd_entry *ent; + int exact_match; + int name_only = indexing_mode == NGHTTP2_HD_NEVER_INDEXING; + + exact_match = 0; + ent = hd_map_find(map, &exact_match, nv, token, hash, name_only); + + if (!exact_match && token >= 0 && token <= NGHTTP2_TOKEN_WWW_AUTHENTICATE) { + return search_static_table(nv, token, name_only); + } + + if (ent == NULL) { + return res; + } + + res.index = + (ssize_t)(context->next_seq - 1 - ent->seq + NGHTTP2_STATIC_TABLE_LENGTH); + res.name_value_match = exact_match; + + return res; +} + +static void hd_context_shrink_table_size(nghttp2_hd_context *context, + nghttp2_hd_map *map) { + nghttp2_mem *mem; + + mem = context->mem; + + while (context->hd_table_bufsize > context->hd_table_bufsize_max && + context->hd_table.len > 0) { + size_t idx = context->hd_table.len - 1; + nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx); + context->hd_table_bufsize -= + entry_room(ent->nv.name->len, ent->nv.value->len); + hd_ringbuf_pop_back(&context->hd_table); + if (map) { + hd_map_remove(map, ent); + } + + nghttp2_hd_entry_free(ent); + nghttp2_mem_free(mem, ent); + } +} + +int nghttp2_hd_deflate_change_table_size( + nghttp2_hd_deflater *deflater, size_t settings_max_dynamic_table_size) { + size_t next_bufsize = nghttp2_min(settings_max_dynamic_table_size, + deflater->deflate_hd_table_bufsize_max); + + deflater->ctx.hd_table_bufsize_max = next_bufsize; + + deflater->min_hd_table_bufsize_max = + nghttp2_min(deflater->min_hd_table_bufsize_max, next_bufsize); + + deflater->notify_table_size_change = 1; + + hd_context_shrink_table_size(&deflater->ctx, &deflater->map); + return 0; +} + +int nghttp2_hd_inflate_change_table_size( + nghttp2_hd_inflater *inflater, size_t settings_max_dynamic_table_size) { + switch (inflater->state) { + case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE: + case NGHTTP2_HD_STATE_INFLATE_START: + break; + default: + return NGHTTP2_ERR_INVALID_STATE; + } + + inflater->settings_hd_table_bufsize_max = settings_max_dynamic_table_size; + + /* It seems that encoder is not required to send dynamic table size + update if the table size is not changed after applying + SETTINGS_HEADER_TABLE_SIZE. RFC 7541 is ambiguous here, but this + is the intention of the editor. If new maximum table size is + strictly smaller than the current negotiated maximum size, + encoder must send dynamic table size update. In other cases, we + cannot expect it to do so. */ + if (inflater->ctx.hd_table_bufsize_max > settings_max_dynamic_table_size) { + inflater->state = NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE; + /* Remember minimum value, and validate that encoder sends the + value less than or equal to this. */ + inflater->min_hd_table_bufsize_max = settings_max_dynamic_table_size; + + inflater->ctx.hd_table_bufsize_max = settings_max_dynamic_table_size; + + hd_context_shrink_table_size(&inflater->ctx, NULL); + } + + return 0; +} + +#define INDEX_RANGE_VALID(context, idx) \ + ((idx) < (context)->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH) + +static size_t get_max_index(nghttp2_hd_context *context) { + return context->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH; +} + +nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t idx) { + assert(INDEX_RANGE_VALID(context, idx)); + if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) { + return hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH) + ->nv; + } else { + const nghttp2_hd_static_entry *ent = &static_table[idx]; + nghttp2_hd_nv nv = {(nghttp2_rcbuf *)&ent->name, + (nghttp2_rcbuf *)&ent->value, ent->token, + NGHTTP2_NV_FLAG_NONE}; + return nv; + } +} + +static const nghttp2_nv *nghttp2_hd_table_get2(nghttp2_hd_context *context, + size_t idx) { + assert(INDEX_RANGE_VALID(context, idx)); + if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) { + return &hd_ringbuf_get(&context->hd_table, + idx - NGHTTP2_STATIC_TABLE_LENGTH) + ->cnv; + } + + return &static_table[idx].cnv; +} + +static int hd_deflate_decide_indexing(nghttp2_hd_deflater *deflater, + const nghttp2_nv *nv, int32_t token) { + if (token == NGHTTP2_TOKEN__PATH || token == NGHTTP2_TOKEN_AGE || + token == NGHTTP2_TOKEN_CONTENT_LENGTH || token == NGHTTP2_TOKEN_ETAG || + token == NGHTTP2_TOKEN_IF_MODIFIED_SINCE || + token == NGHTTP2_TOKEN_IF_NONE_MATCH || token == NGHTTP2_TOKEN_LOCATION || + token == NGHTTP2_TOKEN_SET_COOKIE || + entry_room(nv->namelen, nv->valuelen) > + deflater->ctx.hd_table_bufsize_max * 3 / 4) { + return NGHTTP2_HD_WITHOUT_INDEXING; + } + + return NGHTTP2_HD_WITH_INDEXING; +} + +static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, + const nghttp2_nv *nv) { + int rv; + search_result res; + ssize_t idx; + int indexing_mode; + int32_t token; + nghttp2_mem *mem; + uint32_t hash = 0; + + DEBUGF("deflatehd: deflating %.*s: %.*s\n", (int)nv->namelen, nv->name, + (int)nv->valuelen, nv->value); + + mem = deflater->ctx.mem; + + token = lookup_token(nv->name, nv->namelen); + if (token == -1) { + hash = name_hash(nv); + } else if (token <= NGHTTP2_TOKEN_WWW_AUTHENTICATE) { + hash = static_table[token].hash; + } + + /* Don't index authorization header field since it may contain low + entropy secret data (e.g., id/password). Also cookie header + field with less than 20 bytes value is also never indexed. This + is the same criteria used in Firefox codebase. */ + indexing_mode = + token == NGHTTP2_TOKEN_AUTHORIZATION || + (token == NGHTTP2_TOKEN_COOKIE && nv->valuelen < 20) || + (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) + ? NGHTTP2_HD_NEVER_INDEXING + : hd_deflate_decide_indexing(deflater, nv, token); + + res = search_hd_table(&deflater->ctx, nv, token, indexing_mode, + &deflater->map, hash); + + idx = res.index; + + if (res.name_value_match) { + + DEBUGF("deflatehd: name/value match index=%zd\n", idx); + + rv = emit_indexed_block(bufs, (size_t)idx); + if (rv != 0) { + return rv; + } + + return 0; + } + + if (res.index != -1) { + DEBUGF("deflatehd: name match index=%zd\n", res.index); + } + + if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) { + nghttp2_hd_nv hd_nv; + + if (idx != -1) { + hd_nv.name = nghttp2_hd_table_get(&deflater->ctx, (size_t)idx).name; + nghttp2_rcbuf_incref(hd_nv.name); + } else { + rv = nghttp2_rcbuf_new2(&hd_nv.name, nv->name, nv->namelen, mem); + if (rv != 0) { + return rv; + } + } + + rv = nghttp2_rcbuf_new2(&hd_nv.value, nv->value, nv->valuelen, mem); + + if (rv != 0) { + nghttp2_rcbuf_decref(hd_nv.name); + return rv; + } + + hd_nv.token = token; + hd_nv.flags = NGHTTP2_NV_FLAG_NONE; + + rv = add_hd_table_incremental(&deflater->ctx, &hd_nv, &deflater->map, hash); + + nghttp2_rcbuf_decref(hd_nv.value); + nghttp2_rcbuf_decref(hd_nv.name); + + if (rv != 0) { + return NGHTTP2_ERR_HEADER_COMP; + } + } + if (idx == -1) { + rv = emit_newname_block(bufs, nv, indexing_mode); + } else { + rv = emit_indname_block(bufs, (size_t)idx, nv, indexing_mode); + } + if (rv != 0) { + return rv; + } + + return 0; +} + +int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, + nghttp2_bufs *bufs, const nghttp2_nv *nv, + size_t nvlen) { + size_t i; + int rv = 0; + + if (deflater->ctx.bad) { + return NGHTTP2_ERR_HEADER_COMP; + } + + if (deflater->notify_table_size_change) { + size_t min_hd_table_bufsize_max; + + min_hd_table_bufsize_max = deflater->min_hd_table_bufsize_max; + + deflater->notify_table_size_change = 0; + deflater->min_hd_table_bufsize_max = UINT32_MAX; + + if (deflater->ctx.hd_table_bufsize_max > min_hd_table_bufsize_max) { + + rv = emit_table_size(bufs, min_hd_table_bufsize_max); + + if (rv != 0) { + goto fail; + } + } + + rv = emit_table_size(bufs, deflater->ctx.hd_table_bufsize_max); + + if (rv != 0) { + goto fail; + } + } + + for (i = 0; i < nvlen; ++i) { + rv = deflate_nv(deflater, bufs, &nv[i]); + if (rv != 0) { + goto fail; + } + } + + DEBUGF("deflatehd: all input name/value pairs were deflated\n"); + + return 0; +fail: + DEBUGF("deflatehd: error return %d\n", rv); + + deflater->ctx.bad = 1; + return rv; +} + +ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, + size_t buflen, const nghttp2_nv *nv, + size_t nvlen) { + nghttp2_bufs bufs; + int rv; + nghttp2_mem *mem; + + mem = deflater->ctx.mem; + + rv = nghttp2_bufs_wrap_init(&bufs, buf, buflen, mem); + + if (rv != 0) { + return rv; + } + + rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nv, nvlen); + + buflen = nghttp2_bufs_len(&bufs); + + nghttp2_bufs_wrap_free(&bufs); + + if (rv == NGHTTP2_ERR_BUFFER_ERROR) { + return NGHTTP2_ERR_INSUFF_BUFSIZE; + } + + if (rv != 0) { + return rv; + } + + return (ssize_t)buflen; +} + +ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, + const nghttp2_vec *vec, size_t veclen, + const nghttp2_nv *nv, size_t nvlen) { + nghttp2_bufs bufs; + int rv; + nghttp2_mem *mem; + size_t buflen; + + mem = deflater->ctx.mem; + + rv = nghttp2_bufs_wrap_init2(&bufs, vec, veclen, mem); + + if (rv != 0) { + return rv; + } + + rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nv, nvlen); + + buflen = nghttp2_bufs_len(&bufs); + + nghttp2_bufs_wrap_free(&bufs); + + if (rv == NGHTTP2_ERR_BUFFER_ERROR) { + return NGHTTP2_ERR_INSUFF_BUFSIZE; + } + + if (rv != 0) { + return rv; + } + + return (ssize_t)buflen; +} + +size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, + const nghttp2_nv *nva, size_t nvlen) { + size_t n = 0; + size_t i; + (void)deflater; + + /* Possible Maximum Header Table Size Change. Encoding (1u << 31) - + 1 using 4 bit prefix requires 6 bytes. We may emit this at most + twice. */ + n += 12; + + /* Use Literal Header Field without indexing - New Name, since it is + most space consuming format. Also we choose the less one between + non-huffman and huffman, so using literal byte count is + sufficient for upper bound. + + Encoding (1u << 31) - 1 using 7 bit prefix requires 6 bytes. We + need 2 of this for |nvlen| header fields. */ + n += 6 * 2 * nvlen; + + for (i = 0; i < nvlen; ++i) { + n += nva[i].namelen + nva[i].valuelen; + } + + return n; +} + +int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, + size_t deflate_hd_table_bufsize_max) { + return nghttp2_hd_deflate_new2(deflater_ptr, deflate_hd_table_bufsize_max, + NULL); +} + +int nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, + size_t deflate_hd_table_bufsize_max, + nghttp2_mem *mem) { + int rv; + nghttp2_hd_deflater *deflater; + + if (mem == NULL) { + mem = nghttp2_mem_default(); + } + + deflater = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_deflater)); + + if (deflater == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + rv = nghttp2_hd_deflate_init2(deflater, deflate_hd_table_bufsize_max, mem); + + if (rv != 0) { + nghttp2_mem_free(mem, deflater); + + return rv; + } + + *deflater_ptr = deflater; + + return 0; +} + +void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater) { + nghttp2_mem *mem; + + mem = deflater->ctx.mem; + + nghttp2_hd_deflate_free(deflater); + + nghttp2_mem_free(mem, deflater); +} + +static void hd_inflate_set_huffman_encoded(nghttp2_hd_inflater *inflater, + const uint8_t *in) { + inflater->huffman_encoded = (*in & (1 << 7)) != 0; +} + +/* + * Decodes the integer from the range [in, last). The result is + * assigned to |inflater->left|. If the |inflater->left| is 0, then + * it performs variable integer decoding from scratch. Otherwise, it + * uses the |inflater->left| as the initial value and continues to + * decode assuming that [in, last) begins with intermediary sequence. + * + * This function returns the number of bytes read if it succeeds, or + * one of the following negative error codes: + * + * NGHTTP2_ERR_HEADER_COMP + * Integer decoding failed + */ +static ssize_t hd_inflate_read_len(nghttp2_hd_inflater *inflater, int *rfin, + const uint8_t *in, const uint8_t *last, + size_t prefix, size_t maxlen) { + ssize_t rv; + uint32_t out; + + *rfin = 0; + + rv = decode_length(&out, &inflater->shift, rfin, (uint32_t)inflater->left, + inflater->shift, in, last, prefix); + + if (rv == -1) { + DEBUGF("inflatehd: integer decoding failed\n"); + return NGHTTP2_ERR_HEADER_COMP; + } + + if (out > maxlen) { + DEBUGF("inflatehd: integer exceeded the maximum value %zu\n", maxlen); + return NGHTTP2_ERR_HEADER_COMP; + } + + inflater->left = out; + + DEBUGF("inflatehd: decoded integer is %u\n", out); + + return rv; +} + +/* + * Reads |inflater->left| bytes from the range [in, last) and performs + * huffman decoding against them and pushes the result into the + * |buffer|. + * + * This function returns the number of bytes read if it succeeds, or + * one of the following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_HEADER_COMP + * Huffman decoding failed + */ +static ssize_t hd_inflate_read_huff(nghttp2_hd_inflater *inflater, + nghttp2_buf *buf, const uint8_t *in, + const uint8_t *last) { + ssize_t readlen; + int fin = 0; + if ((size_t)(last - in) >= inflater->left) { + last = in + inflater->left; + fin = 1; + } + readlen = nghttp2_hd_huff_decode(&inflater->huff_decode_ctx, buf, in, + (size_t)(last - in), fin); + + if (readlen < 0) { + DEBUGF("inflatehd: huffman decoding failed\n"); + return readlen; + } + if (nghttp2_hd_huff_decode_failure_state(&inflater->huff_decode_ctx)) { + DEBUGF("inflatehd: huffman decoding failed\n"); + return NGHTTP2_ERR_HEADER_COMP; + } + + inflater->left -= (size_t)readlen; + return readlen; +} + +/* + * Reads |inflater->left| bytes from the range [in, last) and copies + * them into the |buffer|. + * + * This function returns the number of bytes read if it succeeds, or + * one of the following negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_HEADER_COMP + * Header decompression failed + */ +static ssize_t hd_inflate_read(nghttp2_hd_inflater *inflater, nghttp2_buf *buf, + const uint8_t *in, const uint8_t *last) { + size_t len = nghttp2_min((size_t)(last - in), inflater->left); + + buf->last = nghttp2_cpymem(buf->last, in, len); + + inflater->left -= len; + return (ssize_t)len; +} + +/* + * Finalize indexed header representation reception. The referenced + * header is always emitted, and |*nv_out| is filled with that value. + */ +static void hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater, + nghttp2_hd_nv *nv_out) { + nghttp2_hd_nv nv = nghttp2_hd_table_get(&inflater->ctx, inflater->index); + + emit_header(nv_out, &nv); +} + +/* + * Finalize literal header representation - new name- reception. If + * header is emitted, |*nv_out| is filled with that value and 0 is + * returned. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater, + nghttp2_hd_nv *nv_out) { + nghttp2_hd_nv nv; + int rv; + + if (inflater->no_index) { + nv.flags = NGHTTP2_NV_FLAG_NO_INDEX; + } else { + nv.flags = NGHTTP2_NV_FLAG_NONE; + } + + nv.name = inflater->namercbuf; + nv.value = inflater->valuercbuf; + nv.token = lookup_token(inflater->namercbuf->base, inflater->namercbuf->len); + + if (inflater->index_required) { + rv = add_hd_table_incremental(&inflater->ctx, &nv, NULL, 0); + + if (rv != 0) { + return rv; + } + } + + emit_header(nv_out, &nv); + + inflater->nv_name_keep = nv.name; + inflater->nv_value_keep = nv.value; + + inflater->namercbuf = NULL; + inflater->valuercbuf = NULL; + + return 0; +} + +/* + * Finalize literal header representation - indexed name- + * reception. If header is emitted, |*nv_out| is filled with that + * value and 0 is returned. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater, + nghttp2_hd_nv *nv_out) { + nghttp2_hd_nv nv; + int rv; + + nv = nghttp2_hd_table_get(&inflater->ctx, inflater->index); + + if (inflater->no_index) { + nv.flags = NGHTTP2_NV_FLAG_NO_INDEX; + } else { + nv.flags = NGHTTP2_NV_FLAG_NONE; + } + + nghttp2_rcbuf_incref(nv.name); + + nv.value = inflater->valuercbuf; + + if (inflater->index_required) { + rv = add_hd_table_incremental(&inflater->ctx, &nv, NULL, 0); + if (rv != 0) { + nghttp2_rcbuf_decref(nv.name); + return NGHTTP2_ERR_NOMEM; + } + } + + emit_header(nv_out, &nv); + + inflater->nv_name_keep = nv.name; + inflater->nv_value_keep = nv.value; + + inflater->valuercbuf = NULL; + + return 0; +} + +ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, + int *inflate_flags, uint8_t *in, size_t inlen, + int in_final) { + return nghttp2_hd_inflate_hd2(inflater, nv_out, inflate_flags, in, inlen, + in_final); +} + +ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, + nghttp2_nv *nv_out, int *inflate_flags, + const uint8_t *in, size_t inlen, int in_final) { + ssize_t rv; + nghttp2_hd_nv hd_nv; + + rv = nghttp2_hd_inflate_hd_nv(inflater, &hd_nv, inflate_flags, in, inlen, + in_final); + + if (rv < 0) { + return rv; + } + + if (*inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + nv_out->name = hd_nv.name->base; + nv_out->namelen = hd_nv.name->len; + + nv_out->value = hd_nv.value->base; + nv_out->valuelen = hd_nv.value->len; + + nv_out->flags = hd_nv.flags; + } + + return rv; +} + +ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, + nghttp2_hd_nv *nv_out, int *inflate_flags, + const uint8_t *in, size_t inlen, + int in_final) { + ssize_t rv = 0; + const uint8_t *first = in; + const uint8_t *last = in + inlen; + int rfin = 0; + int busy = 0; + nghttp2_mem *mem; + + mem = inflater->ctx.mem; + + if (inflater->ctx.bad) { + return NGHTTP2_ERR_HEADER_COMP; + } + + DEBUGF("inflatehd: start state=%d\n", inflater->state); + hd_inflate_keep_free(inflater); + *inflate_flags = NGHTTP2_HD_INFLATE_NONE; + for (; in != last || busy;) { + busy = 0; + switch (inflater->state) { + case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE: + if ((*in & 0xe0u) != 0x20u) { + DEBUGF("inflatehd: header table size change was expected, but saw " + "0x%02x as first byte", + *in); + rv = NGHTTP2_ERR_HEADER_COMP; + goto fail; + } + /* fall through */ + case NGHTTP2_HD_STATE_INFLATE_START: + case NGHTTP2_HD_STATE_OPCODE: + if ((*in & 0xe0u) == 0x20u) { + DEBUGF("inflatehd: header table size change\n"); + if (inflater->state == NGHTTP2_HD_STATE_OPCODE) { + DEBUGF("inflatehd: header table size change must appear at the head " + "of header block\n"); + rv = NGHTTP2_ERR_HEADER_COMP; + goto fail; + } + inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; + inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE; + } else if (*in & 0x80u) { + DEBUGF("inflatehd: indexed repr\n"); + inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED; + inflater->state = NGHTTP2_HD_STATE_READ_INDEX; + } else { + if (*in == 0x40u || *in == 0 || *in == 0x10u) { + DEBUGF("inflatehd: literal header repr - new name\n"); + inflater->opcode = NGHTTP2_HD_OPCODE_NEWNAME; + inflater->state = NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN; + } else { + DEBUGF("inflatehd: literal header repr - indexed name\n"); + inflater->opcode = NGHTTP2_HD_OPCODE_INDNAME; + inflater->state = NGHTTP2_HD_STATE_READ_INDEX; + } + inflater->index_required = (*in & 0x40) != 0; + inflater->no_index = (*in & 0xf0u) == 0x10u; + DEBUGF("inflatehd: indexing required=%d, no_index=%d\n", + inflater->index_required, inflater->no_index); + if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { + ++in; + } + } + inflater->left = 0; + inflater->shift = 0; + break; + case NGHTTP2_HD_STATE_READ_TABLE_SIZE: + rfin = 0; + rv = hd_inflate_read_len( + inflater, &rfin, in, last, 5, + nghttp2_min(inflater->min_hd_table_bufsize_max, + inflater->settings_hd_table_bufsize_max)); + if (rv < 0) { + goto fail; + } + in += rv; + if (!rfin) { + goto almost_ok; + } + DEBUGF("inflatehd: table_size=%zu\n", inflater->left); + inflater->min_hd_table_bufsize_max = UINT32_MAX; + inflater->ctx.hd_table_bufsize_max = inflater->left; + hd_context_shrink_table_size(&inflater->ctx, NULL); + inflater->state = NGHTTP2_HD_STATE_INFLATE_START; + break; + case NGHTTP2_HD_STATE_READ_INDEX: { + size_t prefixlen; + + if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) { + prefixlen = 7; + } else if (inflater->index_required) { + prefixlen = 6; + } else { + prefixlen = 4; + } + + rfin = 0; + rv = hd_inflate_read_len(inflater, &rfin, in, last, prefixlen, + get_max_index(&inflater->ctx)); + if (rv < 0) { + goto fail; + } + + in += rv; + + if (!rfin) { + goto almost_ok; + } + + if (inflater->left == 0) { + rv = NGHTTP2_ERR_HEADER_COMP; + goto fail; + } + + DEBUGF("inflatehd: index=%zu\n", inflater->left); + if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) { + inflater->index = inflater->left; + --inflater->index; + + hd_inflate_commit_indexed(inflater, nv_out); + + inflater->state = NGHTTP2_HD_STATE_OPCODE; + *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; + return (ssize_t)(in - first); + } else { + inflater->index = inflater->left; + --inflater->index; + + inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; + } + break; + } + case NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN: + hd_inflate_set_huffman_encoded(inflater, in); + inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN; + inflater->left = 0; + inflater->shift = 0; + DEBUGF("inflatehd: huffman encoded=%d\n", inflater->huffman_encoded != 0); + /* Fall through */ + case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN: + rfin = 0; + rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, NGHTTP2_HD_MAX_NV); + if (rv < 0) { + goto fail; + } + in += rv; + if (!rfin) { + DEBUGF("inflatehd: integer not fully decoded. current=%zu\n", + inflater->left); + + goto almost_ok; + } + + if (inflater->huffman_encoded) { + nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx); + + inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF; + + rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left * 2 + 1, + mem); + } else { + inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME; + rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left + 1, mem); + } + + if (rv != 0) { + goto fail; + } + + nghttp2_buf_wrap_init(&inflater->namebuf, inflater->namercbuf->base, + inflater->namercbuf->len); + + break; + case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF: + rv = hd_inflate_read_huff(inflater, &inflater->namebuf, in, last); + if (rv < 0) { + goto fail; + } + + in += rv; + + DEBUGF("inflatehd: %zd bytes read\n", rv); + + if (inflater->left) { + DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); + + goto almost_ok; + } + + *inflater->namebuf.last = '\0'; + inflater->namercbuf->len = nghttp2_buf_len(&inflater->namebuf); + + inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; + + break; + case NGHTTP2_HD_STATE_NEWNAME_READ_NAME: + rv = hd_inflate_read(inflater, &inflater->namebuf, in, last); + if (rv < 0) { + goto fail; + } + + in += rv; + + DEBUGF("inflatehd: %zd bytes read\n", rv); + if (inflater->left) { + DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); + + goto almost_ok; + } + + *inflater->namebuf.last = '\0'; + inflater->namercbuf->len = nghttp2_buf_len(&inflater->namebuf); + + inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; + + break; + case NGHTTP2_HD_STATE_CHECK_VALUELEN: + hd_inflate_set_huffman_encoded(inflater, in); + inflater->state = NGHTTP2_HD_STATE_READ_VALUELEN; + inflater->left = 0; + inflater->shift = 0; + DEBUGF("inflatehd: huffman encoded=%d\n", inflater->huffman_encoded != 0); + /* Fall through */ + case NGHTTP2_HD_STATE_READ_VALUELEN: + rfin = 0; + rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, NGHTTP2_HD_MAX_NV); + if (rv < 0) { + goto fail; + } + + in += rv; + + if (!rfin) { + goto almost_ok; + } + + DEBUGF("inflatehd: valuelen=%zu\n", inflater->left); + + if (inflater->huffman_encoded) { + nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx); + + inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF; + + rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left * 2 + 1, + mem); + } else { + inflater->state = NGHTTP2_HD_STATE_READ_VALUE; + + rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left + 1, mem); + } + + if (rv != 0) { + goto fail; + } + + nghttp2_buf_wrap_init(&inflater->valuebuf, inflater->valuercbuf->base, + inflater->valuercbuf->len); + + busy = 1; + + break; + case NGHTTP2_HD_STATE_READ_VALUEHUFF: + rv = hd_inflate_read_huff(inflater, &inflater->valuebuf, in, last); + if (rv < 0) { + goto fail; + } + + in += rv; + + DEBUGF("inflatehd: %zd bytes read\n", rv); + + if (inflater->left) { + DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); + + goto almost_ok; + } + + *inflater->valuebuf.last = '\0'; + inflater->valuercbuf->len = nghttp2_buf_len(&inflater->valuebuf); + + if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { + rv = hd_inflate_commit_newname(inflater, nv_out); + } else { + rv = hd_inflate_commit_indname(inflater, nv_out); + } + + if (rv != 0) { + goto fail; + } + + inflater->state = NGHTTP2_HD_STATE_OPCODE; + *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; + + return (ssize_t)(in - first); + case NGHTTP2_HD_STATE_READ_VALUE: + rv = hd_inflate_read(inflater, &inflater->valuebuf, in, last); + if (rv < 0) { + DEBUGF("inflatehd: value read failure %zd: %s\n", rv, + nghttp2_strerror((int)rv)); + goto fail; + } + + in += rv; + + DEBUGF("inflatehd: %zd bytes read\n", rv); + + if (inflater->left) { + DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); + goto almost_ok; + } + + *inflater->valuebuf.last = '\0'; + inflater->valuercbuf->len = nghttp2_buf_len(&inflater->valuebuf); + + if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) { + rv = hd_inflate_commit_newname(inflater, nv_out); + } else { + rv = hd_inflate_commit_indname(inflater, nv_out); + } + + if (rv != 0) { + goto fail; + } + + inflater->state = NGHTTP2_HD_STATE_OPCODE; + *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; + + return (ssize_t)(in - first); + } + } + + assert(in == last); + + DEBUGF("inflatehd: all input bytes were processed\n"); + + if (in_final) { + DEBUGF("inflatehd: in_final set\n"); + + if (inflater->state != NGHTTP2_HD_STATE_OPCODE && + inflater->state != NGHTTP2_HD_STATE_INFLATE_START) { + DEBUGF("inflatehd: unacceptable state=%d\n", inflater->state); + rv = NGHTTP2_ERR_HEADER_COMP; + + goto fail; + } + *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL; + } + return (ssize_t)(in - first); + +almost_ok: + if (in_final) { + DEBUGF("inflatehd: input ended prematurely\n"); + + rv = NGHTTP2_ERR_HEADER_COMP; + + goto fail; + } + return (ssize_t)(in - first); + +fail: + DEBUGF("inflatehd: error return %zd\n", rv); + + inflater->ctx.bad = 1; + return rv; +} + +int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater) { + hd_inflate_keep_free(inflater); + inflater->state = NGHTTP2_HD_STATE_INFLATE_START; + return 0; +} + +int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr) { + return nghttp2_hd_inflate_new2(inflater_ptr, NULL); +} + +int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr, + nghttp2_mem *mem) { + int rv; + nghttp2_hd_inflater *inflater; + + if (mem == NULL) { + mem = nghttp2_mem_default(); + } + + inflater = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_inflater)); + + if (inflater == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + rv = nghttp2_hd_inflate_init(inflater, mem); + + if (rv != 0) { + nghttp2_mem_free(mem, inflater); + + return rv; + } + + *inflater_ptr = inflater; + + return 0; +} + +void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater) { + nghttp2_mem *mem; + + mem = inflater->ctx.mem; + nghttp2_hd_inflate_free(inflater); + + nghttp2_mem_free(mem, inflater); +} + +int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t idx, + nghttp2_nv *nv, int indexing_mode) { + + return emit_indname_block(bufs, idx, nv, indexing_mode); +} + +int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv, + int indexing_mode) { + return emit_newname_block(bufs, nv, indexing_mode); +} + +int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size) { + return emit_table_size(bufs, table_size); +} + +ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *fin, + uint32_t initial, size_t shift, uint8_t *in, + uint8_t *last, size_t prefix) { + return decode_length(res, shift_ptr, fin, initial, shift, in, last, prefix); +} + +static const nghttp2_nv *hd_get_table_entry(nghttp2_hd_context *context, + size_t idx) { + if (idx == 0) { + return NULL; + } + + --idx; + + if (!INDEX_RANGE_VALID(context, idx)) { + return NULL; + } + + return nghttp2_hd_table_get2(context, idx); +} + +size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater) { + return get_max_index(&deflater->ctx); +} + +const nghttp2_nv * +nghttp2_hd_deflate_get_table_entry(nghttp2_hd_deflater *deflater, size_t idx) { + return hd_get_table_entry(&deflater->ctx, idx); +} + +size_t +nghttp2_hd_deflate_get_dynamic_table_size(nghttp2_hd_deflater *deflater) { + return deflater->ctx.hd_table_bufsize; +} + +size_t +nghttp2_hd_deflate_get_max_dynamic_table_size(nghttp2_hd_deflater *deflater) { + return deflater->ctx.hd_table_bufsize_max; +} + +size_t nghttp2_hd_inflate_get_num_table_entries(nghttp2_hd_inflater *inflater) { + return get_max_index(&inflater->ctx); +} + +const nghttp2_nv * +nghttp2_hd_inflate_get_table_entry(nghttp2_hd_inflater *inflater, size_t idx) { + return hd_get_table_entry(&inflater->ctx, idx); +} + +size_t +nghttp2_hd_inflate_get_dynamic_table_size(nghttp2_hd_inflater *inflater) { + return inflater->ctx.hd_table_bufsize; +} + +size_t +nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater) { + return inflater->ctx.hd_table_bufsize_max; +} diff --git a/lib/nghttp2/lib/nghttp2_hd.h b/lib/nghttp2/lib/nghttp2_hd.h new file mode 100644 index 00000000000..6de0052aaea --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_hd.h @@ -0,0 +1,440 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_HD_H +#define NGHTTP2_HD_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#include "nghttp2_hd_huffman.h" +#include "nghttp2_buf.h" +#include "nghttp2_mem.h" +#include "nghttp2_rcbuf.h" + +#define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE +#define NGHTTP2_HD_ENTRY_OVERHEAD 32 + +/* The maximum length of one name/value pair. This is the sum of the + length of name and value. This is not specified by the spec. We + just chose the arbitrary size */ +#define NGHTTP2_HD_MAX_NV 65536 + +/* Default size of maximum table buffer size for encoder. Even if + remote decoder notifies larger buffer size for its decoding, + encoder only uses the memory up to this value. */ +#define NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE (1 << 12) + +/* Exported for unit test */ +#define NGHTTP2_STATIC_TABLE_LENGTH 61 + +/* Generated by genlibtokenlookup.py */ +typedef enum { + NGHTTP2_TOKEN__AUTHORITY = 0, + NGHTTP2_TOKEN__METHOD = 1, + NGHTTP2_TOKEN__PATH = 3, + NGHTTP2_TOKEN__SCHEME = 5, + NGHTTP2_TOKEN__STATUS = 7, + NGHTTP2_TOKEN_ACCEPT_CHARSET = 14, + NGHTTP2_TOKEN_ACCEPT_ENCODING = 15, + NGHTTP2_TOKEN_ACCEPT_LANGUAGE = 16, + NGHTTP2_TOKEN_ACCEPT_RANGES = 17, + NGHTTP2_TOKEN_ACCEPT = 18, + NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN = 19, + NGHTTP2_TOKEN_AGE = 20, + NGHTTP2_TOKEN_ALLOW = 21, + NGHTTP2_TOKEN_AUTHORIZATION = 22, + NGHTTP2_TOKEN_CACHE_CONTROL = 23, + NGHTTP2_TOKEN_CONTENT_DISPOSITION = 24, + NGHTTP2_TOKEN_CONTENT_ENCODING = 25, + NGHTTP2_TOKEN_CONTENT_LANGUAGE = 26, + NGHTTP2_TOKEN_CONTENT_LENGTH = 27, + NGHTTP2_TOKEN_CONTENT_LOCATION = 28, + NGHTTP2_TOKEN_CONTENT_RANGE = 29, + NGHTTP2_TOKEN_CONTENT_TYPE = 30, + NGHTTP2_TOKEN_COOKIE = 31, + NGHTTP2_TOKEN_DATE = 32, + NGHTTP2_TOKEN_ETAG = 33, + NGHTTP2_TOKEN_EXPECT = 34, + NGHTTP2_TOKEN_EXPIRES = 35, + NGHTTP2_TOKEN_FROM = 36, + NGHTTP2_TOKEN_HOST = 37, + NGHTTP2_TOKEN_IF_MATCH = 38, + NGHTTP2_TOKEN_IF_MODIFIED_SINCE = 39, + NGHTTP2_TOKEN_IF_NONE_MATCH = 40, + NGHTTP2_TOKEN_IF_RANGE = 41, + NGHTTP2_TOKEN_IF_UNMODIFIED_SINCE = 42, + NGHTTP2_TOKEN_LAST_MODIFIED = 43, + NGHTTP2_TOKEN_LINK = 44, + NGHTTP2_TOKEN_LOCATION = 45, + NGHTTP2_TOKEN_MAX_FORWARDS = 46, + NGHTTP2_TOKEN_PROXY_AUTHENTICATE = 47, + NGHTTP2_TOKEN_PROXY_AUTHORIZATION = 48, + NGHTTP2_TOKEN_RANGE = 49, + NGHTTP2_TOKEN_REFERER = 50, + NGHTTP2_TOKEN_REFRESH = 51, + NGHTTP2_TOKEN_RETRY_AFTER = 52, + NGHTTP2_TOKEN_SERVER = 53, + NGHTTP2_TOKEN_SET_COOKIE = 54, + NGHTTP2_TOKEN_STRICT_TRANSPORT_SECURITY = 55, + NGHTTP2_TOKEN_TRANSFER_ENCODING = 56, + NGHTTP2_TOKEN_USER_AGENT = 57, + NGHTTP2_TOKEN_VARY = 58, + NGHTTP2_TOKEN_VIA = 59, + NGHTTP2_TOKEN_WWW_AUTHENTICATE = 60, + NGHTTP2_TOKEN_TE, + NGHTTP2_TOKEN_CONNECTION, + NGHTTP2_TOKEN_KEEP_ALIVE, + NGHTTP2_TOKEN_PROXY_CONNECTION, + NGHTTP2_TOKEN_UPGRADE, + NGHTTP2_TOKEN__PROTOCOL, + NGHTTP2_TOKEN_PRIORITY, +} nghttp2_token; + +struct nghttp2_hd_entry; +typedef struct nghttp2_hd_entry nghttp2_hd_entry; + +typedef struct { + /* The buffer containing header field name. NULL-termination is + guaranteed. */ + nghttp2_rcbuf *name; + /* The buffer containing header field value. NULL-termination is + guaranteed. */ + nghttp2_rcbuf *value; + /* nghttp2_token value for name. It could be -1 if we have no token + for that header field name. */ + int32_t token; + /* Bitwise OR of one or more of nghttp2_nv_flag. */ + uint8_t flags; +} nghttp2_hd_nv; + +struct nghttp2_hd_entry { + /* The header field name/value pair */ + nghttp2_hd_nv nv; + /* This is solely for nghttp2_hd_{deflate,inflate}_get_table_entry + APIs to keep backward compatibility. */ + nghttp2_nv cnv; + /* The next entry which shares same bucket in hash table. */ + nghttp2_hd_entry *next; + /* The sequence number. We will increment it by one whenever we + store nghttp2_hd_entry to dynamic header table. */ + uint32_t seq; + /* The hash value for header name (nv.name). */ + uint32_t hash; +}; + +/* The entry used for static header table. */ +typedef struct { + nghttp2_rcbuf name; + nghttp2_rcbuf value; + nghttp2_nv cnv; + int32_t token; + uint32_t hash; +} nghttp2_hd_static_entry; + +typedef struct { + nghttp2_hd_entry **buffer; + size_t mask; + size_t first; + size_t len; +} nghttp2_hd_ringbuf; + +typedef enum { + NGHTTP2_HD_OPCODE_NONE, + NGHTTP2_HD_OPCODE_INDEXED, + NGHTTP2_HD_OPCODE_NEWNAME, + NGHTTP2_HD_OPCODE_INDNAME +} nghttp2_hd_opcode; + +typedef enum { + NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE, + NGHTTP2_HD_STATE_INFLATE_START, + NGHTTP2_HD_STATE_OPCODE, + NGHTTP2_HD_STATE_READ_TABLE_SIZE, + NGHTTP2_HD_STATE_READ_INDEX, + NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN, + NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN, + NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF, + NGHTTP2_HD_STATE_NEWNAME_READ_NAME, + NGHTTP2_HD_STATE_CHECK_VALUELEN, + NGHTTP2_HD_STATE_READ_VALUELEN, + NGHTTP2_HD_STATE_READ_VALUEHUFF, + NGHTTP2_HD_STATE_READ_VALUE +} nghttp2_hd_inflate_state; + +typedef enum { + NGHTTP2_HD_WITH_INDEXING, + NGHTTP2_HD_WITHOUT_INDEXING, + NGHTTP2_HD_NEVER_INDEXING +} nghttp2_hd_indexing_mode; + +typedef struct { + /* dynamic header table */ + nghttp2_hd_ringbuf hd_table; + /* Memory allocator */ + nghttp2_mem *mem; + /* Abstract buffer size of hd_table as described in the spec. This + is the sum of length of name/value in hd_table + + NGHTTP2_HD_ENTRY_OVERHEAD bytes overhead per each entry. */ + size_t hd_table_bufsize; + /* The effective header table size. */ + size_t hd_table_bufsize_max; + /* Next sequence number for nghttp2_hd_entry */ + uint32_t next_seq; + /* If inflate/deflate error occurred, this value is set to 1 and + further invocation of inflate/deflate will fail with + NGHTTP2_ERR_HEADER_COMP. */ + uint8_t bad; +} nghttp2_hd_context; + +#define HD_MAP_SIZE 128 + +typedef struct { + nghttp2_hd_entry *table[HD_MAP_SIZE]; +} nghttp2_hd_map; + +struct nghttp2_hd_deflater { + nghttp2_hd_context ctx; + nghttp2_hd_map map; + /* The upper limit of the header table size the deflater accepts. */ + size_t deflate_hd_table_bufsize_max; + /* Minimum header table size notified in the next context update */ + size_t min_hd_table_bufsize_max; + /* If nonzero, send header table size using encoding context update + in the next deflate process */ + uint8_t notify_table_size_change; +}; + +struct nghttp2_hd_inflater { + nghttp2_hd_context ctx; + /* Stores current state of huffman decoding */ + nghttp2_hd_huff_decode_context huff_decode_ctx; + /* header buffer */ + nghttp2_buf namebuf, valuebuf; + nghttp2_rcbuf *namercbuf, *valuercbuf; + /* Pointer to the name/value pair which are used in the current + header emission. */ + nghttp2_rcbuf *nv_name_keep, *nv_value_keep; + /* The number of bytes to read */ + size_t left; + /* The index in indexed repr or indexed name */ + size_t index; + /* The maximum header table size the inflater supports. This is the + same value transmitted in SETTINGS_HEADER_TABLE_SIZE */ + size_t settings_hd_table_bufsize_max; + /* Minimum header table size set by nghttp2_hd_inflate_change_table_size */ + size_t min_hd_table_bufsize_max; + /* The number of next shift to decode integer */ + size_t shift; + nghttp2_hd_opcode opcode; + nghttp2_hd_inflate_state state; + /* nonzero if string is huffman encoded */ + uint8_t huffman_encoded; + /* nonzero if deflater requires that current entry is indexed */ + uint8_t index_required; + /* nonzero if deflater requires that current entry must not be + indexed */ + uint8_t no_index; +}; + +/* + * Initializes the |ent| members. The reference counts of nv->name + * and nv->value are increased by one for each. + */ +void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv); + +/* + * This function decreases the reference counts of nv->name and + * nv->value. + */ +void nghttp2_hd_entry_free(nghttp2_hd_entry *ent); + +/* + * Initializes |deflater| for deflating name/values pairs. + * + * The encoder only uses up to + * NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE bytes for header table + * even if the larger value is specified later in + * nghttp2_hd_change_table_size(). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem); + +/* + * Initializes |deflater| for deflating name/values pairs. + * + * The encoder only uses up to |max_deflate_dynamic_table_size| bytes + * for header table even if the larger value is specified later in + * nghttp2_hd_change_table_size(). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, + size_t max_deflate_dynamic_table_size, + nghttp2_mem *mem); + +/* + * Deallocates any resources allocated for |deflater|. + */ +void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater); + +/* + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |bufs|. + * + * This function expands |bufs| as necessary to store the result. If + * buffers is full and the process still requires more space, this + * function fails and returns NGHTTP2_ERR_HEADER_COMP. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_HEADER_COMP + * Deflation process has failed. + * NGHTTP2_ERR_BUFFER_ERROR + * Out of buffer space. + */ +int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, + nghttp2_bufs *bufs, const nghttp2_nv *nva, + size_t nvlen); + +/* + * Initializes |inflater| for inflating name/values pairs. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem); + +/* + * Deallocates any resources allocated for |inflater|. + */ +void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater); + +/* + * Similar to nghttp2_hd_inflate_hd(), but this takes nghttp2_hd_nv + * instead of nghttp2_nv as output parameter |nv_out|. Other than + * that return values and semantics are the same as + * nghttp2_hd_inflate_hd(). + */ +ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, + nghttp2_hd_nv *nv_out, int *inflate_flags, + const uint8_t *in, size_t inlen, int in_final); + +/* For unittesting purpose */ +int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index, + nghttp2_nv *nv, int indexing_mode); + +/* For unittesting purpose */ +int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv, + int indexing_mode); + +/* For unittesting purpose */ +int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size); + +/* For unittesting purpose */ +nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t index); + +/* For unittesting purpose */ +ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *fin, + uint32_t initial, size_t shift, uint8_t *in, + uint8_t *last, size_t prefix); + +/* Huffman encoding/decoding functions */ + +/* + * Counts the required bytes to encode |src| with length |len|. + * + * This function returns the number of required bytes to encode given + * data, including padding of prefix of terminal symbol code. This + * function always succeeds. + */ +size_t nghttp2_hd_huff_encode_count(const uint8_t *src, size_t len); + +/* + * Encodes the given data |src| with length |srclen| to the |bufs|. + * This function expands extra buffers in |bufs| if necessary. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_BUFFER_ERROR + * Out of buffer space. + */ +int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src, + size_t srclen); + +void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx); + +/* + * Decodes the given data |src| with length |srclen|. The |ctx| must + * be initialized by nghttp2_hd_huff_decode_context_init(). The result + * will be written to |buf|. This function assumes that |buf| has the + * enough room to store the decoded byte string. + * + * The caller must set the |fin| to nonzero if the given input is the + * final block. + * + * This function returns the number of read bytes from the |in|. + * + * If this function fails, it returns one of the following negative + * return codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_HEADER_COMP + * Decoding process has failed. + */ +ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, + nghttp2_buf *buf, const uint8_t *src, + size_t srclen, int fin); + +/* + * nghttp2_hd_huff_decode_failure_state returns nonzero if |ctx| + * indicates that huffman decoding context is in failure state. + */ +int nghttp2_hd_huff_decode_failure_state(nghttp2_hd_huff_decode_context *ctx); + +#endif /* NGHTTP2_HD_H */ diff --git a/lib/nghttp2/lib/nghttp2_hd_huffman.c b/lib/nghttp2/lib/nghttp2_hd_huffman.c new file mode 100644 index 00000000000..ac90f49c44f --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_hd_huffman.c @@ -0,0 +1,144 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_hd_huffman.h" + +#include +#include +#include + +#include "nghttp2_hd.h" +#include "nghttp2_net.h" + +size_t nghttp2_hd_huff_encode_count(const uint8_t *src, size_t len) { + size_t i; + size_t nbits = 0; + + for (i = 0; i < len; ++i) { + nbits += huff_sym_table[src[i]].nbits; + } + /* pad the prefix of EOS (256) */ + return (nbits + 7) / 8; +} + +int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src, + size_t srclen) { + const nghttp2_huff_sym *sym; + const uint8_t *end = src + srclen; + uint64_t code = 0; + uint32_t x; + size_t nbits = 0; + size_t avail; + int rv; + + avail = nghttp2_bufs_cur_avail(bufs); + + for (; src != end;) { + sym = &huff_sym_table[*src++]; + code |= (uint64_t)sym->code << (32 - nbits); + nbits += sym->nbits; + if (nbits < 32) { + continue; + } + if (avail >= 4) { + x = htonl((uint32_t)(code >> 32)); + memcpy(bufs->cur->buf.last, &x, 4); + bufs->cur->buf.last += 4; + avail -= 4; + code <<= 32; + nbits -= 32; + continue; + } + + for (; nbits >= 8;) { + rv = nghttp2_bufs_addb(bufs, (uint8_t)(code >> 56)); + if (rv != 0) { + return rv; + } + code <<= 8; + nbits -= 8; + } + + avail = nghttp2_bufs_cur_avail(bufs); + } + + for (; nbits >= 8;) { + rv = nghttp2_bufs_addb(bufs, (uint8_t)(code >> 56)); + if (rv != 0) { + return rv; + } + code <<= 8; + nbits -= 8; + } + + if (nbits) { + rv = nghttp2_bufs_addb( + bufs, (uint8_t)((uint8_t)(code >> 56) | ((1 << (8 - nbits)) - 1))); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx) { + ctx->fstate = NGHTTP2_HUFF_ACCEPTED; +} + +ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, + nghttp2_buf *buf, const uint8_t *src, + size_t srclen, int final) { + const uint8_t *end = src + srclen; + nghttp2_huff_decode node = {ctx->fstate, 0}; + const nghttp2_huff_decode *t = &node; + uint8_t c; + + /* We use the decoding algorithm described in + http://graphics.ics.uci.edu/pub/Prefix.pdf */ + for (; src != end;) { + c = *src++; + t = &huff_decode_table[t->fstate & 0x1ff][c >> 4]; + if (t->fstate & NGHTTP2_HUFF_SYM) { + *buf->last++ = t->sym; + } + + t = &huff_decode_table[t->fstate & 0x1ff][c & 0xf]; + if (t->fstate & NGHTTP2_HUFF_SYM) { + *buf->last++ = t->sym; + } + } + + ctx->fstate = t->fstate; + + if (final && !(ctx->fstate & NGHTTP2_HUFF_ACCEPTED)) { + return NGHTTP2_ERR_HEADER_COMP; + } + + return (ssize_t)srclen; +} + +int nghttp2_hd_huff_decode_failure_state(nghttp2_hd_huff_decode_context *ctx) { + return ctx->fstate == 0x100; +} diff --git a/lib/nghttp2/lib/nghttp2_hd_huffman.h b/lib/nghttp2/lib/nghttp2_hd_huffman.h new file mode 100644 index 00000000000..2bfd5318165 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_hd_huffman.h @@ -0,0 +1,72 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_HD_HUFFMAN_H +#define NGHTTP2_HD_HUFFMAN_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +typedef enum { + /* FSA accepts this state as the end of huffman encoding + sequence. */ + NGHTTP2_HUFF_ACCEPTED = 1 << 14, + /* This state emits symbol */ + NGHTTP2_HUFF_SYM = 1 << 15, +} nghttp2_huff_decode_flag; + +typedef struct { + /* fstate is the current huffman decoding state, which is actually + the node ID of internal huffman tree with + nghttp2_huff_decode_flag OR-ed. We have 257 leaf nodes, but they + are identical to root node other than emitting a symbol, so we + have 256 internal nodes [1..255], inclusive. The node ID 256 is + a special node and it is a terminal state that means decoding + failed. */ + uint16_t fstate; + /* symbol if NGHTTP2_HUFF_SYM flag set */ + uint8_t sym; +} nghttp2_huff_decode; + +typedef nghttp2_huff_decode huff_decode_table_type[16]; + +typedef struct { + /* fstate is the current huffman decoding state. */ + uint16_t fstate; +} nghttp2_hd_huff_decode_context; + +typedef struct { + /* The number of bits in this code */ + uint32_t nbits; + /* Huffman code aligned to LSB */ + uint32_t code; +} nghttp2_huff_sym; + +extern const nghttp2_huff_sym huff_sym_table[]; +extern const nghttp2_huff_decode huff_decode_table[][16]; + +#endif /* NGHTTP2_HD_HUFFMAN_H */ diff --git a/lib/nghttp2/lib/nghttp2_hd_huffman_data.c b/lib/nghttp2/lib/nghttp2_hd_huffman_data.c new file mode 100644 index 00000000000..2e2e13f7bee --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_hd_huffman_data.c @@ -0,0 +1,4980 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_hd_huffman.h" + +/* Generated by mkhufftbl.py */ + +const nghttp2_huff_sym huff_sym_table[] = { + {13, 0xffc00000u}, {23, 0xffffb000u}, {28, 0xfffffe20u}, {28, 0xfffffe30u}, + {28, 0xfffffe40u}, {28, 0xfffffe50u}, {28, 0xfffffe60u}, {28, 0xfffffe70u}, + {28, 0xfffffe80u}, {24, 0xffffea00u}, {30, 0xfffffff0u}, {28, 0xfffffe90u}, + {28, 0xfffffea0u}, {30, 0xfffffff4u}, {28, 0xfffffeb0u}, {28, 0xfffffec0u}, + {28, 0xfffffed0u}, {28, 0xfffffee0u}, {28, 0xfffffef0u}, {28, 0xffffff00u}, + {28, 0xffffff10u}, {28, 0xffffff20u}, {30, 0xfffffff8u}, {28, 0xffffff30u}, + {28, 0xffffff40u}, {28, 0xffffff50u}, {28, 0xffffff60u}, {28, 0xffffff70u}, + {28, 0xffffff80u}, {28, 0xffffff90u}, {28, 0xffffffa0u}, {28, 0xffffffb0u}, + {6, 0x50000000u}, {10, 0xfe000000u}, {10, 0xfe400000u}, {12, 0xffa00000u}, + {13, 0xffc80000u}, {6, 0x54000000u}, {8, 0xf8000000u}, {11, 0xff400000u}, + {10, 0xfe800000u}, {10, 0xfec00000u}, {8, 0xf9000000u}, {11, 0xff600000u}, + {8, 0xfa000000u}, {6, 0x58000000u}, {6, 0x5c000000u}, {6, 0x60000000u}, + {5, 0x0u}, {5, 0x8000000u}, {5, 0x10000000u}, {6, 0x64000000u}, + {6, 0x68000000u}, {6, 0x6c000000u}, {6, 0x70000000u}, {6, 0x74000000u}, + {6, 0x78000000u}, {6, 0x7c000000u}, {7, 0xb8000000u}, {8, 0xfb000000u}, + {15, 0xfff80000u}, {6, 0x80000000u}, {12, 0xffb00000u}, {10, 0xff000000u}, + {13, 0xffd00000u}, {6, 0x84000000u}, {7, 0xba000000u}, {7, 0xbc000000u}, + {7, 0xbe000000u}, {7, 0xc0000000u}, {7, 0xc2000000u}, {7, 0xc4000000u}, + {7, 0xc6000000u}, {7, 0xc8000000u}, {7, 0xca000000u}, {7, 0xcc000000u}, + {7, 0xce000000u}, {7, 0xd0000000u}, {7, 0xd2000000u}, {7, 0xd4000000u}, + {7, 0xd6000000u}, {7, 0xd8000000u}, {7, 0xda000000u}, {7, 0xdc000000u}, + {7, 0xde000000u}, {7, 0xe0000000u}, {7, 0xe2000000u}, {7, 0xe4000000u}, + {8, 0xfc000000u}, {7, 0xe6000000u}, {8, 0xfd000000u}, {13, 0xffd80000u}, + {19, 0xfffe0000u}, {13, 0xffe00000u}, {14, 0xfff00000u}, {6, 0x88000000u}, + {15, 0xfffa0000u}, {5, 0x18000000u}, {6, 0x8c000000u}, {5, 0x20000000u}, + {6, 0x90000000u}, {5, 0x28000000u}, {6, 0x94000000u}, {6, 0x98000000u}, + {6, 0x9c000000u}, {5, 0x30000000u}, {7, 0xe8000000u}, {7, 0xea000000u}, + {6, 0xa0000000u}, {6, 0xa4000000u}, {6, 0xa8000000u}, {5, 0x38000000u}, + {6, 0xac000000u}, {7, 0xec000000u}, {6, 0xb0000000u}, {5, 0x40000000u}, + {5, 0x48000000u}, {6, 0xb4000000u}, {7, 0xee000000u}, {7, 0xf0000000u}, + {7, 0xf2000000u}, {7, 0xf4000000u}, {7, 0xf6000000u}, {15, 0xfffc0000u}, + {11, 0xff800000u}, {14, 0xfff40000u}, {13, 0xffe80000u}, {28, 0xffffffc0u}, + {20, 0xfffe6000u}, {22, 0xffff4800u}, {20, 0xfffe7000u}, {20, 0xfffe8000u}, + {22, 0xffff4c00u}, {22, 0xffff5000u}, {22, 0xffff5400u}, {23, 0xffffb200u}, + {22, 0xffff5800u}, {23, 0xffffb400u}, {23, 0xffffb600u}, {23, 0xffffb800u}, + {23, 0xffffba00u}, {23, 0xffffbc00u}, {24, 0xffffeb00u}, {23, 0xffffbe00u}, + {24, 0xffffec00u}, {24, 0xffffed00u}, {22, 0xffff5c00u}, {23, 0xffffc000u}, + {24, 0xffffee00u}, {23, 0xffffc200u}, {23, 0xffffc400u}, {23, 0xffffc600u}, + {23, 0xffffc800u}, {21, 0xfffee000u}, {22, 0xffff6000u}, {23, 0xffffca00u}, + {22, 0xffff6400u}, {23, 0xffffcc00u}, {23, 0xffffce00u}, {24, 0xffffef00u}, + {22, 0xffff6800u}, {21, 0xfffee800u}, {20, 0xfffe9000u}, {22, 0xffff6c00u}, + {22, 0xffff7000u}, {23, 0xffffd000u}, {23, 0xffffd200u}, {21, 0xfffef000u}, + {23, 0xffffd400u}, {22, 0xffff7400u}, {22, 0xffff7800u}, {24, 0xfffff000u}, + {21, 0xfffef800u}, {22, 0xffff7c00u}, {23, 0xffffd600u}, {23, 0xffffd800u}, + {21, 0xffff0000u}, {21, 0xffff0800u}, {22, 0xffff8000u}, {21, 0xffff1000u}, + {23, 0xffffda00u}, {22, 0xffff8400u}, {23, 0xffffdc00u}, {23, 0xffffde00u}, + {20, 0xfffea000u}, {22, 0xffff8800u}, {22, 0xffff8c00u}, {22, 0xffff9000u}, + {23, 0xffffe000u}, {22, 0xffff9400u}, {22, 0xffff9800u}, {23, 0xffffe200u}, + {26, 0xfffff800u}, {26, 0xfffff840u}, {20, 0xfffeb000u}, {19, 0xfffe2000u}, + {22, 0xffff9c00u}, {23, 0xffffe400u}, {22, 0xffffa000u}, {25, 0xfffff600u}, + {26, 0xfffff880u}, {26, 0xfffff8c0u}, {26, 0xfffff900u}, {27, 0xfffffbc0u}, + {27, 0xfffffbe0u}, {26, 0xfffff940u}, {24, 0xfffff100u}, {25, 0xfffff680u}, + {19, 0xfffe4000u}, {21, 0xffff1800u}, {26, 0xfffff980u}, {27, 0xfffffc00u}, + {27, 0xfffffc20u}, {26, 0xfffff9c0u}, {27, 0xfffffc40u}, {24, 0xfffff200u}, + {21, 0xffff2000u}, {21, 0xffff2800u}, {26, 0xfffffa00u}, {26, 0xfffffa40u}, + {28, 0xffffffd0u}, {27, 0xfffffc60u}, {27, 0xfffffc80u}, {27, 0xfffffca0u}, + {20, 0xfffec000u}, {24, 0xfffff300u}, {20, 0xfffed000u}, {21, 0xffff3000u}, + {22, 0xffffa400u}, {21, 0xffff3800u}, {21, 0xffff4000u}, {23, 0xffffe600u}, + {22, 0xffffa800u}, {22, 0xffffac00u}, {25, 0xfffff700u}, {25, 0xfffff780u}, + {24, 0xfffff400u}, {24, 0xfffff500u}, {26, 0xfffffa80u}, {23, 0xffffe800u}, + {26, 0xfffffac0u}, {27, 0xfffffcc0u}, {26, 0xfffffb00u}, {26, 0xfffffb40u}, + {27, 0xfffffce0u}, {27, 0xfffffd00u}, {27, 0xfffffd20u}, {27, 0xfffffd40u}, + {27, 0xfffffd60u}, {28, 0xffffffe0u}, {27, 0xfffffd80u}, {27, 0xfffffda0u}, + {27, 0xfffffdc0u}, {27, 0xfffffde0u}, {27, 0xfffffe00u}, {26, 0xfffffb80u}, + {30, 0xfffffffcu}}; + +const nghttp2_huff_decode huff_decode_table[][16] = { + /* 0 */ + { + {0x04, 0}, + {0x05, 0}, + {0x07, 0}, + {0x08, 0}, + {0x0b, 0}, + {0x0c, 0}, + {0x10, 0}, + {0x13, 0}, + {0x19, 0}, + {0x1c, 0}, + {0x20, 0}, + {0x23, 0}, + {0x2a, 0}, + {0x31, 0}, + {0x39, 0}, + {0x4040, 0}, + }, + /* 1 */ + { + {0xc000, 48}, + {0xc000, 49}, + {0xc000, 50}, + {0xc000, 97}, + {0xc000, 99}, + {0xc000, 101}, + {0xc000, 105}, + {0xc000, 111}, + {0xc000, 115}, + {0xc000, 116}, + {0x0d, 0}, + {0x0e, 0}, + {0x11, 0}, + {0x12, 0}, + {0x14, 0}, + {0x15, 0}, + }, + /* 2 */ + { + {0x8001, 48}, + {0xc016, 48}, + {0x8001, 49}, + {0xc016, 49}, + {0x8001, 50}, + {0xc016, 50}, + {0x8001, 97}, + {0xc016, 97}, + {0x8001, 99}, + {0xc016, 99}, + {0x8001, 101}, + {0xc016, 101}, + {0x8001, 105}, + {0xc016, 105}, + {0x8001, 111}, + {0xc016, 111}, + }, + /* 3 */ + { + {0x8002, 48}, + {0x8009, 48}, + {0x8017, 48}, + {0xc028, 48}, + {0x8002, 49}, + {0x8009, 49}, + {0x8017, 49}, + {0xc028, 49}, + {0x8002, 50}, + {0x8009, 50}, + {0x8017, 50}, + {0xc028, 50}, + {0x8002, 97}, + {0x8009, 97}, + {0x8017, 97}, + {0xc028, 97}, + }, + /* 4 */ + { + {0x8003, 48}, + {0x8006, 48}, + {0x800a, 48}, + {0x800f, 48}, + {0x8018, 48}, + {0x801f, 48}, + {0x8029, 48}, + {0xc038, 48}, + {0x8003, 49}, + {0x8006, 49}, + {0x800a, 49}, + {0x800f, 49}, + {0x8018, 49}, + {0x801f, 49}, + {0x8029, 49}, + {0xc038, 49}, + }, + /* 5 */ + { + {0x8003, 50}, + {0x8006, 50}, + {0x800a, 50}, + {0x800f, 50}, + {0x8018, 50}, + {0x801f, 50}, + {0x8029, 50}, + {0xc038, 50}, + {0x8003, 97}, + {0x8006, 97}, + {0x800a, 97}, + {0x800f, 97}, + {0x8018, 97}, + {0x801f, 97}, + {0x8029, 97}, + {0xc038, 97}, + }, + /* 6 */ + { + {0x8002, 99}, + {0x8009, 99}, + {0x8017, 99}, + {0xc028, 99}, + {0x8002, 101}, + {0x8009, 101}, + {0x8017, 101}, + {0xc028, 101}, + {0x8002, 105}, + {0x8009, 105}, + {0x8017, 105}, + {0xc028, 105}, + {0x8002, 111}, + {0x8009, 111}, + {0x8017, 111}, + {0xc028, 111}, + }, + /* 7 */ + { + {0x8003, 99}, + {0x8006, 99}, + {0x800a, 99}, + {0x800f, 99}, + {0x8018, 99}, + {0x801f, 99}, + {0x8029, 99}, + {0xc038, 99}, + {0x8003, 101}, + {0x8006, 101}, + {0x800a, 101}, + {0x800f, 101}, + {0x8018, 101}, + {0x801f, 101}, + {0x8029, 101}, + {0xc038, 101}, + }, + /* 8 */ + { + {0x8003, 105}, + {0x8006, 105}, + {0x800a, 105}, + {0x800f, 105}, + {0x8018, 105}, + {0x801f, 105}, + {0x8029, 105}, + {0xc038, 105}, + {0x8003, 111}, + {0x8006, 111}, + {0x800a, 111}, + {0x800f, 111}, + {0x8018, 111}, + {0x801f, 111}, + {0x8029, 111}, + {0xc038, 111}, + }, + /* 9 */ + { + {0x8001, 115}, + {0xc016, 115}, + {0x8001, 116}, + {0xc016, 116}, + {0xc000, 32}, + {0xc000, 37}, + {0xc000, 45}, + {0xc000, 46}, + {0xc000, 47}, + {0xc000, 51}, + {0xc000, 52}, + {0xc000, 53}, + {0xc000, 54}, + {0xc000, 55}, + {0xc000, 56}, + {0xc000, 57}, + }, + /* 10 */ + { + {0x8002, 115}, + {0x8009, 115}, + {0x8017, 115}, + {0xc028, 115}, + {0x8002, 116}, + {0x8009, 116}, + {0x8017, 116}, + {0xc028, 116}, + {0x8001, 32}, + {0xc016, 32}, + {0x8001, 37}, + {0xc016, 37}, + {0x8001, 45}, + {0xc016, 45}, + {0x8001, 46}, + {0xc016, 46}, + }, + /* 11 */ + { + {0x8003, 115}, + {0x8006, 115}, + {0x800a, 115}, + {0x800f, 115}, + {0x8018, 115}, + {0x801f, 115}, + {0x8029, 115}, + {0xc038, 115}, + {0x8003, 116}, + {0x8006, 116}, + {0x800a, 116}, + {0x800f, 116}, + {0x8018, 116}, + {0x801f, 116}, + {0x8029, 116}, + {0xc038, 116}, + }, + /* 12 */ + { + {0x8002, 32}, + {0x8009, 32}, + {0x8017, 32}, + {0xc028, 32}, + {0x8002, 37}, + {0x8009, 37}, + {0x8017, 37}, + {0xc028, 37}, + {0x8002, 45}, + {0x8009, 45}, + {0x8017, 45}, + {0xc028, 45}, + {0x8002, 46}, + {0x8009, 46}, + {0x8017, 46}, + {0xc028, 46}, + }, + /* 13 */ + { + {0x8003, 32}, + {0x8006, 32}, + {0x800a, 32}, + {0x800f, 32}, + {0x8018, 32}, + {0x801f, 32}, + {0x8029, 32}, + {0xc038, 32}, + {0x8003, 37}, + {0x8006, 37}, + {0x800a, 37}, + {0x800f, 37}, + {0x8018, 37}, + {0x801f, 37}, + {0x8029, 37}, + {0xc038, 37}, + }, + /* 14 */ + { + {0x8003, 45}, + {0x8006, 45}, + {0x800a, 45}, + {0x800f, 45}, + {0x8018, 45}, + {0x801f, 45}, + {0x8029, 45}, + {0xc038, 45}, + {0x8003, 46}, + {0x8006, 46}, + {0x800a, 46}, + {0x800f, 46}, + {0x8018, 46}, + {0x801f, 46}, + {0x8029, 46}, + {0xc038, 46}, + }, + /* 15 */ + { + {0x8001, 47}, + {0xc016, 47}, + {0x8001, 51}, + {0xc016, 51}, + {0x8001, 52}, + {0xc016, 52}, + {0x8001, 53}, + {0xc016, 53}, + {0x8001, 54}, + {0xc016, 54}, + {0x8001, 55}, + {0xc016, 55}, + {0x8001, 56}, + {0xc016, 56}, + {0x8001, 57}, + {0xc016, 57}, + }, + /* 16 */ + { + {0x8002, 47}, + {0x8009, 47}, + {0x8017, 47}, + {0xc028, 47}, + {0x8002, 51}, + {0x8009, 51}, + {0x8017, 51}, + {0xc028, 51}, + {0x8002, 52}, + {0x8009, 52}, + {0x8017, 52}, + {0xc028, 52}, + {0x8002, 53}, + {0x8009, 53}, + {0x8017, 53}, + {0xc028, 53}, + }, + /* 17 */ + { + {0x8003, 47}, + {0x8006, 47}, + {0x800a, 47}, + {0x800f, 47}, + {0x8018, 47}, + {0x801f, 47}, + {0x8029, 47}, + {0xc038, 47}, + {0x8003, 51}, + {0x8006, 51}, + {0x800a, 51}, + {0x800f, 51}, + {0x8018, 51}, + {0x801f, 51}, + {0x8029, 51}, + {0xc038, 51}, + }, + /* 18 */ + { + {0x8003, 52}, + {0x8006, 52}, + {0x800a, 52}, + {0x800f, 52}, + {0x8018, 52}, + {0x801f, 52}, + {0x8029, 52}, + {0xc038, 52}, + {0x8003, 53}, + {0x8006, 53}, + {0x800a, 53}, + {0x800f, 53}, + {0x8018, 53}, + {0x801f, 53}, + {0x8029, 53}, + {0xc038, 53}, + }, + /* 19 */ + { + {0x8002, 54}, + {0x8009, 54}, + {0x8017, 54}, + {0xc028, 54}, + {0x8002, 55}, + {0x8009, 55}, + {0x8017, 55}, + {0xc028, 55}, + {0x8002, 56}, + {0x8009, 56}, + {0x8017, 56}, + {0xc028, 56}, + {0x8002, 57}, + {0x8009, 57}, + {0x8017, 57}, + {0xc028, 57}, + }, + /* 20 */ + { + {0x8003, 54}, + {0x8006, 54}, + {0x800a, 54}, + {0x800f, 54}, + {0x8018, 54}, + {0x801f, 54}, + {0x8029, 54}, + {0xc038, 54}, + {0x8003, 55}, + {0x8006, 55}, + {0x800a, 55}, + {0x800f, 55}, + {0x8018, 55}, + {0x801f, 55}, + {0x8029, 55}, + {0xc038, 55}, + }, + /* 21 */ + { + {0x8003, 56}, + {0x8006, 56}, + {0x800a, 56}, + {0x800f, 56}, + {0x8018, 56}, + {0x801f, 56}, + {0x8029, 56}, + {0xc038, 56}, + {0x8003, 57}, + {0x8006, 57}, + {0x800a, 57}, + {0x800f, 57}, + {0x8018, 57}, + {0x801f, 57}, + {0x8029, 57}, + {0xc038, 57}, + }, + /* 22 */ + { + {0x1a, 0}, + {0x1b, 0}, + {0x1d, 0}, + {0x1e, 0}, + {0x21, 0}, + {0x22, 0}, + {0x24, 0}, + {0x25, 0}, + {0x2b, 0}, + {0x2e, 0}, + {0x32, 0}, + {0x35, 0}, + {0x3a, 0}, + {0x3d, 0}, + {0x41, 0}, + {0x4044, 0}, + }, + /* 23 */ + { + {0xc000, 61}, + {0xc000, 65}, + {0xc000, 95}, + {0xc000, 98}, + {0xc000, 100}, + {0xc000, 102}, + {0xc000, 103}, + {0xc000, 104}, + {0xc000, 108}, + {0xc000, 109}, + {0xc000, 110}, + {0xc000, 112}, + {0xc000, 114}, + {0xc000, 117}, + {0x26, 0}, + {0x27, 0}, + }, + /* 24 */ + { + {0x8001, 61}, + {0xc016, 61}, + {0x8001, 65}, + {0xc016, 65}, + {0x8001, 95}, + {0xc016, 95}, + {0x8001, 98}, + {0xc016, 98}, + {0x8001, 100}, + {0xc016, 100}, + {0x8001, 102}, + {0xc016, 102}, + {0x8001, 103}, + {0xc016, 103}, + {0x8001, 104}, + {0xc016, 104}, + }, + /* 25 */ + { + {0x8002, 61}, + {0x8009, 61}, + {0x8017, 61}, + {0xc028, 61}, + {0x8002, 65}, + {0x8009, 65}, + {0x8017, 65}, + {0xc028, 65}, + {0x8002, 95}, + {0x8009, 95}, + {0x8017, 95}, + {0xc028, 95}, + {0x8002, 98}, + {0x8009, 98}, + {0x8017, 98}, + {0xc028, 98}, + }, + /* 26 */ + { + {0x8003, 61}, + {0x8006, 61}, + {0x800a, 61}, + {0x800f, 61}, + {0x8018, 61}, + {0x801f, 61}, + {0x8029, 61}, + {0xc038, 61}, + {0x8003, 65}, + {0x8006, 65}, + {0x800a, 65}, + {0x800f, 65}, + {0x8018, 65}, + {0x801f, 65}, + {0x8029, 65}, + {0xc038, 65}, + }, + /* 27 */ + { + {0x8003, 95}, + {0x8006, 95}, + {0x800a, 95}, + {0x800f, 95}, + {0x8018, 95}, + {0x801f, 95}, + {0x8029, 95}, + {0xc038, 95}, + {0x8003, 98}, + {0x8006, 98}, + {0x800a, 98}, + {0x800f, 98}, + {0x8018, 98}, + {0x801f, 98}, + {0x8029, 98}, + {0xc038, 98}, + }, + /* 28 */ + { + {0x8002, 100}, + {0x8009, 100}, + {0x8017, 100}, + {0xc028, 100}, + {0x8002, 102}, + {0x8009, 102}, + {0x8017, 102}, + {0xc028, 102}, + {0x8002, 103}, + {0x8009, 103}, + {0x8017, 103}, + {0xc028, 103}, + {0x8002, 104}, + {0x8009, 104}, + {0x8017, 104}, + {0xc028, 104}, + }, + /* 29 */ + { + {0x8003, 100}, + {0x8006, 100}, + {0x800a, 100}, + {0x800f, 100}, + {0x8018, 100}, + {0x801f, 100}, + {0x8029, 100}, + {0xc038, 100}, + {0x8003, 102}, + {0x8006, 102}, + {0x800a, 102}, + {0x800f, 102}, + {0x8018, 102}, + {0x801f, 102}, + {0x8029, 102}, + {0xc038, 102}, + }, + /* 30 */ + { + {0x8003, 103}, + {0x8006, 103}, + {0x800a, 103}, + {0x800f, 103}, + {0x8018, 103}, + {0x801f, 103}, + {0x8029, 103}, + {0xc038, 103}, + {0x8003, 104}, + {0x8006, 104}, + {0x800a, 104}, + {0x800f, 104}, + {0x8018, 104}, + {0x801f, 104}, + {0x8029, 104}, + {0xc038, 104}, + }, + /* 31 */ + { + {0x8001, 108}, + {0xc016, 108}, + {0x8001, 109}, + {0xc016, 109}, + {0x8001, 110}, + {0xc016, 110}, + {0x8001, 112}, + {0xc016, 112}, + {0x8001, 114}, + {0xc016, 114}, + {0x8001, 117}, + {0xc016, 117}, + {0xc000, 58}, + {0xc000, 66}, + {0xc000, 67}, + {0xc000, 68}, + }, + /* 32 */ + { + {0x8002, 108}, + {0x8009, 108}, + {0x8017, 108}, + {0xc028, 108}, + {0x8002, 109}, + {0x8009, 109}, + {0x8017, 109}, + {0xc028, 109}, + {0x8002, 110}, + {0x8009, 110}, + {0x8017, 110}, + {0xc028, 110}, + {0x8002, 112}, + {0x8009, 112}, + {0x8017, 112}, + {0xc028, 112}, + }, + /* 33 */ + { + {0x8003, 108}, + {0x8006, 108}, + {0x800a, 108}, + {0x800f, 108}, + {0x8018, 108}, + {0x801f, 108}, + {0x8029, 108}, + {0xc038, 108}, + {0x8003, 109}, + {0x8006, 109}, + {0x800a, 109}, + {0x800f, 109}, + {0x8018, 109}, + {0x801f, 109}, + {0x8029, 109}, + {0xc038, 109}, + }, + /* 34 */ + { + {0x8003, 110}, + {0x8006, 110}, + {0x800a, 110}, + {0x800f, 110}, + {0x8018, 110}, + {0x801f, 110}, + {0x8029, 110}, + {0xc038, 110}, + {0x8003, 112}, + {0x8006, 112}, + {0x800a, 112}, + {0x800f, 112}, + {0x8018, 112}, + {0x801f, 112}, + {0x8029, 112}, + {0xc038, 112}, + }, + /* 35 */ + { + {0x8002, 114}, + {0x8009, 114}, + {0x8017, 114}, + {0xc028, 114}, + {0x8002, 117}, + {0x8009, 117}, + {0x8017, 117}, + {0xc028, 117}, + {0x8001, 58}, + {0xc016, 58}, + {0x8001, 66}, + {0xc016, 66}, + {0x8001, 67}, + {0xc016, 67}, + {0x8001, 68}, + {0xc016, 68}, + }, + /* 36 */ + { + {0x8003, 114}, + {0x8006, 114}, + {0x800a, 114}, + {0x800f, 114}, + {0x8018, 114}, + {0x801f, 114}, + {0x8029, 114}, + {0xc038, 114}, + {0x8003, 117}, + {0x8006, 117}, + {0x800a, 117}, + {0x800f, 117}, + {0x8018, 117}, + {0x801f, 117}, + {0x8029, 117}, + {0xc038, 117}, + }, + /* 37 */ + { + {0x8002, 58}, + {0x8009, 58}, + {0x8017, 58}, + {0xc028, 58}, + {0x8002, 66}, + {0x8009, 66}, + {0x8017, 66}, + {0xc028, 66}, + {0x8002, 67}, + {0x8009, 67}, + {0x8017, 67}, + {0xc028, 67}, + {0x8002, 68}, + {0x8009, 68}, + {0x8017, 68}, + {0xc028, 68}, + }, + /* 38 */ + { + {0x8003, 58}, + {0x8006, 58}, + {0x800a, 58}, + {0x800f, 58}, + {0x8018, 58}, + {0x801f, 58}, + {0x8029, 58}, + {0xc038, 58}, + {0x8003, 66}, + {0x8006, 66}, + {0x800a, 66}, + {0x800f, 66}, + {0x8018, 66}, + {0x801f, 66}, + {0x8029, 66}, + {0xc038, 66}, + }, + /* 39 */ + { + {0x8003, 67}, + {0x8006, 67}, + {0x800a, 67}, + {0x800f, 67}, + {0x8018, 67}, + {0x801f, 67}, + {0x8029, 67}, + {0xc038, 67}, + {0x8003, 68}, + {0x8006, 68}, + {0x800a, 68}, + {0x800f, 68}, + {0x8018, 68}, + {0x801f, 68}, + {0x8029, 68}, + {0xc038, 68}, + }, + /* 40 */ + { + {0x2c, 0}, + {0x2d, 0}, + {0x2f, 0}, + {0x30, 0}, + {0x33, 0}, + {0x34, 0}, + {0x36, 0}, + {0x37, 0}, + {0x3b, 0}, + {0x3c, 0}, + {0x3e, 0}, + {0x3f, 0}, + {0x42, 0}, + {0x43, 0}, + {0x45, 0}, + {0x4048, 0}, + }, + /* 41 */ + { + {0xc000, 69}, + {0xc000, 70}, + {0xc000, 71}, + {0xc000, 72}, + {0xc000, 73}, + {0xc000, 74}, + {0xc000, 75}, + {0xc000, 76}, + {0xc000, 77}, + {0xc000, 78}, + {0xc000, 79}, + {0xc000, 80}, + {0xc000, 81}, + {0xc000, 82}, + {0xc000, 83}, + {0xc000, 84}, + }, + /* 42 */ + { + {0x8001, 69}, + {0xc016, 69}, + {0x8001, 70}, + {0xc016, 70}, + {0x8001, 71}, + {0xc016, 71}, + {0x8001, 72}, + {0xc016, 72}, + {0x8001, 73}, + {0xc016, 73}, + {0x8001, 74}, + {0xc016, 74}, + {0x8001, 75}, + {0xc016, 75}, + {0x8001, 76}, + {0xc016, 76}, + }, + /* 43 */ + { + {0x8002, 69}, + {0x8009, 69}, + {0x8017, 69}, + {0xc028, 69}, + {0x8002, 70}, + {0x8009, 70}, + {0x8017, 70}, + {0xc028, 70}, + {0x8002, 71}, + {0x8009, 71}, + {0x8017, 71}, + {0xc028, 71}, + {0x8002, 72}, + {0x8009, 72}, + {0x8017, 72}, + {0xc028, 72}, + }, + /* 44 */ + { + {0x8003, 69}, + {0x8006, 69}, + {0x800a, 69}, + {0x800f, 69}, + {0x8018, 69}, + {0x801f, 69}, + {0x8029, 69}, + {0xc038, 69}, + {0x8003, 70}, + {0x8006, 70}, + {0x800a, 70}, + {0x800f, 70}, + {0x8018, 70}, + {0x801f, 70}, + {0x8029, 70}, + {0xc038, 70}, + }, + /* 45 */ + { + {0x8003, 71}, + {0x8006, 71}, + {0x800a, 71}, + {0x800f, 71}, + {0x8018, 71}, + {0x801f, 71}, + {0x8029, 71}, + {0xc038, 71}, + {0x8003, 72}, + {0x8006, 72}, + {0x800a, 72}, + {0x800f, 72}, + {0x8018, 72}, + {0x801f, 72}, + {0x8029, 72}, + {0xc038, 72}, + }, + /* 46 */ + { + {0x8002, 73}, + {0x8009, 73}, + {0x8017, 73}, + {0xc028, 73}, + {0x8002, 74}, + {0x8009, 74}, + {0x8017, 74}, + {0xc028, 74}, + {0x8002, 75}, + {0x8009, 75}, + {0x8017, 75}, + {0xc028, 75}, + {0x8002, 76}, + {0x8009, 76}, + {0x8017, 76}, + {0xc028, 76}, + }, + /* 47 */ + { + {0x8003, 73}, + {0x8006, 73}, + {0x800a, 73}, + {0x800f, 73}, + {0x8018, 73}, + {0x801f, 73}, + {0x8029, 73}, + {0xc038, 73}, + {0x8003, 74}, + {0x8006, 74}, + {0x800a, 74}, + {0x800f, 74}, + {0x8018, 74}, + {0x801f, 74}, + {0x8029, 74}, + {0xc038, 74}, + }, + /* 48 */ + { + {0x8003, 75}, + {0x8006, 75}, + {0x800a, 75}, + {0x800f, 75}, + {0x8018, 75}, + {0x801f, 75}, + {0x8029, 75}, + {0xc038, 75}, + {0x8003, 76}, + {0x8006, 76}, + {0x800a, 76}, + {0x800f, 76}, + {0x8018, 76}, + {0x801f, 76}, + {0x8029, 76}, + {0xc038, 76}, + }, + /* 49 */ + { + {0x8001, 77}, + {0xc016, 77}, + {0x8001, 78}, + {0xc016, 78}, + {0x8001, 79}, + {0xc016, 79}, + {0x8001, 80}, + {0xc016, 80}, + {0x8001, 81}, + {0xc016, 81}, + {0x8001, 82}, + {0xc016, 82}, + {0x8001, 83}, + {0xc016, 83}, + {0x8001, 84}, + {0xc016, 84}, + }, + /* 50 */ + { + {0x8002, 77}, + {0x8009, 77}, + {0x8017, 77}, + {0xc028, 77}, + {0x8002, 78}, + {0x8009, 78}, + {0x8017, 78}, + {0xc028, 78}, + {0x8002, 79}, + {0x8009, 79}, + {0x8017, 79}, + {0xc028, 79}, + {0x8002, 80}, + {0x8009, 80}, + {0x8017, 80}, + {0xc028, 80}, + }, + /* 51 */ + { + {0x8003, 77}, + {0x8006, 77}, + {0x800a, 77}, + {0x800f, 77}, + {0x8018, 77}, + {0x801f, 77}, + {0x8029, 77}, + {0xc038, 77}, + {0x8003, 78}, + {0x8006, 78}, + {0x800a, 78}, + {0x800f, 78}, + {0x8018, 78}, + {0x801f, 78}, + {0x8029, 78}, + {0xc038, 78}, + }, + /* 52 */ + { + {0x8003, 79}, + {0x8006, 79}, + {0x800a, 79}, + {0x800f, 79}, + {0x8018, 79}, + {0x801f, 79}, + {0x8029, 79}, + {0xc038, 79}, + {0x8003, 80}, + {0x8006, 80}, + {0x800a, 80}, + {0x800f, 80}, + {0x8018, 80}, + {0x801f, 80}, + {0x8029, 80}, + {0xc038, 80}, + }, + /* 53 */ + { + {0x8002, 81}, + {0x8009, 81}, + {0x8017, 81}, + {0xc028, 81}, + {0x8002, 82}, + {0x8009, 82}, + {0x8017, 82}, + {0xc028, 82}, + {0x8002, 83}, + {0x8009, 83}, + {0x8017, 83}, + {0xc028, 83}, + {0x8002, 84}, + {0x8009, 84}, + {0x8017, 84}, + {0xc028, 84}, + }, + /* 54 */ + { + {0x8003, 81}, + {0x8006, 81}, + {0x800a, 81}, + {0x800f, 81}, + {0x8018, 81}, + {0x801f, 81}, + {0x8029, 81}, + {0xc038, 81}, + {0x8003, 82}, + {0x8006, 82}, + {0x800a, 82}, + {0x800f, 82}, + {0x8018, 82}, + {0x801f, 82}, + {0x8029, 82}, + {0xc038, 82}, + }, + /* 55 */ + { + {0x8003, 83}, + {0x8006, 83}, + {0x800a, 83}, + {0x800f, 83}, + {0x8018, 83}, + {0x801f, 83}, + {0x8029, 83}, + {0xc038, 83}, + {0x8003, 84}, + {0x8006, 84}, + {0x800a, 84}, + {0x800f, 84}, + {0x8018, 84}, + {0x801f, 84}, + {0x8029, 84}, + {0xc038, 84}, + }, + /* 56 */ + { + {0xc000, 85}, + {0xc000, 86}, + {0xc000, 87}, + {0xc000, 89}, + {0xc000, 106}, + {0xc000, 107}, + {0xc000, 113}, + {0xc000, 118}, + {0xc000, 119}, + {0xc000, 120}, + {0xc000, 121}, + {0xc000, 122}, + {0x46, 0}, + {0x47, 0}, + {0x49, 0}, + {0x404a, 0}, + }, + /* 57 */ + { + {0x8001, 85}, + {0xc016, 85}, + {0x8001, 86}, + {0xc016, 86}, + {0x8001, 87}, + {0xc016, 87}, + {0x8001, 89}, + {0xc016, 89}, + {0x8001, 106}, + {0xc016, 106}, + {0x8001, 107}, + {0xc016, 107}, + {0x8001, 113}, + {0xc016, 113}, + {0x8001, 118}, + {0xc016, 118}, + }, + /* 58 */ + { + {0x8002, 85}, + {0x8009, 85}, + {0x8017, 85}, + {0xc028, 85}, + {0x8002, 86}, + {0x8009, 86}, + {0x8017, 86}, + {0xc028, 86}, + {0x8002, 87}, + {0x8009, 87}, + {0x8017, 87}, + {0xc028, 87}, + {0x8002, 89}, + {0x8009, 89}, + {0x8017, 89}, + {0xc028, 89}, + }, + /* 59 */ + { + {0x8003, 85}, + {0x8006, 85}, + {0x800a, 85}, + {0x800f, 85}, + {0x8018, 85}, + {0x801f, 85}, + {0x8029, 85}, + {0xc038, 85}, + {0x8003, 86}, + {0x8006, 86}, + {0x800a, 86}, + {0x800f, 86}, + {0x8018, 86}, + {0x801f, 86}, + {0x8029, 86}, + {0xc038, 86}, + }, + /* 60 */ + { + {0x8003, 87}, + {0x8006, 87}, + {0x800a, 87}, + {0x800f, 87}, + {0x8018, 87}, + {0x801f, 87}, + {0x8029, 87}, + {0xc038, 87}, + {0x8003, 89}, + {0x8006, 89}, + {0x800a, 89}, + {0x800f, 89}, + {0x8018, 89}, + {0x801f, 89}, + {0x8029, 89}, + {0xc038, 89}, + }, + /* 61 */ + { + {0x8002, 106}, + {0x8009, 106}, + {0x8017, 106}, + {0xc028, 106}, + {0x8002, 107}, + {0x8009, 107}, + {0x8017, 107}, + {0xc028, 107}, + {0x8002, 113}, + {0x8009, 113}, + {0x8017, 113}, + {0xc028, 113}, + {0x8002, 118}, + {0x8009, 118}, + {0x8017, 118}, + {0xc028, 118}, + }, + /* 62 */ + { + {0x8003, 106}, + {0x8006, 106}, + {0x800a, 106}, + {0x800f, 106}, + {0x8018, 106}, + {0x801f, 106}, + {0x8029, 106}, + {0xc038, 106}, + {0x8003, 107}, + {0x8006, 107}, + {0x800a, 107}, + {0x800f, 107}, + {0x8018, 107}, + {0x801f, 107}, + {0x8029, 107}, + {0xc038, 107}, + }, + /* 63 */ + { + {0x8003, 113}, + {0x8006, 113}, + {0x800a, 113}, + {0x800f, 113}, + {0x8018, 113}, + {0x801f, 113}, + {0x8029, 113}, + {0xc038, 113}, + {0x8003, 118}, + {0x8006, 118}, + {0x800a, 118}, + {0x800f, 118}, + {0x8018, 118}, + {0x801f, 118}, + {0x8029, 118}, + {0xc038, 118}, + }, + /* 64 */ + { + {0x8001, 119}, + {0xc016, 119}, + {0x8001, 120}, + {0xc016, 120}, + {0x8001, 121}, + {0xc016, 121}, + {0x8001, 122}, + {0xc016, 122}, + {0xc000, 38}, + {0xc000, 42}, + {0xc000, 44}, + {0xc000, 59}, + {0xc000, 88}, + {0xc000, 90}, + {0x4b, 0}, + {0x4e, 0}, + }, + /* 65 */ + { + {0x8002, 119}, + {0x8009, 119}, + {0x8017, 119}, + {0xc028, 119}, + {0x8002, 120}, + {0x8009, 120}, + {0x8017, 120}, + {0xc028, 120}, + {0x8002, 121}, + {0x8009, 121}, + {0x8017, 121}, + {0xc028, 121}, + {0x8002, 122}, + {0x8009, 122}, + {0x8017, 122}, + {0xc028, 122}, + }, + /* 66 */ + { + {0x8003, 119}, + {0x8006, 119}, + {0x800a, 119}, + {0x800f, 119}, + {0x8018, 119}, + {0x801f, 119}, + {0x8029, 119}, + {0xc038, 119}, + {0x8003, 120}, + {0x8006, 120}, + {0x800a, 120}, + {0x800f, 120}, + {0x8018, 120}, + {0x801f, 120}, + {0x8029, 120}, + {0xc038, 120}, + }, + /* 67 */ + { + {0x8003, 121}, + {0x8006, 121}, + {0x800a, 121}, + {0x800f, 121}, + {0x8018, 121}, + {0x801f, 121}, + {0x8029, 121}, + {0xc038, 121}, + {0x8003, 122}, + {0x8006, 122}, + {0x800a, 122}, + {0x800f, 122}, + {0x8018, 122}, + {0x801f, 122}, + {0x8029, 122}, + {0xc038, 122}, + }, + /* 68 */ + { + {0x8001, 38}, + {0xc016, 38}, + {0x8001, 42}, + {0xc016, 42}, + {0x8001, 44}, + {0xc016, 44}, + {0x8001, 59}, + {0xc016, 59}, + {0x8001, 88}, + {0xc016, 88}, + {0x8001, 90}, + {0xc016, 90}, + {0x4c, 0}, + {0x4d, 0}, + {0x4f, 0}, + {0x51, 0}, + }, + /* 69 */ + { + {0x8002, 38}, + {0x8009, 38}, + {0x8017, 38}, + {0xc028, 38}, + {0x8002, 42}, + {0x8009, 42}, + {0x8017, 42}, + {0xc028, 42}, + {0x8002, 44}, + {0x8009, 44}, + {0x8017, 44}, + {0xc028, 44}, + {0x8002, 59}, + {0x8009, 59}, + {0x8017, 59}, + {0xc028, 59}, + }, + /* 70 */ + { + {0x8003, 38}, + {0x8006, 38}, + {0x800a, 38}, + {0x800f, 38}, + {0x8018, 38}, + {0x801f, 38}, + {0x8029, 38}, + {0xc038, 38}, + {0x8003, 42}, + {0x8006, 42}, + {0x800a, 42}, + {0x800f, 42}, + {0x8018, 42}, + {0x801f, 42}, + {0x8029, 42}, + {0xc038, 42}, + }, + /* 71 */ + { + {0x8003, 44}, + {0x8006, 44}, + {0x800a, 44}, + {0x800f, 44}, + {0x8018, 44}, + {0x801f, 44}, + {0x8029, 44}, + {0xc038, 44}, + {0x8003, 59}, + {0x8006, 59}, + {0x800a, 59}, + {0x800f, 59}, + {0x8018, 59}, + {0x801f, 59}, + {0x8029, 59}, + {0xc038, 59}, + }, + /* 72 */ + { + {0x8002, 88}, + {0x8009, 88}, + {0x8017, 88}, + {0xc028, 88}, + {0x8002, 90}, + {0x8009, 90}, + {0x8017, 90}, + {0xc028, 90}, + {0xc000, 33}, + {0xc000, 34}, + {0xc000, 40}, + {0xc000, 41}, + {0xc000, 63}, + {0x50, 0}, + {0x52, 0}, + {0x54, 0}, + }, + /* 73 */ + { + {0x8003, 88}, + {0x8006, 88}, + {0x800a, 88}, + {0x800f, 88}, + {0x8018, 88}, + {0x801f, 88}, + {0x8029, 88}, + {0xc038, 88}, + {0x8003, 90}, + {0x8006, 90}, + {0x800a, 90}, + {0x800f, 90}, + {0x8018, 90}, + {0x801f, 90}, + {0x8029, 90}, + {0xc038, 90}, + }, + /* 74 */ + { + {0x8001, 33}, + {0xc016, 33}, + {0x8001, 34}, + {0xc016, 34}, + {0x8001, 40}, + {0xc016, 40}, + {0x8001, 41}, + {0xc016, 41}, + {0x8001, 63}, + {0xc016, 63}, + {0xc000, 39}, + {0xc000, 43}, + {0xc000, 124}, + {0x53, 0}, + {0x55, 0}, + {0x58, 0}, + }, + /* 75 */ + { + {0x8002, 33}, + {0x8009, 33}, + {0x8017, 33}, + {0xc028, 33}, + {0x8002, 34}, + {0x8009, 34}, + {0x8017, 34}, + {0xc028, 34}, + {0x8002, 40}, + {0x8009, 40}, + {0x8017, 40}, + {0xc028, 40}, + {0x8002, 41}, + {0x8009, 41}, + {0x8017, 41}, + {0xc028, 41}, + }, + /* 76 */ + { + {0x8003, 33}, + {0x8006, 33}, + {0x800a, 33}, + {0x800f, 33}, + {0x8018, 33}, + {0x801f, 33}, + {0x8029, 33}, + {0xc038, 33}, + {0x8003, 34}, + {0x8006, 34}, + {0x800a, 34}, + {0x800f, 34}, + {0x8018, 34}, + {0x801f, 34}, + {0x8029, 34}, + {0xc038, 34}, + }, + /* 77 */ + { + {0x8003, 40}, + {0x8006, 40}, + {0x800a, 40}, + {0x800f, 40}, + {0x8018, 40}, + {0x801f, 40}, + {0x8029, 40}, + {0xc038, 40}, + {0x8003, 41}, + {0x8006, 41}, + {0x800a, 41}, + {0x800f, 41}, + {0x8018, 41}, + {0x801f, 41}, + {0x8029, 41}, + {0xc038, 41}, + }, + /* 78 */ + { + {0x8002, 63}, + {0x8009, 63}, + {0x8017, 63}, + {0xc028, 63}, + {0x8001, 39}, + {0xc016, 39}, + {0x8001, 43}, + {0xc016, 43}, + {0x8001, 124}, + {0xc016, 124}, + {0xc000, 35}, + {0xc000, 62}, + {0x56, 0}, + {0x57, 0}, + {0x59, 0}, + {0x5a, 0}, + }, + /* 79 */ + { + {0x8003, 63}, + {0x8006, 63}, + {0x800a, 63}, + {0x800f, 63}, + {0x8018, 63}, + {0x801f, 63}, + {0x8029, 63}, + {0xc038, 63}, + {0x8002, 39}, + {0x8009, 39}, + {0x8017, 39}, + {0xc028, 39}, + {0x8002, 43}, + {0x8009, 43}, + {0x8017, 43}, + {0xc028, 43}, + }, + /* 80 */ + { + {0x8003, 39}, + {0x8006, 39}, + {0x800a, 39}, + {0x800f, 39}, + {0x8018, 39}, + {0x801f, 39}, + {0x8029, 39}, + {0xc038, 39}, + {0x8003, 43}, + {0x8006, 43}, + {0x800a, 43}, + {0x800f, 43}, + {0x8018, 43}, + {0x801f, 43}, + {0x8029, 43}, + {0xc038, 43}, + }, + /* 81 */ + { + {0x8002, 124}, + {0x8009, 124}, + {0x8017, 124}, + {0xc028, 124}, + {0x8001, 35}, + {0xc016, 35}, + {0x8001, 62}, + {0xc016, 62}, + {0xc000, 0}, + {0xc000, 36}, + {0xc000, 64}, + {0xc000, 91}, + {0xc000, 93}, + {0xc000, 126}, + {0x5b, 0}, + {0x5c, 0}, + }, + /* 82 */ + { + {0x8003, 124}, + {0x8006, 124}, + {0x800a, 124}, + {0x800f, 124}, + {0x8018, 124}, + {0x801f, 124}, + {0x8029, 124}, + {0xc038, 124}, + {0x8002, 35}, + {0x8009, 35}, + {0x8017, 35}, + {0xc028, 35}, + {0x8002, 62}, + {0x8009, 62}, + {0x8017, 62}, + {0xc028, 62}, + }, + /* 83 */ + { + {0x8003, 35}, + {0x8006, 35}, + {0x800a, 35}, + {0x800f, 35}, + {0x8018, 35}, + {0x801f, 35}, + {0x8029, 35}, + {0xc038, 35}, + {0x8003, 62}, + {0x8006, 62}, + {0x800a, 62}, + {0x800f, 62}, + {0x8018, 62}, + {0x801f, 62}, + {0x8029, 62}, + {0xc038, 62}, + }, + /* 84 */ + { + {0x8001, 0}, + {0xc016, 0}, + {0x8001, 36}, + {0xc016, 36}, + {0x8001, 64}, + {0xc016, 64}, + {0x8001, 91}, + {0xc016, 91}, + {0x8001, 93}, + {0xc016, 93}, + {0x8001, 126}, + {0xc016, 126}, + {0xc000, 94}, + {0xc000, 125}, + {0x5d, 0}, + {0x5e, 0}, + }, + /* 85 */ + { + {0x8002, 0}, + {0x8009, 0}, + {0x8017, 0}, + {0xc028, 0}, + {0x8002, 36}, + {0x8009, 36}, + {0x8017, 36}, + {0xc028, 36}, + {0x8002, 64}, + {0x8009, 64}, + {0x8017, 64}, + {0xc028, 64}, + {0x8002, 91}, + {0x8009, 91}, + {0x8017, 91}, + {0xc028, 91}, + }, + /* 86 */ + { + {0x8003, 0}, + {0x8006, 0}, + {0x800a, 0}, + {0x800f, 0}, + {0x8018, 0}, + {0x801f, 0}, + {0x8029, 0}, + {0xc038, 0}, + {0x8003, 36}, + {0x8006, 36}, + {0x800a, 36}, + {0x800f, 36}, + {0x8018, 36}, + {0x801f, 36}, + {0x8029, 36}, + {0xc038, 36}, + }, + /* 87 */ + { + {0x8003, 64}, + {0x8006, 64}, + {0x800a, 64}, + {0x800f, 64}, + {0x8018, 64}, + {0x801f, 64}, + {0x8029, 64}, + {0xc038, 64}, + {0x8003, 91}, + {0x8006, 91}, + {0x800a, 91}, + {0x800f, 91}, + {0x8018, 91}, + {0x801f, 91}, + {0x8029, 91}, + {0xc038, 91}, + }, + /* 88 */ + { + {0x8002, 93}, + {0x8009, 93}, + {0x8017, 93}, + {0xc028, 93}, + {0x8002, 126}, + {0x8009, 126}, + {0x8017, 126}, + {0xc028, 126}, + {0x8001, 94}, + {0xc016, 94}, + {0x8001, 125}, + {0xc016, 125}, + {0xc000, 60}, + {0xc000, 96}, + {0xc000, 123}, + {0x5f, 0}, + }, + /* 89 */ + { + {0x8003, 93}, + {0x8006, 93}, + {0x800a, 93}, + {0x800f, 93}, + {0x8018, 93}, + {0x801f, 93}, + {0x8029, 93}, + {0xc038, 93}, + {0x8003, 126}, + {0x8006, 126}, + {0x800a, 126}, + {0x800f, 126}, + {0x8018, 126}, + {0x801f, 126}, + {0x8029, 126}, + {0xc038, 126}, + }, + /* 90 */ + { + {0x8002, 94}, + {0x8009, 94}, + {0x8017, 94}, + {0xc028, 94}, + {0x8002, 125}, + {0x8009, 125}, + {0x8017, 125}, + {0xc028, 125}, + {0x8001, 60}, + {0xc016, 60}, + {0x8001, 96}, + {0xc016, 96}, + {0x8001, 123}, + {0xc016, 123}, + {0x60, 0}, + {0x6e, 0}, + }, + /* 91 */ + { + {0x8003, 94}, + {0x8006, 94}, + {0x800a, 94}, + {0x800f, 94}, + {0x8018, 94}, + {0x801f, 94}, + {0x8029, 94}, + {0xc038, 94}, + {0x8003, 125}, + {0x8006, 125}, + {0x800a, 125}, + {0x800f, 125}, + {0x8018, 125}, + {0x801f, 125}, + {0x8029, 125}, + {0xc038, 125}, + }, + /* 92 */ + { + {0x8002, 60}, + {0x8009, 60}, + {0x8017, 60}, + {0xc028, 60}, + {0x8002, 96}, + {0x8009, 96}, + {0x8017, 96}, + {0xc028, 96}, + {0x8002, 123}, + {0x8009, 123}, + {0x8017, 123}, + {0xc028, 123}, + {0x61, 0}, + {0x65, 0}, + {0x6f, 0}, + {0x85, 0}, + }, + /* 93 */ + { + {0x8003, 60}, + {0x8006, 60}, + {0x800a, 60}, + {0x800f, 60}, + {0x8018, 60}, + {0x801f, 60}, + {0x8029, 60}, + {0xc038, 60}, + {0x8003, 96}, + {0x8006, 96}, + {0x800a, 96}, + {0x800f, 96}, + {0x8018, 96}, + {0x801f, 96}, + {0x8029, 96}, + {0xc038, 96}, + }, + /* 94 */ + { + {0x8003, 123}, + {0x8006, 123}, + {0x800a, 123}, + {0x800f, 123}, + {0x8018, 123}, + {0x801f, 123}, + {0x8029, 123}, + {0xc038, 123}, + {0x62, 0}, + {0x63, 0}, + {0x66, 0}, + {0x69, 0}, + {0x70, 0}, + {0x77, 0}, + {0x86, 0}, + {0x99, 0}, + }, + /* 95 */ + { + {0xc000, 92}, + {0xc000, 195}, + {0xc000, 208}, + {0x64, 0}, + {0x67, 0}, + {0x68, 0}, + {0x6a, 0}, + {0x6b, 0}, + {0x71, 0}, + {0x74, 0}, + {0x78, 0}, + {0x7e, 0}, + {0x87, 0}, + {0x8e, 0}, + {0x9a, 0}, + {0xa9, 0}, + }, + /* 96 */ + { + {0x8001, 92}, + {0xc016, 92}, + {0x8001, 195}, + {0xc016, 195}, + {0x8001, 208}, + {0xc016, 208}, + {0xc000, 128}, + {0xc000, 130}, + {0xc000, 131}, + {0xc000, 162}, + {0xc000, 184}, + {0xc000, 194}, + {0xc000, 224}, + {0xc000, 226}, + {0x6c, 0}, + {0x6d, 0}, + }, + /* 97 */ + { + {0x8002, 92}, + {0x8009, 92}, + {0x8017, 92}, + {0xc028, 92}, + {0x8002, 195}, + {0x8009, 195}, + {0x8017, 195}, + {0xc028, 195}, + {0x8002, 208}, + {0x8009, 208}, + {0x8017, 208}, + {0xc028, 208}, + {0x8001, 128}, + {0xc016, 128}, + {0x8001, 130}, + {0xc016, 130}, + }, + /* 98 */ + { + {0x8003, 92}, + {0x8006, 92}, + {0x800a, 92}, + {0x800f, 92}, + {0x8018, 92}, + {0x801f, 92}, + {0x8029, 92}, + {0xc038, 92}, + {0x8003, 195}, + {0x8006, 195}, + {0x800a, 195}, + {0x800f, 195}, + {0x8018, 195}, + {0x801f, 195}, + {0x8029, 195}, + {0xc038, 195}, + }, + /* 99 */ + { + {0x8003, 208}, + {0x8006, 208}, + {0x800a, 208}, + {0x800f, 208}, + {0x8018, 208}, + {0x801f, 208}, + {0x8029, 208}, + {0xc038, 208}, + {0x8002, 128}, + {0x8009, 128}, + {0x8017, 128}, + {0xc028, 128}, + {0x8002, 130}, + {0x8009, 130}, + {0x8017, 130}, + {0xc028, 130}, + }, + /* 100 */ + { + {0x8003, 128}, + {0x8006, 128}, + {0x800a, 128}, + {0x800f, 128}, + {0x8018, 128}, + {0x801f, 128}, + {0x8029, 128}, + {0xc038, 128}, + {0x8003, 130}, + {0x8006, 130}, + {0x800a, 130}, + {0x800f, 130}, + {0x8018, 130}, + {0x801f, 130}, + {0x8029, 130}, + {0xc038, 130}, + }, + /* 101 */ + { + {0x8001, 131}, + {0xc016, 131}, + {0x8001, 162}, + {0xc016, 162}, + {0x8001, 184}, + {0xc016, 184}, + {0x8001, 194}, + {0xc016, 194}, + {0x8001, 224}, + {0xc016, 224}, + {0x8001, 226}, + {0xc016, 226}, + {0xc000, 153}, + {0xc000, 161}, + {0xc000, 167}, + {0xc000, 172}, + }, + /* 102 */ + { + {0x8002, 131}, + {0x8009, 131}, + {0x8017, 131}, + {0xc028, 131}, + {0x8002, 162}, + {0x8009, 162}, + {0x8017, 162}, + {0xc028, 162}, + {0x8002, 184}, + {0x8009, 184}, + {0x8017, 184}, + {0xc028, 184}, + {0x8002, 194}, + {0x8009, 194}, + {0x8017, 194}, + {0xc028, 194}, + }, + /* 103 */ + { + {0x8003, 131}, + {0x8006, 131}, + {0x800a, 131}, + {0x800f, 131}, + {0x8018, 131}, + {0x801f, 131}, + {0x8029, 131}, + {0xc038, 131}, + {0x8003, 162}, + {0x8006, 162}, + {0x800a, 162}, + {0x800f, 162}, + {0x8018, 162}, + {0x801f, 162}, + {0x8029, 162}, + {0xc038, 162}, + }, + /* 104 */ + { + {0x8003, 184}, + {0x8006, 184}, + {0x800a, 184}, + {0x800f, 184}, + {0x8018, 184}, + {0x801f, 184}, + {0x8029, 184}, + {0xc038, 184}, + {0x8003, 194}, + {0x8006, 194}, + {0x800a, 194}, + {0x800f, 194}, + {0x8018, 194}, + {0x801f, 194}, + {0x8029, 194}, + {0xc038, 194}, + }, + /* 105 */ + { + {0x8002, 224}, + {0x8009, 224}, + {0x8017, 224}, + {0xc028, 224}, + {0x8002, 226}, + {0x8009, 226}, + {0x8017, 226}, + {0xc028, 226}, + {0x8001, 153}, + {0xc016, 153}, + {0x8001, 161}, + {0xc016, 161}, + {0x8001, 167}, + {0xc016, 167}, + {0x8001, 172}, + {0xc016, 172}, + }, + /* 106 */ + { + {0x8003, 224}, + {0x8006, 224}, + {0x800a, 224}, + {0x800f, 224}, + {0x8018, 224}, + {0x801f, 224}, + {0x8029, 224}, + {0xc038, 224}, + {0x8003, 226}, + {0x8006, 226}, + {0x800a, 226}, + {0x800f, 226}, + {0x8018, 226}, + {0x801f, 226}, + {0x8029, 226}, + {0xc038, 226}, + }, + /* 107 */ + { + {0x8002, 153}, + {0x8009, 153}, + {0x8017, 153}, + {0xc028, 153}, + {0x8002, 161}, + {0x8009, 161}, + {0x8017, 161}, + {0xc028, 161}, + {0x8002, 167}, + {0x8009, 167}, + {0x8017, 167}, + {0xc028, 167}, + {0x8002, 172}, + {0x8009, 172}, + {0x8017, 172}, + {0xc028, 172}, + }, + /* 108 */ + { + {0x8003, 153}, + {0x8006, 153}, + {0x800a, 153}, + {0x800f, 153}, + {0x8018, 153}, + {0x801f, 153}, + {0x8029, 153}, + {0xc038, 153}, + {0x8003, 161}, + {0x8006, 161}, + {0x800a, 161}, + {0x800f, 161}, + {0x8018, 161}, + {0x801f, 161}, + {0x8029, 161}, + {0xc038, 161}, + }, + /* 109 */ + { + {0x8003, 167}, + {0x8006, 167}, + {0x800a, 167}, + {0x800f, 167}, + {0x8018, 167}, + {0x801f, 167}, + {0x8029, 167}, + {0xc038, 167}, + {0x8003, 172}, + {0x8006, 172}, + {0x800a, 172}, + {0x800f, 172}, + {0x8018, 172}, + {0x801f, 172}, + {0x8029, 172}, + {0xc038, 172}, + }, + /* 110 */ + { + {0x72, 0}, + {0x73, 0}, + {0x75, 0}, + {0x76, 0}, + {0x79, 0}, + {0x7b, 0}, + {0x7f, 0}, + {0x82, 0}, + {0x88, 0}, + {0x8b, 0}, + {0x8f, 0}, + {0x92, 0}, + {0x9b, 0}, + {0xa2, 0}, + {0xaa, 0}, + {0xb4, 0}, + }, + /* 111 */ + { + {0xc000, 176}, + {0xc000, 177}, + {0xc000, 179}, + {0xc000, 209}, + {0xc000, 216}, + {0xc000, 217}, + {0xc000, 227}, + {0xc000, 229}, + {0xc000, 230}, + {0x7a, 0}, + {0x7c, 0}, + {0x7d, 0}, + {0x80, 0}, + {0x81, 0}, + {0x83, 0}, + {0x84, 0}, + }, + /* 112 */ + { + {0x8001, 176}, + {0xc016, 176}, + {0x8001, 177}, + {0xc016, 177}, + {0x8001, 179}, + {0xc016, 179}, + {0x8001, 209}, + {0xc016, 209}, + {0x8001, 216}, + {0xc016, 216}, + {0x8001, 217}, + {0xc016, 217}, + {0x8001, 227}, + {0xc016, 227}, + {0x8001, 229}, + {0xc016, 229}, + }, + /* 113 */ + { + {0x8002, 176}, + {0x8009, 176}, + {0x8017, 176}, + {0xc028, 176}, + {0x8002, 177}, + {0x8009, 177}, + {0x8017, 177}, + {0xc028, 177}, + {0x8002, 179}, + {0x8009, 179}, + {0x8017, 179}, + {0xc028, 179}, + {0x8002, 209}, + {0x8009, 209}, + {0x8017, 209}, + {0xc028, 209}, + }, + /* 114 */ + { + {0x8003, 176}, + {0x8006, 176}, + {0x800a, 176}, + {0x800f, 176}, + {0x8018, 176}, + {0x801f, 176}, + {0x8029, 176}, + {0xc038, 176}, + {0x8003, 177}, + {0x8006, 177}, + {0x800a, 177}, + {0x800f, 177}, + {0x8018, 177}, + {0x801f, 177}, + {0x8029, 177}, + {0xc038, 177}, + }, + /* 115 */ + { + {0x8003, 179}, + {0x8006, 179}, + {0x800a, 179}, + {0x800f, 179}, + {0x8018, 179}, + {0x801f, 179}, + {0x8029, 179}, + {0xc038, 179}, + {0x8003, 209}, + {0x8006, 209}, + {0x800a, 209}, + {0x800f, 209}, + {0x8018, 209}, + {0x801f, 209}, + {0x8029, 209}, + {0xc038, 209}, + }, + /* 116 */ + { + {0x8002, 216}, + {0x8009, 216}, + {0x8017, 216}, + {0xc028, 216}, + {0x8002, 217}, + {0x8009, 217}, + {0x8017, 217}, + {0xc028, 217}, + {0x8002, 227}, + {0x8009, 227}, + {0x8017, 227}, + {0xc028, 227}, + {0x8002, 229}, + {0x8009, 229}, + {0x8017, 229}, + {0xc028, 229}, + }, + /* 117 */ + { + {0x8003, 216}, + {0x8006, 216}, + {0x800a, 216}, + {0x800f, 216}, + {0x8018, 216}, + {0x801f, 216}, + {0x8029, 216}, + {0xc038, 216}, + {0x8003, 217}, + {0x8006, 217}, + {0x800a, 217}, + {0x800f, 217}, + {0x8018, 217}, + {0x801f, 217}, + {0x8029, 217}, + {0xc038, 217}, + }, + /* 118 */ + { + {0x8003, 227}, + {0x8006, 227}, + {0x800a, 227}, + {0x800f, 227}, + {0x8018, 227}, + {0x801f, 227}, + {0x8029, 227}, + {0xc038, 227}, + {0x8003, 229}, + {0x8006, 229}, + {0x800a, 229}, + {0x800f, 229}, + {0x8018, 229}, + {0x801f, 229}, + {0x8029, 229}, + {0xc038, 229}, + }, + /* 119 */ + { + {0x8001, 230}, + {0xc016, 230}, + {0xc000, 129}, + {0xc000, 132}, + {0xc000, 133}, + {0xc000, 134}, + {0xc000, 136}, + {0xc000, 146}, + {0xc000, 154}, + {0xc000, 156}, + {0xc000, 160}, + {0xc000, 163}, + {0xc000, 164}, + {0xc000, 169}, + {0xc000, 170}, + {0xc000, 173}, + }, + /* 120 */ + { + {0x8002, 230}, + {0x8009, 230}, + {0x8017, 230}, + {0xc028, 230}, + {0x8001, 129}, + {0xc016, 129}, + {0x8001, 132}, + {0xc016, 132}, + {0x8001, 133}, + {0xc016, 133}, + {0x8001, 134}, + {0xc016, 134}, + {0x8001, 136}, + {0xc016, 136}, + {0x8001, 146}, + {0xc016, 146}, + }, + /* 121 */ + { + {0x8003, 230}, + {0x8006, 230}, + {0x800a, 230}, + {0x800f, 230}, + {0x8018, 230}, + {0x801f, 230}, + {0x8029, 230}, + {0xc038, 230}, + {0x8002, 129}, + {0x8009, 129}, + {0x8017, 129}, + {0xc028, 129}, + {0x8002, 132}, + {0x8009, 132}, + {0x8017, 132}, + {0xc028, 132}, + }, + /* 122 */ + { + {0x8003, 129}, + {0x8006, 129}, + {0x800a, 129}, + {0x800f, 129}, + {0x8018, 129}, + {0x801f, 129}, + {0x8029, 129}, + {0xc038, 129}, + {0x8003, 132}, + {0x8006, 132}, + {0x800a, 132}, + {0x800f, 132}, + {0x8018, 132}, + {0x801f, 132}, + {0x8029, 132}, + {0xc038, 132}, + }, + /* 123 */ + { + {0x8002, 133}, + {0x8009, 133}, + {0x8017, 133}, + {0xc028, 133}, + {0x8002, 134}, + {0x8009, 134}, + {0x8017, 134}, + {0xc028, 134}, + {0x8002, 136}, + {0x8009, 136}, + {0x8017, 136}, + {0xc028, 136}, + {0x8002, 146}, + {0x8009, 146}, + {0x8017, 146}, + {0xc028, 146}, + }, + /* 124 */ + { + {0x8003, 133}, + {0x8006, 133}, + {0x800a, 133}, + {0x800f, 133}, + {0x8018, 133}, + {0x801f, 133}, + {0x8029, 133}, + {0xc038, 133}, + {0x8003, 134}, + {0x8006, 134}, + {0x800a, 134}, + {0x800f, 134}, + {0x8018, 134}, + {0x801f, 134}, + {0x8029, 134}, + {0xc038, 134}, + }, + /* 125 */ + { + {0x8003, 136}, + {0x8006, 136}, + {0x800a, 136}, + {0x800f, 136}, + {0x8018, 136}, + {0x801f, 136}, + {0x8029, 136}, + {0xc038, 136}, + {0x8003, 146}, + {0x8006, 146}, + {0x800a, 146}, + {0x800f, 146}, + {0x8018, 146}, + {0x801f, 146}, + {0x8029, 146}, + {0xc038, 146}, + }, + /* 126 */ + { + {0x8001, 154}, + {0xc016, 154}, + {0x8001, 156}, + {0xc016, 156}, + {0x8001, 160}, + {0xc016, 160}, + {0x8001, 163}, + {0xc016, 163}, + {0x8001, 164}, + {0xc016, 164}, + {0x8001, 169}, + {0xc016, 169}, + {0x8001, 170}, + {0xc016, 170}, + {0x8001, 173}, + {0xc016, 173}, + }, + /* 127 */ + { + {0x8002, 154}, + {0x8009, 154}, + {0x8017, 154}, + {0xc028, 154}, + {0x8002, 156}, + {0x8009, 156}, + {0x8017, 156}, + {0xc028, 156}, + {0x8002, 160}, + {0x8009, 160}, + {0x8017, 160}, + {0xc028, 160}, + {0x8002, 163}, + {0x8009, 163}, + {0x8017, 163}, + {0xc028, 163}, + }, + /* 128 */ + { + {0x8003, 154}, + {0x8006, 154}, + {0x800a, 154}, + {0x800f, 154}, + {0x8018, 154}, + {0x801f, 154}, + {0x8029, 154}, + {0xc038, 154}, + {0x8003, 156}, + {0x8006, 156}, + {0x800a, 156}, + {0x800f, 156}, + {0x8018, 156}, + {0x801f, 156}, + {0x8029, 156}, + {0xc038, 156}, + }, + /* 129 */ + { + {0x8003, 160}, + {0x8006, 160}, + {0x800a, 160}, + {0x800f, 160}, + {0x8018, 160}, + {0x801f, 160}, + {0x8029, 160}, + {0xc038, 160}, + {0x8003, 163}, + {0x8006, 163}, + {0x800a, 163}, + {0x800f, 163}, + {0x8018, 163}, + {0x801f, 163}, + {0x8029, 163}, + {0xc038, 163}, + }, + /* 130 */ + { + {0x8002, 164}, + {0x8009, 164}, + {0x8017, 164}, + {0xc028, 164}, + {0x8002, 169}, + {0x8009, 169}, + {0x8017, 169}, + {0xc028, 169}, + {0x8002, 170}, + {0x8009, 170}, + {0x8017, 170}, + {0xc028, 170}, + {0x8002, 173}, + {0x8009, 173}, + {0x8017, 173}, + {0xc028, 173}, + }, + /* 131 */ + { + {0x8003, 164}, + {0x8006, 164}, + {0x800a, 164}, + {0x800f, 164}, + {0x8018, 164}, + {0x801f, 164}, + {0x8029, 164}, + {0xc038, 164}, + {0x8003, 169}, + {0x8006, 169}, + {0x800a, 169}, + {0x800f, 169}, + {0x8018, 169}, + {0x801f, 169}, + {0x8029, 169}, + {0xc038, 169}, + }, + /* 132 */ + { + {0x8003, 170}, + {0x8006, 170}, + {0x800a, 170}, + {0x800f, 170}, + {0x8018, 170}, + {0x801f, 170}, + {0x8029, 170}, + {0xc038, 170}, + {0x8003, 173}, + {0x8006, 173}, + {0x800a, 173}, + {0x800f, 173}, + {0x8018, 173}, + {0x801f, 173}, + {0x8029, 173}, + {0xc038, 173}, + }, + /* 133 */ + { + {0x89, 0}, + {0x8a, 0}, + {0x8c, 0}, + {0x8d, 0}, + {0x90, 0}, + {0x91, 0}, + {0x93, 0}, + {0x96, 0}, + {0x9c, 0}, + {0x9f, 0}, + {0xa3, 0}, + {0xa6, 0}, + {0xab, 0}, + {0xae, 0}, + {0xb5, 0}, + {0xbe, 0}, + }, + /* 134 */ + { + {0xc000, 178}, + {0xc000, 181}, + {0xc000, 185}, + {0xc000, 186}, + {0xc000, 187}, + {0xc000, 189}, + {0xc000, 190}, + {0xc000, 196}, + {0xc000, 198}, + {0xc000, 228}, + {0xc000, 232}, + {0xc000, 233}, + {0x94, 0}, + {0x95, 0}, + {0x97, 0}, + {0x98, 0}, + }, + /* 135 */ + { + {0x8001, 178}, + {0xc016, 178}, + {0x8001, 181}, + {0xc016, 181}, + {0x8001, 185}, + {0xc016, 185}, + {0x8001, 186}, + {0xc016, 186}, + {0x8001, 187}, + {0xc016, 187}, + {0x8001, 189}, + {0xc016, 189}, + {0x8001, 190}, + {0xc016, 190}, + {0x8001, 196}, + {0xc016, 196}, + }, + /* 136 */ + { + {0x8002, 178}, + {0x8009, 178}, + {0x8017, 178}, + {0xc028, 178}, + {0x8002, 181}, + {0x8009, 181}, + {0x8017, 181}, + {0xc028, 181}, + {0x8002, 185}, + {0x8009, 185}, + {0x8017, 185}, + {0xc028, 185}, + {0x8002, 186}, + {0x8009, 186}, + {0x8017, 186}, + {0xc028, 186}, + }, + /* 137 */ + { + {0x8003, 178}, + {0x8006, 178}, + {0x800a, 178}, + {0x800f, 178}, + {0x8018, 178}, + {0x801f, 178}, + {0x8029, 178}, + {0xc038, 178}, + {0x8003, 181}, + {0x8006, 181}, + {0x800a, 181}, + {0x800f, 181}, + {0x8018, 181}, + {0x801f, 181}, + {0x8029, 181}, + {0xc038, 181}, + }, + /* 138 */ + { + {0x8003, 185}, + {0x8006, 185}, + {0x800a, 185}, + {0x800f, 185}, + {0x8018, 185}, + {0x801f, 185}, + {0x8029, 185}, + {0xc038, 185}, + {0x8003, 186}, + {0x8006, 186}, + {0x800a, 186}, + {0x800f, 186}, + {0x8018, 186}, + {0x801f, 186}, + {0x8029, 186}, + {0xc038, 186}, + }, + /* 139 */ + { + {0x8002, 187}, + {0x8009, 187}, + {0x8017, 187}, + {0xc028, 187}, + {0x8002, 189}, + {0x8009, 189}, + {0x8017, 189}, + {0xc028, 189}, + {0x8002, 190}, + {0x8009, 190}, + {0x8017, 190}, + {0xc028, 190}, + {0x8002, 196}, + {0x8009, 196}, + {0x8017, 196}, + {0xc028, 196}, + }, + /* 140 */ + { + {0x8003, 187}, + {0x8006, 187}, + {0x800a, 187}, + {0x800f, 187}, + {0x8018, 187}, + {0x801f, 187}, + {0x8029, 187}, + {0xc038, 187}, + {0x8003, 189}, + {0x8006, 189}, + {0x800a, 189}, + {0x800f, 189}, + {0x8018, 189}, + {0x801f, 189}, + {0x8029, 189}, + {0xc038, 189}, + }, + /* 141 */ + { + {0x8003, 190}, + {0x8006, 190}, + {0x800a, 190}, + {0x800f, 190}, + {0x8018, 190}, + {0x801f, 190}, + {0x8029, 190}, + {0xc038, 190}, + {0x8003, 196}, + {0x8006, 196}, + {0x800a, 196}, + {0x800f, 196}, + {0x8018, 196}, + {0x801f, 196}, + {0x8029, 196}, + {0xc038, 196}, + }, + /* 142 */ + { + {0x8001, 198}, + {0xc016, 198}, + {0x8001, 228}, + {0xc016, 228}, + {0x8001, 232}, + {0xc016, 232}, + {0x8001, 233}, + {0xc016, 233}, + {0xc000, 1}, + {0xc000, 135}, + {0xc000, 137}, + {0xc000, 138}, + {0xc000, 139}, + {0xc000, 140}, + {0xc000, 141}, + {0xc000, 143}, + }, + /* 143 */ + { + {0x8002, 198}, + {0x8009, 198}, + {0x8017, 198}, + {0xc028, 198}, + {0x8002, 228}, + {0x8009, 228}, + {0x8017, 228}, + {0xc028, 228}, + {0x8002, 232}, + {0x8009, 232}, + {0x8017, 232}, + {0xc028, 232}, + {0x8002, 233}, + {0x8009, 233}, + {0x8017, 233}, + {0xc028, 233}, + }, + /* 144 */ + { + {0x8003, 198}, + {0x8006, 198}, + {0x800a, 198}, + {0x800f, 198}, + {0x8018, 198}, + {0x801f, 198}, + {0x8029, 198}, + {0xc038, 198}, + {0x8003, 228}, + {0x8006, 228}, + {0x800a, 228}, + {0x800f, 228}, + {0x8018, 228}, + {0x801f, 228}, + {0x8029, 228}, + {0xc038, 228}, + }, + /* 145 */ + { + {0x8003, 232}, + {0x8006, 232}, + {0x800a, 232}, + {0x800f, 232}, + {0x8018, 232}, + {0x801f, 232}, + {0x8029, 232}, + {0xc038, 232}, + {0x8003, 233}, + {0x8006, 233}, + {0x800a, 233}, + {0x800f, 233}, + {0x8018, 233}, + {0x801f, 233}, + {0x8029, 233}, + {0xc038, 233}, + }, + /* 146 */ + { + {0x8001, 1}, + {0xc016, 1}, + {0x8001, 135}, + {0xc016, 135}, + {0x8001, 137}, + {0xc016, 137}, + {0x8001, 138}, + {0xc016, 138}, + {0x8001, 139}, + {0xc016, 139}, + {0x8001, 140}, + {0xc016, 140}, + {0x8001, 141}, + {0xc016, 141}, + {0x8001, 143}, + {0xc016, 143}, + }, + /* 147 */ + { + {0x8002, 1}, + {0x8009, 1}, + {0x8017, 1}, + {0xc028, 1}, + {0x8002, 135}, + {0x8009, 135}, + {0x8017, 135}, + {0xc028, 135}, + {0x8002, 137}, + {0x8009, 137}, + {0x8017, 137}, + {0xc028, 137}, + {0x8002, 138}, + {0x8009, 138}, + {0x8017, 138}, + {0xc028, 138}, + }, + /* 148 */ + { + {0x8003, 1}, + {0x8006, 1}, + {0x800a, 1}, + {0x800f, 1}, + {0x8018, 1}, + {0x801f, 1}, + {0x8029, 1}, + {0xc038, 1}, + {0x8003, 135}, + {0x8006, 135}, + {0x800a, 135}, + {0x800f, 135}, + {0x8018, 135}, + {0x801f, 135}, + {0x8029, 135}, + {0xc038, 135}, + }, + /* 149 */ + { + {0x8003, 137}, + {0x8006, 137}, + {0x800a, 137}, + {0x800f, 137}, + {0x8018, 137}, + {0x801f, 137}, + {0x8029, 137}, + {0xc038, 137}, + {0x8003, 138}, + {0x8006, 138}, + {0x800a, 138}, + {0x800f, 138}, + {0x8018, 138}, + {0x801f, 138}, + {0x8029, 138}, + {0xc038, 138}, + }, + /* 150 */ + { + {0x8002, 139}, + {0x8009, 139}, + {0x8017, 139}, + {0xc028, 139}, + {0x8002, 140}, + {0x8009, 140}, + {0x8017, 140}, + {0xc028, 140}, + {0x8002, 141}, + {0x8009, 141}, + {0x8017, 141}, + {0xc028, 141}, + {0x8002, 143}, + {0x8009, 143}, + {0x8017, 143}, + {0xc028, 143}, + }, + /* 151 */ + { + {0x8003, 139}, + {0x8006, 139}, + {0x800a, 139}, + {0x800f, 139}, + {0x8018, 139}, + {0x801f, 139}, + {0x8029, 139}, + {0xc038, 139}, + {0x8003, 140}, + {0x8006, 140}, + {0x800a, 140}, + {0x800f, 140}, + {0x8018, 140}, + {0x801f, 140}, + {0x8029, 140}, + {0xc038, 140}, + }, + /* 152 */ + { + {0x8003, 141}, + {0x8006, 141}, + {0x800a, 141}, + {0x800f, 141}, + {0x8018, 141}, + {0x801f, 141}, + {0x8029, 141}, + {0xc038, 141}, + {0x8003, 143}, + {0x8006, 143}, + {0x800a, 143}, + {0x800f, 143}, + {0x8018, 143}, + {0x801f, 143}, + {0x8029, 143}, + {0xc038, 143}, + }, + /* 153 */ + { + {0x9d, 0}, + {0x9e, 0}, + {0xa0, 0}, + {0xa1, 0}, + {0xa4, 0}, + {0xa5, 0}, + {0xa7, 0}, + {0xa8, 0}, + {0xac, 0}, + {0xad, 0}, + {0xaf, 0}, + {0xb1, 0}, + {0xb6, 0}, + {0xb9, 0}, + {0xbf, 0}, + {0xcf, 0}, + }, + /* 154 */ + { + {0xc000, 147}, + {0xc000, 149}, + {0xc000, 150}, + {0xc000, 151}, + {0xc000, 152}, + {0xc000, 155}, + {0xc000, 157}, + {0xc000, 158}, + {0xc000, 165}, + {0xc000, 166}, + {0xc000, 168}, + {0xc000, 174}, + {0xc000, 175}, + {0xc000, 180}, + {0xc000, 182}, + {0xc000, 183}, + }, + /* 155 */ + { + {0x8001, 147}, + {0xc016, 147}, + {0x8001, 149}, + {0xc016, 149}, + {0x8001, 150}, + {0xc016, 150}, + {0x8001, 151}, + {0xc016, 151}, + {0x8001, 152}, + {0xc016, 152}, + {0x8001, 155}, + {0xc016, 155}, + {0x8001, 157}, + {0xc016, 157}, + {0x8001, 158}, + {0xc016, 158}, + }, + /* 156 */ + { + {0x8002, 147}, + {0x8009, 147}, + {0x8017, 147}, + {0xc028, 147}, + {0x8002, 149}, + {0x8009, 149}, + {0x8017, 149}, + {0xc028, 149}, + {0x8002, 150}, + {0x8009, 150}, + {0x8017, 150}, + {0xc028, 150}, + {0x8002, 151}, + {0x8009, 151}, + {0x8017, 151}, + {0xc028, 151}, + }, + /* 157 */ + { + {0x8003, 147}, + {0x8006, 147}, + {0x800a, 147}, + {0x800f, 147}, + {0x8018, 147}, + {0x801f, 147}, + {0x8029, 147}, + {0xc038, 147}, + {0x8003, 149}, + {0x8006, 149}, + {0x800a, 149}, + {0x800f, 149}, + {0x8018, 149}, + {0x801f, 149}, + {0x8029, 149}, + {0xc038, 149}, + }, + /* 158 */ + { + {0x8003, 150}, + {0x8006, 150}, + {0x800a, 150}, + {0x800f, 150}, + {0x8018, 150}, + {0x801f, 150}, + {0x8029, 150}, + {0xc038, 150}, + {0x8003, 151}, + {0x8006, 151}, + {0x800a, 151}, + {0x800f, 151}, + {0x8018, 151}, + {0x801f, 151}, + {0x8029, 151}, + {0xc038, 151}, + }, + /* 159 */ + { + {0x8002, 152}, + {0x8009, 152}, + {0x8017, 152}, + {0xc028, 152}, + {0x8002, 155}, + {0x8009, 155}, + {0x8017, 155}, + {0xc028, 155}, + {0x8002, 157}, + {0x8009, 157}, + {0x8017, 157}, + {0xc028, 157}, + {0x8002, 158}, + {0x8009, 158}, + {0x8017, 158}, + {0xc028, 158}, + }, + /* 160 */ + { + {0x8003, 152}, + {0x8006, 152}, + {0x800a, 152}, + {0x800f, 152}, + {0x8018, 152}, + {0x801f, 152}, + {0x8029, 152}, + {0xc038, 152}, + {0x8003, 155}, + {0x8006, 155}, + {0x800a, 155}, + {0x800f, 155}, + {0x8018, 155}, + {0x801f, 155}, + {0x8029, 155}, + {0xc038, 155}, + }, + /* 161 */ + { + {0x8003, 157}, + {0x8006, 157}, + {0x800a, 157}, + {0x800f, 157}, + {0x8018, 157}, + {0x801f, 157}, + {0x8029, 157}, + {0xc038, 157}, + {0x8003, 158}, + {0x8006, 158}, + {0x800a, 158}, + {0x800f, 158}, + {0x8018, 158}, + {0x801f, 158}, + {0x8029, 158}, + {0xc038, 158}, + }, + /* 162 */ + { + {0x8001, 165}, + {0xc016, 165}, + {0x8001, 166}, + {0xc016, 166}, + {0x8001, 168}, + {0xc016, 168}, + {0x8001, 174}, + {0xc016, 174}, + {0x8001, 175}, + {0xc016, 175}, + {0x8001, 180}, + {0xc016, 180}, + {0x8001, 182}, + {0xc016, 182}, + {0x8001, 183}, + {0xc016, 183}, + }, + /* 163 */ + { + {0x8002, 165}, + {0x8009, 165}, + {0x8017, 165}, + {0xc028, 165}, + {0x8002, 166}, + {0x8009, 166}, + {0x8017, 166}, + {0xc028, 166}, + {0x8002, 168}, + {0x8009, 168}, + {0x8017, 168}, + {0xc028, 168}, + {0x8002, 174}, + {0x8009, 174}, + {0x8017, 174}, + {0xc028, 174}, + }, + /* 164 */ + { + {0x8003, 165}, + {0x8006, 165}, + {0x800a, 165}, + {0x800f, 165}, + {0x8018, 165}, + {0x801f, 165}, + {0x8029, 165}, + {0xc038, 165}, + {0x8003, 166}, + {0x8006, 166}, + {0x800a, 166}, + {0x800f, 166}, + {0x8018, 166}, + {0x801f, 166}, + {0x8029, 166}, + {0xc038, 166}, + }, + /* 165 */ + { + {0x8003, 168}, + {0x8006, 168}, + {0x800a, 168}, + {0x800f, 168}, + {0x8018, 168}, + {0x801f, 168}, + {0x8029, 168}, + {0xc038, 168}, + {0x8003, 174}, + {0x8006, 174}, + {0x800a, 174}, + {0x800f, 174}, + {0x8018, 174}, + {0x801f, 174}, + {0x8029, 174}, + {0xc038, 174}, + }, + /* 166 */ + { + {0x8002, 175}, + {0x8009, 175}, + {0x8017, 175}, + {0xc028, 175}, + {0x8002, 180}, + {0x8009, 180}, + {0x8017, 180}, + {0xc028, 180}, + {0x8002, 182}, + {0x8009, 182}, + {0x8017, 182}, + {0xc028, 182}, + {0x8002, 183}, + {0x8009, 183}, + {0x8017, 183}, + {0xc028, 183}, + }, + /* 167 */ + { + {0x8003, 175}, + {0x8006, 175}, + {0x800a, 175}, + {0x800f, 175}, + {0x8018, 175}, + {0x801f, 175}, + {0x8029, 175}, + {0xc038, 175}, + {0x8003, 180}, + {0x8006, 180}, + {0x800a, 180}, + {0x800f, 180}, + {0x8018, 180}, + {0x801f, 180}, + {0x8029, 180}, + {0xc038, 180}, + }, + /* 168 */ + { + {0x8003, 182}, + {0x8006, 182}, + {0x800a, 182}, + {0x800f, 182}, + {0x8018, 182}, + {0x801f, 182}, + {0x8029, 182}, + {0xc038, 182}, + {0x8003, 183}, + {0x8006, 183}, + {0x800a, 183}, + {0x800f, 183}, + {0x8018, 183}, + {0x801f, 183}, + {0x8029, 183}, + {0xc038, 183}, + }, + /* 169 */ + { + {0xc000, 188}, + {0xc000, 191}, + {0xc000, 197}, + {0xc000, 231}, + {0xc000, 239}, + {0xb0, 0}, + {0xb2, 0}, + {0xb3, 0}, + {0xb7, 0}, + {0xb8, 0}, + {0xba, 0}, + {0xbb, 0}, + {0xc0, 0}, + {0xc7, 0}, + {0xd0, 0}, + {0xdf, 0}, + }, + /* 170 */ + { + {0x8001, 188}, + {0xc016, 188}, + {0x8001, 191}, + {0xc016, 191}, + {0x8001, 197}, + {0xc016, 197}, + {0x8001, 231}, + {0xc016, 231}, + {0x8001, 239}, + {0xc016, 239}, + {0xc000, 9}, + {0xc000, 142}, + {0xc000, 144}, + {0xc000, 145}, + {0xc000, 148}, + {0xc000, 159}, + }, + /* 171 */ + { + {0x8002, 188}, + {0x8009, 188}, + {0x8017, 188}, + {0xc028, 188}, + {0x8002, 191}, + {0x8009, 191}, + {0x8017, 191}, + {0xc028, 191}, + {0x8002, 197}, + {0x8009, 197}, + {0x8017, 197}, + {0xc028, 197}, + {0x8002, 231}, + {0x8009, 231}, + {0x8017, 231}, + {0xc028, 231}, + }, + /* 172 */ + { + {0x8003, 188}, + {0x8006, 188}, + {0x800a, 188}, + {0x800f, 188}, + {0x8018, 188}, + {0x801f, 188}, + {0x8029, 188}, + {0xc038, 188}, + {0x8003, 191}, + {0x8006, 191}, + {0x800a, 191}, + {0x800f, 191}, + {0x8018, 191}, + {0x801f, 191}, + {0x8029, 191}, + {0xc038, 191}, + }, + /* 173 */ + { + {0x8003, 197}, + {0x8006, 197}, + {0x800a, 197}, + {0x800f, 197}, + {0x8018, 197}, + {0x801f, 197}, + {0x8029, 197}, + {0xc038, 197}, + {0x8003, 231}, + {0x8006, 231}, + {0x800a, 231}, + {0x800f, 231}, + {0x8018, 231}, + {0x801f, 231}, + {0x8029, 231}, + {0xc038, 231}, + }, + /* 174 */ + { + {0x8002, 239}, + {0x8009, 239}, + {0x8017, 239}, + {0xc028, 239}, + {0x8001, 9}, + {0xc016, 9}, + {0x8001, 142}, + {0xc016, 142}, + {0x8001, 144}, + {0xc016, 144}, + {0x8001, 145}, + {0xc016, 145}, + {0x8001, 148}, + {0xc016, 148}, + {0x8001, 159}, + {0xc016, 159}, + }, + /* 175 */ + { + {0x8003, 239}, + {0x8006, 239}, + {0x800a, 239}, + {0x800f, 239}, + {0x8018, 239}, + {0x801f, 239}, + {0x8029, 239}, + {0xc038, 239}, + {0x8002, 9}, + {0x8009, 9}, + {0x8017, 9}, + {0xc028, 9}, + {0x8002, 142}, + {0x8009, 142}, + {0x8017, 142}, + {0xc028, 142}, + }, + /* 176 */ + { + {0x8003, 9}, + {0x8006, 9}, + {0x800a, 9}, + {0x800f, 9}, + {0x8018, 9}, + {0x801f, 9}, + {0x8029, 9}, + {0xc038, 9}, + {0x8003, 142}, + {0x8006, 142}, + {0x800a, 142}, + {0x800f, 142}, + {0x8018, 142}, + {0x801f, 142}, + {0x8029, 142}, + {0xc038, 142}, + }, + /* 177 */ + { + {0x8002, 144}, + {0x8009, 144}, + {0x8017, 144}, + {0xc028, 144}, + {0x8002, 145}, + {0x8009, 145}, + {0x8017, 145}, + {0xc028, 145}, + {0x8002, 148}, + {0x8009, 148}, + {0x8017, 148}, + {0xc028, 148}, + {0x8002, 159}, + {0x8009, 159}, + {0x8017, 159}, + {0xc028, 159}, + }, + /* 178 */ + { + {0x8003, 144}, + {0x8006, 144}, + {0x800a, 144}, + {0x800f, 144}, + {0x8018, 144}, + {0x801f, 144}, + {0x8029, 144}, + {0xc038, 144}, + {0x8003, 145}, + {0x8006, 145}, + {0x800a, 145}, + {0x800f, 145}, + {0x8018, 145}, + {0x801f, 145}, + {0x8029, 145}, + {0xc038, 145}, + }, + /* 179 */ + { + {0x8003, 148}, + {0x8006, 148}, + {0x800a, 148}, + {0x800f, 148}, + {0x8018, 148}, + {0x801f, 148}, + {0x8029, 148}, + {0xc038, 148}, + {0x8003, 159}, + {0x8006, 159}, + {0x800a, 159}, + {0x800f, 159}, + {0x8018, 159}, + {0x801f, 159}, + {0x8029, 159}, + {0xc038, 159}, + }, + /* 180 */ + { + {0xc000, 171}, + {0xc000, 206}, + {0xc000, 215}, + {0xc000, 225}, + {0xc000, 236}, + {0xc000, 237}, + {0xbc, 0}, + {0xbd, 0}, + {0xc1, 0}, + {0xc4, 0}, + {0xc8, 0}, + {0xcb, 0}, + {0xd1, 0}, + {0xd8, 0}, + {0xe0, 0}, + {0xee, 0}, + }, + /* 181 */ + { + {0x8001, 171}, + {0xc016, 171}, + {0x8001, 206}, + {0xc016, 206}, + {0x8001, 215}, + {0xc016, 215}, + {0x8001, 225}, + {0xc016, 225}, + {0x8001, 236}, + {0xc016, 236}, + {0x8001, 237}, + {0xc016, 237}, + {0xc000, 199}, + {0xc000, 207}, + {0xc000, 234}, + {0xc000, 235}, + }, + /* 182 */ + { + {0x8002, 171}, + {0x8009, 171}, + {0x8017, 171}, + {0xc028, 171}, + {0x8002, 206}, + {0x8009, 206}, + {0x8017, 206}, + {0xc028, 206}, + {0x8002, 215}, + {0x8009, 215}, + {0x8017, 215}, + {0xc028, 215}, + {0x8002, 225}, + {0x8009, 225}, + {0x8017, 225}, + {0xc028, 225}, + }, + /* 183 */ + { + {0x8003, 171}, + {0x8006, 171}, + {0x800a, 171}, + {0x800f, 171}, + {0x8018, 171}, + {0x801f, 171}, + {0x8029, 171}, + {0xc038, 171}, + {0x8003, 206}, + {0x8006, 206}, + {0x800a, 206}, + {0x800f, 206}, + {0x8018, 206}, + {0x801f, 206}, + {0x8029, 206}, + {0xc038, 206}, + }, + /* 184 */ + { + {0x8003, 215}, + {0x8006, 215}, + {0x800a, 215}, + {0x800f, 215}, + {0x8018, 215}, + {0x801f, 215}, + {0x8029, 215}, + {0xc038, 215}, + {0x8003, 225}, + {0x8006, 225}, + {0x800a, 225}, + {0x800f, 225}, + {0x8018, 225}, + {0x801f, 225}, + {0x8029, 225}, + {0xc038, 225}, + }, + /* 185 */ + { + {0x8002, 236}, + {0x8009, 236}, + {0x8017, 236}, + {0xc028, 236}, + {0x8002, 237}, + {0x8009, 237}, + {0x8017, 237}, + {0xc028, 237}, + {0x8001, 199}, + {0xc016, 199}, + {0x8001, 207}, + {0xc016, 207}, + {0x8001, 234}, + {0xc016, 234}, + {0x8001, 235}, + {0xc016, 235}, + }, + /* 186 */ + { + {0x8003, 236}, + {0x8006, 236}, + {0x800a, 236}, + {0x800f, 236}, + {0x8018, 236}, + {0x801f, 236}, + {0x8029, 236}, + {0xc038, 236}, + {0x8003, 237}, + {0x8006, 237}, + {0x800a, 237}, + {0x800f, 237}, + {0x8018, 237}, + {0x801f, 237}, + {0x8029, 237}, + {0xc038, 237}, + }, + /* 187 */ + { + {0x8002, 199}, + {0x8009, 199}, + {0x8017, 199}, + {0xc028, 199}, + {0x8002, 207}, + {0x8009, 207}, + {0x8017, 207}, + {0xc028, 207}, + {0x8002, 234}, + {0x8009, 234}, + {0x8017, 234}, + {0xc028, 234}, + {0x8002, 235}, + {0x8009, 235}, + {0x8017, 235}, + {0xc028, 235}, + }, + /* 188 */ + { + {0x8003, 199}, + {0x8006, 199}, + {0x800a, 199}, + {0x800f, 199}, + {0x8018, 199}, + {0x801f, 199}, + {0x8029, 199}, + {0xc038, 199}, + {0x8003, 207}, + {0x8006, 207}, + {0x800a, 207}, + {0x800f, 207}, + {0x8018, 207}, + {0x801f, 207}, + {0x8029, 207}, + {0xc038, 207}, + }, + /* 189 */ + { + {0x8003, 234}, + {0x8006, 234}, + {0x800a, 234}, + {0x800f, 234}, + {0x8018, 234}, + {0x801f, 234}, + {0x8029, 234}, + {0xc038, 234}, + {0x8003, 235}, + {0x8006, 235}, + {0x800a, 235}, + {0x800f, 235}, + {0x8018, 235}, + {0x801f, 235}, + {0x8029, 235}, + {0xc038, 235}, + }, + /* 190 */ + { + {0xc2, 0}, + {0xc3, 0}, + {0xc5, 0}, + {0xc6, 0}, + {0xc9, 0}, + {0xca, 0}, + {0xcc, 0}, + {0xcd, 0}, + {0xd2, 0}, + {0xd5, 0}, + {0xd9, 0}, + {0xdc, 0}, + {0xe1, 0}, + {0xe7, 0}, + {0xef, 0}, + {0xf6, 0}, + }, + /* 191 */ + { + {0xc000, 192}, + {0xc000, 193}, + {0xc000, 200}, + {0xc000, 201}, + {0xc000, 202}, + {0xc000, 205}, + {0xc000, 210}, + {0xc000, 213}, + {0xc000, 218}, + {0xc000, 219}, + {0xc000, 238}, + {0xc000, 240}, + {0xc000, 242}, + {0xc000, 243}, + {0xc000, 255}, + {0xce, 0}, + }, + /* 192 */ + { + {0x8001, 192}, + {0xc016, 192}, + {0x8001, 193}, + {0xc016, 193}, + {0x8001, 200}, + {0xc016, 200}, + {0x8001, 201}, + {0xc016, 201}, + {0x8001, 202}, + {0xc016, 202}, + {0x8001, 205}, + {0xc016, 205}, + {0x8001, 210}, + {0xc016, 210}, + {0x8001, 213}, + {0xc016, 213}, + }, + /* 193 */ + { + {0x8002, 192}, + {0x8009, 192}, + {0x8017, 192}, + {0xc028, 192}, + {0x8002, 193}, + {0x8009, 193}, + {0x8017, 193}, + {0xc028, 193}, + {0x8002, 200}, + {0x8009, 200}, + {0x8017, 200}, + {0xc028, 200}, + {0x8002, 201}, + {0x8009, 201}, + {0x8017, 201}, + {0xc028, 201}, + }, + /* 194 */ + { + {0x8003, 192}, + {0x8006, 192}, + {0x800a, 192}, + {0x800f, 192}, + {0x8018, 192}, + {0x801f, 192}, + {0x8029, 192}, + {0xc038, 192}, + {0x8003, 193}, + {0x8006, 193}, + {0x800a, 193}, + {0x800f, 193}, + {0x8018, 193}, + {0x801f, 193}, + {0x8029, 193}, + {0xc038, 193}, + }, + /* 195 */ + { + {0x8003, 200}, + {0x8006, 200}, + {0x800a, 200}, + {0x800f, 200}, + {0x8018, 200}, + {0x801f, 200}, + {0x8029, 200}, + {0xc038, 200}, + {0x8003, 201}, + {0x8006, 201}, + {0x800a, 201}, + {0x800f, 201}, + {0x8018, 201}, + {0x801f, 201}, + {0x8029, 201}, + {0xc038, 201}, + }, + /* 196 */ + { + {0x8002, 202}, + {0x8009, 202}, + {0x8017, 202}, + {0xc028, 202}, + {0x8002, 205}, + {0x8009, 205}, + {0x8017, 205}, + {0xc028, 205}, + {0x8002, 210}, + {0x8009, 210}, + {0x8017, 210}, + {0xc028, 210}, + {0x8002, 213}, + {0x8009, 213}, + {0x8017, 213}, + {0xc028, 213}, + }, + /* 197 */ + { + {0x8003, 202}, + {0x8006, 202}, + {0x800a, 202}, + {0x800f, 202}, + {0x8018, 202}, + {0x801f, 202}, + {0x8029, 202}, + {0xc038, 202}, + {0x8003, 205}, + {0x8006, 205}, + {0x800a, 205}, + {0x800f, 205}, + {0x8018, 205}, + {0x801f, 205}, + {0x8029, 205}, + {0xc038, 205}, + }, + /* 198 */ + { + {0x8003, 210}, + {0x8006, 210}, + {0x800a, 210}, + {0x800f, 210}, + {0x8018, 210}, + {0x801f, 210}, + {0x8029, 210}, + {0xc038, 210}, + {0x8003, 213}, + {0x8006, 213}, + {0x800a, 213}, + {0x800f, 213}, + {0x8018, 213}, + {0x801f, 213}, + {0x8029, 213}, + {0xc038, 213}, + }, + /* 199 */ + { + {0x8001, 218}, + {0xc016, 218}, + {0x8001, 219}, + {0xc016, 219}, + {0x8001, 238}, + {0xc016, 238}, + {0x8001, 240}, + {0xc016, 240}, + {0x8001, 242}, + {0xc016, 242}, + {0x8001, 243}, + {0xc016, 243}, + {0x8001, 255}, + {0xc016, 255}, + {0xc000, 203}, + {0xc000, 204}, + }, + /* 200 */ + { + {0x8002, 218}, + {0x8009, 218}, + {0x8017, 218}, + {0xc028, 218}, + {0x8002, 219}, + {0x8009, 219}, + {0x8017, 219}, + {0xc028, 219}, + {0x8002, 238}, + {0x8009, 238}, + {0x8017, 238}, + {0xc028, 238}, + {0x8002, 240}, + {0x8009, 240}, + {0x8017, 240}, + {0xc028, 240}, + }, + /* 201 */ + { + {0x8003, 218}, + {0x8006, 218}, + {0x800a, 218}, + {0x800f, 218}, + {0x8018, 218}, + {0x801f, 218}, + {0x8029, 218}, + {0xc038, 218}, + {0x8003, 219}, + {0x8006, 219}, + {0x800a, 219}, + {0x800f, 219}, + {0x8018, 219}, + {0x801f, 219}, + {0x8029, 219}, + {0xc038, 219}, + }, + /* 202 */ + { + {0x8003, 238}, + {0x8006, 238}, + {0x800a, 238}, + {0x800f, 238}, + {0x8018, 238}, + {0x801f, 238}, + {0x8029, 238}, + {0xc038, 238}, + {0x8003, 240}, + {0x8006, 240}, + {0x800a, 240}, + {0x800f, 240}, + {0x8018, 240}, + {0x801f, 240}, + {0x8029, 240}, + {0xc038, 240}, + }, + /* 203 */ + { + {0x8002, 242}, + {0x8009, 242}, + {0x8017, 242}, + {0xc028, 242}, + {0x8002, 243}, + {0x8009, 243}, + {0x8017, 243}, + {0xc028, 243}, + {0x8002, 255}, + {0x8009, 255}, + {0x8017, 255}, + {0xc028, 255}, + {0x8001, 203}, + {0xc016, 203}, + {0x8001, 204}, + {0xc016, 204}, + }, + /* 204 */ + { + {0x8003, 242}, + {0x8006, 242}, + {0x800a, 242}, + {0x800f, 242}, + {0x8018, 242}, + {0x801f, 242}, + {0x8029, 242}, + {0xc038, 242}, + {0x8003, 243}, + {0x8006, 243}, + {0x800a, 243}, + {0x800f, 243}, + {0x8018, 243}, + {0x801f, 243}, + {0x8029, 243}, + {0xc038, 243}, + }, + /* 205 */ + { + {0x8003, 255}, + {0x8006, 255}, + {0x800a, 255}, + {0x800f, 255}, + {0x8018, 255}, + {0x801f, 255}, + {0x8029, 255}, + {0xc038, 255}, + {0x8002, 203}, + {0x8009, 203}, + {0x8017, 203}, + {0xc028, 203}, + {0x8002, 204}, + {0x8009, 204}, + {0x8017, 204}, + {0xc028, 204}, + }, + /* 206 */ + { + {0x8003, 203}, + {0x8006, 203}, + {0x800a, 203}, + {0x800f, 203}, + {0x8018, 203}, + {0x801f, 203}, + {0x8029, 203}, + {0xc038, 203}, + {0x8003, 204}, + {0x8006, 204}, + {0x800a, 204}, + {0x800f, 204}, + {0x8018, 204}, + {0x801f, 204}, + {0x8029, 204}, + {0xc038, 204}, + }, + /* 207 */ + { + {0xd3, 0}, + {0xd4, 0}, + {0xd6, 0}, + {0xd7, 0}, + {0xda, 0}, + {0xdb, 0}, + {0xdd, 0}, + {0xde, 0}, + {0xe2, 0}, + {0xe4, 0}, + {0xe8, 0}, + {0xeb, 0}, + {0xf0, 0}, + {0xf3, 0}, + {0xf7, 0}, + {0xfa, 0}, + }, + /* 208 */ + { + {0xc000, 211}, + {0xc000, 212}, + {0xc000, 214}, + {0xc000, 221}, + {0xc000, 222}, + {0xc000, 223}, + {0xc000, 241}, + {0xc000, 244}, + {0xc000, 245}, + {0xc000, 246}, + {0xc000, 247}, + {0xc000, 248}, + {0xc000, 250}, + {0xc000, 251}, + {0xc000, 252}, + {0xc000, 253}, + }, + /* 209 */ + { + {0x8001, 211}, + {0xc016, 211}, + {0x8001, 212}, + {0xc016, 212}, + {0x8001, 214}, + {0xc016, 214}, + {0x8001, 221}, + {0xc016, 221}, + {0x8001, 222}, + {0xc016, 222}, + {0x8001, 223}, + {0xc016, 223}, + {0x8001, 241}, + {0xc016, 241}, + {0x8001, 244}, + {0xc016, 244}, + }, + /* 210 */ + { + {0x8002, 211}, + {0x8009, 211}, + {0x8017, 211}, + {0xc028, 211}, + {0x8002, 212}, + {0x8009, 212}, + {0x8017, 212}, + {0xc028, 212}, + {0x8002, 214}, + {0x8009, 214}, + {0x8017, 214}, + {0xc028, 214}, + {0x8002, 221}, + {0x8009, 221}, + {0x8017, 221}, + {0xc028, 221}, + }, + /* 211 */ + { + {0x8003, 211}, + {0x8006, 211}, + {0x800a, 211}, + {0x800f, 211}, + {0x8018, 211}, + {0x801f, 211}, + {0x8029, 211}, + {0xc038, 211}, + {0x8003, 212}, + {0x8006, 212}, + {0x800a, 212}, + {0x800f, 212}, + {0x8018, 212}, + {0x801f, 212}, + {0x8029, 212}, + {0xc038, 212}, + }, + /* 212 */ + { + {0x8003, 214}, + {0x8006, 214}, + {0x800a, 214}, + {0x800f, 214}, + {0x8018, 214}, + {0x801f, 214}, + {0x8029, 214}, + {0xc038, 214}, + {0x8003, 221}, + {0x8006, 221}, + {0x800a, 221}, + {0x800f, 221}, + {0x8018, 221}, + {0x801f, 221}, + {0x8029, 221}, + {0xc038, 221}, + }, + /* 213 */ + { + {0x8002, 222}, + {0x8009, 222}, + {0x8017, 222}, + {0xc028, 222}, + {0x8002, 223}, + {0x8009, 223}, + {0x8017, 223}, + {0xc028, 223}, + {0x8002, 241}, + {0x8009, 241}, + {0x8017, 241}, + {0xc028, 241}, + {0x8002, 244}, + {0x8009, 244}, + {0x8017, 244}, + {0xc028, 244}, + }, + /* 214 */ + { + {0x8003, 222}, + {0x8006, 222}, + {0x800a, 222}, + {0x800f, 222}, + {0x8018, 222}, + {0x801f, 222}, + {0x8029, 222}, + {0xc038, 222}, + {0x8003, 223}, + {0x8006, 223}, + {0x800a, 223}, + {0x800f, 223}, + {0x8018, 223}, + {0x801f, 223}, + {0x8029, 223}, + {0xc038, 223}, + }, + /* 215 */ + { + {0x8003, 241}, + {0x8006, 241}, + {0x800a, 241}, + {0x800f, 241}, + {0x8018, 241}, + {0x801f, 241}, + {0x8029, 241}, + {0xc038, 241}, + {0x8003, 244}, + {0x8006, 244}, + {0x800a, 244}, + {0x800f, 244}, + {0x8018, 244}, + {0x801f, 244}, + {0x8029, 244}, + {0xc038, 244}, + }, + /* 216 */ + { + {0x8001, 245}, + {0xc016, 245}, + {0x8001, 246}, + {0xc016, 246}, + {0x8001, 247}, + {0xc016, 247}, + {0x8001, 248}, + {0xc016, 248}, + {0x8001, 250}, + {0xc016, 250}, + {0x8001, 251}, + {0xc016, 251}, + {0x8001, 252}, + {0xc016, 252}, + {0x8001, 253}, + {0xc016, 253}, + }, + /* 217 */ + { + {0x8002, 245}, + {0x8009, 245}, + {0x8017, 245}, + {0xc028, 245}, + {0x8002, 246}, + {0x8009, 246}, + {0x8017, 246}, + {0xc028, 246}, + {0x8002, 247}, + {0x8009, 247}, + {0x8017, 247}, + {0xc028, 247}, + {0x8002, 248}, + {0x8009, 248}, + {0x8017, 248}, + {0xc028, 248}, + }, + /* 218 */ + { + {0x8003, 245}, + {0x8006, 245}, + {0x800a, 245}, + {0x800f, 245}, + {0x8018, 245}, + {0x801f, 245}, + {0x8029, 245}, + {0xc038, 245}, + {0x8003, 246}, + {0x8006, 246}, + {0x800a, 246}, + {0x800f, 246}, + {0x8018, 246}, + {0x801f, 246}, + {0x8029, 246}, + {0xc038, 246}, + }, + /* 219 */ + { + {0x8003, 247}, + {0x8006, 247}, + {0x800a, 247}, + {0x800f, 247}, + {0x8018, 247}, + {0x801f, 247}, + {0x8029, 247}, + {0xc038, 247}, + {0x8003, 248}, + {0x8006, 248}, + {0x800a, 248}, + {0x800f, 248}, + {0x8018, 248}, + {0x801f, 248}, + {0x8029, 248}, + {0xc038, 248}, + }, + /* 220 */ + { + {0x8002, 250}, + {0x8009, 250}, + {0x8017, 250}, + {0xc028, 250}, + {0x8002, 251}, + {0x8009, 251}, + {0x8017, 251}, + {0xc028, 251}, + {0x8002, 252}, + {0x8009, 252}, + {0x8017, 252}, + {0xc028, 252}, + {0x8002, 253}, + {0x8009, 253}, + {0x8017, 253}, + {0xc028, 253}, + }, + /* 221 */ + { + {0x8003, 250}, + {0x8006, 250}, + {0x800a, 250}, + {0x800f, 250}, + {0x8018, 250}, + {0x801f, 250}, + {0x8029, 250}, + {0xc038, 250}, + {0x8003, 251}, + {0x8006, 251}, + {0x800a, 251}, + {0x800f, 251}, + {0x8018, 251}, + {0x801f, 251}, + {0x8029, 251}, + {0xc038, 251}, + }, + /* 222 */ + { + {0x8003, 252}, + {0x8006, 252}, + {0x800a, 252}, + {0x800f, 252}, + {0x8018, 252}, + {0x801f, 252}, + {0x8029, 252}, + {0xc038, 252}, + {0x8003, 253}, + {0x8006, 253}, + {0x800a, 253}, + {0x800f, 253}, + {0x8018, 253}, + {0x801f, 253}, + {0x8029, 253}, + {0xc038, 253}, + }, + /* 223 */ + { + {0xc000, 254}, + {0xe3, 0}, + {0xe5, 0}, + {0xe6, 0}, + {0xe9, 0}, + {0xea, 0}, + {0xec, 0}, + {0xed, 0}, + {0xf1, 0}, + {0xf2, 0}, + {0xf4, 0}, + {0xf5, 0}, + {0xf8, 0}, + {0xf9, 0}, + {0xfb, 0}, + {0xfc, 0}, + }, + /* 224 */ + { + {0x8001, 254}, + {0xc016, 254}, + {0xc000, 2}, + {0xc000, 3}, + {0xc000, 4}, + {0xc000, 5}, + {0xc000, 6}, + {0xc000, 7}, + {0xc000, 8}, + {0xc000, 11}, + {0xc000, 12}, + {0xc000, 14}, + {0xc000, 15}, + {0xc000, 16}, + {0xc000, 17}, + {0xc000, 18}, + }, + /* 225 */ + { + {0x8002, 254}, + {0x8009, 254}, + {0x8017, 254}, + {0xc028, 254}, + {0x8001, 2}, + {0xc016, 2}, + {0x8001, 3}, + {0xc016, 3}, + {0x8001, 4}, + {0xc016, 4}, + {0x8001, 5}, + {0xc016, 5}, + {0x8001, 6}, + {0xc016, 6}, + {0x8001, 7}, + {0xc016, 7}, + }, + /* 226 */ + { + {0x8003, 254}, + {0x8006, 254}, + {0x800a, 254}, + {0x800f, 254}, + {0x8018, 254}, + {0x801f, 254}, + {0x8029, 254}, + {0xc038, 254}, + {0x8002, 2}, + {0x8009, 2}, + {0x8017, 2}, + {0xc028, 2}, + {0x8002, 3}, + {0x8009, 3}, + {0x8017, 3}, + {0xc028, 3}, + }, + /* 227 */ + { + {0x8003, 2}, + {0x8006, 2}, + {0x800a, 2}, + {0x800f, 2}, + {0x8018, 2}, + {0x801f, 2}, + {0x8029, 2}, + {0xc038, 2}, + {0x8003, 3}, + {0x8006, 3}, + {0x800a, 3}, + {0x800f, 3}, + {0x8018, 3}, + {0x801f, 3}, + {0x8029, 3}, + {0xc038, 3}, + }, + /* 228 */ + { + {0x8002, 4}, + {0x8009, 4}, + {0x8017, 4}, + {0xc028, 4}, + {0x8002, 5}, + {0x8009, 5}, + {0x8017, 5}, + {0xc028, 5}, + {0x8002, 6}, + {0x8009, 6}, + {0x8017, 6}, + {0xc028, 6}, + {0x8002, 7}, + {0x8009, 7}, + {0x8017, 7}, + {0xc028, 7}, + }, + /* 229 */ + { + {0x8003, 4}, + {0x8006, 4}, + {0x800a, 4}, + {0x800f, 4}, + {0x8018, 4}, + {0x801f, 4}, + {0x8029, 4}, + {0xc038, 4}, + {0x8003, 5}, + {0x8006, 5}, + {0x800a, 5}, + {0x800f, 5}, + {0x8018, 5}, + {0x801f, 5}, + {0x8029, 5}, + {0xc038, 5}, + }, + /* 230 */ + { + {0x8003, 6}, + {0x8006, 6}, + {0x800a, 6}, + {0x800f, 6}, + {0x8018, 6}, + {0x801f, 6}, + {0x8029, 6}, + {0xc038, 6}, + {0x8003, 7}, + {0x8006, 7}, + {0x800a, 7}, + {0x800f, 7}, + {0x8018, 7}, + {0x801f, 7}, + {0x8029, 7}, + {0xc038, 7}, + }, + /* 231 */ + { + {0x8001, 8}, + {0xc016, 8}, + {0x8001, 11}, + {0xc016, 11}, + {0x8001, 12}, + {0xc016, 12}, + {0x8001, 14}, + {0xc016, 14}, + {0x8001, 15}, + {0xc016, 15}, + {0x8001, 16}, + {0xc016, 16}, + {0x8001, 17}, + {0xc016, 17}, + {0x8001, 18}, + {0xc016, 18}, + }, + /* 232 */ + { + {0x8002, 8}, + {0x8009, 8}, + {0x8017, 8}, + {0xc028, 8}, + {0x8002, 11}, + {0x8009, 11}, + {0x8017, 11}, + {0xc028, 11}, + {0x8002, 12}, + {0x8009, 12}, + {0x8017, 12}, + {0xc028, 12}, + {0x8002, 14}, + {0x8009, 14}, + {0x8017, 14}, + {0xc028, 14}, + }, + /* 233 */ + { + {0x8003, 8}, + {0x8006, 8}, + {0x800a, 8}, + {0x800f, 8}, + {0x8018, 8}, + {0x801f, 8}, + {0x8029, 8}, + {0xc038, 8}, + {0x8003, 11}, + {0x8006, 11}, + {0x800a, 11}, + {0x800f, 11}, + {0x8018, 11}, + {0x801f, 11}, + {0x8029, 11}, + {0xc038, 11}, + }, + /* 234 */ + { + {0x8003, 12}, + {0x8006, 12}, + {0x800a, 12}, + {0x800f, 12}, + {0x8018, 12}, + {0x801f, 12}, + {0x8029, 12}, + {0xc038, 12}, + {0x8003, 14}, + {0x8006, 14}, + {0x800a, 14}, + {0x800f, 14}, + {0x8018, 14}, + {0x801f, 14}, + {0x8029, 14}, + {0xc038, 14}, + }, + /* 235 */ + { + {0x8002, 15}, + {0x8009, 15}, + {0x8017, 15}, + {0xc028, 15}, + {0x8002, 16}, + {0x8009, 16}, + {0x8017, 16}, + {0xc028, 16}, + {0x8002, 17}, + {0x8009, 17}, + {0x8017, 17}, + {0xc028, 17}, + {0x8002, 18}, + {0x8009, 18}, + {0x8017, 18}, + {0xc028, 18}, + }, + /* 236 */ + { + {0x8003, 15}, + {0x8006, 15}, + {0x800a, 15}, + {0x800f, 15}, + {0x8018, 15}, + {0x801f, 15}, + {0x8029, 15}, + {0xc038, 15}, + {0x8003, 16}, + {0x8006, 16}, + {0x800a, 16}, + {0x800f, 16}, + {0x8018, 16}, + {0x801f, 16}, + {0x8029, 16}, + {0xc038, 16}, + }, + /* 237 */ + { + {0x8003, 17}, + {0x8006, 17}, + {0x800a, 17}, + {0x800f, 17}, + {0x8018, 17}, + {0x801f, 17}, + {0x8029, 17}, + {0xc038, 17}, + {0x8003, 18}, + {0x8006, 18}, + {0x800a, 18}, + {0x800f, 18}, + {0x8018, 18}, + {0x801f, 18}, + {0x8029, 18}, + {0xc038, 18}, + }, + /* 238 */ + { + {0xc000, 19}, + {0xc000, 20}, + {0xc000, 21}, + {0xc000, 23}, + {0xc000, 24}, + {0xc000, 25}, + {0xc000, 26}, + {0xc000, 27}, + {0xc000, 28}, + {0xc000, 29}, + {0xc000, 30}, + {0xc000, 31}, + {0xc000, 127}, + {0xc000, 220}, + {0xc000, 249}, + {0xfd, 0}, + }, + /* 239 */ + { + {0x8001, 19}, + {0xc016, 19}, + {0x8001, 20}, + {0xc016, 20}, + {0x8001, 21}, + {0xc016, 21}, + {0x8001, 23}, + {0xc016, 23}, + {0x8001, 24}, + {0xc016, 24}, + {0x8001, 25}, + {0xc016, 25}, + {0x8001, 26}, + {0xc016, 26}, + {0x8001, 27}, + {0xc016, 27}, + }, + /* 240 */ + { + {0x8002, 19}, + {0x8009, 19}, + {0x8017, 19}, + {0xc028, 19}, + {0x8002, 20}, + {0x8009, 20}, + {0x8017, 20}, + {0xc028, 20}, + {0x8002, 21}, + {0x8009, 21}, + {0x8017, 21}, + {0xc028, 21}, + {0x8002, 23}, + {0x8009, 23}, + {0x8017, 23}, + {0xc028, 23}, + }, + /* 241 */ + { + {0x8003, 19}, + {0x8006, 19}, + {0x800a, 19}, + {0x800f, 19}, + {0x8018, 19}, + {0x801f, 19}, + {0x8029, 19}, + {0xc038, 19}, + {0x8003, 20}, + {0x8006, 20}, + {0x800a, 20}, + {0x800f, 20}, + {0x8018, 20}, + {0x801f, 20}, + {0x8029, 20}, + {0xc038, 20}, + }, + /* 242 */ + { + {0x8003, 21}, + {0x8006, 21}, + {0x800a, 21}, + {0x800f, 21}, + {0x8018, 21}, + {0x801f, 21}, + {0x8029, 21}, + {0xc038, 21}, + {0x8003, 23}, + {0x8006, 23}, + {0x800a, 23}, + {0x800f, 23}, + {0x8018, 23}, + {0x801f, 23}, + {0x8029, 23}, + {0xc038, 23}, + }, + /* 243 */ + { + {0x8002, 24}, + {0x8009, 24}, + {0x8017, 24}, + {0xc028, 24}, + {0x8002, 25}, + {0x8009, 25}, + {0x8017, 25}, + {0xc028, 25}, + {0x8002, 26}, + {0x8009, 26}, + {0x8017, 26}, + {0xc028, 26}, + {0x8002, 27}, + {0x8009, 27}, + {0x8017, 27}, + {0xc028, 27}, + }, + /* 244 */ + { + {0x8003, 24}, + {0x8006, 24}, + {0x800a, 24}, + {0x800f, 24}, + {0x8018, 24}, + {0x801f, 24}, + {0x8029, 24}, + {0xc038, 24}, + {0x8003, 25}, + {0x8006, 25}, + {0x800a, 25}, + {0x800f, 25}, + {0x8018, 25}, + {0x801f, 25}, + {0x8029, 25}, + {0xc038, 25}, + }, + /* 245 */ + { + {0x8003, 26}, + {0x8006, 26}, + {0x800a, 26}, + {0x800f, 26}, + {0x8018, 26}, + {0x801f, 26}, + {0x8029, 26}, + {0xc038, 26}, + {0x8003, 27}, + {0x8006, 27}, + {0x800a, 27}, + {0x800f, 27}, + {0x8018, 27}, + {0x801f, 27}, + {0x8029, 27}, + {0xc038, 27}, + }, + /* 246 */ + { + {0x8001, 28}, + {0xc016, 28}, + {0x8001, 29}, + {0xc016, 29}, + {0x8001, 30}, + {0xc016, 30}, + {0x8001, 31}, + {0xc016, 31}, + {0x8001, 127}, + {0xc016, 127}, + {0x8001, 220}, + {0xc016, 220}, + {0x8001, 249}, + {0xc016, 249}, + {0xfe, 0}, + {0xff, 0}, + }, + /* 247 */ + { + {0x8002, 28}, + {0x8009, 28}, + {0x8017, 28}, + {0xc028, 28}, + {0x8002, 29}, + {0x8009, 29}, + {0x8017, 29}, + {0xc028, 29}, + {0x8002, 30}, + {0x8009, 30}, + {0x8017, 30}, + {0xc028, 30}, + {0x8002, 31}, + {0x8009, 31}, + {0x8017, 31}, + {0xc028, 31}, + }, + /* 248 */ + { + {0x8003, 28}, + {0x8006, 28}, + {0x800a, 28}, + {0x800f, 28}, + {0x8018, 28}, + {0x801f, 28}, + {0x8029, 28}, + {0xc038, 28}, + {0x8003, 29}, + {0x8006, 29}, + {0x800a, 29}, + {0x800f, 29}, + {0x8018, 29}, + {0x801f, 29}, + {0x8029, 29}, + {0xc038, 29}, + }, + /* 249 */ + { + {0x8003, 30}, + {0x8006, 30}, + {0x800a, 30}, + {0x800f, 30}, + {0x8018, 30}, + {0x801f, 30}, + {0x8029, 30}, + {0xc038, 30}, + {0x8003, 31}, + {0x8006, 31}, + {0x800a, 31}, + {0x800f, 31}, + {0x8018, 31}, + {0x801f, 31}, + {0x8029, 31}, + {0xc038, 31}, + }, + /* 250 */ + { + {0x8002, 127}, + {0x8009, 127}, + {0x8017, 127}, + {0xc028, 127}, + {0x8002, 220}, + {0x8009, 220}, + {0x8017, 220}, + {0xc028, 220}, + {0x8002, 249}, + {0x8009, 249}, + {0x8017, 249}, + {0xc028, 249}, + {0xc000, 10}, + {0xc000, 13}, + {0xc000, 22}, + {0x100, 0}, + }, + /* 251 */ + { + {0x8003, 127}, + {0x8006, 127}, + {0x800a, 127}, + {0x800f, 127}, + {0x8018, 127}, + {0x801f, 127}, + {0x8029, 127}, + {0xc038, 127}, + {0x8003, 220}, + {0x8006, 220}, + {0x800a, 220}, + {0x800f, 220}, + {0x8018, 220}, + {0x801f, 220}, + {0x8029, 220}, + {0xc038, 220}, + }, + /* 252 */ + { + {0x8003, 249}, + {0x8006, 249}, + {0x800a, 249}, + {0x800f, 249}, + {0x8018, 249}, + {0x801f, 249}, + {0x8029, 249}, + {0xc038, 249}, + {0x8001, 10}, + {0xc016, 10}, + {0x8001, 13}, + {0xc016, 13}, + {0x8001, 22}, + {0xc016, 22}, + {0x100, 0}, + {0x100, 0}, + }, + /* 253 */ + { + {0x8002, 10}, + {0x8009, 10}, + {0x8017, 10}, + {0xc028, 10}, + {0x8002, 13}, + {0x8009, 13}, + {0x8017, 13}, + {0xc028, 13}, + {0x8002, 22}, + {0x8009, 22}, + {0x8017, 22}, + {0xc028, 22}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, + /* 254 */ + { + {0x8003, 10}, + {0x8006, 10}, + {0x800a, 10}, + {0x800f, 10}, + {0x8018, 10}, + {0x801f, 10}, + {0x8029, 10}, + {0xc038, 10}, + {0x8003, 13}, + {0x8006, 13}, + {0x800a, 13}, + {0x800f, 13}, + {0x8018, 13}, + {0x801f, 13}, + {0x8029, 13}, + {0xc038, 13}, + }, + /* 255 */ + { + {0x8003, 22}, + {0x8006, 22}, + {0x800a, 22}, + {0x800f, 22}, + {0x8018, 22}, + {0x801f, 22}, + {0x8029, 22}, + {0xc038, 22}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, + /* 256 */ + { + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, +}; diff --git a/lib/nghttp2/lib/nghttp2_helper.c b/lib/nghttp2/lib/nghttp2_helper.c new file mode 100644 index 00000000000..93dd4754b7f --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_helper.c @@ -0,0 +1,803 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_helper.h" + +#include +#include + +#include "nghttp2_net.h" + +void nghttp2_put_uint16be(uint8_t *buf, uint16_t n) { + uint16_t x = htons(n); + memcpy(buf, &x, sizeof(uint16_t)); +} + +void nghttp2_put_uint32be(uint8_t *buf, uint32_t n) { + uint32_t x = htonl(n); + memcpy(buf, &x, sizeof(uint32_t)); +} + +uint16_t nghttp2_get_uint16(const uint8_t *data) { + uint16_t n; + memcpy(&n, data, sizeof(uint16_t)); + return ntohs(n); +} + +uint32_t nghttp2_get_uint32(const uint8_t *data) { + uint32_t n; + memcpy(&n, data, sizeof(uint32_t)); + return ntohl(n); +} + +/* Generated by gendowncasetbl.py */ +static const uint8_t DOWNCASE_TBL[] = { + 0 /* NUL */, 1 /* SOH */, 2 /* STX */, 3 /* ETX */, + 4 /* EOT */, 5 /* ENQ */, 6 /* ACK */, 7 /* BEL */, + 8 /* BS */, 9 /* HT */, 10 /* LF */, 11 /* VT */, + 12 /* FF */, 13 /* CR */, 14 /* SO */, 15 /* SI */, + 16 /* DLE */, 17 /* DC1 */, 18 /* DC2 */, 19 /* DC3 */, + 20 /* DC4 */, 21 /* NAK */, 22 /* SYN */, 23 /* ETB */, + 24 /* CAN */, 25 /* EM */, 26 /* SUB */, 27 /* ESC */, + 28 /* FS */, 29 /* GS */, 30 /* RS */, 31 /* US */, + 32 /* SPC */, 33 /* ! */, 34 /* " */, 35 /* # */, + 36 /* $ */, 37 /* % */, 38 /* & */, 39 /* ' */, + 40 /* ( */, 41 /* ) */, 42 /* * */, 43 /* + */, + 44 /* , */, 45 /* - */, 46 /* . */, 47 /* / */, + 48 /* 0 */, 49 /* 1 */, 50 /* 2 */, 51 /* 3 */, + 52 /* 4 */, 53 /* 5 */, 54 /* 6 */, 55 /* 7 */, + 56 /* 8 */, 57 /* 9 */, 58 /* : */, 59 /* ; */, + 60 /* < */, 61 /* = */, 62 /* > */, 63 /* ? */, + 64 /* @ */, 97 /* A */, 98 /* B */, 99 /* C */, + 100 /* D */, 101 /* E */, 102 /* F */, 103 /* G */, + 104 /* H */, 105 /* I */, 106 /* J */, 107 /* K */, + 108 /* L */, 109 /* M */, 110 /* N */, 111 /* O */, + 112 /* P */, 113 /* Q */, 114 /* R */, 115 /* S */, + 116 /* T */, 117 /* U */, 118 /* V */, 119 /* W */, + 120 /* X */, 121 /* Y */, 122 /* Z */, 91 /* [ */, + 92 /* \ */, 93 /* ] */, 94 /* ^ */, 95 /* _ */, + 96 /* ` */, 97 /* a */, 98 /* b */, 99 /* c */, + 100 /* d */, 101 /* e */, 102 /* f */, 103 /* g */, + 104 /* h */, 105 /* i */, 106 /* j */, 107 /* k */, + 108 /* l */, 109 /* m */, 110 /* n */, 111 /* o */, + 112 /* p */, 113 /* q */, 114 /* r */, 115 /* s */, + 116 /* t */, 117 /* u */, 118 /* v */, 119 /* w */, + 120 /* x */, 121 /* y */, 122 /* z */, 123 /* { */, + 124 /* | */, 125 /* } */, 126 /* ~ */, 127 /* DEL */, + 128 /* 0x80 */, 129 /* 0x81 */, 130 /* 0x82 */, 131 /* 0x83 */, + 132 /* 0x84 */, 133 /* 0x85 */, 134 /* 0x86 */, 135 /* 0x87 */, + 136 /* 0x88 */, 137 /* 0x89 */, 138 /* 0x8a */, 139 /* 0x8b */, + 140 /* 0x8c */, 141 /* 0x8d */, 142 /* 0x8e */, 143 /* 0x8f */, + 144 /* 0x90 */, 145 /* 0x91 */, 146 /* 0x92 */, 147 /* 0x93 */, + 148 /* 0x94 */, 149 /* 0x95 */, 150 /* 0x96 */, 151 /* 0x97 */, + 152 /* 0x98 */, 153 /* 0x99 */, 154 /* 0x9a */, 155 /* 0x9b */, + 156 /* 0x9c */, 157 /* 0x9d */, 158 /* 0x9e */, 159 /* 0x9f */, + 160 /* 0xa0 */, 161 /* 0xa1 */, 162 /* 0xa2 */, 163 /* 0xa3 */, + 164 /* 0xa4 */, 165 /* 0xa5 */, 166 /* 0xa6 */, 167 /* 0xa7 */, + 168 /* 0xa8 */, 169 /* 0xa9 */, 170 /* 0xaa */, 171 /* 0xab */, + 172 /* 0xac */, 173 /* 0xad */, 174 /* 0xae */, 175 /* 0xaf */, + 176 /* 0xb0 */, 177 /* 0xb1 */, 178 /* 0xb2 */, 179 /* 0xb3 */, + 180 /* 0xb4 */, 181 /* 0xb5 */, 182 /* 0xb6 */, 183 /* 0xb7 */, + 184 /* 0xb8 */, 185 /* 0xb9 */, 186 /* 0xba */, 187 /* 0xbb */, + 188 /* 0xbc */, 189 /* 0xbd */, 190 /* 0xbe */, 191 /* 0xbf */, + 192 /* 0xc0 */, 193 /* 0xc1 */, 194 /* 0xc2 */, 195 /* 0xc3 */, + 196 /* 0xc4 */, 197 /* 0xc5 */, 198 /* 0xc6 */, 199 /* 0xc7 */, + 200 /* 0xc8 */, 201 /* 0xc9 */, 202 /* 0xca */, 203 /* 0xcb */, + 204 /* 0xcc */, 205 /* 0xcd */, 206 /* 0xce */, 207 /* 0xcf */, + 208 /* 0xd0 */, 209 /* 0xd1 */, 210 /* 0xd2 */, 211 /* 0xd3 */, + 212 /* 0xd4 */, 213 /* 0xd5 */, 214 /* 0xd6 */, 215 /* 0xd7 */, + 216 /* 0xd8 */, 217 /* 0xd9 */, 218 /* 0xda */, 219 /* 0xdb */, + 220 /* 0xdc */, 221 /* 0xdd */, 222 /* 0xde */, 223 /* 0xdf */, + 224 /* 0xe0 */, 225 /* 0xe1 */, 226 /* 0xe2 */, 227 /* 0xe3 */, + 228 /* 0xe4 */, 229 /* 0xe5 */, 230 /* 0xe6 */, 231 /* 0xe7 */, + 232 /* 0xe8 */, 233 /* 0xe9 */, 234 /* 0xea */, 235 /* 0xeb */, + 236 /* 0xec */, 237 /* 0xed */, 238 /* 0xee */, 239 /* 0xef */, + 240 /* 0xf0 */, 241 /* 0xf1 */, 242 /* 0xf2 */, 243 /* 0xf3 */, + 244 /* 0xf4 */, 245 /* 0xf5 */, 246 /* 0xf6 */, 247 /* 0xf7 */, + 248 /* 0xf8 */, 249 /* 0xf9 */, 250 /* 0xfa */, 251 /* 0xfb */, + 252 /* 0xfc */, 253 /* 0xfd */, 254 /* 0xfe */, 255 /* 0xff */, +}; + +void nghttp2_downcase(uint8_t *s, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + s[i] = DOWNCASE_TBL[s[i]]; + } +} + +/* + * local_window_size + * ^ * + * | * recv_window_size + * | * * ^ + * | * * | + * 0+++++++++ + * | * * \ + * | * * | This rage is hidden in flow control. But it must be + * v * * / kept in order to restore it when window size is enlarged. + * recv_reduction + * (+ for negative direction) + * + * recv_window_size could be negative if we decrease + * local_window_size more than recv_window_size: + * + * local_window_size + * ^ * + * | * + * | * + * 0++++++++ + * | * ^ recv_window_size (negative) + * | * | + * v * * + * recv_reduction + */ +int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr, + int32_t *recv_window_size_ptr, + int32_t *recv_reduction_ptr, + int32_t *delta_ptr) { + if (*delta_ptr > 0) { + int32_t recv_reduction_delta; + int32_t delta; + int32_t new_recv_window_size = + nghttp2_max(0, *recv_window_size_ptr) - *delta_ptr; + + if (new_recv_window_size >= 0) { + *recv_window_size_ptr = new_recv_window_size; + return 0; + } + + delta = -new_recv_window_size; + + /* The delta size is strictly more than received bytes. Increase + local_window_size by that difference |delta|. */ + if (*local_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta) { + return NGHTTP2_ERR_FLOW_CONTROL; + } + *local_window_size_ptr += delta; + /* If there is recv_reduction due to earlier window_size + reduction, we have to adjust it too. */ + recv_reduction_delta = nghttp2_min(*recv_reduction_ptr, delta); + *recv_reduction_ptr -= recv_reduction_delta; + if (*recv_window_size_ptr < 0) { + *recv_window_size_ptr += recv_reduction_delta; + } else { + /* If *recv_window_size_ptr > 0, then those bytes are going to + be returned to the remote peer (by WINDOW_UPDATE with the + adjusted *delta_ptr), so it is effectively 0 now. We set to + *recv_reduction_delta, because caller does not take into + account it in *delta_ptr. */ + *recv_window_size_ptr = recv_reduction_delta; + } + /* recv_reduction_delta must be paid from *delta_ptr, since it was + added in window size reduction (see below). */ + *delta_ptr -= recv_reduction_delta; + + return 0; + } + + if (*local_window_size_ptr + *delta_ptr < 0 || + *recv_window_size_ptr < INT32_MIN - *delta_ptr || + *recv_reduction_ptr > INT32_MAX + *delta_ptr) { + return NGHTTP2_ERR_FLOW_CONTROL; + } + /* Decreasing local window size. Note that we achieve this without + noticing to the remote peer. To do this, we cut + recv_window_size by -delta. This means that we don't send + WINDOW_UPDATE for -delta bytes. */ + *local_window_size_ptr += *delta_ptr; + *recv_window_size_ptr += *delta_ptr; + *recv_reduction_ptr -= *delta_ptr; + *delta_ptr = 0; + + return 0; +} + +int nghttp2_increase_local_window_size(int32_t *local_window_size_ptr, + int32_t *recv_window_size_ptr, + int32_t *recv_reduction_ptr, + int32_t *delta_ptr) { + int32_t recv_reduction_delta; + int32_t delta; + + delta = *delta_ptr; + + assert(delta >= 0); + + /* The delta size is strictly more than received bytes. Increase + local_window_size by that difference |delta|. */ + if (*local_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta) { + return NGHTTP2_ERR_FLOW_CONTROL; + } + + *local_window_size_ptr += delta; + /* If there is recv_reduction due to earlier window_size + reduction, we have to adjust it too. */ + recv_reduction_delta = nghttp2_min(*recv_reduction_ptr, delta); + *recv_reduction_ptr -= recv_reduction_delta; + + *recv_window_size_ptr += recv_reduction_delta; + + /* recv_reduction_delta must be paid from *delta_ptr, since it was + added in window size reduction (see below). */ + *delta_ptr -= recv_reduction_delta; + + return 0; +} + +int nghttp2_should_send_window_update(int32_t local_window_size, + int32_t recv_window_size) { + return recv_window_size > 0 && recv_window_size >= local_window_size / 2; +} + +const char *nghttp2_strerror(int error_code) { + switch (error_code) { + case 0: + return "Success"; + case NGHTTP2_ERR_INVALID_ARGUMENT: + return "Invalid argument"; + case NGHTTP2_ERR_BUFFER_ERROR: + return "Out of buffer space"; + case NGHTTP2_ERR_UNSUPPORTED_VERSION: + return "Unsupported SPDY version"; + case NGHTTP2_ERR_WOULDBLOCK: + return "Operation would block"; + case NGHTTP2_ERR_PROTO: + return "Protocol error"; + case NGHTTP2_ERR_INVALID_FRAME: + return "Invalid frame octets"; + case NGHTTP2_ERR_EOF: + return "EOF"; + case NGHTTP2_ERR_DEFERRED: + return "Data transfer deferred"; + case NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE: + return "No more Stream ID available"; + case NGHTTP2_ERR_STREAM_CLOSED: + return "Stream was already closed or invalid"; + case NGHTTP2_ERR_STREAM_CLOSING: + return "Stream is closing"; + case NGHTTP2_ERR_STREAM_SHUT_WR: + return "The transmission is not allowed for this stream"; + case NGHTTP2_ERR_INVALID_STREAM_ID: + return "Stream ID is invalid"; + case NGHTTP2_ERR_INVALID_STREAM_STATE: + return "Invalid stream state"; + case NGHTTP2_ERR_DEFERRED_DATA_EXIST: + return "Another DATA frame has already been deferred"; + case NGHTTP2_ERR_START_STREAM_NOT_ALLOWED: + return "request HEADERS is not allowed"; + case NGHTTP2_ERR_GOAWAY_ALREADY_SENT: + return "GOAWAY has already been sent"; + case NGHTTP2_ERR_INVALID_HEADER_BLOCK: + return "Invalid header block"; + case NGHTTP2_ERR_INVALID_STATE: + return "Invalid state"; + case NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE: + return "The user callback function failed due to the temporal error"; + case NGHTTP2_ERR_FRAME_SIZE_ERROR: + return "The length of the frame is invalid"; + case NGHTTP2_ERR_HEADER_COMP: + return "Header compression/decompression error"; + case NGHTTP2_ERR_FLOW_CONTROL: + return "Flow control error"; + case NGHTTP2_ERR_INSUFF_BUFSIZE: + return "Insufficient buffer size given to function"; + case NGHTTP2_ERR_PAUSE: + return "Callback was paused by the application"; + case NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS: + return "Too many inflight SETTINGS"; + case NGHTTP2_ERR_PUSH_DISABLED: + return "Server push is disabled by peer"; + case NGHTTP2_ERR_DATA_EXIST: + return "DATA or HEADERS frame has already been submitted for the stream"; + case NGHTTP2_ERR_SESSION_CLOSING: + return "The current session is closing"; + case NGHTTP2_ERR_HTTP_HEADER: + return "Invalid HTTP header field was received"; + case NGHTTP2_ERR_HTTP_MESSAGING: + return "Violation in HTTP messaging rule"; + case NGHTTP2_ERR_REFUSED_STREAM: + return "Stream was refused"; + case NGHTTP2_ERR_INTERNAL: + return "Internal error"; + case NGHTTP2_ERR_CANCEL: + return "Cancel"; + case NGHTTP2_ERR_SETTINGS_EXPECTED: + return "When a local endpoint expects to receive SETTINGS frame, it " + "receives an other type of frame"; + case NGHTTP2_ERR_NOMEM: + return "Out of memory"; + case NGHTTP2_ERR_CALLBACK_FAILURE: + return "The user callback function failed"; + case NGHTTP2_ERR_BAD_CLIENT_MAGIC: + return "Received bad client magic byte string"; + case NGHTTP2_ERR_FLOODED: + return "Flooding was detected in this HTTP/2 session, and it must be " + "closed"; + case NGHTTP2_ERR_TOO_MANY_SETTINGS: + return "SETTINGS frame contained more than the maximum allowed entries"; + default: + return "Unknown error code"; + } +} + +/* Generated by gennmchartbl.py */ +static const int VALID_HD_NAME_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, + 0 /* EOT */, 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, + 0 /* BS */, 0 /* HT */, 0 /* LF */, 0 /* VT */, + 0 /* FF */, 0 /* CR */, 0 /* SO */, 0 /* SI */, + 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, + 0 /* CAN */, 0 /* EM */, 0 /* SUB */, 0 /* ESC */, + 0 /* FS */, 0 /* GS */, 0 /* RS */, 0 /* US */, + 0 /* SPC */, 1 /* ! */, 0 /* " */, 1 /* # */, + 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, + 0 /* ( */, 0 /* ) */, 1 /* * */, 1 /* + */, + 0 /* , */, 1 /* - */, 1 /* . */, 0 /* / */, + 1 /* 0 */, 1 /* 1 */, 1 /* 2 */, 1 /* 3 */, + 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, 1 /* 7 */, + 1 /* 8 */, 1 /* 9 */, 0 /* : */, 0 /* ; */, + 0 /* < */, 0 /* = */, 0 /* > */, 0 /* ? */, + 0 /* @ */, 0 /* A */, 0 /* B */, 0 /* C */, + 0 /* D */, 0 /* E */, 0 /* F */, 0 /* G */, + 0 /* H */, 0 /* I */, 0 /* J */, 0 /* K */, + 0 /* L */, 0 /* M */, 0 /* N */, 0 /* O */, + 0 /* P */, 0 /* Q */, 0 /* R */, 0 /* S */, + 0 /* T */, 0 /* U */, 0 /* V */, 0 /* W */, + 0 /* X */, 0 /* Y */, 0 /* Z */, 0 /* [ */, + 0 /* \ */, 0 /* ] */, 1 /* ^ */, 1 /* _ */, + 1 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, + 1 /* h */, 1 /* i */, 1 /* j */, 1 /* k */, + 1 /* l */, 1 /* m */, 1 /* n */, 1 /* o */, + 1 /* p */, 1 /* q */, 1 /* r */, 1 /* s */, + 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, + 1 /* | */, 0 /* } */, 1 /* ~ */, 0 /* DEL */, + 0 /* 0x80 */, 0 /* 0x81 */, 0 /* 0x82 */, 0 /* 0x83 */, + 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, 0 /* 0x87 */, + 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */, + 0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, + 0 /* 0x90 */, 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, + 0 /* 0x94 */, 0 /* 0x95 */, 0 /* 0x96 */, 0 /* 0x97 */, + 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, 0 /* 0x9b */, + 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */, + 0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, + 0 /* 0xa4 */, 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, + 0 /* 0xa8 */, 0 /* 0xa9 */, 0 /* 0xaa */, 0 /* 0xab */, + 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, 0 /* 0xaf */, + 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */, + 0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, + 0 /* 0xb8 */, 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, + 0 /* 0xbc */, 0 /* 0xbd */, 0 /* 0xbe */, 0 /* 0xbf */, + 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, 0 /* 0xc3 */, + 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */, + 0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, + 0 /* 0xcc */, 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, + 0 /* 0xd0 */, 0 /* 0xd1 */, 0 /* 0xd2 */, 0 /* 0xd3 */, + 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, 0 /* 0xd7 */, + 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, + 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, + 0 /* 0xe0 */, 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, + 0 /* 0xe4 */, 0 /* 0xe5 */, 0 /* 0xe6 */, 0 /* 0xe7 */, + 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, 0 /* 0xeb */, + 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */, + 0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, + 0 /* 0xf4 */, 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, + 0 /* 0xf8 */, 0 /* 0xf9 */, 0 /* 0xfa */, 0 /* 0xfb */, + 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, 0 /* 0xff */ +}; + +int nghttp2_check_header_name(const uint8_t *name, size_t len) { + const uint8_t *last; + if (len == 0) { + return 0; + } + if (*name == ':') { + if (len == 1) { + return 0; + } + ++name; + --len; + } + for (last = name + len; name != last; ++name) { + if (!VALID_HD_NAME_CHARS[*name]) { + return 0; + } + } + return 1; +} + +/* Generated by genvchartbl.py */ +static const int VALID_HD_VALUE_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, + 0 /* EOT */, 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, + 0 /* BS */, 1 /* HT */, 0 /* LF */, 0 /* VT */, + 0 /* FF */, 0 /* CR */, 0 /* SO */, 0 /* SI */, + 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, + 0 /* CAN */, 0 /* EM */, 0 /* SUB */, 0 /* ESC */, + 0 /* FS */, 0 /* GS */, 0 /* RS */, 0 /* US */, + 1 /* SPC */, 1 /* ! */, 1 /* " */, 1 /* # */, + 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, + 1 /* ( */, 1 /* ) */, 1 /* * */, 1 /* + */, + 1 /* , */, 1 /* - */, 1 /* . */, 1 /* / */, + 1 /* 0 */, 1 /* 1 */, 1 /* 2 */, 1 /* 3 */, + 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, 1 /* 7 */, + 1 /* 8 */, 1 /* 9 */, 1 /* : */, 1 /* ; */, + 1 /* < */, 1 /* = */, 1 /* > */, 1 /* ? */, + 1 /* @ */, 1 /* A */, 1 /* B */, 1 /* C */, + 1 /* D */, 1 /* E */, 1 /* F */, 1 /* G */, + 1 /* H */, 1 /* I */, 1 /* J */, 1 /* K */, + 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */, + 1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, + 1 /* T */, 1 /* U */, 1 /* V */, 1 /* W */, + 1 /* X */, 1 /* Y */, 1 /* Z */, 1 /* [ */, + 1 /* \ */, 1 /* ] */, 1 /* ^ */, 1 /* _ */, + 1 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, + 1 /* h */, 1 /* i */, 1 /* j */, 1 /* k */, + 1 /* l */, 1 /* m */, 1 /* n */, 1 /* o */, + 1 /* p */, 1 /* q */, 1 /* r */, 1 /* s */, + 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 1 /* { */, + 1 /* | */, 1 /* } */, 1 /* ~ */, 0 /* DEL */, + 1 /* 0x80 */, 1 /* 0x81 */, 1 /* 0x82 */, 1 /* 0x83 */, + 1 /* 0x84 */, 1 /* 0x85 */, 1 /* 0x86 */, 1 /* 0x87 */, + 1 /* 0x88 */, 1 /* 0x89 */, 1 /* 0x8a */, 1 /* 0x8b */, + 1 /* 0x8c */, 1 /* 0x8d */, 1 /* 0x8e */, 1 /* 0x8f */, + 1 /* 0x90 */, 1 /* 0x91 */, 1 /* 0x92 */, 1 /* 0x93 */, + 1 /* 0x94 */, 1 /* 0x95 */, 1 /* 0x96 */, 1 /* 0x97 */, + 1 /* 0x98 */, 1 /* 0x99 */, 1 /* 0x9a */, 1 /* 0x9b */, + 1 /* 0x9c */, 1 /* 0x9d */, 1 /* 0x9e */, 1 /* 0x9f */, + 1 /* 0xa0 */, 1 /* 0xa1 */, 1 /* 0xa2 */, 1 /* 0xa3 */, + 1 /* 0xa4 */, 1 /* 0xa5 */, 1 /* 0xa6 */, 1 /* 0xa7 */, + 1 /* 0xa8 */, 1 /* 0xa9 */, 1 /* 0xaa */, 1 /* 0xab */, + 1 /* 0xac */, 1 /* 0xad */, 1 /* 0xae */, 1 /* 0xaf */, + 1 /* 0xb0 */, 1 /* 0xb1 */, 1 /* 0xb2 */, 1 /* 0xb3 */, + 1 /* 0xb4 */, 1 /* 0xb5 */, 1 /* 0xb6 */, 1 /* 0xb7 */, + 1 /* 0xb8 */, 1 /* 0xb9 */, 1 /* 0xba */, 1 /* 0xbb */, + 1 /* 0xbc */, 1 /* 0xbd */, 1 /* 0xbe */, 1 /* 0xbf */, + 1 /* 0xc0 */, 1 /* 0xc1 */, 1 /* 0xc2 */, 1 /* 0xc3 */, + 1 /* 0xc4 */, 1 /* 0xc5 */, 1 /* 0xc6 */, 1 /* 0xc7 */, + 1 /* 0xc8 */, 1 /* 0xc9 */, 1 /* 0xca */, 1 /* 0xcb */, + 1 /* 0xcc */, 1 /* 0xcd */, 1 /* 0xce */, 1 /* 0xcf */, + 1 /* 0xd0 */, 1 /* 0xd1 */, 1 /* 0xd2 */, 1 /* 0xd3 */, + 1 /* 0xd4 */, 1 /* 0xd5 */, 1 /* 0xd6 */, 1 /* 0xd7 */, + 1 /* 0xd8 */, 1 /* 0xd9 */, 1 /* 0xda */, 1 /* 0xdb */, + 1 /* 0xdc */, 1 /* 0xdd */, 1 /* 0xde */, 1 /* 0xdf */, + 1 /* 0xe0 */, 1 /* 0xe1 */, 1 /* 0xe2 */, 1 /* 0xe3 */, + 1 /* 0xe4 */, 1 /* 0xe5 */, 1 /* 0xe6 */, 1 /* 0xe7 */, + 1 /* 0xe8 */, 1 /* 0xe9 */, 1 /* 0xea */, 1 /* 0xeb */, + 1 /* 0xec */, 1 /* 0xed */, 1 /* 0xee */, 1 /* 0xef */, + 1 /* 0xf0 */, 1 /* 0xf1 */, 1 /* 0xf2 */, 1 /* 0xf3 */, + 1 /* 0xf4 */, 1 /* 0xf5 */, 1 /* 0xf6 */, 1 /* 0xf7 */, + 1 /* 0xf8 */, 1 /* 0xf9 */, 1 /* 0xfa */, 1 /* 0xfb */, + 1 /* 0xfc */, 1 /* 0xfd */, 1 /* 0xfe */, 1 /* 0xff */ +}; + +int nghttp2_check_header_value(const uint8_t *value, size_t len) { + const uint8_t *last; + for (last = value + len; value != last; ++value) { + if (!VALID_HD_VALUE_CHARS[*value]) { + return 0; + } + } + return 1; +} + +int nghttp2_check_header_value_rfc9113(const uint8_t *value, size_t len) { + if (len == 0) { + return 1; + } + + if (*value == ' ' || *value == '\t' || *(value + len - 1) == ' ' || + *(value + len - 1) == '\t') { + return 0; + } + + return nghttp2_check_header_value(value, len); +} + +/* Generated by genmethodchartbl.py */ +static char VALID_METHOD_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, + 0 /* EOT */, 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, + 0 /* BS */, 0 /* HT */, 0 /* LF */, 0 /* VT */, + 0 /* FF */, 0 /* CR */, 0 /* SO */, 0 /* SI */, + 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, + 0 /* CAN */, 0 /* EM */, 0 /* SUB */, 0 /* ESC */, + 0 /* FS */, 0 /* GS */, 0 /* RS */, 0 /* US */, + 0 /* SPC */, 1 /* ! */, 0 /* " */, 1 /* # */, + 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, + 0 /* ( */, 0 /* ) */, 1 /* * */, 1 /* + */, + 0 /* , */, 1 /* - */, 1 /* . */, 0 /* / */, + 1 /* 0 */, 1 /* 1 */, 1 /* 2 */, 1 /* 3 */, + 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, 1 /* 7 */, + 1 /* 8 */, 1 /* 9 */, 0 /* : */, 0 /* ; */, + 0 /* < */, 0 /* = */, 0 /* > */, 0 /* ? */, + 0 /* @ */, 1 /* A */, 1 /* B */, 1 /* C */, + 1 /* D */, 1 /* E */, 1 /* F */, 1 /* G */, + 1 /* H */, 1 /* I */, 1 /* J */, 1 /* K */, + 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */, + 1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, + 1 /* T */, 1 /* U */, 1 /* V */, 1 /* W */, + 1 /* X */, 1 /* Y */, 1 /* Z */, 0 /* [ */, + 0 /* \ */, 0 /* ] */, 1 /* ^ */, 1 /* _ */, + 1 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, + 1 /* h */, 1 /* i */, 1 /* j */, 1 /* k */, + 1 /* l */, 1 /* m */, 1 /* n */, 1 /* o */, + 1 /* p */, 1 /* q */, 1 /* r */, 1 /* s */, + 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, + 1 /* | */, 0 /* } */, 1 /* ~ */, 0 /* DEL */, + 0 /* 0x80 */, 0 /* 0x81 */, 0 /* 0x82 */, 0 /* 0x83 */, + 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, 0 /* 0x87 */, + 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */, + 0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, + 0 /* 0x90 */, 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, + 0 /* 0x94 */, 0 /* 0x95 */, 0 /* 0x96 */, 0 /* 0x97 */, + 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, 0 /* 0x9b */, + 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */, + 0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, + 0 /* 0xa4 */, 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, + 0 /* 0xa8 */, 0 /* 0xa9 */, 0 /* 0xaa */, 0 /* 0xab */, + 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, 0 /* 0xaf */, + 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */, + 0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, + 0 /* 0xb8 */, 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, + 0 /* 0xbc */, 0 /* 0xbd */, 0 /* 0xbe */, 0 /* 0xbf */, + 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, 0 /* 0xc3 */, + 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */, + 0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, + 0 /* 0xcc */, 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, + 0 /* 0xd0 */, 0 /* 0xd1 */, 0 /* 0xd2 */, 0 /* 0xd3 */, + 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, 0 /* 0xd7 */, + 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, + 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, + 0 /* 0xe0 */, 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, + 0 /* 0xe4 */, 0 /* 0xe5 */, 0 /* 0xe6 */, 0 /* 0xe7 */, + 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, 0 /* 0xeb */, + 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */, + 0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, + 0 /* 0xf4 */, 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, + 0 /* 0xf8 */, 0 /* 0xf9 */, 0 /* 0xfa */, 0 /* 0xfb */, + 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, 0 /* 0xff */ +}; + +int nghttp2_check_method(const uint8_t *value, size_t len) { + const uint8_t *last; + if (len == 0) { + return 0; + } + for (last = value + len; value != last; ++value) { + if (!VALID_METHOD_CHARS[*value]) { + return 0; + } + } + return 1; +} + +/* Generated by genpathchartbl.py */ +static char VALID_PATH_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, + 0 /* EOT */, 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, + 0 /* BS */, 0 /* HT */, 0 /* LF */, 0 /* VT */, + 0 /* FF */, 0 /* CR */, 0 /* SO */, 0 /* SI */, + 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, + 0 /* CAN */, 0 /* EM */, 0 /* SUB */, 0 /* ESC */, + 0 /* FS */, 0 /* GS */, 0 /* RS */, 0 /* US */, + 0 /* SPC */, 1 /* ! */, 1 /* " */, 1 /* # */, + 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, + 1 /* ( */, 1 /* ) */, 1 /* * */, 1 /* + */, + 1 /* , */, 1 /* - */, 1 /* . */, 1 /* / */, + 1 /* 0 */, 1 /* 1 */, 1 /* 2 */, 1 /* 3 */, + 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, 1 /* 7 */, + 1 /* 8 */, 1 /* 9 */, 1 /* : */, 1 /* ; */, + 1 /* < */, 1 /* = */, 1 /* > */, 1 /* ? */, + 1 /* @ */, 1 /* A */, 1 /* B */, 1 /* C */, + 1 /* D */, 1 /* E */, 1 /* F */, 1 /* G */, + 1 /* H */, 1 /* I */, 1 /* J */, 1 /* K */, + 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */, + 1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, + 1 /* T */, 1 /* U */, 1 /* V */, 1 /* W */, + 1 /* X */, 1 /* Y */, 1 /* Z */, 1 /* [ */, + 1 /* \ */, 1 /* ] */, 1 /* ^ */, 1 /* _ */, + 1 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, + 1 /* h */, 1 /* i */, 1 /* j */, 1 /* k */, + 1 /* l */, 1 /* m */, 1 /* n */, 1 /* o */, + 1 /* p */, 1 /* q */, 1 /* r */, 1 /* s */, + 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 1 /* { */, + 1 /* | */, 1 /* } */, 1 /* ~ */, 0 /* DEL */, + 1 /* 0x80 */, 1 /* 0x81 */, 1 /* 0x82 */, 1 /* 0x83 */, + 1 /* 0x84 */, 1 /* 0x85 */, 1 /* 0x86 */, 1 /* 0x87 */, + 1 /* 0x88 */, 1 /* 0x89 */, 1 /* 0x8a */, 1 /* 0x8b */, + 1 /* 0x8c */, 1 /* 0x8d */, 1 /* 0x8e */, 1 /* 0x8f */, + 1 /* 0x90 */, 1 /* 0x91 */, 1 /* 0x92 */, 1 /* 0x93 */, + 1 /* 0x94 */, 1 /* 0x95 */, 1 /* 0x96 */, 1 /* 0x97 */, + 1 /* 0x98 */, 1 /* 0x99 */, 1 /* 0x9a */, 1 /* 0x9b */, + 1 /* 0x9c */, 1 /* 0x9d */, 1 /* 0x9e */, 1 /* 0x9f */, + 1 /* 0xa0 */, 1 /* 0xa1 */, 1 /* 0xa2 */, 1 /* 0xa3 */, + 1 /* 0xa4 */, 1 /* 0xa5 */, 1 /* 0xa6 */, 1 /* 0xa7 */, + 1 /* 0xa8 */, 1 /* 0xa9 */, 1 /* 0xaa */, 1 /* 0xab */, + 1 /* 0xac */, 1 /* 0xad */, 1 /* 0xae */, 1 /* 0xaf */, + 1 /* 0xb0 */, 1 /* 0xb1 */, 1 /* 0xb2 */, 1 /* 0xb3 */, + 1 /* 0xb4 */, 1 /* 0xb5 */, 1 /* 0xb6 */, 1 /* 0xb7 */, + 1 /* 0xb8 */, 1 /* 0xb9 */, 1 /* 0xba */, 1 /* 0xbb */, + 1 /* 0xbc */, 1 /* 0xbd */, 1 /* 0xbe */, 1 /* 0xbf */, + 1 /* 0xc0 */, 1 /* 0xc1 */, 1 /* 0xc2 */, 1 /* 0xc3 */, + 1 /* 0xc4 */, 1 /* 0xc5 */, 1 /* 0xc6 */, 1 /* 0xc7 */, + 1 /* 0xc8 */, 1 /* 0xc9 */, 1 /* 0xca */, 1 /* 0xcb */, + 1 /* 0xcc */, 1 /* 0xcd */, 1 /* 0xce */, 1 /* 0xcf */, + 1 /* 0xd0 */, 1 /* 0xd1 */, 1 /* 0xd2 */, 1 /* 0xd3 */, + 1 /* 0xd4 */, 1 /* 0xd5 */, 1 /* 0xd6 */, 1 /* 0xd7 */, + 1 /* 0xd8 */, 1 /* 0xd9 */, 1 /* 0xda */, 1 /* 0xdb */, + 1 /* 0xdc */, 1 /* 0xdd */, 1 /* 0xde */, 1 /* 0xdf */, + 1 /* 0xe0 */, 1 /* 0xe1 */, 1 /* 0xe2 */, 1 /* 0xe3 */, + 1 /* 0xe4 */, 1 /* 0xe5 */, 1 /* 0xe6 */, 1 /* 0xe7 */, + 1 /* 0xe8 */, 1 /* 0xe9 */, 1 /* 0xea */, 1 /* 0xeb */, + 1 /* 0xec */, 1 /* 0xed */, 1 /* 0xee */, 1 /* 0xef */, + 1 /* 0xf0 */, 1 /* 0xf1 */, 1 /* 0xf2 */, 1 /* 0xf3 */, + 1 /* 0xf4 */, 1 /* 0xf5 */, 1 /* 0xf6 */, 1 /* 0xf7 */, + 1 /* 0xf8 */, 1 /* 0xf9 */, 1 /* 0xfa */, 1 /* 0xfb */, + 1 /* 0xfc */, 1 /* 0xfd */, 1 /* 0xfe */, 1 /* 0xff */ +}; + +int nghttp2_check_path(const uint8_t *value, size_t len) { + const uint8_t *last; + for (last = value + len; value != last; ++value) { + if (!VALID_PATH_CHARS[*value]) { + return 0; + } + } + return 1; +} + +/* Generated by genauthoritychartbl.py */ +static char VALID_AUTHORITY_CHARS[] = { + 0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, + 0 /* EOT */, 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, + 0 /* BS */, 0 /* HT */, 0 /* LF */, 0 /* VT */, + 0 /* FF */, 0 /* CR */, 0 /* SO */, 0 /* SI */, + 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */, + 0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, + 0 /* CAN */, 0 /* EM */, 0 /* SUB */, 0 /* ESC */, + 0 /* FS */, 0 /* GS */, 0 /* RS */, 0 /* US */, + 0 /* SPC */, 1 /* ! */, 0 /* " */, 0 /* # */, + 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */, + 1 /* ( */, 1 /* ) */, 1 /* * */, 1 /* + */, + 1 /* , */, 1 /* - */, 1 /* . */, 0 /* / */, + 1 /* 0 */, 1 /* 1 */, 1 /* 2 */, 1 /* 3 */, + 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, 1 /* 7 */, + 1 /* 8 */, 1 /* 9 */, 1 /* : */, 1 /* ; */, + 0 /* < */, 1 /* = */, 0 /* > */, 0 /* ? */, + 1 /* @ */, 1 /* A */, 1 /* B */, 1 /* C */, + 1 /* D */, 1 /* E */, 1 /* F */, 1 /* G */, + 1 /* H */, 1 /* I */, 1 /* J */, 1 /* K */, + 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */, + 1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, + 1 /* T */, 1 /* U */, 1 /* V */, 1 /* W */, + 1 /* X */, 1 /* Y */, 1 /* Z */, 1 /* [ */, + 0 /* \ */, 1 /* ] */, 0 /* ^ */, 1 /* _ */, + 0 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */, + 1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, + 1 /* h */, 1 /* i */, 1 /* j */, 1 /* k */, + 1 /* l */, 1 /* m */, 1 /* n */, 1 /* o */, + 1 /* p */, 1 /* q */, 1 /* r */, 1 /* s */, + 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */, + 1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, + 0 /* | */, 0 /* } */, 1 /* ~ */, 0 /* DEL */, + 0 /* 0x80 */, 0 /* 0x81 */, 0 /* 0x82 */, 0 /* 0x83 */, + 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */, 0 /* 0x87 */, + 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */, + 0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, + 0 /* 0x90 */, 0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, + 0 /* 0x94 */, 0 /* 0x95 */, 0 /* 0x96 */, 0 /* 0x97 */, + 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */, 0 /* 0x9b */, + 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */, + 0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, + 0 /* 0xa4 */, 0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, + 0 /* 0xa8 */, 0 /* 0xa9 */, 0 /* 0xaa */, 0 /* 0xab */, + 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */, 0 /* 0xaf */, + 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */, + 0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, + 0 /* 0xb8 */, 0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, + 0 /* 0xbc */, 0 /* 0xbd */, 0 /* 0xbe */, 0 /* 0xbf */, + 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */, 0 /* 0xc3 */, + 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */, + 0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, + 0 /* 0xcc */, 0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, + 0 /* 0xd0 */, 0 /* 0xd1 */, 0 /* 0xd2 */, 0 /* 0xd3 */, + 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */, 0 /* 0xd7 */, + 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */, + 0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, + 0 /* 0xe0 */, 0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, + 0 /* 0xe4 */, 0 /* 0xe5 */, 0 /* 0xe6 */, 0 /* 0xe7 */, + 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */, 0 /* 0xeb */, + 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */, + 0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, + 0 /* 0xf4 */, 0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, + 0 /* 0xf8 */, 0 /* 0xf9 */, 0 /* 0xfa */, 0 /* 0xfb */, + 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */, 0 /* 0xff */ +}; + +int nghttp2_check_authority(const uint8_t *value, size_t len) { + const uint8_t *last; + for (last = value + len; value != last; ++value) { + if (!VALID_AUTHORITY_CHARS[*value]) { + return 0; + } + } + return 1; +} + +uint8_t *nghttp2_cpymem(uint8_t *dest, const void *src, size_t len) { + if (len == 0) { + return dest; + } + + memcpy(dest, src, len); + + return dest + len; +} + +const char *nghttp2_http2_strerror(uint32_t error_code) { + switch (error_code) { + case NGHTTP2_NO_ERROR: + return "NO_ERROR"; + case NGHTTP2_PROTOCOL_ERROR: + return "PROTOCOL_ERROR"; + case NGHTTP2_INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case NGHTTP2_FLOW_CONTROL_ERROR: + return "FLOW_CONTROL_ERROR"; + case NGHTTP2_SETTINGS_TIMEOUT: + return "SETTINGS_TIMEOUT"; + case NGHTTP2_STREAM_CLOSED: + return "STREAM_CLOSED"; + case NGHTTP2_FRAME_SIZE_ERROR: + return "FRAME_SIZE_ERROR"; + case NGHTTP2_REFUSED_STREAM: + return "REFUSED_STREAM"; + case NGHTTP2_CANCEL: + return "CANCEL"; + case NGHTTP2_COMPRESSION_ERROR: + return "COMPRESSION_ERROR"; + case NGHTTP2_CONNECT_ERROR: + return "CONNECT_ERROR"; + case NGHTTP2_ENHANCE_YOUR_CALM: + return "ENHANCE_YOUR_CALM"; + case NGHTTP2_INADEQUATE_SECURITY: + return "INADEQUATE_SECURITY"; + case NGHTTP2_HTTP_1_1_REQUIRED: + return "HTTP_1_1_REQUIRED"; + default: + return "unknown"; + } +} diff --git a/lib/nghttp2/lib/nghttp2_helper.h b/lib/nghttp2/lib/nghttp2_helper.h new file mode 100644 index 00000000000..b1f18ce541a --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_helper.h @@ -0,0 +1,122 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_HELPER_H +#define NGHTTP2_HELPER_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include + +#include +#include "nghttp2_mem.h" + +#define nghttp2_min(A, B) ((A) < (B) ? (A) : (B)) +#define nghttp2_max(A, B) ((A) > (B) ? (A) : (B)) + +#define lstreq(A, B, N) ((sizeof((A)) - 1) == (N) && memcmp((A), (B), (N)) == 0) + +#define nghttp2_struct_of(ptr, type, member) \ + ((type *)(void *)((char *)(ptr)-offsetof(type, member))) + +/* + * Copies 2 byte unsigned integer |n| in host byte order to |buf| in + * network byte order. + */ +void nghttp2_put_uint16be(uint8_t *buf, uint16_t n); + +/* + * Copies 4 byte unsigned integer |n| in host byte order to |buf| in + * network byte order. + */ +void nghttp2_put_uint32be(uint8_t *buf, uint32_t n); + +/* + * Retrieves 2 byte unsigned integer stored in |data| in network byte + * order and returns it in host byte order. + */ +uint16_t nghttp2_get_uint16(const uint8_t *data); + +/* + * Retrieves 4 byte unsigned integer stored in |data| in network byte + * order and returns it in host byte order. + */ +uint32_t nghttp2_get_uint32(const uint8_t *data); + +void nghttp2_downcase(uint8_t *s, size_t len); + +/* + * Adjusts |*local_window_size_ptr|, |*recv_window_size_ptr|, + * |*recv_reduction_ptr| with |*delta_ptr| which is the + * WINDOW_UPDATE's window_size_increment sent from local side. If + * |delta| is strictly larger than |*recv_window_size_ptr|, + * |*local_window_size_ptr| is increased by delta - + * *recv_window_size_ptr. If |delta| is negative, + * |*local_window_size_ptr| is decreased by delta. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_FLOW_CONTROL + * local_window_size overflow or gets negative. + */ +int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr, + int32_t *recv_window_size_ptr, + int32_t *recv_reduction_ptr, + int32_t *delta_ptr); + +/* + * This function works like nghttp2_adjust_local_window_size(). The + * difference is that this function assumes *delta_ptr >= 0, and + * *recv_window_size_ptr is not decreased by *delta_ptr. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_FLOW_CONTROL + * local_window_size overflow or gets negative. + */ +int nghttp2_increase_local_window_size(int32_t *local_window_size_ptr, + int32_t *recv_window_size_ptr, + int32_t *recv_reduction_ptr, + int32_t *delta_ptr); + +/* + * Returns non-zero if the function decided that WINDOW_UPDATE should + * be sent. + */ +int nghttp2_should_send_window_update(int32_t local_window_size, + int32_t recv_window_size); + +/* + * Copies the buffer |src| of length |len| to the destination pointed + * by the |dest|, assuming that the |dest| is at lest |len| bytes long + * . Returns dest + len. + */ +uint8_t *nghttp2_cpymem(uint8_t *dest, const void *src, size_t len); + +#endif /* NGHTTP2_HELPER_H */ diff --git a/lib/nghttp2/lib/nghttp2_http.c b/lib/nghttp2/lib/nghttp2_http.c new file mode 100644 index 00000000000..ecdeb21ddb6 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_http.c @@ -0,0 +1,631 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_http.h" + +#include +#include +#include + +#include "nghttp2_hd.h" +#include "nghttp2_helper.h" +#include "nghttp2_extpri.h" +#include "sfparse.h" + +static uint8_t downcase(uint8_t c) { + return 'A' <= c && c <= 'Z' ? (uint8_t)(c - 'A' + 'a') : c; +} + +static int memieq(const void *a, const void *b, size_t n) { + size_t i; + const uint8_t *aa = a, *bb = b; + + for (i = 0; i < n; ++i) { + if (downcase(aa[i]) != downcase(bb[i])) { + return 0; + } + } + return 1; +} + +#define lstrieq(A, B, N) ((sizeof((A)) - 1) == (N) && memieq((A), (B), (N))) + +static int64_t parse_uint(const uint8_t *s, size_t len) { + int64_t n = 0; + size_t i; + if (len == 0) { + return -1; + } + for (i = 0; i < len; ++i) { + if ('0' <= s[i] && s[i] <= '9') { + if (n > INT64_MAX / 10) { + return -1; + } + n *= 10; + if (n > INT64_MAX - (s[i] - '0')) { + return -1; + } + n += s[i] - '0'; + continue; + } + return -1; + } + return n; +} + +static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_hd_nv *nv, + uint32_t flag) { + if ((stream->http_flags & flag) || nv->value->len == 0) { + return 0; + } + stream->http_flags = stream->http_flags | flag; + return 1; +} + +static int expect_response_body(nghttp2_stream *stream) { + return (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_HEAD) == 0 && + stream->status_code / 100 != 1 && stream->status_code != 304 && + stream->status_code != 204; +} + +/* For "http" or "https" URIs, OPTIONS request may have "*" in :path + header field to represent system-wide OPTIONS request. Otherwise, + :path header field value must start with "/". This function must + be called after ":method" header field was received. This function + returns nonzero if path is valid.*/ +static int check_path(nghttp2_stream *stream) { + return (stream->http_flags & NGHTTP2_HTTP_FLAG_SCHEME_HTTP) == 0 || + ((stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_REGULAR) || + ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_OPTIONS) && + (stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_ASTERISK))); +} + +static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, + int trailer, int connect_protocol) { + nghttp2_extpri extpri; + + if (nv->name->base[0] == ':') { + if (trailer || + (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + } + + switch (nv->token) { + case NGHTTP2_TOKEN__AUTHORITY: + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; + case NGHTTP2_TOKEN__METHOD: + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + switch (nv->value->len) { + case 4: + if (lstreq("HEAD", nv->value->base, nv->value->len)) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; + } + break; + case 7: + switch (nv->value->base[6]) { + case 'T': + if (lstreq("CONNECT", nv->value->base, nv->value->len)) { + if (stream->stream_id % 2 == 0) { + /* we won't allow CONNECT for push */ + return NGHTTP2_ERR_HTTP_HEADER; + } + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; + } + break; + case 'S': + if (lstreq("OPTIONS", nv->value->base, nv->value->len)) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS; + } + break; + } + break; + } + break; + case NGHTTP2_TOKEN__PATH: + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + if (nv->value->base[0] == '/') { + stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR; + } else if (nv->value->len == 1 && nv->value->base[0] == '*') { + stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK; + } + break; + case NGHTTP2_TOKEN__SCHEME: + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + if ((nv->value->len == 4 && memieq("http", nv->value->base, 4)) || + (nv->value->len == 5 && memieq("https", nv->value->base, 5))) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP; + } + break; + case NGHTTP2_TOKEN__PROTOCOL: + if (!connect_protocol) { + return NGHTTP2_ERR_HTTP_HEADER; + } + + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PROTOCOL)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; + case NGHTTP2_TOKEN_HOST: + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; + case NGHTTP2_TOKEN_CONTENT_LENGTH: { + if (stream->content_length != -1) { + return NGHTTP2_ERR_HTTP_HEADER; + } + stream->content_length = parse_uint(nv->value->base, nv->value->len); + if (stream->content_length == -1) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; + } + /* disallowed header fields */ + case NGHTTP2_TOKEN_CONNECTION: + case NGHTTP2_TOKEN_KEEP_ALIVE: + case NGHTTP2_TOKEN_PROXY_CONNECTION: + case NGHTTP2_TOKEN_TRANSFER_ENCODING: + case NGHTTP2_TOKEN_UPGRADE: + return NGHTTP2_ERR_HTTP_HEADER; + case NGHTTP2_TOKEN_TE: + if (!lstrieq("trailers", nv->value->base, nv->value->len)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; + case NGHTTP2_TOKEN_PRIORITY: + if (!trailer && + /* Do not parse the header field in PUSH_PROMISE. */ + (stream->stream_id & 1) && + (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) && + !(stream->http_flags & NGHTTP2_HTTP_FLAG_BAD_PRIORITY)) { + nghttp2_extpri_from_uint8(&extpri, stream->http_extpri); + if (nghttp2_http_parse_priority(&extpri, nv->value->base, + nv->value->len) == 0) { + stream->http_extpri = nghttp2_extpri_to_uint8(&extpri); + stream->http_flags |= NGHTTP2_HTTP_FLAG_PRIORITY; + } else { + stream->http_flags &= (uint32_t)~NGHTTP2_HTTP_FLAG_PRIORITY; + stream->http_flags |= NGHTTP2_HTTP_FLAG_BAD_PRIORITY; + } + } + break; + default: + if (nv->name->base[0] == ':') { + return NGHTTP2_ERR_HTTP_HEADER; + } + } + + if (nv->name->base[0] != ':') { + stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + } + + return 0; +} + +static int http_response_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv, + int trailer) { + if (nv->name->base[0] == ':') { + if (trailer || + (stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + } + + switch (nv->token) { + case NGHTTP2_TOKEN__STATUS: { + if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + if (nv->value->len != 3) { + return NGHTTP2_ERR_HTTP_HEADER; + } + stream->status_code = (int16_t)parse_uint(nv->value->base, nv->value->len); + if (stream->status_code == -1 || stream->status_code == 101) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; + } + case NGHTTP2_TOKEN_CONTENT_LENGTH: { + if (stream->status_code == 204) { + /* content-length header field in 204 response is prohibited by + RFC 7230. But some widely used servers send content-length: + 0. Until they get fixed, we ignore it. */ + if (stream->content_length != -1) { + /* Found multiple content-length field */ + return NGHTTP2_ERR_HTTP_HEADER; + } + if (!lstrieq("0", nv->value->base, nv->value->len)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + stream->content_length = 0; + return NGHTTP2_ERR_REMOVE_HTTP_HEADER; + } + if (stream->status_code / 100 == 1) { + return NGHTTP2_ERR_HTTP_HEADER; + } + /* https://tools.ietf.org/html/rfc7230#section-3.3.3 */ + if (stream->status_code / 100 == 2 && + (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) { + return NGHTTP2_ERR_REMOVE_HTTP_HEADER; + } + if (stream->content_length != -1) { + return NGHTTP2_ERR_HTTP_HEADER; + } + stream->content_length = parse_uint(nv->value->base, nv->value->len); + if (stream->content_length == -1) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; + } + /* disallowed header fields */ + case NGHTTP2_TOKEN_CONNECTION: + case NGHTTP2_TOKEN_KEEP_ALIVE: + case NGHTTP2_TOKEN_PROXY_CONNECTION: + case NGHTTP2_TOKEN_TRANSFER_ENCODING: + case NGHTTP2_TOKEN_UPGRADE: + return NGHTTP2_ERR_HTTP_HEADER; + case NGHTTP2_TOKEN_TE: + if (!lstrieq("trailers", nv->value->base, nv->value->len)) { + return NGHTTP2_ERR_HTTP_HEADER; + } + break; + default: + if (nv->name->base[0] == ':') { + return NGHTTP2_ERR_HTTP_HEADER; + } + } + + if (nv->name->base[0] != ':') { + stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + } + + return 0; +} + +static int check_scheme(const uint8_t *value, size_t len) { + const uint8_t *last; + if (len == 0) { + return 0; + } + + if (!(('A' <= *value && *value <= 'Z') || ('a' <= *value && *value <= 'z'))) { + return 0; + } + + last = value + len; + ++value; + + for (; value != last; ++value) { + if (!(('A' <= *value && *value <= 'Z') || + ('a' <= *value && *value <= 'z') || + ('0' <= *value && *value <= '9') || *value == '+' || *value == '-' || + *value == '.')) { + return 0; + } + } + return 1; +} + +static int lws(const uint8_t *s, size_t n) { + size_t i; + for (i = 0; i < n; ++i) { + if (s[i] != ' ' && s[i] != '\t') { + return 0; + } + } + return 1; +} + +int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, + nghttp2_frame *frame, nghttp2_hd_nv *nv, + int trailer) { + int rv; + + /* We are strict for pseudo header field. One bad character should + lead to fail. OTOH, we should be a bit forgiving for regular + headers, since existing public internet has so much illegal + headers floating around and if we kill the stream because of + this, we may disrupt many web sites and/or libraries. So we + become conservative here, and just ignore those illegal regular + headers. */ + if (!nghttp2_check_header_name(nv->name->base, nv->name->len)) { + size_t i; + if (nv->name->len > 0 && nv->name->base[0] == ':') { + return NGHTTP2_ERR_HTTP_HEADER; + } + /* header field name must be lower-cased without exception */ + for (i = 0; i < nv->name->len; ++i) { + uint8_t c = nv->name->base[i]; + if ('A' <= c && c <= 'Z') { + return NGHTTP2_ERR_HTTP_HEADER; + } + } + /* When ignoring regular headers, we set this flag so that we + still enforce header field ordering rule for pseudo header + fields. */ + stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + return NGHTTP2_ERR_IGN_HTTP_HEADER; + } + + switch (nv->token) { + case NGHTTP2_TOKEN__METHOD: + rv = nghttp2_check_method(nv->value->base, nv->value->len); + break; + case NGHTTP2_TOKEN__PATH: + rv = nghttp2_check_path(nv->value->base, nv->value->len); + break; + case NGHTTP2_TOKEN__AUTHORITY: + case NGHTTP2_TOKEN_HOST: + if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { + rv = nghttp2_check_authority(nv->value->base, nv->value->len); + } else if ( + stream->flags & + NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) { + rv = nghttp2_check_header_value(nv->value->base, nv->value->len); + } else { + rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len); + } + break; + case NGHTTP2_TOKEN__SCHEME: + rv = check_scheme(nv->value->base, nv->value->len); + break; + case NGHTTP2_TOKEN__PROTOCOL: + /* Check the value consists of just white spaces, which was done + in check_pseudo_header before + nghttp2_check_header_value_rfc9113 has been introduced. */ + if ((stream->flags & + NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) && + lws(nv->value->base, nv->value->len)) { + rv = 0; + break; + } + /* fall through */ + default: + if (stream->flags & + NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) { + rv = nghttp2_check_header_value(nv->value->base, nv->value->len); + } else { + rv = nghttp2_check_header_value_rfc9113(nv->value->base, nv->value->len); + } + } + + if (rv == 0) { + assert(nv->name->len > 0); + if (nv->name->base[0] == ':') { + return NGHTTP2_ERR_HTTP_HEADER; + } + /* When ignoring regular headers, we set this flag so that we + still enforce header field ordering rule for pseudo header + fields. */ + stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + return NGHTTP2_ERR_IGN_HTTP_HEADER; + } + + if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) { + return http_request_on_header(stream, nv, trailer, + session->server && + session->pending_enable_connect_protocol); + } + + return http_response_on_header(stream, nv, trailer); +} + +int nghttp2_http_on_request_headers(nghttp2_stream *stream, + nghttp2_frame *frame) { + if (!(stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) && + (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) { + if ((stream->http_flags & + (NGHTTP2_HTTP_FLAG__SCHEME | NGHTTP2_HTTP_FLAG__PATH)) || + (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) { + return -1; + } + stream->content_length = -1; + } else { + if ((stream->http_flags & NGHTTP2_HTTP_FLAG_REQ_HEADERS) != + NGHTTP2_HTTP_FLAG_REQ_HEADERS || + (stream->http_flags & + (NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) { + return -1; + } + if ((stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) && + ((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) == 0 || + (stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0)) { + return -1; + } + if (!check_path(stream)) { + return -1; + } + } + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + /* we are going to reuse data fields for upcoming response. Clear + them now, except for method flags. */ + stream->http_flags &= NGHTTP2_HTTP_FLAG_METH_ALL; + stream->content_length = -1; + } + + return 0; +} + +int nghttp2_http_on_response_headers(nghttp2_stream *stream) { + if ((stream->http_flags & NGHTTP2_HTTP_FLAG__STATUS) == 0) { + return -1; + } + + if (stream->status_code / 100 == 1) { + /* non-final response */ + stream->http_flags = (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_ALL) | + NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE; + stream->content_length = -1; + stream->status_code = -1; + return 0; + } + + stream->http_flags = + stream->http_flags & (uint32_t)~NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE; + + if (!expect_response_body(stream)) { + stream->content_length = 0; + } else if (stream->http_flags & (NGHTTP2_HTTP_FLAG_METH_CONNECT | + NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND)) { + stream->content_length = -1; + } + + return 0; +} + +int nghttp2_http_on_trailer_headers(nghttp2_stream *stream, + nghttp2_frame *frame) { + (void)stream; + + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + return -1; + } + + return 0; +} + +int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream) { + if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) { + return -1; + } + + if (stream->content_length != -1 && + stream->content_length != stream->recv_content_length) { + return -1; + } + + return 0; +} + +int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n) { + stream->recv_content_length += (int64_t)n; + + if ((stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) || + (stream->content_length != -1 && + stream->recv_content_length > stream->content_length)) { + return -1; + } + + return 0; +} + +void nghttp2_http_record_request_method(nghttp2_stream *stream, + nghttp2_frame *frame) { + const nghttp2_nv *nva; + size_t nvlen; + size_t i; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + nva = frame->headers.nva; + nvlen = frame->headers.nvlen; + break; + case NGHTTP2_PUSH_PROMISE: + nva = frame->push_promise.nva; + nvlen = frame->push_promise.nvlen; + break; + default: + return; + } + + /* TODO we should do this strictly. */ + for (i = 0; i < nvlen; ++i) { + const nghttp2_nv *nv = &nva[i]; + if (!(nv->namelen == 7 && nv->name[6] == 'd' && + memcmp(":metho", nv->name, nv->namelen - 1) == 0)) { + continue; + } + if (lstreq("CONNECT", nv->value, nv->valuelen)) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; + return; + } + if (lstreq("HEAD", nv->value, nv->valuelen)) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; + return; + } + return; + } +} + +int nghttp2_http_parse_priority(nghttp2_extpri *dest, const uint8_t *value, + size_t valuelen) { + nghttp2_extpri pri = *dest; + sf_parser sfp; + sf_vec key; + sf_value val; + int rv; + + sf_parser_init(&sfp, value, valuelen); + + for (;;) { + rv = sf_parser_dict(&sfp, &key, &val); + if (rv != 0) { + if (rv == SF_ERR_EOF) { + break; + } + + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (key.len != 1) { + continue; + } + + switch (key.base[0]) { + case 'i': + if (val.type != SF_TYPE_BOOLEAN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + pri.inc = val.boolean; + + break; + case 'u': + if (val.type != SF_TYPE_INTEGER || + val.integer < NGHTTP2_EXTPRI_URGENCY_HIGH || + NGHTTP2_EXTPRI_URGENCY_LOW < val.integer) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + pri.urgency = (uint32_t)val.integer; + + break; + } + } + + *dest = pri; + + return 0; +} diff --git a/lib/nghttp2/lib/nghttp2_http.h b/lib/nghttp2/lib/nghttp2_http.h new file mode 100644 index 00000000000..d9992fe6908 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_http.h @@ -0,0 +1,100 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_HTTP_H +#define NGHTTP2_HTTP_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include "nghttp2_session.h" +#include "nghttp2_stream.h" + +/* + * This function is called when HTTP header field |nv| in |frame| is + * received for |stream|. This function will validate |nv| against + * the current state of stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_HTTP_HEADER + * Invalid HTTP header field was received. + * NGHTTP2_ERR_IGN_HTTP_HEADER + * Invalid HTTP header field was received but it can be treated as + * if it was not received because of compatibility reasons. + */ +int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream, + nghttp2_frame *frame, nghttp2_hd_nv *nv, + int trailer); + +/* + * This function is called when request header is received. This + * function performs validation and returns 0 if it succeeds, or -1. + */ +int nghttp2_http_on_request_headers(nghttp2_stream *stream, + nghttp2_frame *frame); + +/* + * This function is called when response header is received. This + * function performs validation and returns 0 if it succeeds, or -1. + */ +int nghttp2_http_on_response_headers(nghttp2_stream *stream); + +/* + * This function is called trailer header (for both request and + * response) is received. This function performs validation and + * returns 0 if it succeeds, or -1. + */ +int nghttp2_http_on_trailer_headers(nghttp2_stream *stream, + nghttp2_frame *frame); + +/* + * This function is called when END_STREAM flag is seen in incoming + * frame. This function performs validation and returns 0 if it + * succeeds, or -1. + */ +int nghttp2_http_on_remote_end_stream(nghttp2_stream *stream); + +/* + * This function is called when chunk of data is received. This + * function performs validation and returns 0 if it succeeds, or -1. + */ +int nghttp2_http_on_data_chunk(nghttp2_stream *stream, size_t n); + +/* + * This function inspects header field in |frame| and records its + * method in stream->http_flags. If frame->hd.type is neither + * NGHTTP2_HEADERS nor NGHTTP2_PUSH_PROMISE, this function does + * nothing. + */ +void nghttp2_http_record_request_method(nghttp2_stream *stream, + nghttp2_frame *frame); + +int nghttp2_http_parse_priority(nghttp2_extpri *dest, const uint8_t *value, + size_t valuelen); + +#endif /* NGHTTP2_HTTP_H */ diff --git a/lib/nghttp2/lib/nghttp2_int.h b/lib/nghttp2/lib/nghttp2_int.h new file mode 100644 index 00000000000..b23585ccb27 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_int.h @@ -0,0 +1,58 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_INT_H +#define NGHTTP2_INT_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +/* Macros, types and constants for internal use */ + +/* "less" function, return nonzero if |lhs| is less than |rhs|. */ +typedef int (*nghttp2_less)(const void *lhs, const void *rhs); + +/* Internal error code. They must be in the range [-499, -100], + inclusive. */ +typedef enum { + NGHTTP2_ERR_CREDENTIAL_PENDING = -101, + NGHTTP2_ERR_IGN_HEADER_BLOCK = -103, + NGHTTP2_ERR_IGN_PAYLOAD = -104, + /* + * Invalid HTTP header field was received but it can be treated as + * if it was not received because of compatibility reasons. + */ + NGHTTP2_ERR_IGN_HTTP_HEADER = -105, + /* + * Invalid HTTP header field was received, and it is ignored. + * Unlike NGHTTP2_ERR_IGN_HTTP_HEADER, this does not invoke + * nghttp2_on_invalid_header_callback. + */ + NGHTTP2_ERR_REMOVE_HTTP_HEADER = -106 +} nghttp2_internal_error; + +#endif /* NGHTTP2_INT_H */ diff --git a/lib/nghttp2/lib/nghttp2_map.c b/lib/nghttp2/lib/nghttp2_map.c new file mode 100644 index 00000000000..0aaaf29155c --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_map.c @@ -0,0 +1,338 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * 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 "nghttp2_map.h" + +#include +#include +#include + +#include "nghttp2_helper.h" + +#define NGHTTP2_INITIAL_TABLE_LENBITS 4 + +void nghttp2_map_init(nghttp2_map *map, nghttp2_mem *mem) { + map->mem = mem; + map->tablelen = 0; + map->tablelenbits = 0; + map->table = NULL; + map->size = 0; +} + +void nghttp2_map_free(nghttp2_map *map) { + if (!map) { + return; + } + + nghttp2_mem_free(map->mem, map->table); +} + +void nghttp2_map_each_free(nghttp2_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + uint32_t i; + nghttp2_map_bucket *bkt; + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + continue; + } + + func(bkt->data, ptr); + } +} + +int nghttp2_map_each(nghttp2_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + int rv; + uint32_t i; + nghttp2_map_bucket *bkt; + + if (map->size == 0) { + return 0; + } + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + continue; + } + + rv = func(bkt->data, ptr); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +static uint32_t hash(nghttp2_map_key_type key) { + return (uint32_t)key * 2654435769u; +} + +static size_t h2idx(uint32_t hash, uint32_t bits) { + return hash >> (32 - bits); +} + +static size_t distance(uint32_t tablelen, uint32_t tablelenbits, + nghttp2_map_bucket *bkt, size_t idx) { + return (idx - h2idx(bkt->hash, tablelenbits)) & (tablelen - 1); +} + +static void map_bucket_swap(nghttp2_map_bucket *bkt, uint32_t *phash, + nghttp2_map_key_type *pkey, void **pdata) { + uint32_t h = bkt->hash; + nghttp2_map_key_type key = bkt->key; + void *data = bkt->data; + + bkt->hash = *phash; + bkt->key = *pkey; + bkt->data = *pdata; + + *phash = h; + *pkey = key; + *pdata = data; +} + +static void map_bucket_set_data(nghttp2_map_bucket *bkt, uint32_t hash, + nghttp2_map_key_type key, void *data) { + bkt->hash = hash; + bkt->key = key; + bkt->data = data; +} + +#ifndef WIN32 +void nghttp2_map_print_distance(nghttp2_map *map) { + uint32_t i; + size_t idx; + nghttp2_map_bucket *bkt; + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + fprintf(stderr, "@%u \n", i); + continue; + } + + idx = h2idx(bkt->hash, map->tablelenbits); + fprintf(stderr, "@%u hash=%08x key=%d base=%zu distance=%zu\n", i, + bkt->hash, bkt->key, idx, + distance(map->tablelen, map->tablelenbits, bkt, idx)); + } +} +#endif /* !WIN32 */ + +static int insert(nghttp2_map_bucket *table, uint32_t tablelen, + uint32_t tablelenbits, uint32_t hash, + nghttp2_map_key_type key, void *data) { + size_t idx = h2idx(hash, tablelenbits); + size_t d = 0, dd; + nghttp2_map_bucket *bkt; + + for (;;) { + bkt = &table[idx]; + + if (bkt->data == NULL) { + map_bucket_set_data(bkt, hash, key, data); + return 0; + } + + dd = distance(tablelen, tablelenbits, bkt, idx); + if (d > dd) { + map_bucket_swap(bkt, &hash, &key, &data); + d = dd; + } else if (bkt->key == key) { + /* TODO This check is just a waste after first swap or if this + function is called from map_resize. That said, there is no + difference with or without this conditional in performance + wise. */ + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + ++d; + idx = (idx + 1) & (tablelen - 1); + } +} + +/* new_tablelen must be power of 2 and new_tablelen == (1 << + new_tablelenbits) must hold. */ +static int map_resize(nghttp2_map *map, uint32_t new_tablelen, + uint32_t new_tablelenbits) { + uint32_t i; + nghttp2_map_bucket *new_table; + nghttp2_map_bucket *bkt; + int rv; + (void)rv; + + new_table = + nghttp2_mem_calloc(map->mem, new_tablelen, sizeof(nghttp2_map_bucket)); + if (new_table == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + if (bkt->data == NULL) { + continue; + } + rv = insert(new_table, new_tablelen, new_tablelenbits, bkt->hash, bkt->key, + bkt->data); + + assert(0 == rv); + } + + nghttp2_mem_free(map->mem, map->table); + map->tablelen = new_tablelen; + map->tablelenbits = new_tablelenbits; + map->table = new_table; + + return 0; +} + +int nghttp2_map_insert(nghttp2_map *map, nghttp2_map_key_type key, void *data) { + int rv; + + assert(data); + + /* Load factor is 0.75 */ + if ((map->size + 1) * 4 > map->tablelen * 3) { + if (map->tablelen) { + rv = map_resize(map, map->tablelen * 2, map->tablelenbits + 1); + if (rv != 0) { + return rv; + } + } else { + rv = map_resize(map, 1 << NGHTTP2_INITIAL_TABLE_LENBITS, + NGHTTP2_INITIAL_TABLE_LENBITS); + if (rv != 0) { + return rv; + } + } + } + + rv = insert(map->table, map->tablelen, map->tablelenbits, hash(key), key, + data); + if (rv != 0) { + return rv; + } + ++map->size; + return 0; +} + +void *nghttp2_map_find(nghttp2_map *map, nghttp2_map_key_type key) { + uint32_t h; + size_t idx; + nghttp2_map_bucket *bkt; + size_t d = 0; + + if (map->size == 0) { + return NULL; + } + + h = hash(key); + idx = h2idx(h, map->tablelenbits); + + for (;;) { + bkt = &map->table[idx]; + + if (bkt->data == NULL || + d > distance(map->tablelen, map->tablelenbits, bkt, idx)) { + return NULL; + } + + if (bkt->key == key) { + return bkt->data; + } + + ++d; + idx = (idx + 1) & (map->tablelen - 1); + } +} + +int nghttp2_map_remove(nghttp2_map *map, nghttp2_map_key_type key) { + uint32_t h; + size_t idx, didx; + nghttp2_map_bucket *bkt; + size_t d = 0; + + if (map->size == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + h = hash(key); + idx = h2idx(h, map->tablelenbits); + + for (;;) { + bkt = &map->table[idx]; + + if (bkt->data == NULL || + d > distance(map->tablelen, map->tablelenbits, bkt, idx)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (bkt->key == key) { + map_bucket_set_data(bkt, 0, 0, NULL); + + didx = idx; + idx = (idx + 1) & (map->tablelen - 1); + + for (;;) { + bkt = &map->table[idx]; + if (bkt->data == NULL || + distance(map->tablelen, map->tablelenbits, bkt, idx) == 0) { + break; + } + + map->table[didx] = *bkt; + map_bucket_set_data(bkt, 0, 0, NULL); + didx = idx; + + idx = (idx + 1) & (map->tablelen - 1); + } + + --map->size; + + return 0; + } + + ++d; + idx = (idx + 1) & (map->tablelen - 1); + } +} + +void nghttp2_map_clear(nghttp2_map *map) { + if (map->tablelen == 0) { + return; + } + + memset(map->table, 0, sizeof(*map->table) * map->tablelen); + map->size = 0; +} + +size_t nghttp2_map_size(nghttp2_map *map) { return map->size; } diff --git a/lib/nghttp2/lib/nghttp2_map.h b/lib/nghttp2/lib/nghttp2_map.h new file mode 100644 index 00000000000..236d28296e3 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_map.h @@ -0,0 +1,138 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * 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 NGHTTP2_MAP_H +#define NGHTTP2_MAP_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#include "nghttp2_mem.h" + +/* Implementation of unordered map */ + +typedef int32_t nghttp2_map_key_type; + +typedef struct nghttp2_map_bucket { + uint32_t hash; + nghttp2_map_key_type key; + void *data; +} nghttp2_map_bucket; + +typedef struct nghttp2_map { + nghttp2_map_bucket *table; + nghttp2_mem *mem; + size_t size; + uint32_t tablelen; + uint32_t tablelenbits; +} nghttp2_map; + +/* + * Initializes the map |map|. + */ +void nghttp2_map_init(nghttp2_map *map, nghttp2_mem *mem); + +/* + * Deallocates any resources allocated for |map|. The stored entries + * are not freed by this function. Use nghttp2_map_each_free() to free + * each entries. + */ +void nghttp2_map_free(nghttp2_map *map); + +/* + * Deallocates each entries using |func| function and any resources + * allocated for |map|. The |func| function is responsible for freeing + * given the |data| object. The |ptr| will be passed to the |func| as + * send argument. The return value of the |func| will be ignored. + */ +void nghttp2_map_each_free(nghttp2_map *map, int (*func)(void *data, void *ptr), + void *ptr); + +/* + * Inserts the new |data| with the |key| to the map |map|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_INVALID_ARGUMENT + * The item associated by |key| already exists. + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_map_insert(nghttp2_map *map, nghttp2_map_key_type key, void *data); + +/* + * Returns the data associated by the key |key|. If there is no such + * data, this function returns NULL. + */ +void *nghttp2_map_find(nghttp2_map *map, nghttp2_map_key_type key); + +/* + * Removes the data associated by the key |key| from the |map|. The + * removed data is not freed by this function. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_INVALID_ARGUMENT + * The data associated by |key| does not exist. + */ +int nghttp2_map_remove(nghttp2_map *map, nghttp2_map_key_type key); + +/* + * Removes all entries from |map|. + */ +void nghttp2_map_clear(nghttp2_map *map); + +/* + * Returns the number of items stored in the map |map|. + */ +size_t nghttp2_map_size(nghttp2_map *map); + +/* + * Applies the function |func| to each data in the |map| with the + * optional user supplied pointer |ptr|. + * + * If the |func| returns 0, this function calls the |func| with the + * next data. If the |func| returns nonzero, it will not call the + * |func| for further entries and return the return value of the + * |func| immediately. Thus, this function returns 0 if all the + * invocations of the |func| return 0, or nonzero value which the last + * invocation of |func| returns. + * + * Don't use this function to free each data. Use + * nghttp2_map_each_free() instead. + */ +int nghttp2_map_each(nghttp2_map *map, int (*func)(void *data, void *ptr), + void *ptr); + +#ifndef WIN32 +void nghttp2_map_print_distance(nghttp2_map *map); +#endif /* !WIN32 */ + +#endif /* NGHTTP2_MAP_H */ diff --git a/lib/nghttp2/lib/nghttp2_mem.c b/lib/nghttp2/lib/nghttp2_mem.c new file mode 100644 index 00000000000..6a449cffd70 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_mem.c @@ -0,0 +1,74 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_mem.h" + +static void *default_malloc(size_t size, void *mem_user_data) { + (void)mem_user_data; + + return malloc(size); +} + +static void default_free(void *ptr, void *mem_user_data) { + (void)mem_user_data; + + free(ptr); +} + +static void *default_calloc(size_t nmemb, size_t size, void *mem_user_data) { + (void)mem_user_data; + + return calloc(nmemb, size); +} + +static void *default_realloc(void *ptr, size_t size, void *mem_user_data) { + (void)mem_user_data; + + return realloc(ptr, size); +} + +static nghttp2_mem mem_default = {NULL, default_malloc, default_free, + default_calloc, default_realloc}; + +nghttp2_mem *nghttp2_mem_default(void) { return &mem_default; } + +void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size) { + return mem->malloc(size, mem->mem_user_data); +} + +void nghttp2_mem_free(nghttp2_mem *mem, void *ptr) { + mem->free(ptr, mem->mem_user_data); +} + +void nghttp2_mem_free2(nghttp2_free free_func, void *ptr, void *mem_user_data) { + free_func(ptr, mem_user_data); +} + +void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size) { + return mem->calloc(nmemb, size, mem->mem_user_data); +} + +void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size) { + return mem->realloc(ptr, size, mem->mem_user_data); +} diff --git a/lib/nghttp2/lib/nghttp2_mem.h b/lib/nghttp2/lib/nghttp2_mem.h new file mode 100644 index 00000000000..f83dbcb8f9a --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_mem.h @@ -0,0 +1,45 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_MEM_H +#define NGHTTP2_MEM_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +/* The default, system standard memory allocator */ +nghttp2_mem *nghttp2_mem_default(void); + +/* Convenient wrapper functions to call allocator function in + |mem|. */ +void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size); +void nghttp2_mem_free(nghttp2_mem *mem, void *ptr); +void nghttp2_mem_free2(nghttp2_free free_func, void *ptr, void *mem_user_data); +void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size); +void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size); + +#endif /* NGHTTP2_MEM_H */ diff --git a/lib/nghttp2/lib/nghttp2_net.h b/lib/nghttp2/lib/nghttp2_net.h new file mode 100644 index 00000000000..521f98143e4 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_net.h @@ -0,0 +1,91 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_NET_H +#define NGHTTP2_NET_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#ifdef HAVE_ARPA_INET_H +# include +#endif /* HAVE_ARPA_INET_H */ + +#ifdef HAVE_NETINET_IN_H +# include +#endif /* HAVE_NETINET_IN_H */ + +#include + +#if defined(WIN32) +/* Windows requires ws2_32 library for ntonl family functions. We + define inline functions for those function so that we don't have + dependency on that lib. */ + +# ifdef _MSC_VER +# define STIN static __inline +# else +# define STIN static inline +# endif + +STIN uint32_t htonl(uint32_t hostlong) { + uint32_t res; + unsigned char *p = (unsigned char *)&res; + *p++ = (unsigned char)(hostlong >> 24); + *p++ = (hostlong >> 16) & 0xffu; + *p++ = (hostlong >> 8) & 0xffu; + *p = hostlong & 0xffu; + return res; +} + +STIN uint16_t htons(uint16_t hostshort) { + uint16_t res; + unsigned char *p = (unsigned char *)&res; + *p++ = (unsigned char)(hostshort >> 8); + *p = hostshort & 0xffu; + return res; +} + +STIN uint32_t ntohl(uint32_t netlong) { + uint32_t res; + unsigned char *p = (unsigned char *)&netlong; + res = (uint32_t)(*p++ << 24); + res += (uint32_t)(*p++ << 16); + res += (uint32_t)(*p++ << 8); + res += *p; + return res; +} + +STIN uint16_t ntohs(uint16_t netshort) { + uint16_t res; + unsigned char *p = (unsigned char *)&netshort; + res = (uint16_t)(*p++ << 8); + res += *p; + return res; +} + +#endif /* WIN32 */ + +#endif /* NGHTTP2_NET_H */ diff --git a/lib/nghttp2/lib/nghttp2_npn.c b/lib/nghttp2/lib/nghttp2_npn.c new file mode 100644 index 00000000000..d1384c80758 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_npn.c @@ -0,0 +1,57 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_npn.h" + +#include + +static int select_next_protocol(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const char *key, unsigned int keylen) { + unsigned int i; + for (i = 0; i + keylen <= inlen; i += (unsigned int)(in[i] + 1)) { + if (memcmp(&in[i], key, keylen) == 0) { + *out = (unsigned char *)&in[i + 1]; + *outlen = in[i]; + return 0; + } + } + return -1; +} + +#define NGHTTP2_HTTP_1_1_ALPN "\x8http/1.1" +#define NGHTTP2_HTTP_1_1_ALPN_LEN (sizeof(NGHTTP2_HTTP_1_1_ALPN) - 1) + +int nghttp2_select_next_protocol(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen) { + if (select_next_protocol(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN, + NGHTTP2_PROTO_ALPN_LEN) == 0) { + return 1; + } + if (select_next_protocol(out, outlen, in, inlen, NGHTTP2_HTTP_1_1_ALPN, + NGHTTP2_HTTP_1_1_ALPN_LEN) == 0) { + return 0; + } + return -1; +} diff --git a/lib/nghttp2/lib/nghttp2_npn.h b/lib/nghttp2/lib/nghttp2_npn.h new file mode 100644 index 00000000000..c6f1c04b683 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_npn.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_NPN_H +#define NGHTTP2_NPN_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#endif /* NGHTTP2_NPN_H */ diff --git a/lib/nghttp2/lib/nghttp2_option.c b/lib/nghttp2/lib/nghttp2_option.c new file mode 100644 index 00000000000..43d4e952291 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_option.c @@ -0,0 +1,152 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_option.h" + +#include "nghttp2_session.h" + +int nghttp2_option_new(nghttp2_option **option_ptr) { + *option_ptr = calloc(1, sizeof(nghttp2_option)); + + if (*option_ptr == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + return 0; +} + +void nghttp2_option_del(nghttp2_option *option) { free(option); } + +void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE; + option->no_auto_window_update = val; +} + +void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, + uint32_t val) { + option->opt_set_mask |= NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS; + option->peer_max_concurrent_streams = val; +} + +void nghttp2_option_set_no_recv_client_magic(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC; + option->no_recv_client_magic = val; +} + +void nghttp2_option_set_no_http_messaging(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_HTTP_MESSAGING; + option->no_http_messaging = val; +} + +void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, + uint32_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS; + option->max_reserved_remote_streams = val; +} + +static void set_ext_type(uint8_t *ext_types, uint8_t type) { + ext_types[type / 8] = (uint8_t)(ext_types[type / 8] | (1 << (type & 0x7))); +} + +void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, + uint8_t type) { + if (type < 10) { + return; + } + + option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES; + set_ext_type(option->user_recv_ext_types, type); +} + +void nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, + uint8_t type) { + switch (type) { + case NGHTTP2_ALTSVC: + option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; + option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ALTSVC; + return; + case NGHTTP2_ORIGIN: + option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; + option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_ORIGIN; + return; + case NGHTTP2_PRIORITY_UPDATE: + option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; + option->builtin_recv_ext_types |= NGHTTP2_TYPEMASK_PRIORITY_UPDATE; + return; + default: + return; + } +} + +void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_PING_ACK; + option->no_auto_ping_ack = val; +} + +void nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, + size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH; + option->max_send_header_block_length = val; +} + +void nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option, + size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE; + option->max_deflate_dynamic_table_size = val; +} + +void nghttp2_option_set_no_closed_streams(nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_NO_CLOSED_STREAMS; + option->no_closed_streams = val; +} + +void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_OUTBOUND_ACK; + option->max_outbound_ack = val; +} + +void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) { + option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS; + option->max_settings = val; +} + +void nghttp2_option_set_server_fallback_rfc7540_priorities( + nghttp2_option *option, int val) { + option->opt_set_mask |= NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES; + option->server_fallback_rfc7540_priorities = val; +} + +void nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation( + nghttp2_option *option, int val) { + option->opt_set_mask |= + NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION; + option->no_rfc9113_leading_and_trailing_ws_validation = val; +} + +void nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option, + uint64_t burst, uint64_t rate) { + option->opt_set_mask |= NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT; + option->stream_reset_burst = burst; + option->stream_reset_rate = rate; +} diff --git a/lib/nghttp2/lib/nghttp2_option.h b/lib/nghttp2/lib/nghttp2_option.h new file mode 100644 index 00000000000..2259e1849d8 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_option.h @@ -0,0 +1,152 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_OPTION_H +#define NGHTTP2_OPTION_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +/** + * Configuration options + */ +typedef enum { + /** + * This option prevents the library from sending WINDOW_UPDATE for a + * connection automatically. If this option is set to nonzero, the + * library won't send WINDOW_UPDATE for DATA until application calls + * nghttp2_session_consume() to indicate the amount of consumed + * DATA. By default, this option is set to zero. + */ + NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE = 1, + /** + * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of + * remote endpoint as if it is received in SETTINGS frame. Without + * specifying this option, before the local endpoint receives + * SETTINGS_MAX_CONCURRENT_STREAMS in SETTINGS frame from remote + * endpoint, SETTINGS_MAX_CONCURRENT_STREAMS is unlimited. This may + * cause problem if local endpoint submits lots of requests + * initially and sending them at once to the remote peer may lead to + * the rejection of some requests. Specifying this option to the + * sensible value, say 100, may avoid this kind of issue. This value + * will be overwritten if the local endpoint receives + * SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint. + */ + NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1, + NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC = 1 << 2, + NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3, + NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4, + NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5, + NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6, + NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7, + NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH = 1 << 8, + NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 1 << 9, + NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10, + NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11, + NGHTTP2_OPT_MAX_SETTINGS = 1 << 12, + NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13, + NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 14, + NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT = 1 << 15, +} nghttp2_option_flag; + +/** + * Struct to store option values for nghttp2_session. + */ +struct nghttp2_option { + /** + * NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT + */ + uint64_t stream_reset_burst; + uint64_t stream_reset_rate; + /** + * NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH + */ + size_t max_send_header_block_length; + /** + * NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE + */ + size_t max_deflate_dynamic_table_size; + /** + * NGHTTP2_OPT_MAX_OUTBOUND_ACK + */ + size_t max_outbound_ack; + /** + * NGHTTP2_OPT_MAX_SETTINGS + */ + size_t max_settings; + /** + * Bitwise OR of nghttp2_option_flag to determine that which fields + * are specified. + */ + uint32_t opt_set_mask; + /** + * NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS + */ + uint32_t peer_max_concurrent_streams; + /** + * NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS + */ + uint32_t max_reserved_remote_streams; + /** + * NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES + */ + uint32_t builtin_recv_ext_types; + /** + * NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE + */ + int no_auto_window_update; + /** + * NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC + */ + int no_recv_client_magic; + /** + * NGHTTP2_OPT_NO_HTTP_MESSAGING + */ + int no_http_messaging; + /** + * NGHTTP2_OPT_NO_AUTO_PING_ACK + */ + int no_auto_ping_ack; + /** + * NGHTTP2_OPT_NO_CLOSED_STREAMS + */ + int no_closed_streams; + /** + * NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES + */ + int server_fallback_rfc7540_priorities; + /** + * NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION + */ + int no_rfc9113_leading_and_trailing_ws_validation; + /** + * NGHTTP2_OPT_USER_RECV_EXT_TYPES + */ + uint8_t user_recv_ext_types[32]; +}; + +#endif /* NGHTTP2_OPTION_H */ diff --git a/lib/nghttp2/lib/nghttp2_outbound_item.c b/lib/nghttp2/lib/nghttp2_outbound_item.c new file mode 100644 index 00000000000..2a3041db195 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_outbound_item.c @@ -0,0 +1,130 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_outbound_item.h" + +#include +#include + +void nghttp2_outbound_item_init(nghttp2_outbound_item *item) { + item->cycle = 0; + item->qnext = NULL; + item->queued = 0; + + memset(&item->aux_data, 0, sizeof(nghttp2_aux_data)); +} + +void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) { + nghttp2_frame *frame; + + if (item == NULL) { + return; + } + + frame = &item->frame; + + switch (frame->hd.type) { + case NGHTTP2_DATA: + nghttp2_frame_data_free(&frame->data); + break; + case NGHTTP2_HEADERS: + nghttp2_frame_headers_free(&frame->headers, mem); + break; + case NGHTTP2_PRIORITY: + nghttp2_frame_priority_free(&frame->priority); + break; + case NGHTTP2_RST_STREAM: + nghttp2_frame_rst_stream_free(&frame->rst_stream); + break; + case NGHTTP2_SETTINGS: + nghttp2_frame_settings_free(&frame->settings, mem); + break; + case NGHTTP2_PUSH_PROMISE: + nghttp2_frame_push_promise_free(&frame->push_promise, mem); + break; + case NGHTTP2_PING: + nghttp2_frame_ping_free(&frame->ping); + break; + case NGHTTP2_GOAWAY: + nghttp2_frame_goaway_free(&frame->goaway, mem); + break; + case NGHTTP2_WINDOW_UPDATE: + nghttp2_frame_window_update_free(&frame->window_update); + break; + default: { + nghttp2_ext_aux_data *aux_data; + + aux_data = &item->aux_data.ext; + + if (aux_data->builtin == 0) { + nghttp2_frame_extension_free(&frame->ext); + break; + } + + switch (frame->hd.type) { + case NGHTTP2_ALTSVC: + nghttp2_frame_altsvc_free(&frame->ext, mem); + break; + case NGHTTP2_ORIGIN: + nghttp2_frame_origin_free(&frame->ext, mem); + break; + case NGHTTP2_PRIORITY_UPDATE: + nghttp2_frame_priority_update_free(&frame->ext, mem); + break; + default: + assert(0); + break; + } + } + } +} + +void nghttp2_outbound_queue_init(nghttp2_outbound_queue *q) { + q->head = q->tail = NULL; + q->n = 0; +} + +void nghttp2_outbound_queue_push(nghttp2_outbound_queue *q, + nghttp2_outbound_item *item) { + if (q->tail) { + q->tail = q->tail->qnext = item; + } else { + q->head = q->tail = item; + } + ++q->n; +} + +void nghttp2_outbound_queue_pop(nghttp2_outbound_queue *q) { + nghttp2_outbound_item *item; + if (!q->head) { + return; + } + item = q->head; + q->head = q->head->qnext; + item->qnext = NULL; + if (!q->head) { + q->tail = NULL; + } + --q->n; +} diff --git a/lib/nghttp2/lib/nghttp2_outbound_item.h b/lib/nghttp2/lib/nghttp2_outbound_item.h new file mode 100644 index 00000000000..bd4611b551b --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_outbound_item.h @@ -0,0 +1,166 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_OUTBOUND_ITEM_H +#define NGHTTP2_OUTBOUND_ITEM_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include "nghttp2_frame.h" +#include "nghttp2_mem.h" + +/* struct used for HEADERS and PUSH_PROMISE frame */ +typedef struct { + nghttp2_data_provider data_prd; + void *stream_user_data; + /* error code when request HEADERS is canceled by RST_STREAM while + it is in queue. */ + uint32_t error_code; + /* nonzero if request HEADERS is canceled. The error code is stored + in |error_code|. */ + uint8_t canceled; +} nghttp2_headers_aux_data; + +/* struct used for DATA frame */ +typedef struct { + /** + * The data to be sent for this DATA frame. + */ + nghttp2_data_provider data_prd; + /** + * The flags of DATA frame. We use separate flags here and + * nghttp2_data frame. The latter contains flags actually sent to + * peer. This |flags| may contain NGHTTP2_FLAG_END_STREAM and only + * when |eof| becomes nonzero, flags in nghttp2_data has + * NGHTTP2_FLAG_END_STREAM set. + */ + uint8_t flags; + /** + * The flag to indicate whether EOF was reached or not. Initially + * |eof| is 0. It becomes 1 after all data were read. + */ + uint8_t eof; + /** + * The flag to indicate that NGHTTP2_DATA_FLAG_NO_COPY is used. + */ + uint8_t no_copy; +} nghttp2_data_aux_data; + +typedef enum { + NGHTTP2_GOAWAY_AUX_NONE = 0x0, + /* indicates that session should be terminated after the + transmission of this frame. */ + NGHTTP2_GOAWAY_AUX_TERM_ON_SEND = 0x1, + /* indicates that this GOAWAY is just a notification for graceful + shutdown. No nghttp2_session.goaway_flags should be updated on + the reaction to this frame. */ + NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE = 0x2 +} nghttp2_goaway_aux_flag; + +/* struct used for GOAWAY frame */ +typedef struct { + /* bitwise-OR of one or more of nghttp2_goaway_aux_flag. */ + uint8_t flags; +} nghttp2_goaway_aux_data; + +/* struct used for extension frame */ +typedef struct { + /* nonzero if this extension frame is serialized by library + function, instead of user-defined callbacks. */ + uint8_t builtin; +} nghttp2_ext_aux_data; + +/* Additional data which cannot be stored in nghttp2_frame struct */ +typedef union { + nghttp2_data_aux_data data; + nghttp2_headers_aux_data headers; + nghttp2_goaway_aux_data goaway; + nghttp2_ext_aux_data ext; +} nghttp2_aux_data; + +struct nghttp2_outbound_item; +typedef struct nghttp2_outbound_item nghttp2_outbound_item; + +struct nghttp2_outbound_item { + nghttp2_frame frame; + /* Storage for extension frame payload. frame->ext.payload points + to this structure to avoid frequent memory allocation. */ + nghttp2_ext_frame_payload ext_frame_payload; + nghttp2_aux_data aux_data; + /* The priority used in priority comparison. Smaller is served + earlier. For PING, SETTINGS and non-DATA frames (excluding + response HEADERS frame) have dedicated cycle value defined above. + For DATA frame, cycle is computed by taking into account of + effective weight and frame payload length previously sent, so + that the amount of transmission is distributed across streams + proportional to effective weight (inside a tree). */ + uint64_t cycle; + nghttp2_outbound_item *qnext; + /* nonzero if this object is queued, except for DATA or HEADERS + which are attached to stream as item. */ + uint8_t queued; +}; + +/* + * Initializes |item|. No memory allocation is done in this function. + * Don't call nghttp2_outbound_item_free() until frame member is + * initialized. + */ +void nghttp2_outbound_item_init(nghttp2_outbound_item *item); + +/* + * Deallocates resource for |item|. If |item| is NULL, this function + * does nothing. + */ +void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem); + +/* + * queue for nghttp2_outbound_item. + */ +typedef struct { + nghttp2_outbound_item *head, *tail; + /* number of items in this queue. */ + size_t n; +} nghttp2_outbound_queue; + +void nghttp2_outbound_queue_init(nghttp2_outbound_queue *q); + +/* Pushes |item| into |q| */ +void nghttp2_outbound_queue_push(nghttp2_outbound_queue *q, + nghttp2_outbound_item *item); + +/* Pops |item| at the top from |q|. If |q| is empty, nothing + happens. */ +void nghttp2_outbound_queue_pop(nghttp2_outbound_queue *q); + +/* Returns the top item. */ +#define nghttp2_outbound_queue_top(Q) ((Q)->head) + +/* Returns the size of the queue */ +#define nghttp2_outbound_queue_size(Q) ((Q)->n) + +#endif /* NGHTTP2_OUTBOUND_ITEM_H */ diff --git a/lib/nghttp2/lib/nghttp2_pq.c b/lib/nghttp2/lib/nghttp2_pq.c new file mode 100644 index 00000000000..64353acc961 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_pq.c @@ -0,0 +1,183 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_pq.h" + +#include +#include + +#include "nghttp2_helper.h" + +void nghttp2_pq_init(nghttp2_pq *pq, nghttp2_less less, nghttp2_mem *mem) { + pq->mem = mem; + pq->capacity = 0; + pq->q = NULL; + pq->length = 0; + pq->less = less; +} + +void nghttp2_pq_free(nghttp2_pq *pq) { + nghttp2_mem_free(pq->mem, pq->q); + pq->q = NULL; +} + +static void swap(nghttp2_pq *pq, size_t i, size_t j) { + nghttp2_pq_entry *a = pq->q[i]; + nghttp2_pq_entry *b = pq->q[j]; + + pq->q[i] = b; + b->index = i; + pq->q[j] = a; + a->index = j; +} + +static void bubble_up(nghttp2_pq *pq, size_t index) { + size_t parent; + while (index != 0) { + parent = (index - 1) / 2; + if (!pq->less(pq->q[index], pq->q[parent])) { + return; + } + swap(pq, parent, index); + index = parent; + } +} + +int nghttp2_pq_push(nghttp2_pq *pq, nghttp2_pq_entry *item) { + if (pq->capacity <= pq->length) { + void *nq; + size_t ncapacity; + + ncapacity = nghttp2_max(4, (pq->capacity * 2)); + + nq = nghttp2_mem_realloc(pq->mem, pq->q, + ncapacity * sizeof(nghttp2_pq_entry *)); + if (nq == NULL) { + return NGHTTP2_ERR_NOMEM; + } + pq->capacity = ncapacity; + pq->q = nq; + } + pq->q[pq->length] = item; + item->index = pq->length; + ++pq->length; + bubble_up(pq, pq->length - 1); + return 0; +} + +nghttp2_pq_entry *nghttp2_pq_top(nghttp2_pq *pq) { + if (pq->length == 0) { + return NULL; + } else { + return pq->q[0]; + } +} + +static void bubble_down(nghttp2_pq *pq, size_t index) { + size_t i, j, minindex; + for (;;) { + j = index * 2 + 1; + minindex = index; + for (i = 0; i < 2; ++i, ++j) { + if (j >= pq->length) { + break; + } + if (pq->less(pq->q[j], pq->q[minindex])) { + minindex = j; + } + } + if (minindex == index) { + return; + } + swap(pq, index, minindex); + index = minindex; + } +} + +void nghttp2_pq_pop(nghttp2_pq *pq) { + if (pq->length > 0) { + pq->q[0] = pq->q[pq->length - 1]; + pq->q[0]->index = 0; + --pq->length; + bubble_down(pq, 0); + } +} + +void nghttp2_pq_remove(nghttp2_pq *pq, nghttp2_pq_entry *item) { + assert(pq->q[item->index] == item); + + if (item->index == 0) { + nghttp2_pq_pop(pq); + return; + } + + if (item->index == pq->length - 1) { + --pq->length; + return; + } + + pq->q[item->index] = pq->q[pq->length - 1]; + pq->q[item->index]->index = item->index; + --pq->length; + + if (pq->less(item, pq->q[item->index])) { + bubble_down(pq, item->index); + } else { + bubble_up(pq, item->index); + } +} + +int nghttp2_pq_empty(nghttp2_pq *pq) { return pq->length == 0; } + +size_t nghttp2_pq_size(nghttp2_pq *pq) { return pq->length; } + +void nghttp2_pq_update(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg) { + size_t i; + int rv = 0; + if (pq->length == 0) { + return; + } + for (i = 0; i < pq->length; ++i) { + rv |= (*fun)(pq->q[i], arg); + } + if (rv) { + for (i = pq->length; i > 0; --i) { + bubble_down(pq, i - 1); + } + } +} + +int nghttp2_pq_each(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg) { + size_t i; + + if (pq->length == 0) { + return 0; + } + for (i = 0; i < pq->length; ++i) { + if ((*fun)(pq->q[i], arg)) { + return 1; + } + } + return 0; +} diff --git a/lib/nghttp2/lib/nghttp2_pq.h b/lib/nghttp2/lib/nghttp2_pq.h new file mode 100644 index 00000000000..c8d90ef2cc8 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_pq.h @@ -0,0 +1,124 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_PQ_H +#define NGHTTP2_PQ_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include "nghttp2_int.h" +#include "nghttp2_mem.h" + +/* Implementation of priority queue */ + +typedef struct { + size_t index; +} nghttp2_pq_entry; + +typedef struct { + /* The pointer to the pointer to the item stored */ + nghttp2_pq_entry **q; + /* Memory allocator */ + nghttp2_mem *mem; + /* The number of items stored */ + size_t length; + /* The maximum number of items this pq can store. This is + automatically extended when length is reached to this value. */ + size_t capacity; + /* The less function between items */ + nghttp2_less less; +} nghttp2_pq; + +/* + * Initializes priority queue |pq| with compare function |cmp|. + */ +void nghttp2_pq_init(nghttp2_pq *pq, nghttp2_less less, nghttp2_mem *mem); + +/* + * Deallocates any resources allocated for |pq|. The stored items are + * not freed by this function. + */ +void nghttp2_pq_free(nghttp2_pq *pq); + +/* + * Adds |item| to the priority queue |pq|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_pq_push(nghttp2_pq *pq, nghttp2_pq_entry *item); + +/* + * Returns item at the top of the queue |pq|. If the queue is empty, + * this function returns NULL. + */ +nghttp2_pq_entry *nghttp2_pq_top(nghttp2_pq *pq); + +/* + * Pops item at the top of the queue |pq|. The popped item is not + * freed by this function. + */ +void nghttp2_pq_pop(nghttp2_pq *pq); + +/* + * Returns nonzero if the queue |pq| is empty. + */ +int nghttp2_pq_empty(nghttp2_pq *pq); + +/* + * Returns the number of items in the queue |pq|. + */ +size_t nghttp2_pq_size(nghttp2_pq *pq); + +typedef int (*nghttp2_pq_item_cb)(nghttp2_pq_entry *item, void *arg); + +/* + * Updates each item in |pq| using function |fun| and re-construct + * priority queue. The |fun| must return non-zero if it modifies the + * item in a way that it affects ordering in the priority queue. The + * |arg| is passed to the 2nd parameter of |fun|. + */ +void nghttp2_pq_update(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg); + +/* + * Applies |fun| to each item in |pq|. The |arg| is passed as arg + * parameter to callback function. This function must not change the + * ordering key. If the return value from callback is nonzero, this + * function returns 1 immediately without iterating remaining items. + * Otherwise this function returns 0. + */ +int nghttp2_pq_each(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg); + +/* + * Removes |item| from priority queue. + */ +void nghttp2_pq_remove(nghttp2_pq *pq, nghttp2_pq_entry *item); + +#endif /* NGHTTP2_PQ_H */ diff --git a/lib/nghttp2/lib/nghttp2_priority_spec.c b/lib/nghttp2/lib/nghttp2_priority_spec.c new file mode 100644 index 00000000000..c2196e30630 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_priority_spec.c @@ -0,0 +1,52 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_priority_spec.h" + +void nghttp2_priority_spec_init(nghttp2_priority_spec *pri_spec, + int32_t stream_id, int32_t weight, + int exclusive) { + pri_spec->stream_id = stream_id; + pri_spec->weight = weight; + pri_spec->exclusive = exclusive != 0; +} + +void nghttp2_priority_spec_default_init(nghttp2_priority_spec *pri_spec) { + pri_spec->stream_id = 0; + pri_spec->weight = NGHTTP2_DEFAULT_WEIGHT; + pri_spec->exclusive = 0; +} + +int nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec) { + return pri_spec->stream_id == 0 && + pri_spec->weight == NGHTTP2_DEFAULT_WEIGHT && pri_spec->exclusive == 0; +} + +void nghttp2_priority_spec_normalize_weight(nghttp2_priority_spec *pri_spec) { + if (pri_spec->weight < NGHTTP2_MIN_WEIGHT) { + pri_spec->weight = NGHTTP2_MIN_WEIGHT; + } else if (pri_spec->weight > NGHTTP2_MAX_WEIGHT) { + pri_spec->weight = NGHTTP2_MAX_WEIGHT; + } +} diff --git a/lib/nghttp2/lib/nghttp2_priority_spec.h b/lib/nghttp2/lib/nghttp2_priority_spec.h new file mode 100644 index 00000000000..92ece822a8f --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_priority_spec.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_PRIORITY_SPEC_H +#define NGHTTP2_PRIORITY_SPEC_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +/* + * This function normalizes pri_spec->weight if it is out of range. + * If pri_spec->weight is less than NGHTTP2_MIN_WEIGHT, it is set to + * NGHTTP2_MIN_WEIGHT. If pri_spec->weight is larger than + * NGHTTP2_MAX_WEIGHT, it is set to NGHTTP2_MAX_WEIGHT. + */ +void nghttp2_priority_spec_normalize_weight(nghttp2_priority_spec *pri_spec); + +#endif /* NGHTTP2_PRIORITY_SPEC_H */ diff --git a/lib/nghttp2/lib/nghttp2_queue.c b/lib/nghttp2/lib/nghttp2_queue.c new file mode 100644 index 00000000000..055eb69c7e5 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_queue.c @@ -0,0 +1,85 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_queue.h" + +#include +#include + +void nghttp2_queue_init(nghttp2_queue *queue) { + queue->front = queue->back = NULL; +} + +void nghttp2_queue_free(nghttp2_queue *queue) { + if (!queue) { + return; + } else { + nghttp2_queue_cell *p = queue->front; + while (p) { + nghttp2_queue_cell *next = p->next; + free(p); + p = next; + } + } +} + +int nghttp2_queue_push(nghttp2_queue *queue, void *data) { + nghttp2_queue_cell *new_cell = + (nghttp2_queue_cell *)malloc(sizeof(nghttp2_queue_cell)); + if (!new_cell) { + return NGHTTP2_ERR_NOMEM; + } + new_cell->data = data; + new_cell->next = NULL; + if (queue->back) { + queue->back->next = new_cell; + queue->back = new_cell; + + } else { + queue->front = queue->back = new_cell; + } + return 0; +} + +void nghttp2_queue_pop(nghttp2_queue *queue) { + nghttp2_queue_cell *front = queue->front; + assert(front); + queue->front = front->next; + if (front == queue->back) { + queue->back = NULL; + } + free(front); +} + +void *nghttp2_queue_front(nghttp2_queue *queue) { + assert(queue->front); + return queue->front->data; +} + +void *nghttp2_queue_back(nghttp2_queue *queue) { + assert(queue->back); + return queue->back->data; +} + +int nghttp2_queue_empty(nghttp2_queue *queue) { return queue->front == NULL; } diff --git a/lib/nghttp2/lib/nghttp2_queue.h b/lib/nghttp2/lib/nghttp2_queue.h new file mode 100644 index 00000000000..a06fa6c7a46 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_queue.h @@ -0,0 +1,51 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_QUEUE_H +#define NGHTTP2_QUEUE_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include + +typedef struct nghttp2_queue_cell { + void *data; + struct nghttp2_queue_cell *next; +} nghttp2_queue_cell; + +typedef struct { + nghttp2_queue_cell *front, *back; +} nghttp2_queue; + +void nghttp2_queue_init(nghttp2_queue *queue); +void nghttp2_queue_free(nghttp2_queue *queue); +int nghttp2_queue_push(nghttp2_queue *queue, void *data); +void nghttp2_queue_pop(nghttp2_queue *queue); +void *nghttp2_queue_front(nghttp2_queue *queue); +void *nghttp2_queue_back(nghttp2_queue *queue); +int nghttp2_queue_empty(nghttp2_queue *queue); + +#endif /* NGHTTP2_QUEUE_H */ diff --git a/lib/nghttp2/lib/nghttp2_ratelim.c b/lib/nghttp2/lib/nghttp2_ratelim.c new file mode 100644 index 00000000000..7011655b006 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_ratelim.c @@ -0,0 +1,75 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 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 "nghttp2_ratelim.h" +#include "nghttp2_helper.h" + +void nghttp2_ratelim_init(nghttp2_ratelim *rl, uint64_t burst, uint64_t rate) { + rl->val = rl->burst = burst; + rl->rate = rate; + rl->tstamp = 0; +} + +void nghttp2_ratelim_update(nghttp2_ratelim *rl, uint64_t tstamp) { + uint64_t d, gain; + + if (tstamp == rl->tstamp) { + return; + } + + if (tstamp > rl->tstamp) { + d = tstamp - rl->tstamp; + } else { + d = 1; + } + + rl->tstamp = tstamp; + + if (UINT64_MAX / d < rl->rate) { + rl->val = rl->burst; + + return; + } + + gain = rl->rate * d; + + if (UINT64_MAX - gain < rl->val) { + rl->val = rl->burst; + + return; + } + + rl->val += gain; + rl->val = nghttp2_min(rl->val, rl->burst); +} + +int nghttp2_ratelim_drain(nghttp2_ratelim *rl, uint64_t n) { + if (rl->val < n) { + return -1; + } + + rl->val -= n; + + return 0; +} diff --git a/lib/nghttp2/lib/nghttp2_ratelim.h b/lib/nghttp2/lib/nghttp2_ratelim.h new file mode 100644 index 00000000000..866ed3f00ae --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_ratelim.h @@ -0,0 +1,57 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 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 NGHTTP2_RATELIM_H +#define NGHTTP2_RATELIM_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +typedef struct nghttp2_ratelim { + /* burst is the maximum value of val. */ + uint64_t burst; + /* rate is the amount of value that is regenerated per 1 tstamp. */ + uint64_t rate; + /* val is the amount of value available to drain. */ + uint64_t val; + /* tstamp is the last timestamp in second resolution that is known + to this object. */ + uint64_t tstamp; +} nghttp2_ratelim; + +/* nghttp2_ratelim_init initializes |rl| with the given parameters. */ +void nghttp2_ratelim_init(nghttp2_ratelim *rl, uint64_t burst, uint64_t rate); + +/* nghttp2_ratelim_update updates rl->val with the current |tstamp| + given in second resolution. */ +void nghttp2_ratelim_update(nghttp2_ratelim *rl, uint64_t tstamp); + +/* nghttp2_ratelim_drain drains |n| from rl->val. It returns 0 if it + succeeds, or -1. */ +int nghttp2_ratelim_drain(nghttp2_ratelim *rl, uint64_t n); + +#endif /* NGHTTP2_RATELIM_H */ diff --git a/lib/nghttp2/lib/nghttp2_rcbuf.c b/lib/nghttp2/lib/nghttp2_rcbuf.c new file mode 100644 index 00000000000..7e7814d2d3c --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_rcbuf.c @@ -0,0 +1,102 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_rcbuf.h" + +#include +#include + +#include "nghttp2_mem.h" +#include "nghttp2_helper.h" + +int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, + nghttp2_mem *mem) { + uint8_t *p; + + p = nghttp2_mem_malloc(mem, sizeof(nghttp2_rcbuf) + size); + if (p == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + *rcbuf_ptr = (void *)p; + + (*rcbuf_ptr)->mem_user_data = mem->mem_user_data; + (*rcbuf_ptr)->free = mem->free; + (*rcbuf_ptr)->base = p + sizeof(nghttp2_rcbuf); + (*rcbuf_ptr)->len = size; + (*rcbuf_ptr)->ref = 1; + + return 0; +} + +int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src, + size_t srclen, nghttp2_mem *mem) { + int rv; + + rv = nghttp2_rcbuf_new(rcbuf_ptr, srclen + 1, mem); + if (rv != 0) { + return rv; + } + + (*rcbuf_ptr)->len = srclen; + *nghttp2_cpymem((*rcbuf_ptr)->base, src, srclen) = '\0'; + + return 0; +} + +/* + * Frees |rcbuf| itself, regardless of its reference cout. + */ +void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf) { + nghttp2_mem_free2(rcbuf->free, rcbuf, rcbuf->mem_user_data); +} + +void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf) { + if (rcbuf->ref == -1) { + return; + } + + ++rcbuf->ref; +} + +void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf) { + if (rcbuf == NULL || rcbuf->ref == -1) { + return; + } + + assert(rcbuf->ref > 0); + + if (--rcbuf->ref == 0) { + nghttp2_rcbuf_del(rcbuf); + } +} + +nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf) { + nghttp2_vec res = {rcbuf->base, rcbuf->len}; + return res; +} + +int nghttp2_rcbuf_is_static(const nghttp2_rcbuf *rcbuf) { + return rcbuf->ref == -1; +} diff --git a/lib/nghttp2/lib/nghttp2_rcbuf.h b/lib/nghttp2/lib/nghttp2_rcbuf.h new file mode 100644 index 00000000000..6814e709fb4 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_rcbuf.h @@ -0,0 +1,80 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_RCBUF_H +#define NGHTTP2_RCBUF_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +struct nghttp2_rcbuf { + /* custom memory allocator belongs to the mem parameter when + creating this object. */ + void *mem_user_data; + nghttp2_free free; + /* The pointer to the underlying buffer */ + uint8_t *base; + /* Size of buffer pointed by |base|. */ + size_t len; + /* Reference count */ + int32_t ref; +}; + +/* + * Allocates nghttp2_rcbuf object with |size| as initial buffer size. + * When the function succeeds, the reference count becomes 1. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM: + * Out of memory. + */ +int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, nghttp2_mem *mem); + +/* + * Like nghttp2_rcbuf_new(), but initializes the buffer with |src| of + * length |srclen|. This function allocates additional byte at the + * end and puts '\0' into it, so that the resulting buffer could be + * used as NULL-terminated string. Still (*rcbuf_ptr)->len equals to + * |srclen|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM: + * Out of memory. + */ +int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src, + size_t srclen, nghttp2_mem *mem); + +/* + * Frees |rcbuf| itself, regardless of its reference cout. + */ +void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf); + +#endif /* NGHTTP2_RCBUF_H */ diff --git a/lib/nghttp2/lib/nghttp2_session.c b/lib/nghttp2/lib/nghttp2_session.c new file mode 100644 index 00000000000..c0d86026ae2 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_session.c @@ -0,0 +1,8395 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_session.h" + +#include +#include +#include +#include +#include + +#include "nghttp2_helper.h" +#include "nghttp2_net.h" +#include "nghttp2_priority_spec.h" +#include "nghttp2_option.h" +#include "nghttp2_http.h" +#include "nghttp2_pq.h" +#include "nghttp2_extpri.h" +#include "nghttp2_time.h" +#include "nghttp2_debug.h" + +/* + * Returns non-zero if the number of outgoing opened streams is larger + * than or equal to + * remote_settings.max_concurrent_streams. + */ +static int +session_is_outgoing_concurrent_streams_max(nghttp2_session *session) { + return session->remote_settings.max_concurrent_streams <= + session->num_outgoing_streams; +} + +/* + * Returns non-zero if the number of incoming opened streams is larger + * than or equal to + * local_settings.max_concurrent_streams. + */ +static int +session_is_incoming_concurrent_streams_max(nghttp2_session *session) { + return session->local_settings.max_concurrent_streams <= + session->num_incoming_streams; +} + +/* + * Returns non-zero if the number of incoming opened streams is larger + * than or equal to + * session->pending_local_max_concurrent_stream. + */ +static int +session_is_incoming_concurrent_streams_pending_max(nghttp2_session *session) { + return session->pending_local_max_concurrent_stream <= + session->num_incoming_streams; +} + +/* + * Returns non-zero if |lib_error| is non-fatal error. + */ +static int is_non_fatal(int lib_error_code) { + return lib_error_code < 0 && lib_error_code > NGHTTP2_ERR_FATAL; +} + +int nghttp2_is_fatal(int lib_error_code) { + return lib_error_code < NGHTTP2_ERR_FATAL; +} + +static int session_enforce_http_messaging(nghttp2_session *session) { + return (session->opt_flags & NGHTTP2_OPTMASK_NO_HTTP_MESSAGING) == 0; +} + +/* + * Returns nonzero if |frame| is trailer headers. + */ +static int session_trailer_headers(nghttp2_session *session, + nghttp2_stream *stream, + nghttp2_frame *frame) { + if (!stream || frame->hd.type != NGHTTP2_HEADERS) { + return 0; + } + if (session->server) { + return frame->headers.cat == NGHTTP2_HCAT_HEADERS; + } + + return frame->headers.cat == NGHTTP2_HCAT_HEADERS && + (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) == 0; +} + +/* Returns nonzero if the |stream| is in reserved(remote) state */ +static int state_reserved_remote(nghttp2_session *session, + nghttp2_stream *stream) { + return stream->state == NGHTTP2_STREAM_RESERVED && + !nghttp2_session_is_my_stream_id(session, stream->stream_id); +} + +/* Returns nonzero if the |stream| is in reserved(local) state */ +static int state_reserved_local(nghttp2_session *session, + nghttp2_stream *stream) { + return stream->state == NGHTTP2_STREAM_RESERVED && + nghttp2_session_is_my_stream_id(session, stream->stream_id); +} + +/* + * Checks whether received stream_id is valid. This function returns + * 1 if it succeeds, or 0. + */ +static int session_is_new_peer_stream_id(nghttp2_session *session, + int32_t stream_id) { + return stream_id != 0 && + !nghttp2_session_is_my_stream_id(session, stream_id) && + session->last_recv_stream_id < stream_id; +} + +static int session_detect_idle_stream(nghttp2_session *session, + int32_t stream_id) { + /* Assume that stream object with stream_id does not exist */ + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + if (session->last_sent_stream_id < stream_id) { + return 1; + } + return 0; + } + if (session_is_new_peer_stream_id(session, stream_id)) { + return 1; + } + return 0; +} + +static int session_no_rfc7540_pri_no_fallback(nghttp2_session *session) { + return session->pending_no_rfc7540_priorities == 1 && + !session->fallback_rfc7540_priorities; +} + +static int check_ext_type_set(const uint8_t *ext_types, uint8_t type) { + return (ext_types[type / 8] & (1 << (type & 0x7))) > 0; +} + +static int session_call_error_callback(nghttp2_session *session, + int lib_error_code, const char *fmt, + ...) { + size_t bufsize; + va_list ap; + char *buf; + int rv; + nghttp2_mem *mem; + + if (!session->callbacks.error_callback && + !session->callbacks.error_callback2) { + return 0; + } + + mem = &session->mem; + + va_start(ap, fmt); + rv = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + if (rv < 0) { + return NGHTTP2_ERR_NOMEM; + } + + bufsize = (size_t)(rv + 1); + + buf = nghttp2_mem_malloc(mem, bufsize); + if (buf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + va_start(ap, fmt); + rv = vsnprintf(buf, bufsize, fmt, ap); + va_end(ap); + + if (rv < 0) { + nghttp2_mem_free(mem, buf); + /* vsnprintf may return error because of various things we can + imagine, but typically we don't want to drop session just for + debug callback. */ + DEBUGF("error_callback: vsnprintf failed. The template was %s\n", fmt); + return 0; + } + + if (session->callbacks.error_callback2) { + rv = session->callbacks.error_callback2(session, lib_error_code, buf, + (size_t)rv, session->user_data); + } else { + rv = session->callbacks.error_callback(session, buf, (size_t)rv, + session->user_data); + } + + nghttp2_mem_free(mem, buf); + + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int session_terminate_session(nghttp2_session *session, + int32_t last_stream_id, + uint32_t error_code, const char *reason) { + int rv; + const uint8_t *debug_data; + size_t debug_datalen; + + if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) { + return 0; + } + + /* Ignore all incoming frames because we are going to tear down the + session. */ + session->iframe.state = NGHTTP2_IB_IGN_ALL; + + if (reason == NULL) { + debug_data = NULL; + debug_datalen = 0; + } else { + debug_data = (const uint8_t *)reason; + debug_datalen = strlen(reason); + } + + rv = nghttp2_session_add_goaway(session, last_stream_id, error_code, + debug_data, debug_datalen, + NGHTTP2_GOAWAY_AUX_TERM_ON_SEND); + + if (rv != 0) { + return rv; + } + + session->goaway_flags |= NGHTTP2_GOAWAY_TERM_ON_SEND; + + return 0; +} + +int nghttp2_session_terminate_session(nghttp2_session *session, + uint32_t error_code) { + return session_terminate_session(session, session->last_proc_stream_id, + error_code, NULL); +} + +int nghttp2_session_terminate_session2(nghttp2_session *session, + int32_t last_stream_id, + uint32_t error_code) { + return session_terminate_session(session, last_stream_id, error_code, NULL); +} + +int nghttp2_session_terminate_session_with_reason(nghttp2_session *session, + uint32_t error_code, + const char *reason) { + return session_terminate_session(session, session->last_proc_stream_id, + error_code, reason); +} + +int nghttp2_session_is_my_stream_id(nghttp2_session *session, + int32_t stream_id) { + int rem; + if (stream_id == 0) { + return 0; + } + rem = stream_id & 0x1; + if (session->server) { + return rem == 0; + } + return rem == 1; +} + +nghttp2_stream *nghttp2_session_get_stream(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + stream = (nghttp2_stream *)nghttp2_map_find(&session->streams, stream_id); + + if (stream == NULL || (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) || + stream->state == NGHTTP2_STREAM_IDLE) { + return NULL; + } + + return stream; +} + +nghttp2_stream *nghttp2_session_get_stream_raw(nghttp2_session *session, + int32_t stream_id) { + return (nghttp2_stream *)nghttp2_map_find(&session->streams, stream_id); +} + +static void session_inbound_frame_reset(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_mem *mem = &session->mem; + /* A bit risky code, since if this function is called from + nghttp2_session_new(), we rely on the fact that + iframe->frame.hd.type is 0, so that no free is performed. */ + switch (iframe->frame.hd.type) { + case NGHTTP2_DATA: + break; + case NGHTTP2_HEADERS: + nghttp2_frame_headers_free(&iframe->frame.headers, mem); + break; + case NGHTTP2_PRIORITY: + nghttp2_frame_priority_free(&iframe->frame.priority); + break; + case NGHTTP2_RST_STREAM: + nghttp2_frame_rst_stream_free(&iframe->frame.rst_stream); + break; + case NGHTTP2_SETTINGS: + nghttp2_frame_settings_free(&iframe->frame.settings, mem); + + nghttp2_mem_free(mem, iframe->iv); + + iframe->iv = NULL; + iframe->niv = 0; + iframe->max_niv = 0; + + break; + case NGHTTP2_PUSH_PROMISE: + nghttp2_frame_push_promise_free(&iframe->frame.push_promise, mem); + break; + case NGHTTP2_PING: + nghttp2_frame_ping_free(&iframe->frame.ping); + break; + case NGHTTP2_GOAWAY: + nghttp2_frame_goaway_free(&iframe->frame.goaway, mem); + break; + case NGHTTP2_WINDOW_UPDATE: + nghttp2_frame_window_update_free(&iframe->frame.window_update); + break; + default: + /* extension frame */ + if (check_ext_type_set(session->user_recv_ext_types, + iframe->frame.hd.type)) { + nghttp2_frame_extension_free(&iframe->frame.ext); + } else { + switch (iframe->frame.hd.type) { + case NGHTTP2_ALTSVC: + if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ALTSVC) == 0) { + break; + } + nghttp2_frame_altsvc_free(&iframe->frame.ext, mem); + break; + case NGHTTP2_ORIGIN: + if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN) == 0) { + break; + } + nghttp2_frame_origin_free(&iframe->frame.ext, mem); + break; + case NGHTTP2_PRIORITY_UPDATE: + if ((session->builtin_recv_ext_types & + NGHTTP2_TYPEMASK_PRIORITY_UPDATE) == 0) { + break; + } + /* Do not call nghttp2_frame_priority_update_free, because all + fields point to sbuf. */ + break; + } + } + + break; + } + + memset(&iframe->frame, 0, sizeof(nghttp2_frame)); + memset(&iframe->ext_frame_payload, 0, sizeof(nghttp2_ext_frame_payload)); + + iframe->state = NGHTTP2_IB_READ_HEAD; + + nghttp2_buf_wrap_init(&iframe->sbuf, iframe->raw_sbuf, + sizeof(iframe->raw_sbuf)); + iframe->sbuf.mark += NGHTTP2_FRAME_HDLEN; + + nghttp2_buf_free(&iframe->lbuf, mem); + nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); + + iframe->raw_lbuf = NULL; + + iframe->payloadleft = 0; + iframe->padlen = 0; +} + +static void init_settings(nghttp2_settings_storage *settings) { + settings->header_table_size = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; + settings->enable_push = 1; + settings->max_concurrent_streams = NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; + settings->initial_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; + settings->max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN; + settings->max_header_list_size = UINT32_MAX; + settings->no_rfc7540_priorities = UINT32_MAX; +} + +static void active_outbound_item_reset(nghttp2_active_outbound_item *aob, + nghttp2_mem *mem) { + DEBUGF("send: reset nghttp2_active_outbound_item\n"); + DEBUGF("send: aob->item = %p\n", aob->item); + nghttp2_outbound_item_free(aob->item, mem); + nghttp2_mem_free(mem, aob->item); + aob->item = NULL; + nghttp2_bufs_reset(&aob->framebufs); + aob->state = NGHTTP2_OB_POP_ITEM; +} + +#define NGHTTP2_STREAM_MAX_CYCLE_GAP ((uint64_t)NGHTTP2_MAX_FRAME_SIZE_MAX) + +static int stream_less(const void *lhsx, const void *rhsx) { + const nghttp2_stream *lhs, *rhs; + + lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry); + rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry); + + if (lhs->cycle == rhs->cycle) { + return lhs->seq < rhs->seq; + } + + return rhs->cycle - lhs->cycle <= NGHTTP2_STREAM_MAX_CYCLE_GAP; +} + +int nghttp2_enable_strict_preface = 1; + +static int session_new(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, int server, + const nghttp2_option *option, nghttp2_mem *mem) { + int rv; + size_t nbuffer; + size_t max_deflate_dynamic_table_size = + NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE; + size_t i; + + if (mem == NULL) { + mem = nghttp2_mem_default(); + } + + *session_ptr = nghttp2_mem_calloc(mem, 1, sizeof(nghttp2_session)); + if (*session_ptr == NULL) { + rv = NGHTTP2_ERR_NOMEM; + goto fail_session; + } + + (*session_ptr)->mem = *mem; + mem = &(*session_ptr)->mem; + + /* next_stream_id is initialized in either + nghttp2_session_client_new2 or nghttp2_session_server_new2 */ + + nghttp2_stream_init(&(*session_ptr)->root, 0, NGHTTP2_STREAM_FLAG_NONE, + NGHTTP2_STREAM_IDLE, NGHTTP2_DEFAULT_WEIGHT, 0, 0, NULL, + mem); + + (*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; + (*session_ptr)->recv_window_size = 0; + (*session_ptr)->consumed_size = 0; + (*session_ptr)->recv_reduction = 0; + (*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE; + + (*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE; + (*session_ptr)->local_last_stream_id = (1u << 31) - 1; + (*session_ptr)->remote_last_stream_id = (1u << 31) - 1; + + (*session_ptr)->pending_local_max_concurrent_stream = + NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; + (*session_ptr)->pending_enable_push = 1; + (*session_ptr)->pending_no_rfc7540_priorities = UINT8_MAX; + + nghttp2_ratelim_init(&(*session_ptr)->stream_reset_ratelim, + NGHTTP2_DEFAULT_STREAM_RESET_BURST, + NGHTTP2_DEFAULT_STREAM_RESET_RATE); + + if (server) { + (*session_ptr)->server = 1; + } + + init_settings(&(*session_ptr)->remote_settings); + init_settings(&(*session_ptr)->local_settings); + + (*session_ptr)->max_incoming_reserved_streams = + NGHTTP2_MAX_INCOMING_RESERVED_STREAMS; + + /* Limit max outgoing concurrent streams to sensible value */ + (*session_ptr)->remote_settings.max_concurrent_streams = 100; + + (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN; + (*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; + (*session_ptr)->max_settings = NGHTTP2_DEFAULT_MAX_SETTINGS; + + if (option) { + if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) && + option->no_auto_window_update) { + + (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE; + } + + if (option->opt_set_mask & NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS) { + + (*session_ptr)->remote_settings.max_concurrent_streams = + option->peer_max_concurrent_streams; + } + + if (option->opt_set_mask & NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS) { + + (*session_ptr)->max_incoming_reserved_streams = + option->max_reserved_remote_streams; + } + + if ((option->opt_set_mask & NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC) && + option->no_recv_client_magic) { + + (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC; + } + + if ((option->opt_set_mask & NGHTTP2_OPT_NO_HTTP_MESSAGING) && + option->no_http_messaging) { + + (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING; + } + + if (option->opt_set_mask & NGHTTP2_OPT_USER_RECV_EXT_TYPES) { + memcpy((*session_ptr)->user_recv_ext_types, option->user_recv_ext_types, + sizeof((*session_ptr)->user_recv_ext_types)); + } + + if (option->opt_set_mask & NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES) { + (*session_ptr)->builtin_recv_ext_types = option->builtin_recv_ext_types; + } + + if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_PING_ACK) && + option->no_auto_ping_ack) { + (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_PING_ACK; + } + + if (option->opt_set_mask & NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH) { + (*session_ptr)->max_send_header_block_length = + option->max_send_header_block_length; + } + + if (option->opt_set_mask & NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE) { + max_deflate_dynamic_table_size = option->max_deflate_dynamic_table_size; + } + + if ((option->opt_set_mask & NGHTTP2_OPT_NO_CLOSED_STREAMS) && + option->no_closed_streams) { + (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_CLOSED_STREAMS; + } + + if (option->opt_set_mask & NGHTTP2_OPT_MAX_OUTBOUND_ACK) { + (*session_ptr)->max_outbound_ack = option->max_outbound_ack; + } + + if ((option->opt_set_mask & NGHTTP2_OPT_MAX_SETTINGS) && + option->max_settings) { + (*session_ptr)->max_settings = option->max_settings; + } + + if ((option->opt_set_mask & + NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES) && + option->server_fallback_rfc7540_priorities) { + (*session_ptr)->opt_flags |= + NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES; + } + + if ((option->opt_set_mask & + NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) && + option->no_rfc9113_leading_and_trailing_ws_validation) { + (*session_ptr)->opt_flags |= + NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION; + } + + if (option->opt_set_mask & NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT) { + nghttp2_ratelim_init(&(*session_ptr)->stream_reset_ratelim, + option->stream_reset_burst, + option->stream_reset_rate); + } + } + + rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater, + max_deflate_dynamic_table_size, mem); + if (rv != 0) { + goto fail_hd_deflater; + } + rv = nghttp2_hd_inflate_init(&(*session_ptr)->hd_inflater, mem); + if (rv != 0) { + goto fail_hd_inflater; + } + + nbuffer = ((*session_ptr)->max_send_header_block_length + + NGHTTP2_FRAMEBUF_CHUNKLEN - 1) / + NGHTTP2_FRAMEBUF_CHUNKLEN; + + if (nbuffer == 0) { + nbuffer = 1; + } + + /* 1 for Pad Field. */ + rv = nghttp2_bufs_init3(&(*session_ptr)->aob.framebufs, + NGHTTP2_FRAMEBUF_CHUNKLEN, nbuffer, 1, + NGHTTP2_FRAME_HDLEN + 1, mem); + if (rv != 0) { + goto fail_aob_framebuf; + } + + nghttp2_map_init(&(*session_ptr)->streams, mem); + + active_outbound_item_reset(&(*session_ptr)->aob, mem); + + (*session_ptr)->callbacks = *callbacks; + (*session_ptr)->user_data = user_data; + + session_inbound_frame_reset(*session_ptr); + + if (nghttp2_enable_strict_preface) { + nghttp2_inbound_frame *iframe = &(*session_ptr)->iframe; + + if (server && ((*session_ptr)->opt_flags & + NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC) == 0) { + iframe->state = NGHTTP2_IB_READ_CLIENT_MAGIC; + iframe->payloadleft = NGHTTP2_CLIENT_MAGIC_LEN; + } else { + iframe->state = NGHTTP2_IB_READ_FIRST_SETTINGS; + } + + if (!server) { + (*session_ptr)->aob.state = NGHTTP2_OB_SEND_CLIENT_MAGIC; + nghttp2_bufs_add(&(*session_ptr)->aob.framebufs, NGHTTP2_CLIENT_MAGIC, + NGHTTP2_CLIENT_MAGIC_LEN); + } + } + + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + nghttp2_pq_init(&(*session_ptr)->sched[i].ob_data, stream_less, mem); + } + + return 0; + +fail_aob_framebuf: + nghttp2_hd_inflate_free(&(*session_ptr)->hd_inflater); +fail_hd_inflater: + nghttp2_hd_deflate_free(&(*session_ptr)->hd_deflater); +fail_hd_deflater: + nghttp2_mem_free(mem, *session_ptr); +fail_session: + return rv; +} + +int nghttp2_session_client_new(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data) { + return nghttp2_session_client_new3(session_ptr, callbacks, user_data, NULL, + NULL); +} + +int nghttp2_session_client_new2(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option) { + return nghttp2_session_client_new3(session_ptr, callbacks, user_data, option, + NULL); +} + +int nghttp2_session_client_new3(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option, + nghttp2_mem *mem) { + int rv; + nghttp2_session *session; + + rv = session_new(&session, callbacks, user_data, 0, option, mem); + + if (rv != 0) { + return rv; + } + /* IDs for use in client */ + session->next_stream_id = 1; + + *session_ptr = session; + + return 0; +} + +int nghttp2_session_server_new(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data) { + return nghttp2_session_server_new3(session_ptr, callbacks, user_data, NULL, + NULL); +} + +int nghttp2_session_server_new2(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option) { + return nghttp2_session_server_new3(session_ptr, callbacks, user_data, option, + NULL); +} + +int nghttp2_session_server_new3(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option, + nghttp2_mem *mem) { + int rv; + nghttp2_session *session; + + rv = session_new(&session, callbacks, user_data, 1, option, mem); + + if (rv != 0) { + return rv; + } + /* IDs for use in client */ + session->next_stream_id = 2; + + *session_ptr = session; + + return 0; +} + +static int free_streams(void *entry, void *ptr) { + nghttp2_session *session; + nghttp2_stream *stream; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + session = (nghttp2_session *)ptr; + mem = &session->mem; + stream = (nghttp2_stream *)entry; + item = stream->item; + + if (item && !item->queued && item != session->aob.item) { + nghttp2_outbound_item_free(item, mem); + nghttp2_mem_free(mem, item); + } + + nghttp2_stream_free(stream); + nghttp2_mem_free(mem, stream); + + return 0; +} + +static void ob_q_free(nghttp2_outbound_queue *q, nghttp2_mem *mem) { + nghttp2_outbound_item *item, *next; + for (item = q->head; item;) { + next = item->qnext; + nghttp2_outbound_item_free(item, mem); + nghttp2_mem_free(mem, item); + item = next; + } +} + +static int inflight_settings_new(nghttp2_inflight_settings **settings_ptr, + const nghttp2_settings_entry *iv, size_t niv, + nghttp2_mem *mem) { + *settings_ptr = nghttp2_mem_malloc(mem, sizeof(nghttp2_inflight_settings)); + if (!*settings_ptr) { + return NGHTTP2_ERR_NOMEM; + } + + if (niv > 0) { + (*settings_ptr)->iv = nghttp2_frame_iv_copy(iv, niv, mem); + if (!(*settings_ptr)->iv) { + nghttp2_mem_free(mem, *settings_ptr); + return NGHTTP2_ERR_NOMEM; + } + } else { + (*settings_ptr)->iv = NULL; + } + + (*settings_ptr)->niv = niv; + (*settings_ptr)->next = NULL; + + return 0; +} + +static void inflight_settings_del(nghttp2_inflight_settings *settings, + nghttp2_mem *mem) { + if (!settings) { + return; + } + + nghttp2_mem_free(mem, settings->iv); + nghttp2_mem_free(mem, settings); +} + +void nghttp2_session_del(nghttp2_session *session) { + nghttp2_mem *mem; + nghttp2_inflight_settings *settings; + size_t i; + + if (session == NULL) { + return; + } + + mem = &session->mem; + + for (settings = session->inflight_settings_head; settings;) { + nghttp2_inflight_settings *next = settings->next; + inflight_settings_del(settings, mem); + settings = next; + } + + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + nghttp2_pq_free(&session->sched[i].ob_data); + } + nghttp2_stream_free(&session->root); + + /* Have to free streams first, so that we can check + stream->item->queued */ + nghttp2_map_each_free(&session->streams, free_streams, session); + nghttp2_map_free(&session->streams); + + ob_q_free(&session->ob_urgent, mem); + ob_q_free(&session->ob_reg, mem); + ob_q_free(&session->ob_syn, mem); + + active_outbound_item_reset(&session->aob, mem); + session_inbound_frame_reset(session); + nghttp2_hd_deflate_free(&session->hd_deflater); + nghttp2_hd_inflate_free(&session->hd_inflater); + nghttp2_bufs_free(&session->aob.framebufs); + nghttp2_mem_free(mem, session); +} + +int nghttp2_session_reprioritize_stream( + nghttp2_session *session, nghttp2_stream *stream, + const nghttp2_priority_spec *pri_spec_in) { + int rv; + nghttp2_stream *dep_stream = NULL; + nghttp2_priority_spec pri_spec_default; + const nghttp2_priority_spec *pri_spec = pri_spec_in; + + assert((!session->server && session->pending_no_rfc7540_priorities != 1) || + (session->server && !session_no_rfc7540_pri_no_fallback(session))); + assert(pri_spec->stream_id != stream->stream_id); + + if (!nghttp2_stream_in_dep_tree(stream)) { + return 0; + } + + if (pri_spec->stream_id != 0) { + dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id); + + if (!dep_stream && + session_detect_idle_stream(session, pri_spec->stream_id)) { + + nghttp2_priority_spec_default_init(&pri_spec_default); + + dep_stream = nghttp2_session_open_stream( + session, pri_spec->stream_id, NGHTTP2_FLAG_NONE, &pri_spec_default, + NGHTTP2_STREAM_IDLE, NULL); + + if (dep_stream == NULL) { + return NGHTTP2_ERR_NOMEM; + } + } else if (!dep_stream || !nghttp2_stream_in_dep_tree(dep_stream)) { + nghttp2_priority_spec_default_init(&pri_spec_default); + pri_spec = &pri_spec_default; + } + } + + if (pri_spec->stream_id == 0) { + dep_stream = &session->root; + } else if (nghttp2_stream_dep_find_ancestor(dep_stream, stream)) { + DEBUGF("stream: cycle detected, dep_stream(%p)=%d stream(%p)=%d\n", + dep_stream, dep_stream->stream_id, stream, stream->stream_id); + + nghttp2_stream_dep_remove_subtree(dep_stream); + rv = nghttp2_stream_dep_add_subtree(stream->dep_prev, dep_stream); + if (rv != 0) { + return rv; + } + } + + assert(dep_stream); + + if (dep_stream == stream->dep_prev && !pri_spec->exclusive) { + /* This is minor optimization when just weight is changed. */ + nghttp2_stream_change_weight(stream, pri_spec->weight); + + return 0; + } + + nghttp2_stream_dep_remove_subtree(stream); + + /* We have to update weight after removing stream from tree */ + stream->weight = pri_spec->weight; + + if (pri_spec->exclusive) { + rv = nghttp2_stream_dep_insert_subtree(dep_stream, stream); + } else { + rv = nghttp2_stream_dep_add_subtree(dep_stream, stream); + } + + if (rv != 0) { + return rv; + } + + return 0; +} + +static uint64_t pq_get_first_cycle(nghttp2_pq *pq) { + nghttp2_stream *stream; + + if (nghttp2_pq_empty(pq)) { + return 0; + } + + stream = nghttp2_struct_of(nghttp2_pq_top(pq), nghttp2_stream, pq_entry); + return stream->cycle; +} + +static int session_ob_data_push(nghttp2_session *session, + nghttp2_stream *stream) { + int rv; + uint32_t urgency; + int inc; + nghttp2_pq *pq; + + assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES); + assert(stream->queued == 0); + + urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + inc = nghttp2_extpri_uint8_inc(stream->extpri); + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + pq = &session->sched[urgency].ob_data; + + stream->cycle = pq_get_first_cycle(pq); + if (inc) { + stream->cycle += stream->last_writelen; + } + + rv = nghttp2_pq_push(pq, &stream->pq_entry); + if (rv != 0) { + return rv; + } + + stream->queued = 1; + + return 0; +} + +static void session_ob_data_remove(nghttp2_session *session, + nghttp2_stream *stream) { + uint32_t urgency; + + assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES); + assert(stream->queued == 1); + + urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + nghttp2_pq_remove(&session->sched[urgency].ob_data, &stream->pq_entry); + + stream->queued = 0; +} + +static int session_attach_stream_item(nghttp2_session *session, + nghttp2_stream *stream, + nghttp2_outbound_item *item) { + int rv; + + rv = nghttp2_stream_attach_item(stream, item); + if (rv != 0) { + return rv; + } + + if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) { + return 0; + } + + return session_ob_data_push(session, stream); +} + +static void session_detach_stream_item(nghttp2_session *session, + nghttp2_stream *stream) { + nghttp2_stream_detach_item(stream); + + if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) || + !stream->queued) { + return; + } + + session_ob_data_remove(session, stream); +} + +static void session_defer_stream_item(nghttp2_session *session, + nghttp2_stream *stream, uint8_t flags) { + nghttp2_stream_defer_item(stream, flags); + + if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) || + !stream->queued) { + return; + } + + session_ob_data_remove(session, stream); +} + +static int session_resume_deferred_stream_item(nghttp2_session *session, + nghttp2_stream *stream, + uint8_t flags) { + int rv; + + rv = nghttp2_stream_resume_deferred_item(stream, flags); + if (rv != 0) { + return rv; + } + + if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) || + (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL)) { + return 0; + } + + return session_ob_data_push(session, stream); +} + +static nghttp2_outbound_item * +session_sched_get_next_outbound_item(nghttp2_session *session) { + size_t i; + nghttp2_pq_entry *ent; + nghttp2_stream *stream; + + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + ent = nghttp2_pq_top(&session->sched[i].ob_data); + if (!ent) { + continue; + } + + stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry); + return stream->item; + } + + return NULL; +} + +static int session_sched_empty(nghttp2_session *session) { + size_t i; + + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + if (!nghttp2_pq_empty(&session->sched[i].ob_data)) { + return 0; + } + } + + return 1; +} + +static void session_sched_reschedule_stream(nghttp2_session *session, + nghttp2_stream *stream) { + nghttp2_pq *pq; + uint32_t urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + int inc = nghttp2_extpri_uint8_inc(stream->extpri); + uint64_t penalty = (uint64_t)stream->last_writelen; + int rv; + + (void)rv; + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + pq = &session->sched[urgency].ob_data; + + if (!inc || nghttp2_pq_size(pq) == 1) { + return; + } + + nghttp2_pq_remove(pq, &stream->pq_entry); + + stream->cycle += penalty; + + rv = nghttp2_pq_push(pq, &stream->pq_entry); + + assert(0 == rv); +} + +static int session_update_stream_priority(nghttp2_session *session, + nghttp2_stream *stream, + uint8_t u8extpri) { + if (stream->extpri == u8extpri) { + return 0; + } + + if (stream->queued) { + session_ob_data_remove(session, stream); + + stream->extpri = u8extpri; + + return session_ob_data_push(session, stream); + } + + stream->extpri = u8extpri; + + return 0; +} + +int nghttp2_session_add_item(nghttp2_session *session, + nghttp2_outbound_item *item) { + /* TODO Return error if stream is not found for the frame requiring + stream presence. */ + int rv = 0; + nghttp2_stream *stream; + nghttp2_frame *frame; + + frame = &item->frame; + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + + switch (frame->hd.type) { + case NGHTTP2_DATA: + if (!stream) { + return NGHTTP2_ERR_STREAM_CLOSED; + } + + if (stream->item) { + return NGHTTP2_ERR_DATA_EXIST; + } + + rv = session_attach_stream_item(session, stream, item); + + if (rv != 0) { + return rv; + } + + return 0; + case NGHTTP2_HEADERS: + /* We push request HEADERS and push response HEADERS to + dedicated queue because their transmission is affected by + SETTINGS_MAX_CONCURRENT_STREAMS */ + /* TODO If 2 HEADERS are submitted for reserved stream, then + both of them are queued into ob_syn, which is not + desirable. */ + if (frame->headers.cat == NGHTTP2_HCAT_REQUEST || + (stream && stream->state == NGHTTP2_STREAM_RESERVED)) { + nghttp2_outbound_queue_push(&session->ob_syn, item); + item->queued = 1; + return 0; + ; + } + + nghttp2_outbound_queue_push(&session->ob_reg, item); + item->queued = 1; + return 0; + case NGHTTP2_SETTINGS: + case NGHTTP2_PING: + nghttp2_outbound_queue_push(&session->ob_urgent, item); + item->queued = 1; + return 0; + case NGHTTP2_RST_STREAM: + if (stream) { + stream->state = NGHTTP2_STREAM_CLOSING; + } + nghttp2_outbound_queue_push(&session->ob_reg, item); + item->queued = 1; + return 0; + case NGHTTP2_PUSH_PROMISE: { + nghttp2_headers_aux_data *aux_data; + nghttp2_priority_spec pri_spec; + + aux_data = &item->aux_data.headers; + + if (!stream) { + return NGHTTP2_ERR_STREAM_CLOSED; + } + + nghttp2_priority_spec_init(&pri_spec, stream->stream_id, + NGHTTP2_DEFAULT_WEIGHT, 0); + + if (!nghttp2_session_open_stream( + session, frame->push_promise.promised_stream_id, + NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED, + aux_data->stream_user_data)) { + return NGHTTP2_ERR_NOMEM; + } + + /* We don't have to call nghttp2_session_adjust_closed_stream() + here, since stream->stream_id is local stream_id, and it does + not affect closed stream count. */ + + nghttp2_outbound_queue_push(&session->ob_reg, item); + item->queued = 1; + + return 0; + } + case NGHTTP2_WINDOW_UPDATE: + if (stream) { + stream->window_update_queued = 1; + } else if (frame->hd.stream_id == 0) { + session->window_update_queued = 1; + } + nghttp2_outbound_queue_push(&session->ob_reg, item); + item->queued = 1; + return 0; + default: + nghttp2_outbound_queue_push(&session->ob_reg, item); + item->queued = 1; + return 0; + } +} + +int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id, + uint32_t error_code) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = &session->mem; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream && stream->state == NGHTTP2_STREAM_CLOSING) { + return 0; + } + + /* Sending RST_STREAM to an idle stream is subject to protocol + violation. Historically, nghttp2 allows this. In order not to + disrupt the existing applications, we don't error out this case + and simply ignore it. */ + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + if ((uint32_t)stream_id >= session->next_stream_id) { + return 0; + } + } else if (session->last_recv_stream_id < stream_id) { + return 0; + } + + /* Cancel pending request HEADERS in ob_syn if this RST_STREAM + refers to that stream. */ + if (!session->server && nghttp2_session_is_my_stream_id(session, stream_id) && + nghttp2_outbound_queue_top(&session->ob_syn)) { + nghttp2_headers_aux_data *aux_data; + nghttp2_frame *headers_frame; + + headers_frame = &nghttp2_outbound_queue_top(&session->ob_syn)->frame; + assert(headers_frame->hd.type == NGHTTP2_HEADERS); + + if (headers_frame->hd.stream_id <= stream_id) { + + for (item = session->ob_syn.head; item; item = item->qnext) { + aux_data = &item->aux_data.headers; + + if (item->frame.hd.stream_id < stream_id) { + continue; + } + + /* stream_id in ob_syn queue must be strictly increasing. If + we found larger ID, then we can break here. */ + if (item->frame.hd.stream_id > stream_id || aux_data->canceled) { + break; + } + + aux_data->error_code = error_code; + aux_data->canceled = 1; + + return 0; + } + } + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_rst_stream_init(&frame->rst_stream, stream_id, error_code); + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_rst_stream_free(&frame->rst_stream); + nghttp2_mem_free(mem, item); + return rv; + } + return 0; +} + +nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session, + int32_t stream_id, uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data) { + int rv; + nghttp2_stream *stream; + nghttp2_stream *dep_stream = NULL; + int stream_alloc = 0; + nghttp2_priority_spec pri_spec_default; + nghttp2_priority_spec *pri_spec = pri_spec_in; + nghttp2_mem *mem; + + mem = &session->mem; + stream = nghttp2_session_get_stream_raw(session, stream_id); + + if (session->opt_flags & + NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) { + flags |= NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION; + } + + if (stream) { + assert(stream->state == NGHTTP2_STREAM_IDLE); + assert((stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) || + nghttp2_stream_in_dep_tree(stream)); + + if (nghttp2_stream_in_dep_tree(stream)) { + assert(!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)); + nghttp2_session_detach_idle_stream(session, stream); + rv = nghttp2_stream_dep_remove(stream); + if (rv != 0) { + return NULL; + } + + if (session_no_rfc7540_pri_no_fallback(session)) { + stream->flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES; + } + } + } else { + stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream)); + if (stream == NULL) { + return NULL; + } + + stream_alloc = 1; + } + + if (session_no_rfc7540_pri_no_fallback(session) || + session->remote_settings.no_rfc7540_priorities == 1) { + /* For client which has not received server + SETTINGS_NO_RFC7540_PRIORITIES = 1, send a priority signal + opportunistically. */ + if (session->server || + session->remote_settings.no_rfc7540_priorities == 1) { + nghttp2_priority_spec_default_init(&pri_spec_default); + pri_spec = &pri_spec_default; + } + + if (session->pending_no_rfc7540_priorities == 1) { + flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES; + } + } else if (pri_spec->stream_id != 0) { + dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id); + + if (!dep_stream && + session_detect_idle_stream(session, pri_spec->stream_id)) { + /* Depends on idle stream, which does not exist in memory. + Assign default priority for it. */ + nghttp2_priority_spec_default_init(&pri_spec_default); + + dep_stream = nghttp2_session_open_stream( + session, pri_spec->stream_id, NGHTTP2_FLAG_NONE, &pri_spec_default, + NGHTTP2_STREAM_IDLE, NULL); + + if (dep_stream == NULL) { + if (stream_alloc) { + nghttp2_mem_free(mem, stream); + } + + return NULL; + } + } else if (!dep_stream || !nghttp2_stream_in_dep_tree(dep_stream)) { + /* If dep_stream is not part of dependency tree, stream will get + default priority. This handles the case when + pri_spec->stream_id == stream_id. This happens because we + don't check pri_spec->stream_id against new stream ID in + nghttp2_submit_request. This also handles the case when idle + stream created by PRIORITY frame was opened. Somehow we + first remove the idle stream from dependency tree. This is + done to simplify code base, but ideally we should retain old + dependency. But I'm not sure this adds values. */ + nghttp2_priority_spec_default_init(&pri_spec_default); + pri_spec = &pri_spec_default; + } + } + + if (initial_state == NGHTTP2_STREAM_RESERVED) { + flags |= NGHTTP2_STREAM_FLAG_PUSH; + } + + if (stream_alloc) { + nghttp2_stream_init(stream, stream_id, flags, initial_state, + pri_spec->weight, + (int32_t)session->remote_settings.initial_window_size, + (int32_t)session->local_settings.initial_window_size, + stream_user_data, mem); + + if (session_no_rfc7540_pri_no_fallback(session)) { + stream->seq = session->stream_seq++; + } + + rv = nghttp2_map_insert(&session->streams, stream_id, stream); + if (rv != 0) { + nghttp2_stream_free(stream); + nghttp2_mem_free(mem, stream); + return NULL; + } + } else { + stream->flags = flags; + stream->state = initial_state; + stream->weight = pri_spec->weight; + stream->stream_user_data = stream_user_data; + } + + switch (initial_state) { + case NGHTTP2_STREAM_RESERVED: + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + /* reserved (local) */ + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + } else { + /* reserved (remote) */ + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + ++session->num_incoming_reserved_streams; + } + /* Reserved stream does not count in the concurrent streams + limit. That is one of the DOS vector. */ + break; + case NGHTTP2_STREAM_IDLE: + /* Idle stream does not count toward the concurrent streams limit. + This is used as anchor node in dependency tree. */ + nghttp2_session_keep_idle_stream(session, stream); + break; + default: + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + ++session->num_outgoing_streams; + } else { + ++session->num_incoming_streams; + } + } + + if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) { + return stream; + } + + if (pri_spec->stream_id == 0) { + dep_stream = &session->root; + } + + assert(dep_stream); + + if (pri_spec->exclusive) { + rv = nghttp2_stream_dep_insert(dep_stream, stream); + if (rv != 0) { + return NULL; + } + } else { + nghttp2_stream_dep_add(dep_stream, stream); + } + + return stream; +} + +int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id, + uint32_t error_code) { + int rv; + nghttp2_stream *stream; + nghttp2_mem *mem; + int is_my_stream_id; + + mem = &session->mem; + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + DEBUGF("stream: stream(%p)=%d close\n", stream, stream->stream_id); + + if (stream->item) { + nghttp2_outbound_item *item; + + item = stream->item; + + session_detach_stream_item(session, stream); + + /* If item is queued, it will be deleted when it is popped + (nghttp2_session_prep_frame() will fail). If session->aob.item + points to this item, let active_outbound_item_reset() + free the item. */ + if (!item->queued && item != session->aob.item) { + nghttp2_outbound_item_free(item, mem); + nghttp2_mem_free(mem, item); + } + } + + /* We call on_stream_close_callback even if stream->state is + NGHTTP2_STREAM_INITIAL. This will happen while sending request + HEADERS, a local endpoint receives RST_STREAM for that stream. It + may be PROTOCOL_ERROR, but without notifying stream closure will + hang the stream in a local endpoint. + */ + + if (session->callbacks.on_stream_close_callback) { + if (session->callbacks.on_stream_close_callback( + session, stream_id, error_code, session->user_data) != 0) { + + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + is_my_stream_id = nghttp2_session_is_my_stream_id(session, stream_id); + + /* pushed streams which is not opened yet is not counted toward max + concurrent limits */ + if ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH)) { + if (!is_my_stream_id) { + --session->num_incoming_reserved_streams; + } + } else { + if (is_my_stream_id) { + --session->num_outgoing_streams; + } else { + --session->num_incoming_streams; + } + } + + /* Closes both directions just in case they are not closed yet */ + stream->flags |= NGHTTP2_STREAM_FLAG_CLOSED; + + if (session->pending_no_rfc7540_priorities == 1) { + return nghttp2_session_destroy_stream(session, stream); + } + + if ((session->opt_flags & NGHTTP2_OPTMASK_NO_CLOSED_STREAMS) == 0 && + session->server && !is_my_stream_id && + nghttp2_stream_in_dep_tree(stream)) { + /* On server side, retain stream at most MAX_CONCURRENT_STREAMS + combined with the current active incoming streams to make + dependency tree work better. */ + nghttp2_session_keep_closed_stream(session, stream); + } else { + rv = nghttp2_session_destroy_stream(session, stream); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +int nghttp2_session_destroy_stream(nghttp2_session *session, + nghttp2_stream *stream) { + nghttp2_mem *mem; + int rv; + + DEBUGF("stream: destroy closed stream(%p)=%d\n", stream, stream->stream_id); + + mem = &session->mem; + + if (nghttp2_stream_in_dep_tree(stream)) { + rv = nghttp2_stream_dep_remove(stream); + if (rv != 0) { + return rv; + } + } + + nghttp2_map_remove(&session->streams, stream->stream_id); + nghttp2_stream_free(stream); + nghttp2_mem_free(mem, stream); + + return 0; +} + +void nghttp2_session_keep_closed_stream(nghttp2_session *session, + nghttp2_stream *stream) { + DEBUGF("stream: keep closed stream(%p)=%d, state=%d\n", stream, + stream->stream_id, stream->state); + + if (session->closed_stream_tail) { + session->closed_stream_tail->closed_next = stream; + stream->closed_prev = session->closed_stream_tail; + } else { + session->closed_stream_head = stream; + } + session->closed_stream_tail = stream; + + ++session->num_closed_streams; +} + +void nghttp2_session_keep_idle_stream(nghttp2_session *session, + nghttp2_stream *stream) { + DEBUGF("stream: keep idle stream(%p)=%d, state=%d\n", stream, + stream->stream_id, stream->state); + + if (session->idle_stream_tail) { + session->idle_stream_tail->closed_next = stream; + stream->closed_prev = session->idle_stream_tail; + } else { + session->idle_stream_head = stream; + } + session->idle_stream_tail = stream; + + ++session->num_idle_streams; +} + +void nghttp2_session_detach_idle_stream(nghttp2_session *session, + nghttp2_stream *stream) { + nghttp2_stream *prev_stream, *next_stream; + + DEBUGF("stream: detach idle stream(%p)=%d, state=%d\n", stream, + stream->stream_id, stream->state); + + prev_stream = stream->closed_prev; + next_stream = stream->closed_next; + + if (prev_stream) { + prev_stream->closed_next = next_stream; + } else { + session->idle_stream_head = next_stream; + } + + if (next_stream) { + next_stream->closed_prev = prev_stream; + } else { + session->idle_stream_tail = prev_stream; + } + + stream->closed_prev = NULL; + stream->closed_next = NULL; + + --session->num_idle_streams; +} + +int nghttp2_session_adjust_closed_stream(nghttp2_session *session) { + size_t num_stream_max; + int rv; + + if (session->local_settings.max_concurrent_streams == + NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS) { + num_stream_max = session->pending_local_max_concurrent_stream; + } else { + num_stream_max = session->local_settings.max_concurrent_streams; + } + + DEBUGF("stream: adjusting kept closed streams num_closed_streams=%zu, " + "num_incoming_streams=%zu, max_concurrent_streams=%zu\n", + session->num_closed_streams, session->num_incoming_streams, + num_stream_max); + + while (session->num_closed_streams > 0 && + session->num_closed_streams + session->num_incoming_streams > + num_stream_max) { + nghttp2_stream *head_stream; + nghttp2_stream *next; + + head_stream = session->closed_stream_head; + + assert(head_stream); + + next = head_stream->closed_next; + + rv = nghttp2_session_destroy_stream(session, head_stream); + if (rv != 0) { + return rv; + } + + /* head_stream is now freed */ + + session->closed_stream_head = next; + + if (session->closed_stream_head) { + session->closed_stream_head->closed_prev = NULL; + } else { + session->closed_stream_tail = NULL; + } + + --session->num_closed_streams; + } + + return 0; +} + +int nghttp2_session_adjust_idle_stream(nghttp2_session *session) { + size_t max; + int rv; + + /* Make minimum number of idle streams 16, and maximum 100, which + are arbitrary chosen numbers. */ + max = nghttp2_min( + 100, nghttp2_max( + 16, nghttp2_min(session->local_settings.max_concurrent_streams, + session->pending_local_max_concurrent_stream))); + + DEBUGF("stream: adjusting kept idle streams num_idle_streams=%zu, max=%zu\n", + session->num_idle_streams, max); + + while (session->num_idle_streams > max) { + nghttp2_stream *head; + nghttp2_stream *next; + + head = session->idle_stream_head; + assert(head); + + next = head->closed_next; + + rv = nghttp2_session_destroy_stream(session, head); + if (rv != 0) { + return rv; + } + + /* head is now destroyed */ + + session->idle_stream_head = next; + + if (session->idle_stream_head) { + session->idle_stream_head->closed_prev = NULL; + } else { + session->idle_stream_tail = NULL; + } + + --session->num_idle_streams; + } + + return 0; +} + +/* + * Closes stream with stream ID |stream_id| if both transmission and + * reception of the stream were disallowed. The |error_code| indicates + * the reason of the closure. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_INVALID_ARGUMENT + * The stream is not found. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session, + nghttp2_stream *stream) { + if ((stream->shut_flags & NGHTTP2_SHUT_RDWR) == NGHTTP2_SHUT_RDWR) { + return nghttp2_session_close_stream(session, stream->stream_id, + NGHTTP2_NO_ERROR); + } + return 0; +} + +/* + * Returns nonzero if local endpoint allows reception of new stream + * from remote. + */ +static int session_allow_incoming_new_stream(nghttp2_session *session) { + return (session->goaway_flags & + (NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_SENT)) == 0; +} + +/* + * This function returns nonzero if session is closing. + */ +static int session_is_closing(nghttp2_session *session) { + return (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) != 0 || + (nghttp2_session_want_read(session) == 0 && + nghttp2_session_want_write(session) == 0); +} + +/* + * Check that we can send a frame to the |stream|. This function + * returns 0 if we can send a frame to the |frame|, or one of the + * following negative error codes: + * + * NGHTTP2_ERR_STREAM_CLOSED + * The stream is already closed. + * NGHTTP2_ERR_STREAM_SHUT_WR + * The stream is half-closed for transmission. + * NGHTTP2_ERR_SESSION_CLOSING + * This session is closing. + */ +static int session_predicate_for_stream_send(nghttp2_session *session, + nghttp2_stream *stream) { + if (stream == NULL) { + return NGHTTP2_ERR_STREAM_CLOSED; + } + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + if (stream->shut_flags & NGHTTP2_SHUT_WR) { + return NGHTTP2_ERR_STREAM_SHUT_WR; + } + return 0; +} + +int nghttp2_session_check_request_allowed(nghttp2_session *session) { + return !session->server && session->next_stream_id <= INT32_MAX && + (session->goaway_flags & NGHTTP2_GOAWAY_RECV) == 0 && + !session_is_closing(session); +} + +/* + * This function checks request HEADERS frame, which opens stream, can + * be sent at this time. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED + * New stream cannot be created because of GOAWAY: session is + * going down or received last_stream_id is strictly less than + * frame->hd.stream_id. + * NGHTTP2_ERR_STREAM_CLOSING + * request HEADERS was canceled by RST_STREAM while it is in queue. + */ +static int session_predicate_request_headers_send(nghttp2_session *session, + nghttp2_outbound_item *item) { + if (item->aux_data.headers.canceled) { + return NGHTTP2_ERR_STREAM_CLOSING; + } + /* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND), + GOAWAY was received from peer, or session is about to close, new + request is not allowed. */ + if ((session->goaway_flags & NGHTTP2_GOAWAY_RECV) || + session_is_closing(session)) { + return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED; + } + return 0; +} + +/* + * This function checks HEADERS, which is the first frame from the + * server, with the |stream| can be sent at this time. The |stream| + * can be NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_STREAM_CLOSED + * The stream is already closed or does not exist. + * NGHTTP2_ERR_STREAM_SHUT_WR + * The transmission is not allowed for this stream (e.g., a frame + * with END_STREAM flag set has already sent) + * NGHTTP2_ERR_INVALID_STREAM_ID + * The stream ID is invalid. + * NGHTTP2_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + * NGHTTP2_ERR_INVALID_STREAM_STATE + * The state of the stream is not valid. + * NGHTTP2_ERR_SESSION_CLOSING + * This session is closing. + * NGHTTP2_ERR_PROTO + * Client side attempted to send response. + */ +static int session_predicate_response_headers_send(nghttp2_session *session, + nghttp2_stream *stream) { + int rv; + rv = session_predicate_for_stream_send(session, stream); + if (rv != 0) { + return rv; + } + assert(stream); + if (!session->server) { + return NGHTTP2_ERR_PROTO; + } + if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) { + return NGHTTP2_ERR_INVALID_STREAM_ID; + } + switch (stream->state) { + case NGHTTP2_STREAM_OPENING: + return 0; + case NGHTTP2_STREAM_CLOSING: + return NGHTTP2_ERR_STREAM_CLOSING; + default: + return NGHTTP2_ERR_INVALID_STREAM_STATE; + } +} + +/* + * This function checks HEADERS for reserved stream can be sent. The + * |stream| must be reserved state and the |session| is server side. + * The |stream| can be NULL. + * + * This function returns 0 if it succeeds, or one of the following + * error codes: + * + * NGHTTP2_ERR_STREAM_CLOSED + * The stream is already closed. + * NGHTTP2_ERR_STREAM_SHUT_WR + * The stream is half-closed for transmission. + * NGHTTP2_ERR_PROTO + * The stream is not reserved state + * NGHTTP2_ERR_STREAM_CLOSED + * RST_STREAM was queued for this stream. + * NGHTTP2_ERR_SESSION_CLOSING + * This session is closing. + * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED + * New stream cannot be created because GOAWAY is already sent or + * received. + * NGHTTP2_ERR_PROTO + * Client side attempted to send push response. + */ +static int +session_predicate_push_response_headers_send(nghttp2_session *session, + nghttp2_stream *stream) { + int rv; + /* TODO Should disallow HEADERS if GOAWAY has already been issued? */ + rv = session_predicate_for_stream_send(session, stream); + if (rv != 0) { + return rv; + } + assert(stream); + if (!session->server) { + return NGHTTP2_ERR_PROTO; + } + if (stream->state != NGHTTP2_STREAM_RESERVED) { + return NGHTTP2_ERR_PROTO; + } + if (session->goaway_flags & NGHTTP2_GOAWAY_RECV) { + return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED; + } + return 0; +} + +/* + * This function checks HEADERS, which is neither stream-opening nor + * first response header, with the |stream| can be sent at this time. + * The |stream| can be NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_STREAM_CLOSED + * The stream is already closed or does not exist. + * NGHTTP2_ERR_STREAM_SHUT_WR + * The transmission is not allowed for this stream (e.g., a frame + * with END_STREAM flag set has already sent) + * NGHTTP2_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + * NGHTTP2_ERR_INVALID_STREAM_STATE + * The state of the stream is not valid. + * NGHTTP2_ERR_SESSION_CLOSING + * This session is closing. + */ +static int session_predicate_headers_send(nghttp2_session *session, + nghttp2_stream *stream) { + int rv; + rv = session_predicate_for_stream_send(session, stream); + if (rv != 0) { + return rv; + } + assert(stream); + + switch (stream->state) { + case NGHTTP2_STREAM_OPENED: + return 0; + case NGHTTP2_STREAM_CLOSING: + return NGHTTP2_ERR_STREAM_CLOSING; + default: + if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) { + return 0; + } + return NGHTTP2_ERR_INVALID_STREAM_STATE; + } +} + +/* + * This function checks PUSH_PROMISE frame |frame| with the |stream| + * can be sent at this time. The |stream| can be NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED + * New stream cannot be created because GOAWAY is already sent or + * received. + * NGHTTP2_ERR_PROTO + * The client side attempts to send PUSH_PROMISE, or the server + * sends PUSH_PROMISE for the stream not initiated by the client. + * NGHTTP2_ERR_STREAM_CLOSED + * The stream is already closed or does not exist. + * NGHTTP2_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + * NGHTTP2_ERR_STREAM_SHUT_WR + * The transmission is not allowed for this stream (e.g., a frame + * with END_STREAM flag set has already sent) + * NGHTTP2_ERR_PUSH_DISABLED + * The remote peer disabled reception of PUSH_PROMISE. + * NGHTTP2_ERR_SESSION_CLOSING + * This session is closing. + */ +static int session_predicate_push_promise_send(nghttp2_session *session, + nghttp2_stream *stream) { + int rv; + + if (!session->server) { + return NGHTTP2_ERR_PROTO; + } + + rv = session_predicate_for_stream_send(session, stream); + if (rv != 0) { + return rv; + } + + assert(stream); + + if (session->remote_settings.enable_push == 0) { + return NGHTTP2_ERR_PUSH_DISABLED; + } + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_STREAM_CLOSING; + } + if (session->goaway_flags & NGHTTP2_GOAWAY_RECV) { + return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED; + } + return 0; +} + +/* + * This function checks WINDOW_UPDATE with the stream ID |stream_id| + * can be sent at this time. Note that END_STREAM flag of the previous + * frame does not affect the transmission of the WINDOW_UPDATE frame. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_STREAM_CLOSED + * The stream is already closed or does not exist. + * NGHTTP2_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + * NGHTTP2_ERR_INVALID_STREAM_STATE + * The state of the stream is not valid. + * NGHTTP2_ERR_SESSION_CLOSING + * This session is closing. + */ +static int session_predicate_window_update_send(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + + if (stream_id == 0) { + /* Connection-level window update */ + return 0; + } + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return NGHTTP2_ERR_STREAM_CLOSED; + } + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_STREAM_CLOSING; + } + if (state_reserved_local(session, stream)) { + return NGHTTP2_ERR_INVALID_STREAM_STATE; + } + return 0; +} + +static int session_predicate_altsvc_send(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + + if (stream_id == 0) { + return 0; + } + + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return NGHTTP2_ERR_STREAM_CLOSED; + } + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_STREAM_CLOSING; + } + + return 0; +} + +static int session_predicate_origin_send(nghttp2_session *session) { + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + return 0; +} + +static int session_predicate_priority_update_send(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return 0; + } + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_STREAM_CLOSING; + } + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return NGHTTP2_ERR_INVALID_STREAM_STATE; + } + + return 0; +} + +/* Take into account settings max frame size and both connection-level + flow control here */ +static ssize_t +nghttp2_session_enforce_flow_control_limits(nghttp2_session *session, + nghttp2_stream *stream, + ssize_t requested_window_size) { + DEBUGF("send: remote windowsize connection=%d, remote maxframsize=%u, " + "stream(id %d)=%d\n", + session->remote_window_size, session->remote_settings.max_frame_size, + stream->stream_id, stream->remote_window_size); + + return nghttp2_min(nghttp2_min(nghttp2_min(requested_window_size, + stream->remote_window_size), + session->remote_window_size), + (int32_t)session->remote_settings.max_frame_size); +} + +/* + * Returns the maximum length of next data read. If the + * connection-level and/or stream-wise flow control are enabled, the + * return value takes into account those current window sizes. The remote + * settings for max frame size is also taken into account. + */ +static size_t nghttp2_session_next_data_read(nghttp2_session *session, + nghttp2_stream *stream) { + ssize_t window_size; + + window_size = nghttp2_session_enforce_flow_control_limits( + session, stream, NGHTTP2_DATA_PAYLOADLEN); + + DEBUGF("send: available window=%zd\n", window_size); + + return window_size > 0 ? (size_t)window_size : 0; +} + +/* + * This function checks DATA with the |stream| can be sent at this + * time. The |stream| can be NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_STREAM_CLOSED + * The stream is already closed or does not exist. + * NGHTTP2_ERR_STREAM_SHUT_WR + * The transmission is not allowed for this stream (e.g., a frame + * with END_STREAM flag set has already sent) + * NGHTTP2_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + * NGHTTP2_ERR_INVALID_STREAM_STATE + * The state of the stream is not valid. + * NGHTTP2_ERR_SESSION_CLOSING + * This session is closing. + */ +static int nghttp2_session_predicate_data_send(nghttp2_session *session, + nghttp2_stream *stream) { + int rv; + rv = session_predicate_for_stream_send(session, stream); + if (rv != 0) { + return rv; + } + assert(stream); + if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) { + /* Request body data */ + /* If stream->state is NGHTTP2_STREAM_CLOSING, RST_STREAM was + queued but not yet sent. In this case, we won't send DATA + frames. */ + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_STREAM_CLOSING; + } + if (stream->state == NGHTTP2_STREAM_RESERVED) { + return NGHTTP2_ERR_INVALID_STREAM_STATE; + } + return 0; + } + /* Response body data */ + if (stream->state == NGHTTP2_STREAM_OPENED) { + return 0; + } + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_STREAM_CLOSING; + } + return NGHTTP2_ERR_INVALID_STREAM_STATE; +} + +static ssize_t session_call_select_padding(nghttp2_session *session, + const nghttp2_frame *frame, + size_t max_payloadlen) { + ssize_t rv; + + if (frame->hd.length >= max_payloadlen) { + return (ssize_t)frame->hd.length; + } + + if (session->callbacks.select_padding_callback) { + size_t max_paddedlen; + + max_paddedlen = + nghttp2_min(frame->hd.length + NGHTTP2_MAX_PADLEN, max_payloadlen); + + rv = session->callbacks.select_padding_callback( + session, frame, max_paddedlen, session->user_data); + if (rv < (ssize_t)frame->hd.length || rv > (ssize_t)max_paddedlen) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return rv; + } + return (ssize_t)frame->hd.length; +} + +/* Add padding to HEADERS or PUSH_PROMISE. We use + frame->headers.padlen in this function to use the fact that + frame->push_promise has also padlen in the same position. */ +static int session_headers_add_pad(nghttp2_session *session, + nghttp2_frame *frame) { + ssize_t padded_payloadlen; + nghttp2_active_outbound_item *aob; + nghttp2_bufs *framebufs; + size_t padlen; + size_t max_payloadlen; + + aob = &session->aob; + framebufs = &aob->framebufs; + + max_payloadlen = nghttp2_min(NGHTTP2_MAX_PAYLOADLEN, + frame->hd.length + NGHTTP2_MAX_PADLEN); + + padded_payloadlen = + session_call_select_padding(session, frame, max_payloadlen); + + if (nghttp2_is_fatal((int)padded_payloadlen)) { + return (int)padded_payloadlen; + } + + padlen = (size_t)padded_payloadlen - frame->hd.length; + + DEBUGF("send: padding selected: payloadlen=%zd, padlen=%zu\n", + padded_payloadlen, padlen); + + nghttp2_frame_add_pad(framebufs, &frame->hd, padlen, 0); + + frame->headers.padlen = padlen; + + return 0; +} + +static size_t session_estimate_headers_payload(nghttp2_session *session, + const nghttp2_nv *nva, + size_t nvlen, + size_t additional) { + return nghttp2_hd_deflate_bound(&session->hd_deflater, nva, nvlen) + + additional; +} + +static int session_pack_extension(nghttp2_session *session, nghttp2_bufs *bufs, + nghttp2_frame *frame) { + ssize_t rv; + nghttp2_buf *buf; + size_t buflen; + size_t framelen; + + assert(session->callbacks.pack_extension_callback); + + buf = &bufs->head->buf; + buflen = nghttp2_min(nghttp2_buf_avail(buf), NGHTTP2_MAX_PAYLOADLEN); + + rv = session->callbacks.pack_extension_callback(session, buf->last, buflen, + frame, session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return (int)rv; + } + + if (rv < 0 || (size_t)rv > buflen) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + framelen = (size_t)rv; + + frame->hd.length = framelen; + + assert(buf->pos == buf->last); + buf->last += framelen; + buf->pos -= NGHTTP2_FRAME_HDLEN; + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + return 0; +} + +/* + * This function serializes frame for transmission. + * + * This function returns 0 if it succeeds, or one of negative error + * codes, including both fatal and non-fatal ones. + */ +static int session_prep_frame(nghttp2_session *session, + nghttp2_outbound_item *item) { + int rv; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = &session->mem; + frame = &item->frame; + + switch (frame->hd.type) { + case NGHTTP2_DATA: { + size_t next_readmax; + nghttp2_stream *stream; + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + + if (stream) { + assert(stream->item == item); + } + + rv = nghttp2_session_predicate_data_send(session, stream); + if (rv != 0) { + // If stream was already closed, nghttp2_session_get_stream() + // returns NULL, but item is still attached to the stream. + // Search stream including closed again. + stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); + if (stream) { + session_detach_stream_item(session, stream); + } + + return rv; + } + /* Assuming stream is not NULL */ + assert(stream); + next_readmax = nghttp2_session_next_data_read(session, stream); + + if (next_readmax == 0) { + + /* This must be true since we only pop DATA frame item from + queue when session->remote_window_size > 0 */ + assert(session->remote_window_size > 0); + + session_defer_stream_item(session, stream, + NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); + + session->aob.item = NULL; + active_outbound_item_reset(&session->aob, mem); + return NGHTTP2_ERR_DEFERRED; + } + + rv = nghttp2_session_pack_data(session, &session->aob.framebufs, + next_readmax, frame, &item->aux_data.data, + stream); + if (rv == NGHTTP2_ERR_PAUSE) { + return rv; + } + if (rv == NGHTTP2_ERR_DEFERRED) { + session_defer_stream_item(session, stream, + NGHTTP2_STREAM_FLAG_DEFERRED_USER); + + session->aob.item = NULL; + active_outbound_item_reset(&session->aob, mem); + return NGHTTP2_ERR_DEFERRED; + } + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + session_detach_stream_item(session, stream); + + rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + if (rv != 0) { + session_detach_stream_item(session, stream); + + return rv; + } + return 0; + } + case NGHTTP2_HEADERS: { + nghttp2_headers_aux_data *aux_data; + size_t estimated_payloadlen; + + aux_data = &item->aux_data.headers; + + if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { + /* initial HEADERS, which opens stream */ + nghttp2_stream *stream; + + stream = nghttp2_session_open_stream( + session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, + &frame->headers.pri_spec, NGHTTP2_STREAM_INITIAL, + aux_data->stream_user_data); + + if (stream == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + /* We don't call nghttp2_session_adjust_closed_stream() here, + since we don't keep closed stream in client side */ + + rv = session_predicate_request_headers_send(session, item); + if (rv != 0) { + return rv; + } + + if (session_enforce_http_messaging(session)) { + nghttp2_http_record_request_method(stream, frame); + } + } else { + nghttp2_stream *stream; + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + + if (stream && stream->state == NGHTTP2_STREAM_RESERVED) { + rv = session_predicate_push_response_headers_send(session, stream); + if (rv == 0) { + frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; + + if (aux_data->stream_user_data) { + stream->stream_user_data = aux_data->stream_user_data; + } + } + } else if (session_predicate_response_headers_send(session, stream) == + 0) { + frame->headers.cat = NGHTTP2_HCAT_RESPONSE; + rv = 0; + } else { + frame->headers.cat = NGHTTP2_HCAT_HEADERS; + + rv = session_predicate_headers_send(session, stream); + } + + if (rv != 0) { + return rv; + } + } + + estimated_payloadlen = session_estimate_headers_payload( + session, frame->headers.nva, frame->headers.nvlen, + NGHTTP2_PRIORITY_SPECLEN); + + if (estimated_payloadlen > session->max_send_header_block_length) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + + rv = nghttp2_frame_pack_headers(&session->aob.framebufs, &frame->headers, + &session->hd_deflater); + + if (rv != 0) { + return rv; + } + + DEBUGF("send: before padding, HEADERS serialized in %zd bytes\n", + nghttp2_bufs_len(&session->aob.framebufs)); + + rv = session_headers_add_pad(session, frame); + + if (rv != 0) { + return rv; + } + + DEBUGF("send: HEADERS finally serialized in %zd bytes\n", + nghttp2_bufs_len(&session->aob.framebufs)); + + if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { + assert(session->last_sent_stream_id < frame->hd.stream_id); + session->last_sent_stream_id = frame->hd.stream_id; + } + + return 0; + } + case NGHTTP2_PRIORITY: { + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + /* PRIORITY frame can be sent at any time and to any stream + ID. */ + nghttp2_frame_pack_priority(&session->aob.framebufs, &frame->priority); + + /* Peer can send PRIORITY frame against idle stream to create + "anchor" in dependency tree. Only client can do this in + nghttp2. In nghttp2, only server retains non-active (closed + or idle) streams in memory, so we don't open stream here. */ + return 0; + } + case NGHTTP2_RST_STREAM: + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + nghttp2_frame_pack_rst_stream(&session->aob.framebufs, &frame->rst_stream); + return 0; + case NGHTTP2_SETTINGS: { + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + assert(session->obq_flood_counter_ > 0); + --session->obq_flood_counter_; + /* When session is about to close, don't send SETTINGS ACK. + We are required to send SETTINGS without ACK though; for + example, we have to send SETTINGS as a part of connection + preface. */ + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + } + + rv = nghttp2_frame_pack_settings(&session->aob.framebufs, &frame->settings); + if (rv != 0) { + return rv; + } + return 0; + } + case NGHTTP2_PUSH_PROMISE: { + nghttp2_stream *stream; + size_t estimated_payloadlen; + + /* stream could be NULL if associated stream was already + closed. */ + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + + /* predicate should fail if stream is NULL. */ + rv = session_predicate_push_promise_send(session, stream); + if (rv != 0) { + return rv; + } + + assert(stream); + + estimated_payloadlen = session_estimate_headers_payload( + session, frame->push_promise.nva, frame->push_promise.nvlen, 0); + + if (estimated_payloadlen > session->max_send_header_block_length) { + return NGHTTP2_ERR_FRAME_SIZE_ERROR; + } + + rv = nghttp2_frame_pack_push_promise( + &session->aob.framebufs, &frame->push_promise, &session->hd_deflater); + if (rv != 0) { + return rv; + } + rv = session_headers_add_pad(session, frame); + if (rv != 0) { + return rv; + } + + assert(session->last_sent_stream_id + 2 <= + frame->push_promise.promised_stream_id); + session->last_sent_stream_id = frame->push_promise.promised_stream_id; + + return 0; + } + case NGHTTP2_PING: + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + assert(session->obq_flood_counter_ > 0); + --session->obq_flood_counter_; + } + /* PING frame is allowed to be sent unless termination GOAWAY is + sent */ + if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + nghttp2_frame_pack_ping(&session->aob.framebufs, &frame->ping); + return 0; + case NGHTTP2_GOAWAY: + rv = nghttp2_frame_pack_goaway(&session->aob.framebufs, &frame->goaway); + if (rv != 0) { + return rv; + } + session->local_last_stream_id = frame->goaway.last_stream_id; + + return 0; + case NGHTTP2_WINDOW_UPDATE: + rv = session_predicate_window_update_send(session, frame->hd.stream_id); + if (rv != 0) { + return rv; + } + nghttp2_frame_pack_window_update(&session->aob.framebufs, + &frame->window_update); + return 0; + case NGHTTP2_CONTINUATION: + /* We never handle CONTINUATION here. */ + assert(0); + return 0; + default: { + nghttp2_ext_aux_data *aux_data; + + /* extension frame */ + + aux_data = &item->aux_data.ext; + + if (aux_data->builtin == 0) { + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + + return session_pack_extension(session, &session->aob.framebufs, frame); + } + + switch (frame->hd.type) { + case NGHTTP2_ALTSVC: + rv = session_predicate_altsvc_send(session, frame->hd.stream_id); + if (rv != 0) { + return rv; + } + + nghttp2_frame_pack_altsvc(&session->aob.framebufs, &frame->ext); + + return 0; + case NGHTTP2_ORIGIN: + rv = session_predicate_origin_send(session); + if (rv != 0) { + return rv; + } + + rv = nghttp2_frame_pack_origin(&session->aob.framebufs, &frame->ext); + if (rv != 0) { + return rv; + } + + return 0; + case NGHTTP2_PRIORITY_UPDATE: { + nghttp2_ext_priority_update *priority_update = frame->ext.payload; + rv = session_predicate_priority_update_send(session, + priority_update->stream_id); + if (rv != 0) { + return rv; + } + + nghttp2_frame_pack_priority_update(&session->aob.framebufs, &frame->ext); + + return 0; + } + default: + /* Unreachable here */ + assert(0); + return 0; + } + } + } +} + +nghttp2_outbound_item * +nghttp2_session_get_next_ob_item(nghttp2_session *session) { + nghttp2_outbound_item *item; + + if (nghttp2_outbound_queue_top(&session->ob_urgent)) { + return nghttp2_outbound_queue_top(&session->ob_urgent); + } + + if (nghttp2_outbound_queue_top(&session->ob_reg)) { + return nghttp2_outbound_queue_top(&session->ob_reg); + } + + if (!session_is_outgoing_concurrent_streams_max(session)) { + if (nghttp2_outbound_queue_top(&session->ob_syn)) { + return nghttp2_outbound_queue_top(&session->ob_syn); + } + } + + if (session->remote_window_size > 0) { + item = nghttp2_stream_next_outbound_item(&session->root); + if (item) { + return item; + } + + return session_sched_get_next_outbound_item(session); + } + + return NULL; +} + +nghttp2_outbound_item * +nghttp2_session_pop_next_ob_item(nghttp2_session *session) { + nghttp2_outbound_item *item; + + item = nghttp2_outbound_queue_top(&session->ob_urgent); + if (item) { + nghttp2_outbound_queue_pop(&session->ob_urgent); + item->queued = 0; + return item; + } + + item = nghttp2_outbound_queue_top(&session->ob_reg); + if (item) { + nghttp2_outbound_queue_pop(&session->ob_reg); + item->queued = 0; + return item; + } + + if (!session_is_outgoing_concurrent_streams_max(session)) { + item = nghttp2_outbound_queue_top(&session->ob_syn); + if (item) { + nghttp2_outbound_queue_pop(&session->ob_syn); + item->queued = 0; + return item; + } + } + + if (session->remote_window_size > 0) { + item = nghttp2_stream_next_outbound_item(&session->root); + if (item) { + return item; + } + + return session_sched_get_next_outbound_item(session); + } + + return NULL; +} + +static int session_call_before_frame_send(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + if (session->callbacks.before_frame_send_callback) { + rv = session->callbacks.before_frame_send_callback(session, frame, + session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return rv; + } + + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_call_on_frame_send(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + if (session->callbacks.on_frame_send_callback) { + rv = session->callbacks.on_frame_send_callback(session, frame, + session->user_data); + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int find_stream_on_goaway_func(void *entry, void *ptr) { + nghttp2_close_stream_on_goaway_arg *arg; + nghttp2_stream *stream; + + arg = (nghttp2_close_stream_on_goaway_arg *)ptr; + stream = (nghttp2_stream *)entry; + + if (nghttp2_session_is_my_stream_id(arg->session, stream->stream_id)) { + if (arg->incoming) { + return 0; + } + } else if (!arg->incoming) { + return 0; + } + + if (stream->state != NGHTTP2_STREAM_IDLE && + (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) == 0 && + stream->stream_id > arg->last_stream_id) { + /* We are collecting streams to close because we cannot call + nghttp2_session_close_stream() inside nghttp2_map_each(). + Reuse closed_next member.. bad choice? */ + assert(stream->closed_next == NULL); + assert(stream->closed_prev == NULL); + + if (arg->head) { + stream->closed_next = arg->head; + arg->head = stream; + } else { + arg->head = stream; + } + } + + return 0; +} + +/* Closes non-idle and non-closed streams whose stream ID > + last_stream_id. If incoming is nonzero, we are going to close + incoming streams. Otherwise, close outgoing streams. */ +static int session_close_stream_on_goaway(nghttp2_session *session, + int32_t last_stream_id, + int incoming) { + int rv; + nghttp2_stream *stream, *next_stream; + nghttp2_close_stream_on_goaway_arg arg = {session, NULL, last_stream_id, + incoming}; + + rv = nghttp2_map_each(&session->streams, find_stream_on_goaway_func, &arg); + assert(rv == 0); + + stream = arg.head; + while (stream) { + next_stream = stream->closed_next; + stream->closed_next = NULL; + rv = nghttp2_session_close_stream(session, stream->stream_id, + NGHTTP2_REFUSED_STREAM); + + /* stream may be deleted here */ + + stream = next_stream; + + if (nghttp2_is_fatal(rv)) { + /* Clean up closed_next member just in case */ + while (stream) { + next_stream = stream->closed_next; + stream->closed_next = NULL; + stream = next_stream; + } + return rv; + } + } + + return 0; +} + +static void session_reschedule_stream(nghttp2_session *session, + nghttp2_stream *stream) { + stream->last_writelen = stream->item->frame.hd.length; + + if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) { + nghttp2_stream_reschedule(stream); + return; + } + + if (!session->server) { + return; + } + + session_sched_reschedule_stream(session, stream); +} + +static int session_update_stream_consumed_size(nghttp2_session *session, + nghttp2_stream *stream, + size_t delta_size); + +static int session_update_connection_consumed_size(nghttp2_session *session, + size_t delta_size); + +/* + * Called after a frame is sent. This function runs + * on_frame_send_callback and handles stream closure upon END_STREAM + * or RST_STREAM. This function does not reset session->aob. It is a + * responsibility of session_after_frame_sent2. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +static int session_after_frame_sent1(nghttp2_session *session) { + int rv; + nghttp2_active_outbound_item *aob = &session->aob; + nghttp2_outbound_item *item = aob->item; + nghttp2_bufs *framebufs = &aob->framebufs; + nghttp2_frame *frame; + nghttp2_stream *stream; + + frame = &item->frame; + + if (frame->hd.type == NGHTTP2_DATA) { + nghttp2_data_aux_data *aux_data; + + aux_data = &item->aux_data.data; + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + /* We update flow control window after a frame was completely + sent. This is possible because we choose payload length not to + exceed the window */ + session->remote_window_size -= (int32_t)frame->hd.length; + if (stream) { + stream->remote_window_size -= (int32_t)frame->hd.length; + } + + if (stream && aux_data->eof) { + session_detach_stream_item(session, stream); + + /* Call on_frame_send_callback after + nghttp2_stream_detach_item(), so that application can issue + nghttp2_submit_data() in the callback. */ + if (session->callbacks.on_frame_send_callback) { + rv = session_call_on_frame_send(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + int stream_closed; + + stream_closed = + (stream->shut_flags & NGHTTP2_SHUT_RDWR) == NGHTTP2_SHUT_RDWR; + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if (nghttp2_is_fatal(rv)) { + return rv; + } + /* stream may be NULL if it was closed */ + if (stream_closed) { + stream = NULL; + } + } + return 0; + } + + if (session->callbacks.on_frame_send_callback) { + rv = session_call_on_frame_send(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + return 0; + } + + /* non-DATA frame */ + + if (frame->hd.type == NGHTTP2_HEADERS || + frame->hd.type == NGHTTP2_PUSH_PROMISE) { + if (nghttp2_bufs_next_present(framebufs)) { + DEBUGF("send: CONTINUATION exists, just return\n"); + return 0; + } + } + rv = session_call_on_frame_send(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + switch (frame->hd.type) { + case NGHTTP2_HEADERS: { + nghttp2_headers_aux_data *aux_data; + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream) { + return 0; + } + + switch (frame->headers.cat) { + case NGHTTP2_HCAT_REQUEST: { + stream->state = NGHTTP2_STREAM_OPENING; + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + } + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if (nghttp2_is_fatal(rv)) { + return rv; + } + /* We assume aux_data is a pointer to nghttp2_headers_aux_data */ + aux_data = &item->aux_data.headers; + if (aux_data->data_prd.read_callback) { + /* nghttp2_submit_data() makes a copy of aux_data->data_prd */ + rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, + frame->hd.stream_id, &aux_data->data_prd); + if (nghttp2_is_fatal(rv)) { + return rv; + } + /* TODO nghttp2_submit_data() may fail if stream has already + DATA frame item. We might have to handle it here. */ + } + return 0; + } + case NGHTTP2_HCAT_PUSH_RESPONSE: + stream->flags = (uint8_t)(stream->flags & ~NGHTTP2_STREAM_FLAG_PUSH); + ++session->num_outgoing_streams; + /* Fall through */ + case NGHTTP2_HCAT_RESPONSE: + stream->state = NGHTTP2_STREAM_OPENED; + /* Fall through */ + case NGHTTP2_HCAT_HEADERS: + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + } + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if (nghttp2_is_fatal(rv)) { + return rv; + } + /* We assume aux_data is a pointer to nghttp2_headers_aux_data */ + aux_data = &item->aux_data.headers; + if (aux_data->data_prd.read_callback) { + rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, + frame->hd.stream_id, &aux_data->data_prd); + if (nghttp2_is_fatal(rv)) { + return rv; + } + /* TODO nghttp2_submit_data() may fail if stream has already + DATA frame item. We might have to handle it here. */ + } + return 0; + default: + /* Unreachable */ + assert(0); + return 0; + } + } + case NGHTTP2_PRIORITY: + if (session->server || session->pending_no_rfc7540_priorities == 1) { + return 0; + } + + stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); + + if (!stream) { + if (!session_detect_idle_stream(session, frame->hd.stream_id)) { + return 0; + } + + stream = nghttp2_session_open_stream( + session, frame->hd.stream_id, NGHTTP2_FLAG_NONE, + &frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL); + if (!stream) { + return NGHTTP2_ERR_NOMEM; + } + } else { + rv = nghttp2_session_reprioritize_stream(session, stream, + &frame->priority.pri_spec); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + rv = nghttp2_session_adjust_idle_stream(session); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; + case NGHTTP2_RST_STREAM: + rv = nghttp2_session_close_stream(session, frame->hd.stream_id, + frame->rst_stream.error_code); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return 0; + case NGHTTP2_GOAWAY: { + nghttp2_goaway_aux_data *aux_data; + + aux_data = &item->aux_data.goaway; + + if ((aux_data->flags & NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE) == 0) { + + if (aux_data->flags & NGHTTP2_GOAWAY_AUX_TERM_ON_SEND) { + session->goaway_flags |= NGHTTP2_GOAWAY_TERM_SENT; + } + + session->goaway_flags |= NGHTTP2_GOAWAY_SENT; + + rv = session_close_stream_on_goaway(session, frame->goaway.last_stream_id, + 1); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + return 0; + } + case NGHTTP2_WINDOW_UPDATE: + if (frame->hd.stream_id == 0) { + session->window_update_queued = 0; + if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + rv = session_update_connection_consumed_size(session, 0); + } else { + rv = nghttp2_session_update_recv_connection_window_size(session, 0); + } + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; + } + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream) { + return 0; + } + + stream->window_update_queued = 0; + + /* We don't have to send WINDOW_UPDATE if END_STREAM from peer + is seen. */ + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return 0; + } + + if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + rv = session_update_stream_consumed_size(session, stream, 0); + } else { + rv = + nghttp2_session_update_recv_stream_window_size(session, stream, 0, 1); + } + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; + default: + return 0; + } +} + +/* + * Called after a frame is sent and session_after_frame_sent1. This + * function is responsible to reset session->aob. + */ +static void session_after_frame_sent2(nghttp2_session *session) { + nghttp2_active_outbound_item *aob = &session->aob; + nghttp2_outbound_item *item = aob->item; + nghttp2_bufs *framebufs = &aob->framebufs; + nghttp2_frame *frame; + nghttp2_mem *mem; + nghttp2_stream *stream; + nghttp2_data_aux_data *aux_data; + + mem = &session->mem; + frame = &item->frame; + + if (frame->hd.type != NGHTTP2_DATA) { + + if (frame->hd.type == NGHTTP2_HEADERS || + frame->hd.type == NGHTTP2_PUSH_PROMISE) { + + if (nghttp2_bufs_next_present(framebufs)) { + framebufs->cur = framebufs->cur->next; + + DEBUGF("send: next CONTINUATION frame, %zu bytes\n", + nghttp2_buf_len(&framebufs->cur->buf)); + + return; + } + } + + active_outbound_item_reset(&session->aob, mem); + + return; + } + + /* DATA frame */ + + aux_data = &item->aux_data.data; + + /* On EOF, we have already detached data. Please note that + application may issue nghttp2_submit_data() in + on_frame_send_callback (call from session_after_frame_sent1), + which attach data to stream. We don't want to detach it. */ + if (aux_data->eof) { + active_outbound_item_reset(aob, mem); + + return; + } + + /* Reset no_copy here because next write may not use this. */ + aux_data->no_copy = 0; + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + + /* If session is closed or RST_STREAM was queued, we won't send + further data. */ + if (nghttp2_session_predicate_data_send(session, stream) != 0) { + if (stream) { + session_detach_stream_item(session, stream); + } + + active_outbound_item_reset(aob, mem); + + return; + } + + aob->item = NULL; + active_outbound_item_reset(&session->aob, mem); + + return; +} + +static int session_call_send_data(nghttp2_session *session, + nghttp2_outbound_item *item, + nghttp2_bufs *framebufs) { + int rv; + nghttp2_buf *buf; + size_t length; + nghttp2_frame *frame; + nghttp2_data_aux_data *aux_data; + + buf = &framebufs->cur->buf; + frame = &item->frame; + length = frame->hd.length - frame->data.padlen; + aux_data = &item->aux_data.data; + + rv = session->callbacks.send_data_callback(session, frame, buf->pos, length, + &aux_data->data_prd.source, + session->user_data); + + switch (rv) { + case 0: + case NGHTTP2_ERR_WOULDBLOCK: + case NGHTTP2_ERR_PAUSE: + case NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE: + return rv; + default: + return NGHTTP2_ERR_CALLBACK_FAILURE; + } +} + +static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session, + const uint8_t **data_ptr, + int fast_cb) { + int rv; + nghttp2_active_outbound_item *aob; + nghttp2_bufs *framebufs; + nghttp2_mem *mem; + + mem = &session->mem; + aob = &session->aob; + framebufs = &aob->framebufs; + + /* We may have idle streams more than we expect (e.g., + nghttp2_session_change_stream_priority() or + nghttp2_session_create_idle_stream()). Adjust them here. */ + rv = nghttp2_session_adjust_idle_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + for (;;) { + switch (aob->state) { + case NGHTTP2_OB_POP_ITEM: { + nghttp2_outbound_item *item; + + item = nghttp2_session_pop_next_ob_item(session); + if (item == NULL) { + return 0; + } + + rv = session_prep_frame(session, item); + if (rv == NGHTTP2_ERR_PAUSE) { + return 0; + } + if (rv == NGHTTP2_ERR_DEFERRED) { + DEBUGF("send: frame transmission deferred\n"); + break; + } + if (rv < 0) { + int32_t opened_stream_id = 0; + uint32_t error_code = NGHTTP2_INTERNAL_ERROR; + int rv2 = 0; + + DEBUGF("send: frame preparation failed with %s\n", + nghttp2_strerror(rv)); + /* TODO If the error comes from compressor, the connection + must be closed. */ + if (item->frame.hd.type != NGHTTP2_DATA && + session->callbacks.on_frame_not_send_callback && is_non_fatal(rv)) { + nghttp2_frame *frame = &item->frame; + /* The library is responsible for the transmission of + WINDOW_UPDATE frame, so we don't call error callback for + it. */ + if (frame->hd.type != NGHTTP2_WINDOW_UPDATE && + session->callbacks.on_frame_not_send_callback( + session, frame, rv, session->user_data) != 0) { + + nghttp2_outbound_item_free(item, mem); + nghttp2_mem_free(mem, item); + + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + /* We have to close stream opened by failed request HEADERS + or PUSH_PROMISE. */ + switch (item->frame.hd.type) { + case NGHTTP2_HEADERS: + if (item->frame.headers.cat == NGHTTP2_HCAT_REQUEST) { + opened_stream_id = item->frame.hd.stream_id; + if (item->aux_data.headers.canceled) { + error_code = item->aux_data.headers.error_code; + } else { + /* Set error_code to REFUSED_STREAM so that application + can send request again. */ + error_code = NGHTTP2_REFUSED_STREAM; + } + } + break; + case NGHTTP2_PUSH_PROMISE: + opened_stream_id = item->frame.push_promise.promised_stream_id; + break; + } + if (opened_stream_id) { + /* careful not to override rv */ + rv2 = nghttp2_session_close_stream(session, opened_stream_id, + error_code); + } + + nghttp2_outbound_item_free(item, mem); + nghttp2_mem_free(mem, item); + active_outbound_item_reset(aob, mem); + + if (nghttp2_is_fatal(rv2)) { + return rv2; + } + + if (rv == NGHTTP2_ERR_HEADER_COMP) { + /* If header compression error occurred, should terminiate + connection. */ + rv = nghttp2_session_terminate_session(session, + NGHTTP2_INTERNAL_ERROR); + } + if (nghttp2_is_fatal(rv)) { + return rv; + } + break; + } + + aob->item = item; + + nghttp2_bufs_rewind(framebufs); + + if (item->frame.hd.type != NGHTTP2_DATA) { + nghttp2_frame *frame; + + frame = &item->frame; + + DEBUGF("send: next frame: payloadlen=%zu, type=%u, flags=0x%02x, " + "stream_id=%d\n", + frame->hd.length, frame->hd.type, frame->hd.flags, + frame->hd.stream_id); + + rv = session_call_before_frame_send(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv == NGHTTP2_ERR_CANCEL) { + int32_t opened_stream_id = 0; + uint32_t error_code = NGHTTP2_INTERNAL_ERROR; + + if (session->callbacks.on_frame_not_send_callback) { + if (session->callbacks.on_frame_not_send_callback( + session, frame, rv, session->user_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + /* We have to close stream opened by canceled request + HEADERS or PUSH_PROMISE. */ + switch (item->frame.hd.type) { + case NGHTTP2_HEADERS: + if (item->frame.headers.cat == NGHTTP2_HCAT_REQUEST) { + opened_stream_id = item->frame.hd.stream_id; + /* We don't have to check + item->aux_data.headers.canceled since it has already + been checked. */ + /* Set error_code to REFUSED_STREAM so that application + can send request again. */ + error_code = NGHTTP2_REFUSED_STREAM; + } + break; + case NGHTTP2_PUSH_PROMISE: + opened_stream_id = item->frame.push_promise.promised_stream_id; + break; + } + if (opened_stream_id) { + /* careful not to override rv */ + int rv2; + rv2 = nghttp2_session_close_stream(session, opened_stream_id, + error_code); + + if (nghttp2_is_fatal(rv2)) { + return rv2; + } + } + + active_outbound_item_reset(aob, mem); + + break; + } + } else { + DEBUGF("send: next frame: DATA\n"); + + if (item->aux_data.data.no_copy) { + aob->state = NGHTTP2_OB_SEND_NO_COPY; + break; + } + } + + DEBUGF("send: start transmitting frame type=%u, length=%zd\n", + framebufs->cur->buf.pos[3], + framebufs->cur->buf.last - framebufs->cur->buf.pos); + + aob->state = NGHTTP2_OB_SEND_DATA; + + break; + } + case NGHTTP2_OB_SEND_DATA: { + size_t datalen; + nghttp2_buf *buf; + + buf = &framebufs->cur->buf; + + if (buf->pos == buf->last) { + DEBUGF("send: end transmission of a frame\n"); + + /* Frame has completely sent */ + if (fast_cb) { + session_after_frame_sent2(session); + } else { + rv = session_after_frame_sent1(session); + if (rv < 0) { + /* FATAL */ + assert(nghttp2_is_fatal(rv)); + return rv; + } + session_after_frame_sent2(session); + } + /* We have already adjusted the next state */ + break; + } + + *data_ptr = buf->pos; + datalen = nghttp2_buf_len(buf); + + /* We increment the offset here. If send_callback does not send + everything, we will adjust it. */ + buf->pos += datalen; + + return (ssize_t)datalen; + } + case NGHTTP2_OB_SEND_NO_COPY: { + nghttp2_stream *stream; + nghttp2_frame *frame; + int pause; + + DEBUGF("send: no copy DATA\n"); + + frame = &aob->item->frame; + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (stream == NULL) { + DEBUGF("send: no copy DATA cancelled because stream was closed\n"); + + active_outbound_item_reset(aob, mem); + + break; + } + + rv = session_call_send_data(session, aob->item, framebufs); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + session_detach_stream_item(session, stream); + + rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + active_outbound_item_reset(aob, mem); + + break; + } + + if (rv == NGHTTP2_ERR_WOULDBLOCK) { + return 0; + } + + pause = (rv == NGHTTP2_ERR_PAUSE); + + rv = session_after_frame_sent1(session); + if (rv < 0) { + assert(nghttp2_is_fatal(rv)); + return rv; + } + session_after_frame_sent2(session); + + /* We have already adjusted the next state */ + + if (pause) { + return 0; + } + + break; + } + case NGHTTP2_OB_SEND_CLIENT_MAGIC: { + size_t datalen; + nghttp2_buf *buf; + + buf = &framebufs->cur->buf; + + if (buf->pos == buf->last) { + DEBUGF("send: end transmission of client magic\n"); + active_outbound_item_reset(aob, mem); + break; + } + + *data_ptr = buf->pos; + datalen = nghttp2_buf_len(buf); + + buf->pos += datalen; + + return (ssize_t)datalen; + } + } + } +} + +ssize_t nghttp2_session_mem_send(nghttp2_session *session, + const uint8_t **data_ptr) { + int rv; + ssize_t len; + + *data_ptr = NULL; + + len = nghttp2_session_mem_send_internal(session, data_ptr, 1); + if (len <= 0) { + return len; + } + + if (session->aob.item) { + /* We have to call session_after_frame_sent1 here to handle stream + closure upon transmission of frames. Otherwise, END_STREAM may + be reached to client before we call nghttp2_session_mem_send + again and we may get exceeding number of incoming streams. */ + rv = session_after_frame_sent1(session); + if (rv < 0) { + assert(nghttp2_is_fatal(rv)); + return (ssize_t)rv; + } + } + + return len; +} + +int nghttp2_session_send(nghttp2_session *session) { + const uint8_t *data = NULL; + ssize_t datalen; + ssize_t sentlen; + nghttp2_bufs *framebufs; + + framebufs = &session->aob.framebufs; + + for (;;) { + datalen = nghttp2_session_mem_send_internal(session, &data, 0); + if (datalen <= 0) { + return (int)datalen; + } + sentlen = session->callbacks.send_callback(session, data, (size_t)datalen, + 0, session->user_data); + if (sentlen < 0) { + if (sentlen == NGHTTP2_ERR_WOULDBLOCK) { + /* Transmission canceled. Rewind the offset */ + framebufs->cur->buf.pos -= datalen; + + return 0; + } + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + /* Rewind the offset to the amount of unsent bytes */ + framebufs->cur->buf.pos -= datalen - sentlen; + } +} + +static ssize_t session_recv(nghttp2_session *session, uint8_t *buf, + size_t len) { + ssize_t rv; + rv = session->callbacks.recv_callback(session, buf, len, 0, + session->user_data); + if (rv > 0) { + if ((size_t)rv > len) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } else if (rv < 0 && rv != NGHTTP2_ERR_WOULDBLOCK && rv != NGHTTP2_ERR_EOF) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return rv; +} + +static int session_call_on_begin_frame(nghttp2_session *session, + const nghttp2_frame_hd *hd) { + int rv; + + if (session->callbacks.on_begin_frame_callback) { + + rv = session->callbacks.on_begin_frame_callback(session, hd, + session->user_data); + + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + return 0; +} + +static int session_call_on_frame_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + if (session->callbacks.on_frame_recv_callback) { + rv = session->callbacks.on_frame_recv_callback(session, frame, + session->user_data); + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_call_on_begin_headers(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + DEBUGF("recv: call on_begin_headers callback stream_id=%d\n", + frame->hd.stream_id); + if (session->callbacks.on_begin_headers_callback) { + rv = session->callbacks.on_begin_headers_callback(session, frame, + session->user_data); + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_call_on_header(nghttp2_session *session, + const nghttp2_frame *frame, + const nghttp2_hd_nv *nv) { + int rv = 0; + if (session->callbacks.on_header_callback2) { + rv = session->callbacks.on_header_callback2( + session, frame, nv->name, nv->value, nv->flags, session->user_data); + } else if (session->callbacks.on_header_callback) { + rv = session->callbacks.on_header_callback( + session, frame, nv->name->base, nv->name->len, nv->value->base, + nv->value->len, nv->flags, session->user_data); + } + + if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int session_call_on_invalid_header(nghttp2_session *session, + const nghttp2_frame *frame, + const nghttp2_hd_nv *nv) { + int rv; + if (session->callbacks.on_invalid_header_callback2) { + rv = session->callbacks.on_invalid_header_callback2( + session, frame, nv->name, nv->value, nv->flags, session->user_data); + } else if (session->callbacks.on_invalid_header_callback) { + rv = session->callbacks.on_invalid_header_callback( + session, frame, nv->name->base, nv->name->len, nv->value->base, + nv->value->len, nv->flags, session->user_data); + } else { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int +session_call_on_extension_chunk_recv_callback(nghttp2_session *session, + const uint8_t *data, size_t len) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + if (session->callbacks.on_extension_chunk_recv_callback) { + rv = session->callbacks.on_extension_chunk_recv_callback( + session, &frame->hd, data, len, session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + return 0; +} + +static int session_call_unpack_extension_callback(nghttp2_session *session) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + void *payload = NULL; + + rv = session->callbacks.unpack_extension_callback( + session, &payload, &frame->hd, session->user_data); + if (rv == NGHTTP2_ERR_CANCEL) { + return rv; + } + if (rv != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + frame->ext.payload = payload; + + return 0; +} + +/* + * Handles frame size error. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int session_handle_frame_size_error(nghttp2_session *session) { + /* TODO Currently no callback is called for this error, because we + call this callback before reading any payload */ + return nghttp2_session_terminate_session(session, NGHTTP2_FRAME_SIZE_ERROR); +} + +static uint32_t get_error_code_from_lib_error_code(int lib_error_code) { + switch (lib_error_code) { + case NGHTTP2_ERR_STREAM_CLOSED: + return NGHTTP2_STREAM_CLOSED; + case NGHTTP2_ERR_HEADER_COMP: + return NGHTTP2_COMPRESSION_ERROR; + case NGHTTP2_ERR_FRAME_SIZE_ERROR: + return NGHTTP2_FRAME_SIZE_ERROR; + case NGHTTP2_ERR_FLOW_CONTROL: + return NGHTTP2_FLOW_CONTROL_ERROR; + case NGHTTP2_ERR_REFUSED_STREAM: + return NGHTTP2_REFUSED_STREAM; + case NGHTTP2_ERR_PROTO: + case NGHTTP2_ERR_HTTP_HEADER: + case NGHTTP2_ERR_HTTP_MESSAGING: + return NGHTTP2_PROTOCOL_ERROR; + default: + return NGHTTP2_INTERNAL_ERROR; + } +} + +/* + * Calls on_invalid_frame_recv_callback if it is set to |session|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * User defined callback function fails. + */ +static int session_call_on_invalid_frame_recv_callback(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code) { + if (session->callbacks.on_invalid_frame_recv_callback) { + if (session->callbacks.on_invalid_frame_recv_callback( + session, frame, lib_error_code, session->user_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_handle_invalid_stream2(nghttp2_session *session, + int32_t stream_id, + nghttp2_frame *frame, + int lib_error_code) { + int rv; + rv = nghttp2_session_add_rst_stream( + session, stream_id, get_error_code_from_lib_error_code(lib_error_code)); + if (rv != 0) { + return rv; + } + if (session->callbacks.on_invalid_frame_recv_callback) { + if (session->callbacks.on_invalid_frame_recv_callback( + session, frame, lib_error_code, session->user_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static int session_handle_invalid_stream(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code) { + return session_handle_invalid_stream2(session, frame->hd.stream_id, frame, + lib_error_code); +} + +static int session_inflate_handle_invalid_stream(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code) { + int rv; + rv = session_handle_invalid_stream(session, frame, lib_error_code); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_IGN_HEADER_BLOCK; +} + +/* + * Handles invalid frame which causes connection error. + */ +static int session_handle_invalid_connection(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code, + const char *reason) { + if (session->callbacks.on_invalid_frame_recv_callback) { + if (session->callbacks.on_invalid_frame_recv_callback( + session, frame, lib_error_code, session->user_data) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + return nghttp2_session_terminate_session_with_reason( + session, get_error_code_from_lib_error_code(lib_error_code), reason); +} + +static int session_inflate_handle_invalid_connection(nghttp2_session *session, + nghttp2_frame *frame, + int lib_error_code, + const char *reason) { + int rv; + rv = + session_handle_invalid_connection(session, frame, lib_error_code, reason); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_IGN_HEADER_BLOCK; +} + +/* + * Inflates header block in the memory pointed by |in| with |inlen| + * bytes. If this function returns NGHTTP2_ERR_PAUSE, the caller must + * call this function again, until it returns 0 or one of negative + * error code. If |call_header_cb| is zero, the on_header_callback + * are not invoked and the function never return NGHTTP2_ERR_PAUSE. If + * the given |in| is the last chunk of header block, the |final| must + * be nonzero. If header block is successfully processed (which is + * indicated by the return value 0, NGHTTP2_ERR_PAUSE or + * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE), the number of processed + * input bytes is assigned to the |*readlen_ptr|. + * + * This function return 0 if it succeeds, or one of the negative error + * codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE + * The callback returns this error code, indicating that this + * stream should be RST_STREAMed. + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_PAUSE + * The callback function returned NGHTTP2_ERR_PAUSE + * NGHTTP2_ERR_HEADER_COMP + * Header decompression failed + */ +static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame, + size_t *readlen_ptr, uint8_t *in, size_t inlen, + int final, int call_header_cb) { + ssize_t proclen; + int rv; + int inflate_flags; + nghttp2_hd_nv nv; + nghttp2_stream *stream; + nghttp2_stream *subject_stream; + int trailer = 0; + + *readlen_ptr = 0; + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + subject_stream = nghttp2_session_get_stream( + session, frame->push_promise.promised_stream_id); + } else { + subject_stream = stream; + trailer = session_trailer_headers(session, stream, frame); + } + + DEBUGF("recv: decoding header block %zu bytes\n", inlen); + for (;;) { + inflate_flags = 0; + proclen = nghttp2_hd_inflate_hd_nv(&session->hd_inflater, &nv, + &inflate_flags, in, inlen, final); + if (nghttp2_is_fatal((int)proclen)) { + return (int)proclen; + } + if (proclen < 0) { + if (session->iframe.state == NGHTTP2_IB_READ_HEADER_BLOCK) { + if (subject_stream && subject_stream->state != NGHTTP2_STREAM_CLOSING) { + /* Adding RST_STREAM here is very important. It prevents + from invoking subsequent callbacks for the same stream + ID. */ + rv = nghttp2_session_add_rst_stream( + session, subject_stream->stream_id, NGHTTP2_COMPRESSION_ERROR); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + } + rv = + nghttp2_session_terminate_session(session, NGHTTP2_COMPRESSION_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return NGHTTP2_ERR_HEADER_COMP; + } + in += proclen; + inlen -= (size_t)proclen; + *readlen_ptr += (size_t)proclen; + + DEBUGF("recv: proclen=%zd\n", proclen); + + if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) { + rv = 0; + if (subject_stream) { + if (session_enforce_http_messaging(session)) { + rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, + trailer); + + if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) { + /* Don't overwrite rv here */ + int rv2; + + rv2 = session_call_on_invalid_header(session, frame, &nv); + if (rv2 == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = NGHTTP2_ERR_HTTP_HEADER; + } else { + if (rv2 != 0) { + return rv2; + } + + /* header is ignored */ + DEBUGF("recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n", + frame->hd.type, frame->hd.stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base); + + rv2 = session_call_error_callback( + session, NGHTTP2_ERR_HTTP_HEADER, + "Ignoring received invalid HTTP header field: frame type: " + "%u, stream: %d, name: [%.*s], value: [%.*s]", + frame->hd.type, frame->hd.stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base); + + if (nghttp2_is_fatal(rv2)) { + return rv2; + } + } + } + + if (rv == NGHTTP2_ERR_HTTP_HEADER) { + DEBUGF("recv: HTTP error: type=%u, id=%d, header %.*s: %.*s\n", + frame->hd.type, frame->hd.stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base); + + rv = session_call_error_callback( + session, NGHTTP2_ERR_HTTP_HEADER, + "Invalid HTTP header field was received: frame type: " + "%u, stream: %d, name: [%.*s], value: [%.*s]", + frame->hd.type, frame->hd.stream_id, (int)nv.name->len, + nv.name->base, (int)nv.value->len, nv.value->base); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + rv = session_handle_invalid_stream2(session, + subject_stream->stream_id, + frame, NGHTTP2_ERR_HTTP_HEADER); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + } + if (rv == 0) { + rv = session_call_on_header(session, frame, &nv); + /* This handles NGHTTP2_ERR_PAUSE and + NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */ + if (rv != 0) { + return rv; + } + } + } + } + if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + nghttp2_hd_inflate_end_headers(&session->hd_inflater); + break; + } + if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) { + break; + } + } + return 0; +} + +/* + * Call this function when HEADERS frame was completely received. + * + * This function returns 0 if it succeeds, or one of negative error + * codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int session_end_stream_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) { + int rv; + + assert(frame->hd.type == NGHTTP2_HEADERS); + + if (session->server && session_enforce_http_messaging(session) && + frame->headers.cat == NGHTTP2_HCAT_REQUEST && + (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) && + !(stream->flags & NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES) && + (stream->http_flags & NGHTTP2_HTTP_FLAG_PRIORITY)) { + rv = session_update_stream_priority(session, stream, stream->http_extpri); + if (rv != 0) { + assert(nghttp2_is_fatal(rv)); + return rv; + } + } + + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + return 0; + } + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +static int session_after_header_block_received(nghttp2_session *session) { + int rv = 0; + nghttp2_frame *frame = &session->iframe.frame; + nghttp2_stream *stream; + + /* We don't call on_frame_recv_callback if stream has been closed + already or being closed. */ + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) { + return 0; + } + + if (session_enforce_http_messaging(session)) { + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + nghttp2_stream *subject_stream; + + subject_stream = nghttp2_session_get_stream( + session, frame->push_promise.promised_stream_id); + if (subject_stream) { + rv = nghttp2_http_on_request_headers(subject_stream, frame); + } + } else { + assert(frame->hd.type == NGHTTP2_HEADERS); + switch (frame->headers.cat) { + case NGHTTP2_HCAT_REQUEST: + rv = nghttp2_http_on_request_headers(stream, frame); + break; + case NGHTTP2_HCAT_RESPONSE: + case NGHTTP2_HCAT_PUSH_RESPONSE: + rv = nghttp2_http_on_response_headers(stream); + break; + case NGHTTP2_HCAT_HEADERS: + if (stream->http_flags & NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE) { + assert(!session->server); + rv = nghttp2_http_on_response_headers(stream); + } else { + rv = nghttp2_http_on_trailer_headers(stream, frame); + } + break; + default: + assert(0); + } + if (rv == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + rv = nghttp2_http_on_remote_end_stream(stream); + } + } + if (rv != 0) { + int32_t stream_id; + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + stream_id = frame->push_promise.promised_stream_id; + } else { + stream_id = frame->hd.stream_id; + } + + rv = session_handle_invalid_stream2(session, stream_id, frame, + NGHTTP2_ERR_HTTP_MESSAGING); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (frame->hd.type == NGHTTP2_HEADERS && + (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + /* Don't call nghttp2_session_close_stream_if_shut_rdwr + because RST_STREAM has been submitted. */ + } + return 0; + } + } + + rv = session_call_on_frame_received(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (frame->hd.type != NGHTTP2_HEADERS) { + return 0; + } + + return session_end_stream_headers_received(session, frame, stream); +} + +int nghttp2_session_on_request_headers_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv = 0; + nghttp2_stream *stream; + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: stream_id == 0"); + } + + /* If client receives idle stream from server, it is invalid + regardless stream ID is even or odd. This is because client is + not expected to receive request from server. */ + if (!session->server) { + if (session_detect_idle_stream(session, frame->hd.stream_id)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "request HEADERS: client received request"); + } + + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + assert(session->server); + + if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) { + if (frame->hd.stream_id == 0 || + nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "request HEADERS: invalid stream_id"); + } + + /* RFC 7540 says if an endpoint receives a HEADERS with invalid + * stream ID (e.g, numerically smaller than previous), it MUST + * issue connection error with error code PROTOCOL_ERROR. It is a + * bit hard to detect this, since we cannot remember all streams + * we observed so far. + * + * You might imagine this is really easy. But no. HTTP/2 is + * asynchronous protocol, and usually client and server do not + * share the complete picture of open/closed stream status. For + * example, after server sends RST_STREAM for a stream, client may + * send trailer HEADERS for that stream. If naive server detects + * that, and issued connection error, then it is a bug of server + * implementation since client is not wrong if it did not get + * RST_STREAM when it issued trailer HEADERS. + * + * At the moment, we are very conservative here. We only use + * connection error if stream ID refers idle stream, or we are + * sure that stream is half-closed(remote) or closed. Otherwise + * we just ignore HEADERS for now. + */ + stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); + if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); + } + + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + session->last_recv_stream_id = frame->hd.stream_id; + + if (session_is_incoming_concurrent_streams_max(session)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "request HEADERS: max concurrent streams exceeded"); + } + + if (!session_allow_incoming_new_stream(session)) { + /* We just ignore stream after GOAWAY was sent */ + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: depend on itself"); + } + + if (session_is_incoming_concurrent_streams_pending_max(session)) { + return session_inflate_handle_invalid_stream(session, frame, + NGHTTP2_ERR_REFUSED_STREAM); + } + + stream = nghttp2_session_open_stream( + session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, + &frame->headers.pri_spec, NGHTTP2_STREAM_OPENING, NULL); + if (!stream) { + return NGHTTP2_ERR_NOMEM; + } + + rv = nghttp2_session_adjust_closed_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session->last_proc_stream_id = session->last_recv_stream_id; + + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; +} + +int nghttp2_session_on_response_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) { + int rv; + /* This function is only called if stream->state == + NGHTTP2_STREAM_OPENING and stream_id is local side initiated. */ + assert(stream->state == NGHTTP2_STREAM_OPENING && + nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)); + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "response HEADERS: stream_id == 0"); + } + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + /* half closed (remote): from the spec: + + If an endpoint receives additional frames for a stream that is + in this state it MUST respond with a stream error (Section + 5.4.2) of type STREAM_CLOSED. + + We go further, and make it connection error. + */ + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); + } + stream->state = NGHTTP2_STREAM_OPENED; + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; +} + +int nghttp2_session_on_push_response_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) { + int rv = 0; + assert(stream->state == NGHTTP2_STREAM_RESERVED); + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "push response HEADERS: stream_id == 0"); + } + + if (session->server) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "HEADERS: no HEADERS allowed from client in reserved state"); + } + + if (session_is_incoming_concurrent_streams_max(session)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "push response HEADERS: max concurrent streams exceeded"); + } + + if (!session_allow_incoming_new_stream(session)) { + /* We don't accept new stream after GOAWAY was sent. */ + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + if (session_is_incoming_concurrent_streams_pending_max(session)) { + return session_inflate_handle_invalid_stream(session, frame, + NGHTTP2_ERR_REFUSED_STREAM); + } + + nghttp2_stream_promise_fulfilled(stream); + if (!nghttp2_session_is_my_stream_id(session, stream->stream_id)) { + --session->num_incoming_reserved_streams; + } + ++session->num_incoming_streams; + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; +} + +int nghttp2_session_on_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream) { + int rv = 0; + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream_id == 0"); + } + if ((stream->shut_flags & NGHTTP2_SHUT_RD)) { + /* half closed (remote): from the spec: + + If an endpoint receives additional frames for a stream that is + in this state it MUST respond with a stream error (Section + 5.4.2) of type STREAM_CLOSED. + + we go further, and make it connection error. + */ + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed"); + } + if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + if (stream->state == NGHTTP2_STREAM_OPENED) { + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; + } + + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + /* If this is remote peer initiated stream, it is OK unless it + has sent END_STREAM frame already. But if stream is in + NGHTTP2_STREAM_CLOSING, we discard the frame. This is a race + condition. */ + if (stream->state != NGHTTP2_STREAM_CLOSING) { + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; + } + return NGHTTP2_ERR_IGN_HEADER_BLOCK; +} + +static int session_process_headers_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + nghttp2_stream *stream; + + nghttp2_frame_unpack_headers_payload(&frame->headers, iframe->sbuf.pos); + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream) { + frame->headers.cat = NGHTTP2_HCAT_REQUEST; + return nghttp2_session_on_request_headers_received(session, frame); + } + + if (stream->state == NGHTTP2_STREAM_RESERVED) { + frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; + return nghttp2_session_on_push_response_headers_received(session, frame, + stream); + } + + if (stream->state == NGHTTP2_STREAM_OPENING && + nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + frame->headers.cat = NGHTTP2_HCAT_RESPONSE; + return nghttp2_session_on_response_headers_received(session, frame, stream); + } + + frame->headers.cat = NGHTTP2_HCAT_HEADERS; + return nghttp2_session_on_headers_received(session, frame, stream); +} + +int nghttp2_session_on_priority_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + nghttp2_stream *stream; + + assert(!session_no_rfc7540_pri_no_fallback(session)); + + if (frame->hd.stream_id == 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY: stream_id == 0"); + } + + if (frame->priority.pri_spec.stream_id == frame->hd.stream_id) { + return nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "depend on itself"); + } + + if (!session->server) { + /* Re-prioritization works only in server */ + return session_call_on_frame_received(session, frame); + } + + stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); + + if (!stream) { + /* PRIORITY against idle stream can create anchor node in + dependency tree. */ + if (!session_detect_idle_stream(session, frame->hd.stream_id)) { + return 0; + } + + stream = nghttp2_session_open_stream( + session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE, + &frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL); + + if (stream == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + rv = nghttp2_session_adjust_idle_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } else { + rv = nghttp2_session_reprioritize_stream(session, stream, + &frame->priority.pri_spec); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + rv = nghttp2_session_adjust_idle_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + return session_call_on_frame_received(session, frame); +} + +static int session_process_priority_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + assert(!session_no_rfc7540_pri_no_fallback(session)); + + nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos); + + return nghttp2_session_on_priority_received(session, frame); +} + +static int session_update_stream_reset_ratelim(nghttp2_session *session) { + if (!session->server || (session->goaway_flags & NGHTTP2_GOAWAY_SUBMITTED)) { + return 0; + } + + nghttp2_ratelim_update(&session->stream_reset_ratelim, + nghttp2_time_now_sec()); + + if (nghttp2_ratelim_drain(&session->stream_reset_ratelim, 1) == 0) { + return 0; + } + + return nghttp2_session_add_goaway(session, session->last_recv_stream_id, + NGHTTP2_INTERNAL_ERROR, NULL, 0, + NGHTTP2_GOAWAY_AUX_NONE); +} + +int nghttp2_session_on_rst_stream_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + nghttp2_stream *stream; + if (frame->hd.stream_id == 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "RST_STREAM: stream_id == 0"); + } + + if (session_detect_idle_stream(session, frame->hd.stream_id)) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "RST_STREAM: stream in idle"); + } + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (stream) { + /* We may use stream->shut_flags for strict error checking. */ + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + } + + rv = session_call_on_frame_received(session, frame); + if (rv != 0) { + return rv; + } + rv = nghttp2_session_close_stream(session, frame->hd.stream_id, + frame->rst_stream.error_code); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return session_update_stream_reset_ratelim(session); +} + +static int session_process_rst_stream_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_rst_stream_payload(&frame->rst_stream, iframe->sbuf.pos); + + return nghttp2_session_on_rst_stream_received(session, frame); +} + +static int update_remote_initial_window_size_func(void *entry, void *ptr) { + int rv; + nghttp2_update_window_size_arg *arg; + nghttp2_stream *stream; + + arg = (nghttp2_update_window_size_arg *)ptr; + stream = (nghttp2_stream *)entry; + + rv = nghttp2_stream_update_remote_initial_window_size( + stream, arg->new_window_size, arg->old_window_size); + if (rv != 0) { + return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, + NGHTTP2_FLOW_CONTROL_ERROR); + } + + /* If window size gets positive, push deferred DATA frame to + outbound queue. */ + if (stream->remote_window_size > 0 && + nghttp2_stream_check_deferred_by_flow_control(stream)) { + + rv = session_resume_deferred_stream_item( + arg->session, stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + return 0; +} + +/* + * Updates the remote initial window size of all active streams. If + * error occurs, all streams may not be updated. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int +session_update_remote_initial_window_size(nghttp2_session *session, + int32_t new_initial_window_size) { + nghttp2_update_window_size_arg arg; + + arg.session = session; + arg.new_window_size = new_initial_window_size; + arg.old_window_size = (int32_t)session->remote_settings.initial_window_size; + + return nghttp2_map_each(&session->streams, + update_remote_initial_window_size_func, &arg); +} + +static int update_local_initial_window_size_func(void *entry, void *ptr) { + int rv; + nghttp2_update_window_size_arg *arg; + nghttp2_stream *stream; + arg = (nghttp2_update_window_size_arg *)ptr; + stream = (nghttp2_stream *)entry; + rv = nghttp2_stream_update_local_initial_window_size( + stream, arg->new_window_size, arg->old_window_size); + if (rv != 0) { + return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, + NGHTTP2_FLOW_CONTROL_ERROR); + } + + if (stream->window_update_queued) { + return 0; + } + + if (arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + return session_update_stream_consumed_size(arg->session, stream, 0); + } + + if (nghttp2_should_send_window_update(stream->local_window_size, + stream->recv_window_size)) { + + rv = nghttp2_session_add_window_update(arg->session, NGHTTP2_FLAG_NONE, + stream->stream_id, + stream->recv_window_size); + if (rv != 0) { + return rv; + } + + stream->recv_window_size = 0; + } + return 0; +} + +/* + * Updates the local initial window size of all active streams. If + * error occurs, all streams may not be updated. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int +session_update_local_initial_window_size(nghttp2_session *session, + int32_t new_initial_window_size, + int32_t old_initial_window_size) { + nghttp2_update_window_size_arg arg; + arg.session = session; + arg.new_window_size = new_initial_window_size; + arg.old_window_size = old_initial_window_size; + return nghttp2_map_each(&session->streams, + update_local_initial_window_size_func, &arg); +} + +/* + * Apply SETTINGS values |iv| having |niv| elements to the local + * settings. We assumes that all values in |iv| is correct, since we + * validated them in nghttp2_session_add_settings() already. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_HEADER_COMP + * The header table size is out of range + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_session_update_local_settings(nghttp2_session *session, + nghttp2_settings_entry *iv, + size_t niv) { + int rv; + size_t i; + int32_t new_initial_window_size = -1; + uint32_t header_table_size = 0; + uint32_t min_header_table_size = UINT32_MAX; + uint8_t header_table_size_seen = 0; + /* For NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, use the value last + seen. For NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, use both minimum + value and last seen value. */ + for (i = 0; i < niv; ++i) { + switch (iv[i].settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + header_table_size_seen = 1; + header_table_size = iv[i].value; + min_header_table_size = nghttp2_min(min_header_table_size, iv[i].value); + break; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + new_initial_window_size = (int32_t)iv[i].value; + break; + } + } + if (header_table_size_seen) { + if (min_header_table_size < header_table_size) { + rv = nghttp2_hd_inflate_change_table_size(&session->hd_inflater, + min_header_table_size); + if (rv != 0) { + return rv; + } + } + + rv = nghttp2_hd_inflate_change_table_size(&session->hd_inflater, + header_table_size); + if (rv != 0) { + return rv; + } + } + if (new_initial_window_size != -1) { + rv = session_update_local_initial_window_size( + session, new_initial_window_size, + (int32_t)session->local_settings.initial_window_size); + if (rv != 0) { + return rv; + } + } + + for (i = 0; i < niv; ++i) { + switch (iv[i].settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + session->local_settings.header_table_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + session->local_settings.enable_push = iv[i].value; + break; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + session->local_settings.max_concurrent_streams = iv[i].value; + break; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + session->local_settings.initial_window_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + session->local_settings.max_frame_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + session->local_settings.max_header_list_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + session->local_settings.enable_connect_protocol = iv[i].value; + break; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + session->local_settings.no_rfc7540_priorities = iv[i].value; + break; + } + } + + return 0; +} + +int nghttp2_session_on_settings_received(nghttp2_session *session, + nghttp2_frame *frame, int noack) { + int rv; + size_t i; + nghttp2_mem *mem; + nghttp2_inflight_settings *settings; + + mem = &session->mem; + + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: stream_id != 0"); + } + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + if (frame->settings.niv != 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_FRAME_SIZE_ERROR, + "SETTINGS: ACK and payload != 0"); + } + + settings = session->inflight_settings_head; + + if (!settings) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "SETTINGS: unexpected ACK"); + } + + rv = nghttp2_session_update_local_settings(session, settings->iv, + settings->niv); + + session->inflight_settings_head = settings->next; + + inflight_settings_del(settings, mem); + + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + return session_handle_invalid_connection(session, frame, rv, NULL); + } + return session_call_on_frame_received(session, frame); + } + + if (!session->remote_settings_received) { + session->remote_settings.max_concurrent_streams = + NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; + session->remote_settings_received = 1; + } + + for (i = 0; i < frame->settings.niv; ++i) { + nghttp2_settings_entry *entry = &frame->settings.iv[i]; + + switch (entry->settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + + rv = nghttp2_hd_deflate_change_table_size(&session->hd_deflater, + entry->value); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } else { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_HEADER_COMP, NULL); + } + } + + session->remote_settings.header_table_size = entry->value; + + break; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + + if (entry->value != 0 && entry->value != 1) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: invalid SETTINGS_ENBLE_PUSH"); + } + + if (!session->server && entry->value != 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: server attempted to enable push"); + } + + session->remote_settings.enable_push = entry->value; + + break; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + + session->remote_settings.max_concurrent_streams = entry->value; + + break; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + + /* Update the initial window size of the all active streams */ + /* Check that initial_window_size < (1u << 31) */ + if (entry->value > NGHTTP2_MAX_WINDOW_SIZE) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_FLOW_CONTROL, + "SETTINGS: too large SETTINGS_INITIAL_WINDOW_SIZE"); + } + + rv = session_update_remote_initial_window_size(session, + (int32_t)entry->value); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv != 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_FLOW_CONTROL, NULL); + } + + session->remote_settings.initial_window_size = entry->value; + + break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + + if (entry->value < NGHTTP2_MAX_FRAME_SIZE_MIN || + entry->value > NGHTTP2_MAX_FRAME_SIZE_MAX) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: invalid SETTINGS_MAX_FRAME_SIZE"); + } + + session->remote_settings.max_frame_size = entry->value; + + break; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + + session->remote_settings.max_header_list_size = entry->value; + + break; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + + if (entry->value != 0 && entry->value != 1) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: invalid SETTINGS_ENABLE_CONNECT_PROTOCOL"); + } + + if (!session->server && + session->remote_settings.enable_connect_protocol && + entry->value == 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: server attempted to disable " + "SETTINGS_ENABLE_CONNECT_PROTOCOL"); + } + + session->remote_settings.enable_connect_protocol = entry->value; + + break; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + + if (entry->value != 0 && entry->value != 1) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: invalid SETTINGS_NO_RFC7540_PRIORITIES"); + } + + if (session->remote_settings.no_rfc7540_priorities != UINT32_MAX && + session->remote_settings.no_rfc7540_priorities != entry->value) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "SETTINGS: SETTINGS_NO_RFC7540_PRIORITIES cannot be changed"); + } + + session->remote_settings.no_rfc7540_priorities = entry->value; + + break; + } + } + + if (session->remote_settings.no_rfc7540_priorities == UINT32_MAX) { + session->remote_settings.no_rfc7540_priorities = 0; + + if (session->server && session->pending_no_rfc7540_priorities && + (session->opt_flags & + NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES)) { + session->fallback_rfc7540_priorities = 1; + } + } + + if (!noack && !session_is_closing(session)) { + rv = nghttp2_session_add_settings(session, NGHTTP2_FLAG_ACK, NULL, 0); + + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return session_handle_invalid_connection(session, frame, + NGHTTP2_ERR_INTERNAL, NULL); + } + } + + return session_call_on_frame_received(session, frame); +} + +static int session_process_settings_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + size_t i; + nghttp2_settings_entry min_header_size_entry; + + if (iframe->max_niv) { + min_header_size_entry = iframe->iv[iframe->max_niv - 1]; + + if (min_header_size_entry.value < UINT32_MAX) { + /* If we have less value, then we must have + SETTINGS_HEADER_TABLE_SIZE in i < iframe->niv */ + for (i = 0; i < iframe->niv; ++i) { + if (iframe->iv[i].settings_id == NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) { + break; + } + } + + assert(i < iframe->niv); + + if (min_header_size_entry.value != iframe->iv[i].value) { + iframe->iv[iframe->niv++] = iframe->iv[i]; + iframe->iv[i] = min_header_size_entry; + } + } + } + + nghttp2_frame_unpack_settings_payload(&frame->settings, iframe->iv, + iframe->niv); + + iframe->iv = NULL; + iframe->niv = 0; + iframe->max_niv = 0; + + return nghttp2_session_on_settings_received(session, frame, 0 /* ACK */); +} + +int nghttp2_session_on_push_promise_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + nghttp2_stream *stream; + nghttp2_stream *promised_stream; + nghttp2_priority_spec pri_spec; + + if (frame->hd.stream_id == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream_id == 0"); + } + if (session->server || session->local_settings.enable_push == 0) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: push disabled"); + } + + if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: invalid stream_id"); + } + + if (!session_allow_incoming_new_stream(session)) { + /* We just discard PUSH_PROMISE after GOAWAY was sent */ + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + if (!session_is_new_peer_stream_id(session, + frame->push_promise.promised_stream_id)) { + /* The spec says if an endpoint receives a PUSH_PROMISE with + illegal stream ID is subject to a connection error of type + PROTOCOL_ERROR. */ + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "PUSH_PROMISE: invalid promised_stream_id"); + } + + if (session_detect_idle_stream(session, frame->hd.stream_id)) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle"); + } + + session->last_recv_stream_id = frame->push_promise.promised_stream_id; + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream || stream->state == NGHTTP2_STREAM_CLOSING || + !session->pending_enable_push || + session->num_incoming_reserved_streams >= + session->max_incoming_reserved_streams) { + /* Currently, client does not retain closed stream, so we don't + check NGHTTP2_SHUT_RD condition here. */ + + rv = nghttp2_session_add_rst_stream( + session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL); + if (rv != 0) { + return rv; + } + return NGHTTP2_ERR_IGN_HEADER_BLOCK; + } + + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return session_inflate_handle_invalid_connection( + session, frame, NGHTTP2_ERR_STREAM_CLOSED, + "PUSH_PROMISE: stream closed"); + } + + nghttp2_priority_spec_init(&pri_spec, stream->stream_id, + NGHTTP2_DEFAULT_WEIGHT, 0); + + promised_stream = nghttp2_session_open_stream( + session, frame->push_promise.promised_stream_id, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_RESERVED, NULL); + + if (!promised_stream) { + return NGHTTP2_ERR_NOMEM; + } + + /* We don't call nghttp2_session_adjust_closed_stream(), since we + don't keep closed stream in client side */ + + session->last_proc_stream_id = session->last_recv_stream_id; + rv = session_call_on_begin_headers(session, frame); + if (rv != 0) { + return rv; + } + return 0; +} + +static int session_process_push_promise_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_push_promise_payload(&frame->push_promise, + iframe->sbuf.pos); + + return nghttp2_session_on_push_promise_received(session, frame); +} + +int nghttp2_session_on_ping_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv = 0; + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "PING: stream_id != 0"); + } + if ((session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK) == 0 && + (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 && + !session_is_closing(session)) { + /* Peer sent ping, so ping it back */ + rv = nghttp2_session_add_ping(session, NGHTTP2_FLAG_ACK, + frame->ping.opaque_data); + if (rv != 0) { + return rv; + } + } + return session_call_on_frame_received(session, frame); +} + +static int session_process_ping_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_ping_payload(&frame->ping, iframe->sbuf.pos); + + return nghttp2_session_on_ping_received(session, frame); +} + +int nghttp2_session_on_goaway_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "GOAWAY: stream_id != 0"); + } + /* Spec says Endpoints MUST NOT increase the value they send in the + last stream identifier. */ + if ((frame->goaway.last_stream_id > 0 && + !nghttp2_session_is_my_stream_id(session, + frame->goaway.last_stream_id)) || + session->remote_last_stream_id < frame->goaway.last_stream_id) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "GOAWAY: invalid last_stream_id"); + } + + session->goaway_flags |= NGHTTP2_GOAWAY_RECV; + + session->remote_last_stream_id = frame->goaway.last_stream_id; + + rv = session_call_on_frame_received(session, frame); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return session_close_stream_on_goaway(session, frame->goaway.last_stream_id, + 0); +} + +static int session_process_goaway_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_goaway_payload(&frame->goaway, iframe->sbuf.pos, + iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf)); + + nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); + + return nghttp2_session_on_goaway_received(session, frame); +} + +static int +session_on_connection_window_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + /* Handle connection-level flow control */ + if (frame->window_update.window_size_increment == 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "WINDOW_UPDATE: window_size_increment == 0"); + } + + if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < + session->remote_window_size) { + return session_handle_invalid_connection(session, frame, + NGHTTP2_ERR_FLOW_CONTROL, NULL); + } + session->remote_window_size += frame->window_update.window_size_increment; + + return session_call_on_frame_received(session, frame); +} + +static int session_on_stream_window_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv; + nghttp2_stream *stream; + + if (session_detect_idle_stream(session, frame->hd.stream_id)) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "WINDOW_UPDATE to idle stream"); + } + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream) { + return 0; + } + if (state_reserved_remote(session, stream)) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, "WINDOW_UPADATE to reserved stream"); + } + if (frame->window_update.window_size_increment == 0) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "WINDOW_UPDATE: window_size_increment == 0"); + } + if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < + stream->remote_window_size) { + return session_handle_invalid_stream(session, frame, + NGHTTP2_ERR_FLOW_CONTROL); + } + stream->remote_window_size += frame->window_update.window_size_increment; + + if (stream->remote_window_size > 0 && + nghttp2_stream_check_deferred_by_flow_control(stream)) { + + rv = session_resume_deferred_stream_item( + session, stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + return session_call_on_frame_received(session, frame); +} + +int nghttp2_session_on_window_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + if (frame->hd.stream_id == 0) { + return session_on_connection_window_update_received(session, frame); + } else { + return session_on_stream_window_update_received(session, frame); + } +} + +static int session_process_window_update_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_window_update_payload(&frame->window_update, + iframe->sbuf.pos); + + return nghttp2_session_on_window_update_received(session, frame); +} + +int nghttp2_session_on_altsvc_received(nghttp2_session *session, + nghttp2_frame *frame) { + nghttp2_ext_altsvc *altsvc; + nghttp2_stream *stream; + + altsvc = frame->ext.payload; + + /* session->server case has been excluded */ + + if (frame->hd.stream_id == 0) { + if (altsvc->origin_len == 0) { + return session_call_on_invalid_frame_recv_callback(session, frame, + NGHTTP2_ERR_PROTO); + } + } else { + if (altsvc->origin_len > 0) { + return session_call_on_invalid_frame_recv_callback(session, frame, + NGHTTP2_ERR_PROTO); + } + + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream) { + return 0; + } + + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return 0; + } + } + + if (altsvc->field_value_len == 0) { + return session_call_on_invalid_frame_recv_callback(session, frame, + NGHTTP2_ERR_PROTO); + } + + return session_call_on_frame_received(session, frame); +} + +int nghttp2_session_on_origin_received(nghttp2_session *session, + nghttp2_frame *frame) { + return session_call_on_frame_received(session, frame); +} + +int nghttp2_session_on_priority_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + nghttp2_ext_priority_update *priority_update; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + nghttp2_extpri extpri; + int rv; + + assert(session->server); + + priority_update = frame->ext.payload; + + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: stream_id == 0"); + } + + if (nghttp2_session_is_my_stream_id(session, priority_update->stream_id)) { + if (session_detect_idle_stream(session, priority_update->stream_id)) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: prioritizing idle push is not allowed"); + } + + /* TODO Ignore priority signal to a push stream for now */ + return session_call_on_frame_received(session, frame); + } + + stream = nghttp2_session_get_stream_raw(session, priority_update->stream_id); + if (stream) { + /* Stream already exists. */ + if (stream->flags & NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES) { + return session_call_on_frame_received(session, frame); + } + } else if (session_detect_idle_stream(session, priority_update->stream_id)) { + if (session->num_idle_streams + session->num_incoming_streams >= + session->local_settings.max_concurrent_streams) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: max concurrent streams exceeded"); + } + + nghttp2_priority_spec_default_init(&pri_spec); + stream = nghttp2_session_open_stream(session, priority_update->stream_id, + NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_IDLE, NULL); + if (!stream) { + return NGHTTP2_ERR_NOMEM; + } + } else { + return session_call_on_frame_received(session, frame); + } + + extpri.urgency = NGHTTP2_EXTPRI_DEFAULT_URGENCY; + extpri.inc = 0; + + rv = nghttp2_http_parse_priority(&extpri, priority_update->field_value, + priority_update->field_value_len); + if (rv != 0) { + /* Just ignore field_value if it cannot be parsed. */ + return session_call_on_frame_received(session, frame); + } + + rv = session_update_stream_priority(session, stream, + nghttp2_extpri_to_uint8(&extpri)); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + return session_call_on_frame_received(session, frame); +} + +static int session_process_altsvc_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_altsvc_payload( + &frame->ext, nghttp2_get_uint16(iframe->sbuf.pos), iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf)); + + /* nghttp2_frame_unpack_altsvc_payload steals buffer from + iframe->lbuf */ + nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); + + return nghttp2_session_on_altsvc_received(session, frame); +} + +static int session_process_origin_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + nghttp2_mem *mem = &session->mem; + int rv; + + rv = nghttp2_frame_unpack_origin_payload(&frame->ext, iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf), mem); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + /* Ignore ORIGIN frame which cannot be parsed. */ + return 0; + } + + return nghttp2_session_on_origin_received(session, frame); +} + +static int session_process_priority_update_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_priority_update_payload(&frame->ext, iframe->sbuf.pos, + nghttp2_buf_len(&iframe->sbuf)); + + return nghttp2_session_on_priority_update_received(session, frame); +} + +static int session_process_extension_frame(nghttp2_session *session) { + int rv; + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + rv = session_call_unpack_extension_callback(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + /* This handles the case where rv == NGHTTP2_ERR_CANCEL as well */ + if (rv != 0) { + return 0; + } + + return session_call_on_frame_received(session, frame); +} + +int nghttp2_session_on_data_received(nghttp2_session *session, + nghttp2_frame *frame) { + int rv = 0; + nghttp2_stream *stream; + + /* We don't call on_frame_recv_callback if stream has been closed + already or being closed. */ + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) { + /* This should be treated as stream error, but it results in lots + of RST_STREAM. So just ignore frame against nonexistent stream + for now. */ + return 0; + } + + if (session_enforce_http_messaging(session) && + (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + if (nghttp2_http_on_remote_end_stream(stream) != 0) { + rv = nghttp2_session_add_rst_stream(session, stream->stream_id, + NGHTTP2_PROTOCOL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + /* Don't call nghttp2_session_close_stream_if_shut_rdwr because + RST_STREAM has been submitted. */ + return 0; + } + } + + rv = session_call_on_frame_received(session, frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + return 0; +} + +/* For errors, this function only returns FATAL error. */ +static int session_process_data_frame(nghttp2_session *session) { + int rv; + nghttp2_frame *public_data_frame = &session->iframe.frame; + rv = nghttp2_session_on_data_received(session, public_data_frame); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return 0; +} + +/* + * Now we have SETTINGS synchronization, flow control error can be + * detected strictly. If DATA frame is received with length > 0 and + * current received window size + delta length is strictly larger than + * local window size, it is subject to FLOW_CONTROL_ERROR, so return + * -1. Note that local_window_size is calculated after SETTINGS ACK is + * received from peer, so peer must honor this limit. If the resulting + * recv_window_size is strictly larger than NGHTTP2_MAX_WINDOW_SIZE, + * return -1 too. + */ +static int adjust_recv_window_size(int32_t *recv_window_size_ptr, size_t delta, + int32_t local_window_size) { + if (*recv_window_size_ptr > local_window_size - (int32_t)delta || + *recv_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - (int32_t)delta) { + return -1; + } + *recv_window_size_ptr += (int32_t)delta; + return 0; +} + +int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session, + nghttp2_stream *stream, + size_t delta_size, + int send_window_update) { + int rv; + rv = adjust_recv_window_size(&stream->recv_window_size, delta_size, + stream->local_window_size); + if (rv != 0) { + return nghttp2_session_add_rst_stream(session, stream->stream_id, + NGHTTP2_FLOW_CONTROL_ERROR); + } + /* We don't have to send WINDOW_UPDATE if the data received is the + last chunk in the incoming stream. */ + /* We have to use local_settings here because it is the constraint + the remote endpoint should honor. */ + if (send_window_update && + !(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && + stream->window_update_queued == 0 && + nghttp2_should_send_window_update(stream->local_window_size, + stream->recv_window_size)) { + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, + stream->stream_id, + stream->recv_window_size); + if (rv != 0) { + return rv; + } + + stream->recv_window_size = 0; + } + return 0; +} + +int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session, + size_t delta_size) { + int rv; + rv = adjust_recv_window_size(&session->recv_window_size, delta_size, + session->local_window_size); + if (rv != 0) { + return nghttp2_session_terminate_session(session, + NGHTTP2_FLOW_CONTROL_ERROR); + } + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && + session->window_update_queued == 0 && + nghttp2_should_send_window_update(session->local_window_size, + session->recv_window_size)) { + /* Use stream ID 0 to update connection-level flow control + window */ + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0, + session->recv_window_size); + if (rv != 0) { + return rv; + } + + session->recv_window_size = 0; + } + return 0; +} + +static int session_update_consumed_size(nghttp2_session *session, + int32_t *consumed_size_ptr, + int32_t *recv_window_size_ptr, + uint8_t window_update_queued, + int32_t stream_id, size_t delta_size, + int32_t local_window_size) { + int32_t recv_size; + int rv; + + if ((size_t)*consumed_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta_size) { + return nghttp2_session_terminate_session(session, + NGHTTP2_FLOW_CONTROL_ERROR); + } + + *consumed_size_ptr += (int32_t)delta_size; + + if (window_update_queued == 0) { + /* recv_window_size may be smaller than consumed_size, because it + may be decreased by negative value with + nghttp2_submit_window_update(). */ + recv_size = nghttp2_min(*consumed_size_ptr, *recv_window_size_ptr); + + if (nghttp2_should_send_window_update(local_window_size, recv_size)) { + rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, + stream_id, recv_size); + + if (rv != 0) { + return rv; + } + + *recv_window_size_ptr -= recv_size; + *consumed_size_ptr -= recv_size; + } + } + + return 0; +} + +static int session_update_stream_consumed_size(nghttp2_session *session, + nghttp2_stream *stream, + size_t delta_size) { + return session_update_consumed_size( + session, &stream->consumed_size, &stream->recv_window_size, + stream->window_update_queued, stream->stream_id, delta_size, + stream->local_window_size); +} + +static int session_update_connection_consumed_size(nghttp2_session *session, + size_t delta_size) { + return session_update_consumed_size( + session, &session->consumed_size, &session->recv_window_size, + session->window_update_queued, 0, delta_size, session->local_window_size); +} + +/* + * Checks that we can receive the DATA frame for stream, which is + * indicated by |session->iframe.frame.hd.stream_id|. If it is a + * connection error situation, GOAWAY frame will be issued by this + * function. + * + * If the DATA frame is allowed, returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_IGN_PAYLOAD + * The reception of DATA frame is connection error; or should be + * ignored. + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +static int session_on_data_received_fail_fast(nghttp2_session *session) { + int rv; + nghttp2_stream *stream; + nghttp2_inbound_frame *iframe; + int32_t stream_id; + const char *failure_reason; + uint32_t error_code = NGHTTP2_PROTOCOL_ERROR; + + iframe = &session->iframe; + stream_id = iframe->frame.hd.stream_id; + + if (stream_id == 0) { + /* The spec says that if a DATA frame is received whose stream ID + is 0, the recipient MUST respond with a connection error of + type PROTOCOL_ERROR. */ + failure_reason = "DATA: stream_id == 0"; + goto fail; + } + + if (session_detect_idle_stream(session, stream_id)) { + failure_reason = "DATA: stream in idle"; + error_code = NGHTTP2_PROTOCOL_ERROR; + goto fail; + } + + stream = nghttp2_session_get_stream(session, stream_id); + if (!stream) { + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) { + failure_reason = "DATA: stream closed"; + error_code = NGHTTP2_STREAM_CLOSED; + goto fail; + } + + return NGHTTP2_ERR_IGN_PAYLOAD; + } + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + failure_reason = "DATA: stream in half-closed(remote)"; + error_code = NGHTTP2_STREAM_CLOSED; + goto fail; + } + + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_IGN_PAYLOAD; + } + if (stream->state != NGHTTP2_STREAM_OPENED) { + failure_reason = "DATA: stream not opened"; + goto fail; + } + return 0; + } + if (stream->state == NGHTTP2_STREAM_RESERVED) { + failure_reason = "DATA: stream in reserved"; + goto fail; + } + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_IGN_PAYLOAD; + } + return 0; +fail: + rv = nghttp2_session_terminate_session_with_reason(session, error_code, + failure_reason); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return NGHTTP2_ERR_IGN_PAYLOAD; +} + +static size_t inbound_frame_payload_readlen(nghttp2_inbound_frame *iframe, + const uint8_t *in, + const uint8_t *last) { + return nghttp2_min((size_t)(last - in), iframe->payloadleft); +} + +/* + * Resets iframe->sbuf and advance its mark pointer by |left| bytes. + */ +static void inbound_frame_set_mark(nghttp2_inbound_frame *iframe, size_t left) { + nghttp2_buf_reset(&iframe->sbuf); + iframe->sbuf.mark += left; +} + +static size_t inbound_frame_buf_read(nghttp2_inbound_frame *iframe, + const uint8_t *in, const uint8_t *last) { + size_t readlen; + + readlen = + nghttp2_min((size_t)(last - in), nghttp2_buf_mark_avail(&iframe->sbuf)); + + iframe->sbuf.last = nghttp2_cpymem(iframe->sbuf.last, in, readlen); + + return readlen; +} + +/* + * Unpacks SETTINGS entry in iframe->sbuf. + */ +static void inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe) { + nghttp2_settings_entry iv; + nghttp2_settings_entry *min_header_table_size_entry; + size_t i; + + nghttp2_frame_unpack_settings_entry(&iv, iframe->sbuf.pos); + + switch (iv.settings_id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + case NGHTTP2_SETTINGS_ENABLE_PUSH: + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + break; + default: + DEBUGF("recv: unknown settings id=0x%02x\n", iv.settings_id); + + iframe->iv[iframe->niv++] = iv; + + return; + } + + for (i = 0; i < iframe->niv; ++i) { + if (iframe->iv[i].settings_id == iv.settings_id) { + iframe->iv[i] = iv; + break; + } + } + + if (i == iframe->niv) { + iframe->iv[iframe->niv++] = iv; + } + + if (iv.settings_id == NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) { + /* Keep track of minimum value of SETTINGS_HEADER_TABLE_SIZE */ + min_header_table_size_entry = &iframe->iv[iframe->max_niv - 1]; + + if (iv.value < min_header_table_size_entry->value) { + min_header_table_size_entry->value = iv.value; + } + } +} + +/* + * Checks PADDED flags and set iframe->sbuf to read them accordingly. + * If padding is set, this function returns 1. If no padding is set, + * this function returns 0. On error, returns -1. + */ +static int inbound_frame_handle_pad(nghttp2_inbound_frame *iframe, + nghttp2_frame_hd *hd) { + if (hd->flags & NGHTTP2_FLAG_PADDED) { + if (hd->length < 1) { + return -1; + } + inbound_frame_set_mark(iframe, 1); + return 1; + } + DEBUGF("recv: no padding in payload\n"); + return 0; +} + +/* + * Computes number of padding based on flags. This function returns + * the calculated length if it succeeds, or -1. + */ +static ssize_t inbound_frame_compute_pad(nghttp2_inbound_frame *iframe) { + size_t padlen; + + /* 1 for Pad Length field */ + padlen = (size_t)(iframe->sbuf.pos[0] + 1); + + DEBUGF("recv: padlen=%zu\n", padlen); + + /* We cannot use iframe->frame.hd.length because of CONTINUATION */ + if (padlen - 1 > iframe->payloadleft) { + return -1; + } + + iframe->padlen = padlen; + + return (ssize_t)padlen; +} + +/* + * This function returns the effective payload length in the data of + * length |readlen| when the remaining payload is |payloadleft|. The + * |payloadleft| does not include |readlen|. If padding was started + * strictly before this data chunk, this function returns -1. + */ +static ssize_t inbound_frame_effective_readlen(nghttp2_inbound_frame *iframe, + size_t payloadleft, + size_t readlen) { + size_t trail_padlen = + nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen); + + if (trail_padlen > payloadleft) { + size_t padlen; + padlen = trail_padlen - payloadleft; + if (readlen < padlen) { + return -1; + } + return (ssize_t)(readlen - padlen); + } + return (ssize_t)(readlen); +} + +static const uint8_t static_in[] = {0}; + +ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, + size_t inlen) { + const uint8_t *first, *last; + nghttp2_inbound_frame *iframe = &session->iframe; + size_t readlen; + ssize_t padlen; + int rv; + int busy = 0; + nghttp2_frame_hd cont_hd; + nghttp2_stream *stream; + size_t pri_fieldlen; + nghttp2_mem *mem; + + if (in == NULL) { + assert(inlen == 0); + in = static_in; + } + + first = in; + last = in + inlen; + + DEBUGF("recv: connection recv_window_size=%d, local_window=%d\n", + session->recv_window_size, session->local_window_size); + + mem = &session->mem; + + /* We may have idle streams more than we expect (e.g., + nghttp2_session_change_stream_priority() or + nghttp2_session_create_idle_stream()). Adjust them here. */ + rv = nghttp2_session_adjust_idle_stream(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (!nghttp2_session_want_read(session)) { + return (ssize_t)inlen; + } + + for (;;) { + switch (iframe->state) { + case NGHTTP2_IB_READ_CLIENT_MAGIC: + readlen = nghttp2_min(inlen, iframe->payloadleft); + + if (memcmp(&NGHTTP2_CLIENT_MAGIC[NGHTTP2_CLIENT_MAGIC_LEN - + iframe->payloadleft], + in, readlen) != 0) { + return NGHTTP2_ERR_BAD_CLIENT_MAGIC; + } + + iframe->payloadleft -= readlen; + in += readlen; + + if (iframe->payloadleft == 0) { + session_inbound_frame_reset(session); + iframe->state = NGHTTP2_IB_READ_FIRST_SETTINGS; + } + + break; + case NGHTTP2_IB_READ_FIRST_SETTINGS: + DEBUGF("recv: [IB_READ_FIRST_SETTINGS]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + if (iframe->sbuf.pos[3] != NGHTTP2_SETTINGS || + (iframe->sbuf.pos[4] & NGHTTP2_FLAG_ACK)) { + rv = session_call_error_callback( + session, NGHTTP2_ERR_SETTINGS_EXPECTED, + "Remote peer returned unexpected data while we expected " + "SETTINGS frame. Perhaps, peer does not support HTTP/2 " + "properly."); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "SETTINGS expected"); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return (ssize_t)inlen; + } + + iframe->state = NGHTTP2_IB_READ_HEAD; + + /* Fall through */ + case NGHTTP2_IB_READ_HEAD: { + int on_begin_frame_called = 0; + + DEBUGF("recv: [IB_READ_HEAD]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->sbuf.pos); + iframe->payloadleft = iframe->frame.hd.length; + + DEBUGF("recv: payloadlen=%zu, type=%u, flags=0x%02x, stream_id=%d\n", + iframe->frame.hd.length, iframe->frame.hd.type, + iframe->frame.hd.flags, iframe->frame.hd.stream_id); + + if (iframe->frame.hd.length > session->local_settings.max_frame_size) { + DEBUGF("recv: length is too large %zu > %u\n", iframe->frame.hd.length, + session->local_settings.max_frame_size); + + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_FRAME_SIZE_ERROR, "too large frame size"); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return (ssize_t)inlen; + } + + switch (iframe->frame.hd.type) { + case NGHTTP2_DATA: { + DEBUGF("recv: DATA\n"); + + iframe->frame.hd.flags &= + (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PADDED); + /* Check stream is open. If it is not open or closing, + ignore payload. */ + busy = 1; + + rv = session_on_data_received_fail_fast(session); + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + if (rv == NGHTTP2_ERR_IGN_PAYLOAD) { + DEBUGF("recv: DATA not allowed stream_id=%d\n", + iframe->frame.hd.stream_id); + iframe->state = NGHTTP2_IB_IGN_DATA; + break; + } + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); + if (rv < 0) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "DATA: insufficient padding space"); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (rv == 1) { + iframe->state = NGHTTP2_IB_READ_PAD_DATA; + break; + } + + iframe->state = NGHTTP2_IB_READ_DATA; + break; + } + case NGHTTP2_HEADERS: + + DEBUGF("recv: HEADERS\n"); + + iframe->frame.hd.flags &= + (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PADDED | NGHTTP2_FLAG_PRIORITY); + + rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); + if (rv < 0) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "HEADERS: insufficient padding space"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (rv == 1) { + iframe->state = NGHTTP2_IB_READ_NBYTE; + break; + } + + pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); + + if (pri_fieldlen > 0) { + if (iframe->payloadleft < pri_fieldlen) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, pri_fieldlen); + + break; + } + + /* Call on_begin_frame_callback here because + session_process_headers_frame() may call + on_begin_headers_callback */ + rv = session_call_on_begin_frame(session, &iframe->frame.hd); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + on_begin_frame_called = 1; + + rv = session_process_headers_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + busy = 1; + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + break; + case NGHTTP2_PRIORITY: + DEBUGF("recv: PRIORITY\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft != NGHTTP2_PRIORITY_SPECLEN) { + busy = 1; + + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, NGHTTP2_PRIORITY_SPECLEN); + + break; + case NGHTTP2_RST_STREAM: + case NGHTTP2_WINDOW_UPDATE: +#ifdef DEBUGBUILD + switch (iframe->frame.hd.type) { + case NGHTTP2_RST_STREAM: + DEBUGF("recv: RST_STREAM\n"); + break; + case NGHTTP2_WINDOW_UPDATE: + DEBUGF("recv: WINDOW_UPDATE\n"); + break; + } +#endif /* DEBUGBUILD */ + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft != 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, 4); + + break; + case NGHTTP2_SETTINGS: + DEBUGF("recv: SETTINGS\n"); + + iframe->frame.hd.flags &= NGHTTP2_FLAG_ACK; + + if ((iframe->frame.hd.length % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) || + ((iframe->frame.hd.flags & NGHTTP2_FLAG_ACK) && + iframe->payloadleft > 0)) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + /* Check the settings flood counter early to be safe */ + if (session->obq_flood_counter_ >= session->max_outbound_ack && + !(iframe->frame.hd.flags & NGHTTP2_FLAG_ACK)) { + return NGHTTP2_ERR_FLOODED; + } + + iframe->state = NGHTTP2_IB_READ_SETTINGS; + + if (iframe->payloadleft) { + nghttp2_settings_entry *min_header_table_size_entry; + + /* We allocate iv with additional one entry, to store the + minimum header table size. */ + iframe->max_niv = + iframe->frame.hd.length / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH + 1; + + if (iframe->max_niv - 1 > session->max_settings) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_ENHANCE_YOUR_CALM, + "SETTINGS: too many setting entries"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + iframe->iv = nghttp2_mem_malloc(mem, sizeof(nghttp2_settings_entry) * + iframe->max_niv); + + if (!iframe->iv) { + return NGHTTP2_ERR_NOMEM; + } + + min_header_table_size_entry = &iframe->iv[iframe->max_niv - 1]; + min_header_table_size_entry->settings_id = + NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + min_header_table_size_entry->value = UINT32_MAX; + + inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH); + break; + } + + busy = 1; + + inbound_frame_set_mark(iframe, 0); + + break; + case NGHTTP2_PUSH_PROMISE: + DEBUGF("recv: PUSH_PROMISE\n"); + + iframe->frame.hd.flags &= + (NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED); + + rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd); + if (rv < 0) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "PUSH_PROMISE: insufficient padding space"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (rv == 1) { + iframe->state = NGHTTP2_IB_READ_NBYTE; + break; + } + + if (iframe->payloadleft < 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, 4); + + break; + case NGHTTP2_PING: + DEBUGF("recv: PING\n"); + + iframe->frame.hd.flags &= NGHTTP2_FLAG_ACK; + + if (iframe->payloadleft != 8) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 8); + + break; + case NGHTTP2_GOAWAY: + DEBUGF("recv: GOAWAY\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft < 8) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 8); + + break; + case NGHTTP2_CONTINUATION: + DEBUGF("recv: unexpected CONTINUATION\n"); + + /* Receiving CONTINUATION in this state are subject to + connection error of type PROTOCOL_ERROR */ + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "CONTINUATION: unexpected"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return (ssize_t)inlen; + default: + DEBUGF("recv: extension frame\n"); + + if (check_ext_type_set(session->user_recv_ext_types, + iframe->frame.hd.type)) { + if (!session->callbacks.unpack_extension_callback) { + /* Silently ignore unknown frame type. */ + + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD; + + break; + } else { + switch (iframe->frame.hd.type) { + case NGHTTP2_ALTSVC: + if ((session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ALTSVC) == + 0) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + DEBUGF("recv: ALTSVC\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + iframe->frame.ext.payload = &iframe->ext_frame_payload.altsvc; + + if (session->server) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + if (iframe->payloadleft < 2) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 2); + + break; + case NGHTTP2_ORIGIN: + if (!(session->builtin_recv_ext_types & NGHTTP2_TYPEMASK_ORIGIN)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + DEBUGF("recv: ORIGIN\n"); + + iframe->frame.ext.payload = &iframe->ext_frame_payload.origin; + + if (session->server || iframe->frame.hd.stream_id || + (iframe->frame.hd.flags & 0xf0)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft) { + iframe->raw_lbuf = nghttp2_mem_malloc(mem, iframe->payloadleft); + + if (iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, + iframe->payloadleft); + } else { + busy = 1; + } + + iframe->state = NGHTTP2_IB_READ_ORIGIN_PAYLOAD; + + break; + case NGHTTP2_PRIORITY_UPDATE: + if ((session->builtin_recv_ext_types & + NGHTTP2_TYPEMASK_PRIORITY_UPDATE) == 0) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + DEBUGF("recv: PRIORITY_UPDATE\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + iframe->frame.ext.payload = + &iframe->ext_frame_payload.priority_update; + + if (!session->server) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "PRIORITY_UPDATE is received from server"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (iframe->payloadleft < 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + if (!session_no_rfc7540_pri_no_fallback(session) || + iframe->payloadleft > sizeof(iframe->raw_sbuf)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, iframe->payloadleft); + + break; + default: + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + } + } + + if (!on_begin_frame_called) { + switch (iframe->state) { + case NGHTTP2_IB_IGN_HEADER_BLOCK: + case NGHTTP2_IB_IGN_PAYLOAD: + case NGHTTP2_IB_FRAME_SIZE_ERROR: + case NGHTTP2_IB_IGN_DATA: + case NGHTTP2_IB_IGN_ALL: + break; + default: + rv = session_call_on_begin_frame(session, &iframe->frame.hd); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + } + + break; + } + case NGHTTP2_IB_READ_NBYTE: + DEBUGF("recv: [IB_READ_NBYTE]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + iframe->payloadleft -= readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu, left=%zd\n", readlen, + iframe->payloadleft, nghttp2_buf_mark_avail(&iframe->sbuf)); + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + switch (iframe->frame.hd.type) { + case NGHTTP2_HEADERS: + if (iframe->padlen == 0 && + (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) { + pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags); + padlen = inbound_frame_compute_pad(iframe); + if (padlen < 0 || + (size_t)padlen + pri_fieldlen > 1 + iframe->payloadleft) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "HEADERS: invalid padding"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + iframe->frame.headers.padlen = (size_t)padlen; + + if (pri_fieldlen > 0) { + if (iframe->payloadleft < pri_fieldlen) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, pri_fieldlen); + break; + } else { + /* Truncate buffers used for padding spec */ + inbound_frame_set_mark(iframe, 0); + } + } + + rv = session_process_headers_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + busy = 1; + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + break; + case NGHTTP2_PRIORITY: + if (!session_no_rfc7540_pri_no_fallback(session) && + session->remote_settings.no_rfc7540_priorities != 1) { + rv = session_process_priority_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_RST_STREAM: + rv = session_process_rst_stream_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_PUSH_PROMISE: + if (iframe->padlen == 0 && + (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) { + padlen = inbound_frame_compute_pad(iframe); + if (padlen < 0 || (size_t)padlen + 4 /* promised stream id */ + > 1 + iframe->payloadleft) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "PUSH_PROMISE: invalid padding"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + iframe->frame.push_promise.padlen = (size_t)padlen; + + if (iframe->payloadleft < 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + iframe->state = NGHTTP2_IB_READ_NBYTE; + + inbound_frame_set_mark(iframe, 4); + + break; + } + + rv = session_process_push_promise_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + busy = 1; + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.push_promise.promised_stream_id, + NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + break; + case NGHTTP2_PING: + rv = session_process_ping_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_GOAWAY: { + size_t debuglen; + + /* 8 is Last-stream-ID + Error Code */ + debuglen = iframe->frame.hd.length - 8; + + if (debuglen > 0) { + iframe->raw_lbuf = nghttp2_mem_malloc(mem, debuglen); + + if (iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, debuglen); + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_GOAWAY_DEBUG; + + break; + } + case NGHTTP2_WINDOW_UPDATE: + rv = session_process_window_update_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_ALTSVC: { + size_t origin_len; + + origin_len = nghttp2_get_uint16(iframe->sbuf.pos); + + DEBUGF("recv: origin_len=%zu\n", origin_len); + + if (origin_len > iframe->payloadleft) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + if (iframe->frame.hd.length > 2) { + iframe->raw_lbuf = + nghttp2_mem_malloc(mem, iframe->frame.hd.length - 2); + + if (iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, + iframe->frame.hd.length); + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_ALTSVC_PAYLOAD; + + break; + case NGHTTP2_PRIORITY_UPDATE: + DEBUGF("recv: prioritized_stream_id=%d\n", + nghttp2_get_uint32(iframe->sbuf.pos) & NGHTTP2_STREAM_ID_MASK); + + rv = session_process_priority_update_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + } + default: + /* This is unknown frame */ + session_inbound_frame_reset(session); + + break; + } + break; + case NGHTTP2_IB_READ_HEADER_BLOCK: + case NGHTTP2_IB_IGN_HEADER_BLOCK: { + ssize_t data_readlen; + size_t trail_padlen; + int final; +#ifdef DEBUGBUILD + if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { + DEBUGF("recv: [IB_READ_HEADER_BLOCK]\n"); + } else { + DEBUGF("recv: [IB_IGN_HEADER_BLOCK]\n"); + } +#endif /* DEBUGBUILD */ + + readlen = inbound_frame_payload_readlen(iframe, in, last); + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft - readlen); + + data_readlen = inbound_frame_effective_readlen( + iframe, iframe->payloadleft - readlen, readlen); + + if (data_readlen == -1) { + /* everything is padding */ + data_readlen = 0; + } + + trail_padlen = nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen); + + final = (iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) && + iframe->payloadleft - (size_t)data_readlen == trail_padlen; + + if (data_readlen > 0 || (data_readlen == 0 && final)) { + size_t hd_proclen = 0; + + DEBUGF("recv: block final=%d\n", final); + + rv = + inflate_header_block(session, &iframe->frame, &hd_proclen, + (uint8_t *)in, (size_t)data_readlen, final, + iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (rv == NGHTTP2_ERR_PAUSE) { + in += hd_proclen; + iframe->payloadleft -= hd_proclen; + + return (ssize_t)(in - first); + } + + if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) { + /* The application says no more headers. We decompress the + rest of the header block but not invoke on_header_callback + and on_frame_recv_callback. */ + in += hd_proclen; + iframe->payloadleft -= hd_proclen; + + /* Use promised stream ID for PUSH_PROMISE */ + rv = nghttp2_session_add_rst_stream( + session, + iframe->frame.hd.type == NGHTTP2_PUSH_PROMISE + ? iframe->frame.push_promise.promised_stream_id + : iframe->frame.hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + busy = 1; + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + break; + } + + in += readlen; + iframe->payloadleft -= readlen; + + if (rv == NGHTTP2_ERR_HEADER_COMP) { + /* GOAWAY is already issued */ + if (iframe->payloadleft == 0) { + session_inbound_frame_reset(session); + } else { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + } + break; + } + } else { + in += readlen; + iframe->payloadleft -= readlen; + } + + if (iframe->payloadleft) { + break; + } + + if ((iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) { + + inbound_frame_set_mark(iframe, NGHTTP2_FRAME_HDLEN); + + iframe->padlen = 0; + + if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { + iframe->state = NGHTTP2_IB_EXPECT_CONTINUATION; + } else { + iframe->state = NGHTTP2_IB_IGN_CONTINUATION; + } + } else { + if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) { + rv = session_after_header_block_received(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + session_inbound_frame_reset(session); + } + break; + } + case NGHTTP2_IB_IGN_PAYLOAD: + DEBUGF("recv: [IB_IGN_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + break; + } + + switch (iframe->frame.hd.type) { + case NGHTTP2_HEADERS: + case NGHTTP2_PUSH_PROMISE: + case NGHTTP2_CONTINUATION: + /* Mark inflater bad so that we won't perform further decoding */ + session->hd_inflater.ctx.bad = 1; + break; + default: + break; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_FRAME_SIZE_ERROR: + DEBUGF("recv: [IB_FRAME_SIZE_ERROR]\n"); + + rv = session_handle_frame_size_error(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + assert(iframe->state == NGHTTP2_IB_IGN_ALL); + + return (ssize_t)inlen; + case NGHTTP2_IB_READ_SETTINGS: + DEBUGF("recv: [IB_READ_SETTINGS]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + break; + } + + if (readlen > 0) { + inbound_frame_set_settings_entry(iframe); + } + if (iframe->payloadleft) { + inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH); + break; + } + + rv = session_process_settings_frame(session); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_READ_GOAWAY_DEBUG: + DEBUGF("recv: [IB_READ_GOAWAY_DEBUG]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + + if (readlen > 0) { + iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); + + iframe->payloadleft -= readlen; + in += readlen; + } + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + assert(nghttp2_buf_avail(&iframe->lbuf) > 0); + + break; + } + + rv = session_process_goaway_frame(session); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_EXPECT_CONTINUATION: + case NGHTTP2_IB_IGN_CONTINUATION: +#ifdef DEBUGBUILD + if (iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { + fprintf(stderr, "recv: [IB_EXPECT_CONTINUATION]\n"); + } else { + fprintf(stderr, "recv: [IB_IGN_CONTINUATION]\n"); + } +#endif /* DEBUGBUILD */ + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + nghttp2_frame_unpack_frame_hd(&cont_hd, iframe->sbuf.pos); + iframe->payloadleft = cont_hd.length; + + DEBUGF("recv: payloadlen=%zu, type=%u, flags=0x%02x, stream_id=%d\n", + cont_hd.length, cont_hd.type, cont_hd.flags, cont_hd.stream_id); + + if (cont_hd.type != NGHTTP2_CONTINUATION || + cont_hd.stream_id != iframe->frame.hd.stream_id) { + DEBUGF("recv: expected stream_id=%d, type=%d, but got stream_id=%d, " + "type=%u\n", + iframe->frame.hd.stream_id, NGHTTP2_CONTINUATION, + cont_hd.stream_id, cont_hd.type); + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "unexpected non-CONTINUATION frame or stream_id is invalid"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return (ssize_t)inlen; + } + + /* CONTINUATION won't bear NGHTTP2_PADDED flag */ + + iframe->frame.hd.flags = + (uint8_t)(iframe->frame.hd.flags | + (cont_hd.flags & NGHTTP2_FLAG_END_HEADERS)); + iframe->frame.hd.length += cont_hd.length; + + busy = 1; + + if (iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) { + iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK; + + rv = session_call_on_begin_frame(session, &cont_hd); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + } else { + iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK; + } + + break; + case NGHTTP2_IB_READ_PAD_DATA: + DEBUGF("recv: [IB_READ_PAD_DATA]\n"); + + readlen = inbound_frame_buf_read(iframe, in, last); + in += readlen; + iframe->payloadleft -= readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu, left=%zu\n", readlen, + iframe->payloadleft, nghttp2_buf_mark_avail(&iframe->sbuf)); + + if (nghttp2_buf_mark_avail(&iframe->sbuf)) { + return (ssize_t)(in - first); + } + + /* Pad Length field is subject to flow control */ + rv = nghttp2_session_update_recv_connection_window_size(session, readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + /* Pad Length field is consumed immediately */ + rv = + nghttp2_session_consume(session, iframe->frame.hd.stream_id, readlen); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id); + if (stream) { + rv = nghttp2_session_update_recv_stream_window_size( + session, stream, readlen, + iframe->payloadleft || + (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + busy = 1; + + padlen = inbound_frame_compute_pad(iframe); + if (padlen < 0) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, "DATA: invalid padding"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + iframe->frame.data.padlen = (size_t)padlen; + + iframe->state = NGHTTP2_IB_READ_DATA; + + break; + case NGHTTP2_IB_READ_DATA: + stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id); + + if (!stream) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_DATA; + break; + } + + DEBUGF("recv: [IB_READ_DATA]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (readlen > 0) { + ssize_t data_readlen; + + rv = nghttp2_session_update_recv_connection_window_size(session, + readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + rv = nghttp2_session_update_recv_stream_window_size( + session, stream, readlen, + iframe->payloadleft || + (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + data_readlen = inbound_frame_effective_readlen( + iframe, iframe->payloadleft, readlen); + + if (data_readlen == -1) { + /* everything is padding */ + data_readlen = 0; + } + + padlen = (ssize_t)readlen - data_readlen; + + if (padlen > 0) { + /* Padding is considered as "consumed" immediately */ + rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id, + (size_t)padlen); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + } + + DEBUGF("recv: data_readlen=%zd\n", data_readlen); + + if (data_readlen > 0) { + if (session_enforce_http_messaging(session)) { + if (nghttp2_http_on_data_chunk(stream, (size_t)data_readlen) != 0) { + if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + /* Consume all data for connection immediately here */ + rv = session_update_connection_consumed_size( + session, (size_t)data_readlen); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_DATA) { + return (ssize_t)inlen; + } + } + + rv = nghttp2_session_add_rst_stream( + session, iframe->frame.hd.stream_id, NGHTTP2_PROTOCOL_ERROR); + if (nghttp2_is_fatal(rv)) { + return rv; + } + busy = 1; + iframe->state = NGHTTP2_IB_IGN_DATA; + break; + } + } + if (session->callbacks.on_data_chunk_recv_callback) { + rv = session->callbacks.on_data_chunk_recv_callback( + session, iframe->frame.hd.flags, iframe->frame.hd.stream_id, + in - readlen, (size_t)data_readlen, session->user_data); + if (rv == NGHTTP2_ERR_PAUSE) { + return (ssize_t)(in - first); + } + + if (nghttp2_is_fatal(rv)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + } + } + + if (iframe->payloadleft) { + break; + } + + rv = session_process_data_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_IGN_DATA: + DEBUGF("recv: [IB_IGN_DATA]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (readlen > 0) { + /* Update connection-level flow control window for ignored + DATA frame too */ + rv = nghttp2_session_update_recv_connection_window_size(session, + readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + + /* Ignored DATA is considered as "consumed" immediately. */ + rv = session_update_connection_consumed_size(session, readlen); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + } + } + + if (iframe->payloadleft) { + break; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_IGN_ALL: + return (ssize_t)inlen; + case NGHTTP2_IB_READ_EXTENSION_PAYLOAD: + DEBUGF("recv: [IB_READ_EXTENSION_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + iframe->payloadleft -= readlen; + in += readlen; + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (readlen > 0) { + rv = session_call_on_extension_chunk_recv_callback( + session, in - readlen, readlen); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (rv != 0) { + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + } + + if (iframe->payloadleft > 0) { + break; + } + + rv = session_process_extension_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_READ_ALTSVC_PAYLOAD: + DEBUGF("recv: [IB_READ_ALTSVC_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + if (readlen > 0) { + iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); + + iframe->payloadleft -= readlen; + in += readlen; + } + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + assert(nghttp2_buf_avail(&iframe->lbuf) > 0); + + break; + } + + rv = session_process_altsvc_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; + case NGHTTP2_IB_READ_ORIGIN_PAYLOAD: + DEBUGF("recv: [IB_READ_ORIGIN_PAYLOAD]\n"); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + + if (readlen > 0) { + iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); + + iframe->payloadleft -= readlen; + in += readlen; + } + + DEBUGF("recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft); + + if (iframe->payloadleft) { + assert(nghttp2_buf_avail(&iframe->lbuf) > 0); + + break; + } + + rv = session_process_origin_frame(session); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + if (iframe->state == NGHTTP2_IB_IGN_ALL) { + return (ssize_t)inlen; + } + + session_inbound_frame_reset(session); + + break; + } + + if (!busy && in == last) { + break; + } + + busy = 0; + } + + assert(in == last); + + return (ssize_t)(in - first); +} + +int nghttp2_session_recv(nghttp2_session *session) { + uint8_t buf[NGHTTP2_INBOUND_BUFFER_LENGTH]; + while (1) { + ssize_t readlen; + readlen = session_recv(session, buf, sizeof(buf)); + if (readlen > 0) { + ssize_t proclen = nghttp2_session_mem_recv(session, buf, (size_t)readlen); + if (proclen < 0) { + return (int)proclen; + } + assert(proclen == readlen); + } else if (readlen == 0 || readlen == NGHTTP2_ERR_WOULDBLOCK) { + return 0; + } else if (readlen == NGHTTP2_ERR_EOF) { + return NGHTTP2_ERR_EOF; + } else if (readlen < 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } +} + +/* + * Returns the number of active streams, which includes streams in + * reserved state. + */ +static size_t session_get_num_active_streams(nghttp2_session *session) { + return nghttp2_map_size(&session->streams) - session->num_closed_streams - + session->num_idle_streams; +} + +int nghttp2_session_want_read(nghttp2_session *session) { + size_t num_active_streams; + + /* If this flag is set, we don't want to read. The application + should drop the connection. */ + if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) { + return 0; + } + + num_active_streams = session_get_num_active_streams(session); + + /* Unless termination GOAWAY is sent or received, we always want to + read incoming frames. */ + + if (num_active_streams > 0) { + return 1; + } + + /* If there is no active streams and GOAWAY has been sent or + received, we are done with this session. */ + return (session->goaway_flags & + (NGHTTP2_GOAWAY_SENT | NGHTTP2_GOAWAY_RECV)) == 0; +} + +int nghttp2_session_want_write(nghttp2_session *session) { + /* If these flag is set, we don't want to write any data. The + application should drop the connection. */ + if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) { + return 0; + } + + /* + * Unless termination GOAWAY is sent or received, we want to write + * frames if there is pending ones. If pending frame is request/push + * response HEADERS and concurrent stream limit is reached, we don't + * want to write them. + */ + return session->aob.item || nghttp2_outbound_queue_top(&session->ob_urgent) || + nghttp2_outbound_queue_top(&session->ob_reg) || + ((!nghttp2_pq_empty(&session->root.obq) || + !session_sched_empty(session)) && + session->remote_window_size > 0) || + (nghttp2_outbound_queue_top(&session->ob_syn) && + !session_is_outgoing_concurrent_streams_max(session)); +} + +int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags, + const uint8_t *opaque_data) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = &session->mem; + + if ((flags & NGHTTP2_FLAG_ACK) && + session->obq_flood_counter_ >= session->max_outbound_ack) { + return NGHTTP2_ERR_FLOODED; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_ping_init(&frame->ping, flags, opaque_data); + + rv = nghttp2_session_add_item(session, item); + + if (rv != 0) { + nghttp2_frame_ping_free(&frame->ping); + nghttp2_mem_free(mem, item); + return rv; + } + + if (flags & NGHTTP2_FLAG_ACK) { + ++session->obq_flood_counter_; + } + + return 0; +} + +int nghttp2_session_add_goaway(nghttp2_session *session, int32_t last_stream_id, + uint32_t error_code, const uint8_t *opaque_data, + size_t opaque_data_len, uint8_t aux_flags) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + uint8_t *opaque_data_copy = NULL; + nghttp2_goaway_aux_data *aux_data; + nghttp2_mem *mem; + + mem = &session->mem; + + if (nghttp2_session_is_my_stream_id(session, last_stream_id)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (opaque_data_len) { + if (opaque_data_len + 8 > NGHTTP2_MAX_PAYLOADLEN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + opaque_data_copy = nghttp2_mem_malloc(mem, opaque_data_len); + if (opaque_data_copy == NULL) { + return NGHTTP2_ERR_NOMEM; + } + memcpy(opaque_data_copy, opaque_data, opaque_data_len); + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + nghttp2_mem_free(mem, opaque_data_copy); + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + /* last_stream_id must not be increased from the value previously + sent */ + last_stream_id = nghttp2_min(last_stream_id, session->local_last_stream_id); + + nghttp2_frame_goaway_init(&frame->goaway, last_stream_id, error_code, + opaque_data_copy, opaque_data_len); + + aux_data = &item->aux_data.goaway; + aux_data->flags = aux_flags; + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_goaway_free(&frame->goaway, mem); + nghttp2_mem_free(mem, item); + return rv; + } + + session->goaway_flags |= NGHTTP2_GOAWAY_SUBMITTED; + + return 0; +} + +int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + int32_t window_size_increment) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = &session->mem; + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_window_update_init(&frame->window_update, flags, stream_id, + window_size_increment); + + rv = nghttp2_session_add_item(session, item); + + if (rv != 0) { + nghttp2_frame_window_update_free(&frame->window_update); + nghttp2_mem_free(mem, item); + return rv; + } + return 0; +} + +static void +session_append_inflight_settings(nghttp2_session *session, + nghttp2_inflight_settings *settings) { + nghttp2_inflight_settings **i; + + for (i = &session->inflight_settings_head; *i; i = &(*i)->next) + ; + + *i = settings; +} + +int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, + const nghttp2_settings_entry *iv, size_t niv) { + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_settings_entry *iv_copy; + size_t i; + int rv; + nghttp2_mem *mem; + nghttp2_inflight_settings *inflight_settings = NULL; + uint8_t no_rfc7540_pri = session->pending_no_rfc7540_priorities; + + mem = &session->mem; + + if (flags & NGHTTP2_FLAG_ACK) { + if (niv != 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (session->obq_flood_counter_ >= session->max_outbound_ack) { + return NGHTTP2_ERR_FLOODED; + } + } + + if (!nghttp2_iv_check(iv, niv)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + for (i = 0; i < niv; ++i) { + if (iv[i].settings_id != NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES) { + continue; + } + + if (no_rfc7540_pri == UINT8_MAX) { + no_rfc7540_pri = (uint8_t)iv[i].value; + continue; + } + + if (iv[i].value != (uint32_t)no_rfc7540_pri) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + if (niv > 0) { + iv_copy = nghttp2_frame_iv_copy(iv, niv, mem); + if (iv_copy == NULL) { + nghttp2_mem_free(mem, item); + return NGHTTP2_ERR_NOMEM; + } + } else { + iv_copy = NULL; + } + + if ((flags & NGHTTP2_FLAG_ACK) == 0) { + rv = inflight_settings_new(&inflight_settings, iv, niv, mem); + if (rv != 0) { + assert(nghttp2_is_fatal(rv)); + nghttp2_mem_free(mem, iv_copy); + nghttp2_mem_free(mem, item); + return rv; + } + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_settings_init(&frame->settings, flags, iv_copy, niv); + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + /* The only expected error is fatal one */ + assert(nghttp2_is_fatal(rv)); + + inflight_settings_del(inflight_settings, mem); + + nghttp2_frame_settings_free(&frame->settings, mem); + nghttp2_mem_free(mem, item); + + return rv; + } + + if (flags & NGHTTP2_FLAG_ACK) { + ++session->obq_flood_counter_; + } else { + session_append_inflight_settings(session, inflight_settings); + } + + /* Extract NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS and ENABLE_PUSH + here. We use it to refuse the incoming stream and PUSH_PROMISE + with RST_STREAM. */ + + for (i = niv; i > 0; --i) { + if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) { + session->pending_local_max_concurrent_stream = iv[i - 1].value; + break; + } + } + + for (i = niv; i > 0; --i) { + if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_ENABLE_PUSH) { + session->pending_enable_push = (uint8_t)iv[i - 1].value; + break; + } + } + + for (i = niv; i > 0; --i) { + if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) { + session->pending_enable_connect_protocol = (uint8_t)iv[i - 1].value; + break; + } + } + + if (no_rfc7540_pri == UINT8_MAX) { + session->pending_no_rfc7540_priorities = 0; + } else { + session->pending_no_rfc7540_priorities = no_rfc7540_pri; + } + + return 0; +} + +int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs, + size_t datamax, nghttp2_frame *frame, + nghttp2_data_aux_data *aux_data, + nghttp2_stream *stream) { + int rv; + uint32_t data_flags; + ssize_t payloadlen; + ssize_t padded_payloadlen; + nghttp2_buf *buf; + size_t max_payloadlen; + + assert(bufs->head == bufs->cur); + + buf = &bufs->cur->buf; + + if (session->callbacks.read_length_callback) { + + payloadlen = session->callbacks.read_length_callback( + session, frame->hd.type, stream->stream_id, session->remote_window_size, + stream->remote_window_size, session->remote_settings.max_frame_size, + session->user_data); + + DEBUGF("send: read_length_callback=%zd\n", payloadlen); + + payloadlen = nghttp2_session_enforce_flow_control_limits(session, stream, + payloadlen); + + DEBUGF("send: read_length_callback after flow control=%zd\n", payloadlen); + + if (payloadlen <= 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if ((size_t)payloadlen > nghttp2_buf_avail(buf)) { + /* Resize the current buffer(s). The reason why we do +1 for + buffer size is for possible padding field. */ + rv = nghttp2_bufs_realloc(&session->aob.framebufs, + (size_t)(NGHTTP2_FRAME_HDLEN + 1 + payloadlen)); + + if (rv != 0) { + DEBUGF("send: realloc buffer failed rv=%d", rv); + /* If reallocation failed, old buffers are still in tact. So + use safe limit. */ + payloadlen = (ssize_t)datamax; + + DEBUGF("send: use safe limit payloadlen=%zd", payloadlen); + } else { + assert(&session->aob.framebufs == bufs); + + buf = &bufs->cur->buf; + } + } + datamax = (size_t)payloadlen; + } + + /* Current max DATA length is less then buffer chunk size */ + assert(nghttp2_buf_avail(buf) >= datamax); + + data_flags = NGHTTP2_DATA_FLAG_NONE; + payloadlen = aux_data->data_prd.read_callback( + session, frame->hd.stream_id, buf->pos, datamax, &data_flags, + &aux_data->data_prd.source, session->user_data); + + if (payloadlen == NGHTTP2_ERR_DEFERRED || + payloadlen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE || + payloadlen == NGHTTP2_ERR_PAUSE) { + DEBUGF("send: DATA postponed due to %s\n", + nghttp2_strerror((int)payloadlen)); + + return (int)payloadlen; + } + + if (payloadlen < 0 || datamax < (size_t)payloadlen) { + /* This is the error code when callback is failed. */ + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + buf->last = buf->pos + payloadlen; + buf->pos -= NGHTTP2_FRAME_HDLEN; + + /* Clear flags, because this may contain previous flags of previous + DATA */ + frame->hd.flags = NGHTTP2_FLAG_NONE; + + if (data_flags & NGHTTP2_DATA_FLAG_EOF) { + aux_data->eof = 1; + /* If NGHTTP2_DATA_FLAG_NO_END_STREAM is set, don't set + NGHTTP2_FLAG_END_STREAM */ + if ((aux_data->flags & NGHTTP2_FLAG_END_STREAM) && + (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM) == 0) { + frame->hd.flags |= NGHTTP2_FLAG_END_STREAM; + } + } + + if (data_flags & NGHTTP2_DATA_FLAG_NO_COPY) { + if (session->callbacks.send_data_callback == NULL) { + DEBUGF("NGHTTP2_DATA_FLAG_NO_COPY requires send_data_callback set\n"); + + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + aux_data->no_copy = 1; + } + + frame->hd.length = (size_t)payloadlen; + frame->data.padlen = 0; + + max_payloadlen = nghttp2_min(datamax, frame->hd.length + NGHTTP2_MAX_PADLEN); + + padded_payloadlen = + session_call_select_padding(session, frame, max_payloadlen); + + if (nghttp2_is_fatal((int)padded_payloadlen)) { + return (int)padded_payloadlen; + } + + frame->data.padlen = (size_t)(padded_payloadlen - payloadlen); + + nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd); + + nghttp2_frame_add_pad(bufs, &frame->hd, frame->data.padlen, + aux_data->no_copy); + + session_reschedule_stream(session, stream); + + if (frame->hd.length == 0 && (data_flags & NGHTTP2_DATA_FLAG_EOF) && + (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM)) { + /* DATA payload length is 0, and DATA frame does not bear + END_STREAM. In this case, there is no point to send 0 length + DATA frame. */ + return NGHTTP2_ERR_CANCEL; + } + + return 0; +} + +void *nghttp2_session_get_stream_user_data(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream) { + return stream->stream_user_data; + } else { + return NULL; + } +} + +int nghttp2_session_set_stream_user_data(nghttp2_session *session, + int32_t stream_id, + void *stream_user_data) { + nghttp2_stream *stream; + nghttp2_frame *frame; + nghttp2_outbound_item *item; + + stream = nghttp2_session_get_stream(session, stream_id); + if (stream) { + stream->stream_user_data = stream_user_data; + return 0; + } + + if (session->server || !nghttp2_session_is_my_stream_id(session, stream_id) || + !nghttp2_outbound_queue_top(&session->ob_syn)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + frame = &nghttp2_outbound_queue_top(&session->ob_syn)->frame; + assert(frame->hd.type == NGHTTP2_HEADERS); + + if (frame->hd.stream_id > stream_id || + (uint32_t)stream_id >= session->next_stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + for (item = session->ob_syn.head; item; item = item->qnext) { + if (item->frame.hd.stream_id < stream_id) { + continue; + } + + if (item->frame.hd.stream_id > stream_id) { + break; + } + + item->aux_data.headers.stream_user_data = stream_user_data; + return 0; + } + + return NGHTTP2_ERR_INVALID_ARGUMENT; +} + +int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id) { + int rv; + nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL || !nghttp2_stream_check_deferred_item(stream)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + rv = session_resume_deferred_stream_item(session, stream, + NGHTTP2_STREAM_FLAG_DEFERRED_USER); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +size_t nghttp2_session_get_outbound_queue_size(nghttp2_session *session) { + return nghttp2_outbound_queue_size(&session->ob_urgent) + + nghttp2_outbound_queue_size(&session->ob_reg) + + nghttp2_outbound_queue_size(&session->ob_syn); + /* TODO account for item attached to stream */ +} + +int32_t +nghttp2_session_get_stream_effective_recv_data_length(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return -1; + } + return stream->recv_window_size < 0 ? 0 : stream->recv_window_size; +} + +int32_t +nghttp2_session_get_stream_effective_local_window_size(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return -1; + } + return stream->local_window_size; +} + +int32_t nghttp2_session_get_stream_local_window_size(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + int32_t size; + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return -1; + } + + size = stream->local_window_size - stream->recv_window_size; + + /* size could be negative if local endpoint reduced + SETTINGS_INITIAL_WINDOW_SIZE */ + if (size < 0) { + return 0; + } + + return size; +} + +int32_t +nghttp2_session_get_effective_recv_data_length(nghttp2_session *session) { + return session->recv_window_size < 0 ? 0 : session->recv_window_size; +} + +int32_t +nghttp2_session_get_effective_local_window_size(nghttp2_session *session) { + return session->local_window_size; +} + +int32_t nghttp2_session_get_local_window_size(nghttp2_session *session) { + return session->local_window_size - session->recv_window_size; +} + +int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return -1; + } + + /* stream->remote_window_size can be negative when + SETTINGS_INITIAL_WINDOW_SIZE is changed. */ + return nghttp2_max(0, stream->remote_window_size); +} + +int32_t nghttp2_session_get_remote_window_size(nghttp2_session *session) { + return session->remote_window_size; +} + +uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session, + nghttp2_settings_id id) { + switch (id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + return session->remote_settings.header_table_size; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + return session->remote_settings.enable_push; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + return session->remote_settings.max_concurrent_streams; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + return session->remote_settings.initial_window_size; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + return session->remote_settings.max_frame_size; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + return session->remote_settings.max_header_list_size; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + return session->remote_settings.enable_connect_protocol; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + return session->remote_settings.no_rfc7540_priorities; + } + + assert(0); + abort(); /* if NDEBUG is set */ +} + +uint32_t nghttp2_session_get_local_settings(nghttp2_session *session, + nghttp2_settings_id id) { + switch (id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + return session->local_settings.header_table_size; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + return session->local_settings.enable_push; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + return session->local_settings.max_concurrent_streams; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + return session->local_settings.initial_window_size; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + return session->local_settings.max_frame_size; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + return session->local_settings.max_header_list_size; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + return session->local_settings.enable_connect_protocol; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + return session->local_settings.no_rfc7540_priorities; + } + + assert(0); + abort(); /* if NDEBUG is set */ +} + +static int nghttp2_session_upgrade_internal(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, + void *stream_user_data) { + nghttp2_stream *stream; + nghttp2_frame frame; + nghttp2_settings_entry *iv; + size_t niv; + int rv; + nghttp2_priority_spec pri_spec; + nghttp2_mem *mem; + + mem = &session->mem; + + if ((!session->server && session->next_stream_id != 1) || + (session->server && session->last_recv_stream_id >= 1)) { + return NGHTTP2_ERR_PROTO; + } + if (settings_payloadlen % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + /* SETTINGS frame contains too many settings */ + if (settings_payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH > + session->max_settings) { + return NGHTTP2_ERR_TOO_MANY_SETTINGS; + } + rv = nghttp2_frame_unpack_settings_payload2(&iv, &niv, settings_payload, + settings_payloadlen, mem); + if (rv != 0) { + return rv; + } + + if (session->server) { + nghttp2_frame_hd_init(&frame.hd, settings_payloadlen, NGHTTP2_SETTINGS, + NGHTTP2_FLAG_NONE, 0); + frame.settings.iv = iv; + frame.settings.niv = niv; + rv = nghttp2_session_on_settings_received(session, &frame, 1 /* No ACK */); + } else { + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv); + } + nghttp2_mem_free(mem, iv); + if (rv != 0) { + return rv; + } + + nghttp2_priority_spec_default_init(&pri_spec); + + stream = nghttp2_session_open_stream( + session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_OPENING, + session->server ? NULL : stream_user_data); + if (stream == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + /* We don't call nghttp2_session_adjust_closed_stream(), since this + should be the first stream open. */ + + if (session->server) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + session->last_recv_stream_id = 1; + session->last_proc_stream_id = 1; + } else { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + session->last_sent_stream_id = 1; + session->next_stream_id += 2; + } + return 0; +} + +int nghttp2_session_upgrade(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, + void *stream_user_data) { + int rv; + nghttp2_stream *stream; + + rv = nghttp2_session_upgrade_internal(session, settings_payload, + settings_payloadlen, stream_user_data); + if (rv != 0) { + return rv; + } + + stream = nghttp2_session_get_stream(session, 1); + assert(stream); + + /* We have no information about request header fields when Upgrade + was happened. So we don't know the request method here. If + request method is HEAD, we have a trouble because we may have + nonzero content-length header field in response headers, and we + will going to check it against the actual DATA frames, but we may + get mismatch because HEAD response body must be empty. Because + of this reason, nghttp2_session_upgrade() was deprecated in favor + of nghttp2_session_upgrade2(), which has |head_request| parameter + to indicate that request method is HEAD or not. */ + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND; + return 0; +} + +int nghttp2_session_upgrade2(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, int head_request, + void *stream_user_data) { + int rv; + nghttp2_stream *stream; + + rv = nghttp2_session_upgrade_internal(session, settings_payload, + settings_payloadlen, stream_user_data); + if (rv != 0) { + return rv; + } + + stream = nghttp2_session_get_stream(session, 1); + assert(stream); + + if (head_request) { + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD; + } + + return 0; +} + +int nghttp2_session_get_stream_local_close(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return -1; + } + + return (stream->shut_flags & NGHTTP2_SHUT_WR) != 0; +} + +int nghttp2_session_get_stream_remote_close(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return -1; + } + + return (stream->shut_flags & NGHTTP2_SHUT_RD) != 0; +} + +int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id, + size_t size) { + int rv; + nghttp2_stream *stream; + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + rv = session_update_connection_consumed_size(session, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return 0; + } + + rv = session_update_stream_consumed_size(session, stream, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +int nghttp2_session_consume_connection(nghttp2_session *session, size_t size) { + int rv; + + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + rv = session_update_connection_consumed_size(session, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +int nghttp2_session_consume_stream(nghttp2_session *session, int32_t stream_id, + size_t size) { + int rv; + nghttp2_stream *stream; + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return 0; + } + + rv = session_update_stream_consumed_size(session, stream, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +int nghttp2_session_set_next_stream_id(nghttp2_session *session, + int32_t next_stream_id) { + if (next_stream_id <= 0 || + session->next_stream_id > (uint32_t)next_stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (session->server) { + if (next_stream_id % 2) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + } else if (next_stream_id % 2 == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + session->next_stream_id = (uint32_t)next_stream_id; + return 0; +} + +uint32_t nghttp2_session_get_next_stream_id(nghttp2_session *session) { + return session->next_stream_id; +} + +int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session) { + return session->last_proc_stream_id; +} + +nghttp2_stream *nghttp2_session_find_stream(nghttp2_session *session, + int32_t stream_id) { + if (stream_id == 0) { + return &session->root; + } + + return nghttp2_session_get_stream_raw(session, stream_id); +} + +nghttp2_stream *nghttp2_session_get_root_stream(nghttp2_session *session) { + return &session->root; +} + +int nghttp2_session_check_server_session(nghttp2_session *session) { + return session->server; +} + +int nghttp2_session_change_stream_priority( + nghttp2_session *session, int32_t stream_id, + const nghttp2_priority_spec *pri_spec) { + int rv; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec_copy; + + if (session->pending_no_rfc7540_priorities == 1) { + return 0; + } + + if (stream_id == 0 || stream_id == pri_spec->stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (!stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + pri_spec_copy = *pri_spec; + nghttp2_priority_spec_normalize_weight(&pri_spec_copy); + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + /* We don't intentionally call nghttp2_session_adjust_idle_stream() + so that idle stream created by this function, and existing ones + are kept for application. We will adjust number of idle stream + in nghttp2_session_mem_send or nghttp2_session_mem_recv is + called. */ + return 0; +} + +int nghttp2_session_create_idle_stream(nghttp2_session *session, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec) { + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec_copy; + + if (session->pending_no_rfc7540_priorities == 1) { + return 0; + } + + if (stream_id == 0 || stream_id == pri_spec->stream_id || + !session_detect_idle_stream(session, stream_id)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + pri_spec_copy = *pri_spec; + nghttp2_priority_spec_normalize_weight(&pri_spec_copy); + + stream = + nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_copy, NGHTTP2_STREAM_IDLE, NULL); + if (!stream) { + return NGHTTP2_ERR_NOMEM; + } + + /* We don't intentionally call nghttp2_session_adjust_idle_stream() + so that idle stream created by this function, and existing ones + are kept for application. We will adjust number of idle stream + in nghttp2_session_mem_send or nghttp2_session_mem_recv is + called. */ + return 0; +} + +size_t +nghttp2_session_get_hd_inflate_dynamic_table_size(nghttp2_session *session) { + return nghttp2_hd_inflate_get_dynamic_table_size(&session->hd_inflater); +} + +size_t +nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session) { + return nghttp2_hd_deflate_get_dynamic_table_size(&session->hd_deflater); +} + +void nghttp2_session_set_user_data(nghttp2_session *session, void *user_data) { + session->user_data = user_data; +} + +int nghttp2_session_change_extpri_stream_priority( + nghttp2_session *session, int32_t stream_id, + const nghttp2_extpri *extpri_in, int ignore_client_signal) { + nghttp2_stream *stream; + nghttp2_extpri extpri = *extpri_in; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (session->pending_no_rfc7540_priorities != 1) { + return 0; + } + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (!stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (extpri.urgency > NGHTTP2_EXTPRI_URGENCY_LOW) { + extpri.urgency = NGHTTP2_EXTPRI_URGENCY_LOW; + } + + if (ignore_client_signal) { + stream->flags |= NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES; + } + + return session_update_stream_priority(session, stream, + nghttp2_extpri_to_uint8(&extpri)); +} + +int nghttp2_session_get_extpri_stream_priority(nghttp2_session *session, + nghttp2_extpri *extpri, + int32_t stream_id) { + nghttp2_stream *stream; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (session->pending_no_rfc7540_priorities != 1) { + return 0; + } + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (!stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + nghttp2_extpri_from_uint8(extpri, stream->extpri); + + return 0; +} diff --git a/lib/nghttp2/lib/nghttp2_session.h b/lib/nghttp2/lib/nghttp2_session.h new file mode 100644 index 00000000000..b119329a04d --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_session.h @@ -0,0 +1,974 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_SESSION_H +#define NGHTTP2_SESSION_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include "nghttp2_map.h" +#include "nghttp2_frame.h" +#include "nghttp2_hd.h" +#include "nghttp2_stream.h" +#include "nghttp2_outbound_item.h" +#include "nghttp2_int.h" +#include "nghttp2_buf.h" +#include "nghttp2_callbacks.h" +#include "nghttp2_mem.h" +#include "nghttp2_ratelim.h" + +/* The global variable for tests where we want to disable strict + preface handling. */ +extern int nghttp2_enable_strict_preface; + +/* + * Option flags. + */ +typedef enum { + NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0, + NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC = 1 << 1, + NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2, + NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3, + NGHTTP2_OPTMASK_NO_CLOSED_STREAMS = 1 << 4, + NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 5, + NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 6, +} nghttp2_optmask; + +/* + * bitmask for built-in type to enable the default handling for that + * type of the frame. + */ +typedef enum { + NGHTTP2_TYPEMASK_NONE = 0, + NGHTTP2_TYPEMASK_ALTSVC = 1 << 0, + NGHTTP2_TYPEMASK_ORIGIN = 1 << 1, + NGHTTP2_TYPEMASK_PRIORITY_UPDATE = 1 << 2 +} nghttp2_typemask; + +typedef enum { + NGHTTP2_OB_POP_ITEM, + NGHTTP2_OB_SEND_DATA, + NGHTTP2_OB_SEND_NO_COPY, + NGHTTP2_OB_SEND_CLIENT_MAGIC +} nghttp2_outbound_state; + +typedef struct { + nghttp2_outbound_item *item; + nghttp2_bufs framebufs; + nghttp2_outbound_state state; +} nghttp2_active_outbound_item; + +/* Buffer length for inbound raw byte stream used in + nghttp2_session_recv(). */ +#define NGHTTP2_INBOUND_BUFFER_LENGTH 16384 + +/* The default maximum number of incoming reserved streams */ +#define NGHTTP2_MAX_INCOMING_RESERVED_STREAMS 200 + +/* Even if we have less SETTINGS_MAX_CONCURRENT_STREAMS than this + number, we keep NGHTTP2_MIN_IDLE_STREAMS streams in idle state */ +#define NGHTTP2_MIN_IDLE_STREAMS 16 + +/* The maximum number of items in outbound queue, which is considered + as flooding caused by peer. All frames are not considered here. + We only consider PING + ACK and SETTINGS + ACK. This is because + they both are response to the frame initiated by peer and peer can + send as many of them as they want. If peer does not read network, + response frames are stacked up, which leads to memory exhaustion. + The value selected here is arbitrary, but safe value and if we have + these frames in this number, it is considered suspicious. */ +#define NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM 1000 + +/* The default value of maximum number of concurrent streams. */ +#define NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu + +/* The default values for stream reset rate limiter. */ +#define NGHTTP2_DEFAULT_STREAM_RESET_BURST 1000 +#define NGHTTP2_DEFAULT_STREAM_RESET_RATE 33 + +/* Internal state when receiving incoming frame */ +typedef enum { + /* Receiving frame header */ + NGHTTP2_IB_READ_CLIENT_MAGIC, + NGHTTP2_IB_READ_FIRST_SETTINGS, + NGHTTP2_IB_READ_HEAD, + NGHTTP2_IB_READ_NBYTE, + NGHTTP2_IB_READ_HEADER_BLOCK, + NGHTTP2_IB_IGN_HEADER_BLOCK, + NGHTTP2_IB_IGN_PAYLOAD, + NGHTTP2_IB_FRAME_SIZE_ERROR, + NGHTTP2_IB_READ_SETTINGS, + NGHTTP2_IB_READ_GOAWAY_DEBUG, + NGHTTP2_IB_EXPECT_CONTINUATION, + NGHTTP2_IB_IGN_CONTINUATION, + NGHTTP2_IB_READ_PAD_DATA, + NGHTTP2_IB_READ_DATA, + NGHTTP2_IB_IGN_DATA, + NGHTTP2_IB_IGN_ALL, + NGHTTP2_IB_READ_ALTSVC_PAYLOAD, + NGHTTP2_IB_READ_ORIGIN_PAYLOAD, + NGHTTP2_IB_READ_EXTENSION_PAYLOAD +} nghttp2_inbound_state; + +typedef struct { + nghttp2_frame frame; + /* Storage for extension frame payload. frame->ext.payload points + to this structure to avoid frequent memory allocation. */ + nghttp2_ext_frame_payload ext_frame_payload; + /* The received SETTINGS entry. For the standard settings entries, + we only keep the last seen value. For + SETTINGS_HEADER_TABLE_SIZE, we also keep minimum value in the + last index. */ + nghttp2_settings_entry *iv; + /* buffer pointers to small buffer, raw_sbuf */ + nghttp2_buf sbuf; + /* buffer pointers to large buffer, raw_lbuf */ + nghttp2_buf lbuf; + /* Large buffer, malloced on demand */ + uint8_t *raw_lbuf; + /* The number of entry filled in |iv| */ + size_t niv; + /* The number of entries |iv| can store. */ + size_t max_niv; + /* How many bytes we still need to receive for current frame */ + size_t payloadleft; + /* padding length for the current frame */ + size_t padlen; + nghttp2_inbound_state state; + /* Small fixed sized buffer. */ + uint8_t raw_sbuf[32]; +} nghttp2_inbound_frame; + +typedef struct { + uint32_t header_table_size; + uint32_t enable_push; + uint32_t max_concurrent_streams; + uint32_t initial_window_size; + uint32_t max_frame_size; + uint32_t max_header_list_size; + uint32_t enable_connect_protocol; + uint32_t no_rfc7540_priorities; +} nghttp2_settings_storage; + +typedef enum { + NGHTTP2_GOAWAY_NONE = 0, + /* Flag means that connection should be terminated after sending GOAWAY. */ + NGHTTP2_GOAWAY_TERM_ON_SEND = 0x1, + /* Flag means GOAWAY to terminate session has been sent */ + NGHTTP2_GOAWAY_TERM_SENT = 0x2, + /* Flag means GOAWAY was sent */ + NGHTTP2_GOAWAY_SENT = 0x4, + /* Flag means GOAWAY was received */ + NGHTTP2_GOAWAY_RECV = 0x8, + /* Flag means GOAWAY has been submitted at least once */ + NGHTTP2_GOAWAY_SUBMITTED = 0x10 +} nghttp2_goaway_flag; + +/* nghttp2_inflight_settings stores the SETTINGS entries which local + endpoint has sent to the remote endpoint, and has not received ACK + yet. */ +struct nghttp2_inflight_settings { + struct nghttp2_inflight_settings *next; + nghttp2_settings_entry *iv; + size_t niv; +}; + +typedef struct nghttp2_inflight_settings nghttp2_inflight_settings; + +struct nghttp2_session { + nghttp2_map /* */ streams; + /* root of dependency tree*/ + nghttp2_stream root; + /* Queue for outbound urgent frames (PING and SETTINGS) */ + nghttp2_outbound_queue ob_urgent; + /* Queue for non-DATA frames */ + nghttp2_outbound_queue ob_reg; + /* Queue for outbound stream-creating HEADERS (request or push + response) frame, which are subject to + SETTINGS_MAX_CONCURRENT_STREAMS limit. */ + nghttp2_outbound_queue ob_syn; + /* Queues for DATA frames which is used when + SETTINGS_NO_RFC7540_PRIORITIES is enabled. This implements RFC + 9218 extensible prioritization scheme. */ + struct { + nghttp2_pq ob_data; + } sched[NGHTTP2_EXTPRI_URGENCY_LEVELS]; + nghttp2_active_outbound_item aob; + nghttp2_inbound_frame iframe; + nghttp2_hd_deflater hd_deflater; + nghttp2_hd_inflater hd_inflater; + nghttp2_session_callbacks callbacks; + /* Memory allocator */ + nghttp2_mem mem; + void *user_data; + /* Points to the latest incoming closed stream. NULL if there is no + closed stream. Only used when session is initialized as + server. */ + nghttp2_stream *closed_stream_head; + /* Points to the oldest incoming closed stream. NULL if there is no + closed stream. Only used when session is initialized as + server. */ + nghttp2_stream *closed_stream_tail; + /* Points to the latest idle stream. NULL if there is no idle + stream. Only used when session is initialized as server .*/ + nghttp2_stream *idle_stream_head; + /* Points to the oldest idle stream. NULL if there is no idle + stream. Only used when session is initialized as erver. */ + nghttp2_stream *idle_stream_tail; + /* Queue of In-flight SETTINGS values. SETTINGS bearing ACK is not + considered as in-flight. */ + nghttp2_inflight_settings *inflight_settings_head; + /* Stream reset rate limiter. If receiving excessive amount of + stream resets, GOAWAY will be sent. */ + nghttp2_ratelim stream_reset_ratelim; + /* Sequential number across all streams to process streams in + FIFO. */ + uint64_t stream_seq; + /* The number of outgoing streams. This will be capped by + remote_settings.max_concurrent_streams. */ + size_t num_outgoing_streams; + /* The number of incoming streams. This will be capped by + local_settings.max_concurrent_streams. */ + size_t num_incoming_streams; + /* The number of incoming reserved streams. This is the number of + streams in reserved (remote) state. RFC 7540 does not limit this + number. nghttp2 offers + nghttp2_option_set_max_reserved_remote_streams() to achieve this. + If it is used, num_incoming_streams is capped by + max_incoming_reserved_streams. Client application should + consider to set this because without that server can send + arbitrary number of PUSH_PROMISE, and exhaust client's memory. */ + size_t num_incoming_reserved_streams; + /* The maximum number of incoming reserved streams (reserved + (remote) state). RST_STREAM will be sent for the pushed stream + which exceeds this limit. */ + size_t max_incoming_reserved_streams; + /* The number of closed streams still kept in |streams| hash. The + closed streams can be accessed through single linked list + |closed_stream_head|. The current implementation only keeps + incoming streams and session is initialized as server. */ + size_t num_closed_streams; + /* The number of idle streams kept in |streams| hash. The idle + streams can be accessed through doubly linked list + |idle_stream_head|. The current implementation only keeps idle + streams if session is initialized as server. */ + size_t num_idle_streams; + /* The number of bytes allocated for nvbuf */ + size_t nvbuflen; + /* Counter for detecting flooding in outbound queue. If it exceeds + max_outbound_ack, session will be closed. */ + size_t obq_flood_counter_; + /* The maximum number of outgoing SETTINGS ACK and PING ACK in + outbound queue. */ + size_t max_outbound_ack; + /* The maximum length of header block to send. Calculated by the + same way as nghttp2_hd_deflate_bound() does. */ + size_t max_send_header_block_length; + /* The maximum number of settings accepted per SETTINGS frame. */ + size_t max_settings; + /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */ + uint32_t next_stream_id; + /* The last stream ID this session initiated. For client session, + this is the last stream ID it has sent. For server session, it + is the last promised stream ID sent in PUSH_PROMISE. */ + int32_t last_sent_stream_id; + /* The largest stream ID received so far */ + int32_t last_recv_stream_id; + /* The largest stream ID which has been processed in some way. This + value will be used as last-stream-id when sending GOAWAY + frame. */ + int32_t last_proc_stream_id; + /* Counter of unique ID of PING. Wraps when it exceeds + NGHTTP2_MAX_UNIQUE_ID */ + uint32_t next_unique_id; + /* This is the last-stream-ID we have sent in GOAWAY */ + int32_t local_last_stream_id; + /* This is the value in GOAWAY frame received from remote endpoint. */ + int32_t remote_last_stream_id; + /* Current sender window size. This value is computed against the + current initial window size of remote endpoint. */ + int32_t remote_window_size; + /* Keep track of the number of bytes received without + WINDOW_UPDATE. This could be negative after submitting negative + value to WINDOW_UPDATE. */ + int32_t recv_window_size; + /* The number of bytes consumed by the application and now is + subject to WINDOW_UPDATE. This is only used when auto + WINDOW_UPDATE is turned off. */ + int32_t consumed_size; + /* The amount of recv_window_size cut using submitting negative + value to WINDOW_UPDATE */ + int32_t recv_reduction; + /* window size for local flow control. It is initially set to + NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE and could be + increased/decreased by submitting WINDOW_UPDATE. See + nghttp2_submit_window_update(). */ + int32_t local_window_size; + /* This flag is used to indicate that the local endpoint received initial + SETTINGS frame from the remote endpoint. */ + uint8_t remote_settings_received; + /* Settings value received from the remote endpoint. */ + nghttp2_settings_storage remote_settings; + /* Settings value of the local endpoint. */ + nghttp2_settings_storage local_settings; + /* Option flags. This is bitwise-OR of 0 or more of nghttp2_optmask. */ + uint32_t opt_flags; + /* Unacked local SETTINGS_MAX_CONCURRENT_STREAMS value. We use this + to refuse the incoming stream if it exceeds this value. */ + uint32_t pending_local_max_concurrent_stream; + /* The bitwise OR of zero or more of nghttp2_typemask to indicate + that the default handling of extension frame is enabled. */ + uint32_t builtin_recv_ext_types; + /* Unacked local ENABLE_PUSH value. We use this to refuse + PUSH_PROMISE before SETTINGS ACK is received. */ + uint8_t pending_enable_push; + /* Unacked local ENABLE_CONNECT_PROTOCOL value. We use this to + accept :protocol header field before SETTINGS_ACK is received. */ + uint8_t pending_enable_connect_protocol; + /* Unacked local SETTINGS_NO_RFC7540_PRIORITIES value, which is + effective before it is acknowledged. */ + uint8_t pending_no_rfc7540_priorities; + /* Turn on fallback to RFC 7540 priorities; for server use only. */ + uint8_t fallback_rfc7540_priorities; + /* Nonzero if the session is server side. */ + uint8_t server; + /* Flags indicating GOAWAY is sent and/or received. The flags are + composed by bitwise OR-ing nghttp2_goaway_flag. */ + uint8_t goaway_flags; + /* This flag is used to reduce excessive queuing of WINDOW_UPDATE to + this session. The nonzero does not necessarily mean + WINDOW_UPDATE is not queued. */ + uint8_t window_update_queued; + /* Bitfield of extension frame types that application is willing to + receive. To designate the bit of given frame type i, use + user_recv_ext_types[i / 8] & (1 << (i & 0x7)). First 10 frame + types are standard frame types and not used in this bitfield. If + bit is set, it indicates that incoming frame with that type is + passed to user defined callbacks, otherwise they are ignored. */ + uint8_t user_recv_ext_types[32]; +}; + +/* Struct used when updating initial window size of each active + stream. */ +typedef struct { + nghttp2_session *session; + int32_t new_window_size, old_window_size; +} nghttp2_update_window_size_arg; + +typedef struct { + nghttp2_session *session; + /* linked list of streams to close */ + nghttp2_stream *head; + int32_t last_stream_id; + /* nonzero if GOAWAY is sent to peer, which means we are going to + close incoming streams. zero if GOAWAY is received from peer and + we are going to close outgoing streams. */ + int incoming; +} nghttp2_close_stream_on_goaway_arg; + +/* TODO stream timeout etc */ + +/* + * Returns nonzero value if |stream_id| is initiated by local + * endpoint. + */ +int nghttp2_session_is_my_stream_id(nghttp2_session *session, + int32_t stream_id); + +/* + * Adds |item| to the outbound queue in |session|. When this function + * succeeds, it takes ownership of |item|. So caller must not free it + * on success. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_STREAM_CLOSED + * Stream already closed (DATA and PUSH_PROMISE frame only) + */ +int nghttp2_session_add_item(nghttp2_session *session, + nghttp2_outbound_item *item); + +/* + * Adds RST_STREAM frame for the stream |stream_id| with the error + * code |error_code|. This is a convenient function built on top of + * nghttp2_session_add_frame() to add RST_STREAM easily. + * + * This function simply returns 0 without adding RST_STREAM frame if + * given stream is in NGHTTP2_STREAM_CLOSING state, because multiple + * RST_STREAM for a stream is redundant. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id, + uint32_t error_code); + +/* + * Adds PING frame. This is a convenient function built on top of + * nghttp2_session_add_frame() to add PING easily. + * + * If the |opaque_data| is not NULL, it must point to 8 bytes memory + * region of data. The data pointed by |opaque_data| is copied. It can + * be NULL. In this case, 8 bytes NULL is used. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_FLOODED + * There are too many items in outbound queue; this only happens + * if NGHTTP2_FLAG_ACK is set in |flags| + */ +int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags, + const uint8_t *opaque_data); + +/* + * Adds GOAWAY frame with the last-stream-ID |last_stream_id| and the + * error code |error_code|. This is a convenient function built on top + * of nghttp2_session_add_frame() to add GOAWAY easily. The + * |aux_flags| are bitwise-OR of one or more of + * nghttp2_goaway_aux_flag. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_INVALID_ARGUMENT + * The |opaque_data_len| is too large. + */ +int nghttp2_session_add_goaway(nghttp2_session *session, int32_t last_stream_id, + uint32_t error_code, const uint8_t *opaque_data, + size_t opaque_data_len, uint8_t aux_flags); + +/* + * Adds WINDOW_UPDATE frame with stream ID |stream_id| and + * window-size-increment |window_size_increment|. This is a convenient + * function built on top of nghttp2_session_add_frame() to add + * WINDOW_UPDATE easily. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + int32_t window_size_increment); + +/* + * Adds SETTINGS frame. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_FLOODED + * There are too many items in outbound queue; this only happens + * if NGHTTP2_FLAG_ACK is set in |flags| + */ +int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, + const nghttp2_settings_entry *iv, size_t niv); + +/* + * Creates new stream in |session| with stream ID |stream_id|, + * priority |pri_spec| and flags |flags|. The |flags| is bitwise OR + * of nghttp2_stream_flag. Since this function is called when initial + * HEADERS is sent or received, these flags are taken from it. The + * state of stream is set to |initial_state|. The |stream_user_data| + * is a pointer to the arbitrary user supplied data to be associated + * to this stream. + * + * If |initial_state| is NGHTTP2_STREAM_RESERVED, this function sets + * NGHTTP2_STREAM_FLAG_PUSH flag set. + * + * This function returns a pointer to created new stream object, or + * NULL. + * + * This function adjusts neither the number of closed streams or idle + * streams. The caller should manually call + * nghttp2_session_adjust_closed_stream() or + * nghttp2_session_adjust_idle_stream() respectively. + */ +nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session, + int32_t stream_id, uint8_t flags, + nghttp2_priority_spec *pri_spec, + nghttp2_stream_state initial_state, + void *stream_user_data); + +/* + * Closes stream whose stream ID is |stream_id|. The reason of closure + * is indicated by the |error_code|. When closing the stream, + * on_stream_close_callback will be called. + * + * If the session is initialized as server and |stream| is incoming + * stream, stream is just marked closed and this function calls + * nghttp2_session_keep_closed_stream() with |stream|. Otherwise, + * |stream| will be deleted from memory. + * + * This function returns 0 if it succeeds, or one the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_INVALID_ARGUMENT + * The specified stream does not exist. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id, + uint32_t error_code); + +/* + * Deletes |stream| from memory. After this function returns, stream + * cannot be accessed. + * + * This function returns 0 if it succeeds, or one the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_session_destroy_stream(nghttp2_session *session, + nghttp2_stream *stream); + +/* + * Tries to keep incoming closed stream |stream|. Due to the + * limitation of maximum number of streams in memory, |stream| is not + * closed and just deleted from memory (see + * nghttp2_session_destroy_stream). + */ +void nghttp2_session_keep_closed_stream(nghttp2_session *session, + nghttp2_stream *stream); + +/* + * Appends |stream| to linked list |session->idle_stream_head|. We + * apply fixed limit for list size. To fit into that limit, one or + * more oldest streams are removed from list as necessary. + */ +void nghttp2_session_keep_idle_stream(nghttp2_session *session, + nghttp2_stream *stream); + +/* + * Detaches |stream| from idle streams linked list. + */ +void nghttp2_session_detach_idle_stream(nghttp2_session *session, + nghttp2_stream *stream); + +/* + * Deletes closed stream to ensure that number of incoming streams + * including active and closed is in the maximum number of allowed + * stream. + * + * This function returns 0 if it succeeds, or one the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_session_adjust_closed_stream(nghttp2_session *session); + +/* + * Deletes idle stream to ensure that number of idle streams is in + * certain limit. + * + * This function returns 0 if it succeeds, or one the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_session_adjust_idle_stream(nghttp2_session *session); + +/* + * If further receptions and transmissions over the stream |stream_id| + * are disallowed, close the stream with error code NGHTTP2_NO_ERROR. + * + * This function returns 0 if it + * succeeds, or one of the following negative error codes: + * + * NGHTTP2_ERR_INVALID_ARGUMENT + * The specified stream does not exist. + */ +int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session, + nghttp2_stream *stream); + +int nghttp2_session_on_request_headers_received(nghttp2_session *session, + nghttp2_frame *frame); + +int nghttp2_session_on_response_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream); + +int nghttp2_session_on_push_response_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream); + +/* + * Called when HEADERS is received, assuming |frame| is properly + * initialized. This function does first validate received frame and + * then open stream and call callback functions. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_IGN_HEADER_BLOCK + * Frame was rejected and header block must be decoded but + * result must be ignored. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed + */ +int nghttp2_session_on_headers_received(nghttp2_session *session, + nghttp2_frame *frame, + nghttp2_stream *stream); + +/* + * Called when PRIORITY is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed + */ +int nghttp2_session_on_priority_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when RST_STREAM is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed + */ +int nghttp2_session_on_rst_stream_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when SETTINGS is received, assuming |frame| is properly + * initialized. If |noack| is non-zero, SETTINGS with ACK will not be + * submitted. If |frame| has NGHTTP2_FLAG_ACK flag set, no SETTINGS + * with ACK will not be submitted regardless of |noack|. + * + * This function returns 0 if it succeeds, or one the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed + * NGHTTP2_ERR_FLOODED + * There are too many items in outbound queue, and this is most + * likely caused by misbehaviour of peer. + */ +int nghttp2_session_on_settings_received(nghttp2_session *session, + nghttp2_frame *frame, int noack); + +/* + * Called when PUSH_PROMISE is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_IGN_HEADER_BLOCK + * Frame was rejected and header block must be decoded but + * result must be ignored. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed + */ +int nghttp2_session_on_push_promise_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when PING is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + * NGHTTP2_ERR_FLOODED + * There are too many items in outbound queue, and this is most + * likely caused by misbehaviour of peer. + */ +int nghttp2_session_on_ping_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when GOAWAY is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_goaway_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when WINDOW_UPDATE is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_window_update_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when ALTSVC is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_altsvc_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when ORIGIN is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_origin_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when PRIORITY_UPDATE is received, assuming |frame| is + * properly initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_priority_update_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Called when DATA is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_data_received(nghttp2_session *session, + nghttp2_frame *frame); + +/* + * Returns nghttp2_stream* object whose stream ID is |stream_id|. It + * could be NULL if such stream does not exist. This function returns + * NULL if stream is marked as closed. + */ +nghttp2_stream *nghttp2_session_get_stream(nghttp2_session *session, + int32_t stream_id); + +/* + * This function behaves like nghttp2_session_get_stream(), but it + * returns stream object even if it is marked as closed or in + * NGHTTP2_STREAM_IDLE state. + */ +nghttp2_stream *nghttp2_session_get_stream_raw(nghttp2_session *session, + int32_t stream_id); + +/* + * Packs DATA frame |frame| in wire frame format and stores it in + * |bufs|. Payload will be read using |aux_data->data_prd|. The + * length of payload is at most |datamax| bytes. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_DEFERRED + * The DATA frame is postponed. + * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE + * The read_callback failed (stream error). + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_CALLBACK_FAILURE + * The read_callback failed (session error). + */ +int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs, + size_t datamax, nghttp2_frame *frame, + nghttp2_data_aux_data *aux_data, + nghttp2_stream *stream); + +/* + * Pops and returns next item to send. If there is no such item, + * returns NULL. This function takes into account max concurrent + * streams. That means if session->ob_syn has item and max concurrent + * streams is reached, the even if other queues contain items, then + * this function returns NULL. + */ +nghttp2_outbound_item * +nghttp2_session_pop_next_ob_item(nghttp2_session *session); + +/* + * Returns next item to send. If there is no such item, this function + * returns NULL. This function takes into account max concurrent + * streams. That means if session->ob_syn has item and max concurrent + * streams is reached, the even if other queues contain items, then + * this function returns NULL. + */ +nghttp2_outbound_item * +nghttp2_session_get_next_ob_item(nghttp2_session *session); + +/* + * Updates local settings with the |iv|. The number of elements in the + * array pointed by the |iv| is given by the |niv|. This function + * assumes that the all settings_id member in |iv| are in range 1 to + * NGHTTP2_SETTINGS_MAX, inclusive. + * + * While updating individual stream's local window size, if the window + * size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE, + * RST_STREAM is issued against such a stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_session_update_local_settings(nghttp2_session *session, + nghttp2_settings_entry *iv, + size_t niv); + +/* + * Re-prioritize |stream|. The new priority specification is + * |pri_spec|. Caller must ensure that stream->hd.stream_id != + * pri_spec->stream_id. + * + * This function does not adjust the number of idle streams. The + * caller should call nghttp2_session_adjust_idle_stream() later. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_session_reprioritize_stream(nghttp2_session *session, + nghttp2_stream *stream, + const nghttp2_priority_spec *pri_spec); + +/* + * Terminates current |session| with the |error_code|. The |reason| + * is NULL-terminated debug string. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + * NGHTTP2_ERR_INVALID_ARGUMENT + * The |reason| is too long. + */ +int nghttp2_session_terminate_session_with_reason(nghttp2_session *session, + uint32_t error_code, + const char *reason); + +/* + * Accumulates received bytes |delta_size| for connection-level flow + * control and decides whether to send WINDOW_UPDATE to the + * connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, + * WINDOW_UPDATE will not be sent. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session, + size_t delta_size); + +/* + * Accumulates received bytes |delta_size| for stream-level flow + * control and decides whether to send WINDOW_UPDATE to that stream. + * If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not + * be sent. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session, + nghttp2_stream *stream, + size_t delta_size, + int send_window_update); + +#endif /* NGHTTP2_SESSION_H */ diff --git a/lib/nghttp2/lib/nghttp2_stream.c b/lib/nghttp2/lib/nghttp2_stream.c new file mode 100644 index 00000000000..f1951f879d7 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_stream.c @@ -0,0 +1,1016 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_stream.h" + +#include +#include + +#include "nghttp2_session.h" +#include "nghttp2_helper.h" +#include "nghttp2_debug.h" +#include "nghttp2_frame.h" + +/* Maximum distance between any two stream's cycle in the same + priority queue. Imagine stream A's cycle is A, and stream B's + cycle is B, and A < B. The cycle is unsigned 32 bit integer, it + may get overflow. Because of how we calculate the next cycle + value, if B - A is less than or equals to + NGHTTP2_MAX_CYCLE_DISTANCE, A and B are in the same scale, in other + words, B is really greater than or equal to A. Otherwise, A is a + result of overflow, and it is actually A > B if we consider that + fact. */ +#define NGHTTP2_MAX_CYCLE_DISTANCE \ + ((uint64_t)NGHTTP2_MAX_FRAME_SIZE_MAX * 256 + 255) + +static int stream_less(const void *lhsx, const void *rhsx) { + const nghttp2_stream *lhs, *rhs; + + lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry); + rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry); + + if (lhs->cycle == rhs->cycle) { + return lhs->seq < rhs->seq; + } + + return rhs->cycle - lhs->cycle <= NGHTTP2_MAX_CYCLE_DISTANCE; +} + +void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, + uint8_t flags, nghttp2_stream_state initial_state, + int32_t weight, int32_t remote_initial_window_size, + int32_t local_initial_window_size, + void *stream_user_data, nghttp2_mem *mem) { + nghttp2_pq_init(&stream->obq, stream_less, mem); + + stream->stream_id = stream_id; + stream->flags = flags; + stream->state = initial_state; + stream->shut_flags = NGHTTP2_SHUT_NONE; + stream->stream_user_data = stream_user_data; + stream->item = NULL; + stream->remote_window_size = remote_initial_window_size; + stream->local_window_size = local_initial_window_size; + stream->recv_window_size = 0; + stream->consumed_size = 0; + stream->recv_reduction = 0; + stream->window_update_queued = 0; + + stream->dep_prev = NULL; + stream->dep_next = NULL; + stream->sib_prev = NULL; + stream->sib_next = NULL; + + stream->closed_prev = NULL; + stream->closed_next = NULL; + + stream->weight = weight; + stream->sum_dep_weight = 0; + + stream->http_flags = NGHTTP2_HTTP_FLAG_NONE; + stream->content_length = -1; + stream->recv_content_length = 0; + stream->status_code = -1; + + stream->queued = 0; + stream->descendant_last_cycle = 0; + stream->cycle = 0; + stream->pending_penalty = 0; + stream->descendant_next_seq = 0; + stream->seq = 0; + stream->last_writelen = 0; + + stream->extpri = stream->http_extpri = NGHTTP2_EXTPRI_DEFAULT_URGENCY; +} + +void nghttp2_stream_free(nghttp2_stream *stream) { + nghttp2_pq_free(&stream->obq); + /* We don't free stream->item. If it is assigned to aob, then + active_outbound_item_reset() will delete it. Otherwise, + nghttp2_stream_close() or session_del() will delete it. */ +} + +void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag) { + stream->shut_flags = (uint8_t)(stream->shut_flags | flag); +} + +/* + * Returns nonzero if |stream| is active. This function does not take + * into account its descendants. + */ +static int stream_active(nghttp2_stream *stream) { + return stream->item && + (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) == 0; +} + +/* + * Returns nonzero if |stream| or one of its descendants is active + */ +static int stream_subtree_active(nghttp2_stream *stream) { + return stream_active(stream) || !nghttp2_pq_empty(&stream->obq); +} + +/* + * Returns next cycle for |stream|. + */ +static void stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) { + uint64_t penalty; + + penalty = (uint64_t)stream->last_writelen * NGHTTP2_MAX_WEIGHT + + stream->pending_penalty; + + stream->cycle = last_cycle + penalty / (uint32_t)stream->weight; + stream->pending_penalty = (uint32_t)(penalty % (uint32_t)stream->weight); +} + +static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) { + int rv; + + for (; dep_stream && !stream->queued; + stream = dep_stream, dep_stream = dep_stream->dep_prev) { + stream_next_cycle(stream, dep_stream->descendant_last_cycle); + stream->seq = dep_stream->descendant_next_seq++; + + DEBUGF("stream: stream=%d obq push cycle=%lu\n", stream->stream_id, + stream->cycle); + + DEBUGF("stream: push stream %d to stream %d\n", stream->stream_id, + dep_stream->stream_id); + + rv = nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry); + if (rv != 0) { + return rv; + } + stream->queued = 1; + } + + return 0; +} + +/* + * Removes |stream| from parent's obq. If removal of |stream| makes + * parent's obq empty, and parent is not active, then parent is also + * removed. This process is repeated recursively. + */ +static void stream_obq_remove(nghttp2_stream *stream) { + nghttp2_stream *dep_stream; + + dep_stream = stream->dep_prev; + + if (!stream->queued) { + return; + } + + for (; dep_stream; stream = dep_stream, dep_stream = dep_stream->dep_prev) { + DEBUGF("stream: remove stream %d from stream %d\n", stream->stream_id, + dep_stream->stream_id); + + nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry); + + assert(stream->queued); + + stream->queued = 0; + stream->cycle = 0; + stream->pending_penalty = 0; + stream->descendant_last_cycle = 0; + stream->last_writelen = 0; + + if (stream_subtree_active(dep_stream)) { + return; + } + } +} + +/* + * Moves |stream| from |src|'s obq to |dest|'s obq. Removal from + * |src|'s obq is just done calling nghttp2_pq_remove(), so it does + * not recursively remove |src| and ancestors, like + * stream_obq_remove(). + */ +static int stream_obq_move(nghttp2_stream *dest, nghttp2_stream *src, + nghttp2_stream *stream) { + if (!stream->queued) { + return 0; + } + + DEBUGF("stream: remove stream %d from stream %d (move)\n", stream->stream_id, + src->stream_id); + + nghttp2_pq_remove(&src->obq, &stream->pq_entry); + stream->queued = 0; + + return stream_obq_push(dest, stream); +} + +void nghttp2_stream_reschedule(nghttp2_stream *stream) { + nghttp2_stream *dep_stream; + + assert(stream->queued); + + dep_stream = stream->dep_prev; + + for (; dep_stream; stream = dep_stream, dep_stream = dep_stream->dep_prev) { + nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry); + + stream_next_cycle(stream, dep_stream->descendant_last_cycle); + stream->seq = dep_stream->descendant_next_seq++; + + nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry); + + DEBUGF("stream: stream=%d obq resched cycle=%lu\n", stream->stream_id, + stream->cycle); + + dep_stream->last_writelen = stream->last_writelen; + } +} + +void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) { + nghttp2_stream *dep_stream; + uint64_t last_cycle; + int32_t old_weight; + uint64_t wlen_penalty; + + if (stream->weight == weight) { + return; + } + + old_weight = stream->weight; + stream->weight = weight; + + dep_stream = stream->dep_prev; + + if (!dep_stream) { + return; + } + + dep_stream->sum_dep_weight += weight - old_weight; + + if (!stream->queued) { + return; + } + + nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry); + + wlen_penalty = (uint64_t)stream->last_writelen * NGHTTP2_MAX_WEIGHT; + + /* Compute old stream->pending_penalty we used to calculate + stream->cycle */ + stream->pending_penalty = + (uint32_t)((stream->pending_penalty + (uint32_t)old_weight - + (wlen_penalty % (uint32_t)old_weight)) % + (uint32_t)old_weight); + + last_cycle = stream->cycle - + (wlen_penalty + stream->pending_penalty) / (uint32_t)old_weight; + + /* Now we have old stream->pending_penalty and new stream->weight in + place */ + stream_next_cycle(stream, last_cycle); + + if (dep_stream->descendant_last_cycle - stream->cycle <= + NGHTTP2_MAX_CYCLE_DISTANCE) { + stream->cycle = dep_stream->descendant_last_cycle; + } + + /* Continue to use same stream->seq */ + + nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry); + + DEBUGF("stream: stream=%d obq resched cycle=%lu\n", stream->stream_id, + stream->cycle); +} + +static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) { + for (; stream->sib_next; stream = stream->sib_next) + ; + + return stream; +} + +int32_t nghttp2_stream_dep_distributed_weight(nghttp2_stream *stream, + int32_t weight) { + weight = stream->weight * weight / stream->sum_dep_weight; + + return nghttp2_max(1, weight); +} + +#ifdef STREAM_DEP_DEBUG + +static void ensure_inactive(nghttp2_stream *stream) { + nghttp2_stream *si; + + if (stream->queued) { + fprintf(stderr, "stream(%p)=%d, stream->queued = 1; want 0\n", stream, + stream->stream_id); + assert(0); + } + + if (stream_active(stream)) { + fprintf(stderr, "stream(%p)=%d, stream_active(stream) = 1; want 0\n", + stream, stream->stream_id); + assert(0); + } + + if (!nghttp2_pq_empty(&stream->obq)) { + fprintf(stderr, "stream(%p)=%d, nghttp2_pq_size() = %zu; want 0\n", stream, + stream->stream_id, nghttp2_pq_size(&stream->obq)); + assert(0); + } + + for (si = stream->dep_next; si; si = si->sib_next) { + ensure_inactive(si); + } +} + +static void check_queued(nghttp2_stream *stream) { + nghttp2_stream *si; + int queued; + + if (stream->queued) { + if (!stream_subtree_active(stream)) { + fprintf(stderr, + "stream(%p)=%d, stream->queued == 1, but " + "stream_active() == %d and nghttp2_pq_size(&stream->obq) = %zu\n", + stream, stream->stream_id, stream_active(stream), + nghttp2_pq_size(&stream->obq)); + assert(0); + } + if (!stream_active(stream)) { + queued = 0; + for (si = stream->dep_next; si; si = si->sib_next) { + if (si->queued) { + ++queued; + } + } + if (queued == 0) { + fprintf(stderr, + "stream(%p)=%d, stream->queued == 1, and " + "!stream_active(), but no descendants is queued\n", + stream, stream->stream_id); + assert(0); + } + } + + for (si = stream->dep_next; si; si = si->sib_next) { + check_queued(si); + } + } else { + if (stream_active(stream) || !nghttp2_pq_empty(&stream->obq)) { + fprintf(stderr, + "stream(%p) = %d, stream->queued == 0, but " + "stream_active(stream) == %d and " + "nghttp2_pq_size(&stream->obq) = %zu\n", + stream, stream->stream_id, stream_active(stream), + nghttp2_pq_size(&stream->obq)); + assert(0); + } + for (si = stream->dep_next; si; si = si->sib_next) { + ensure_inactive(si); + } + } +} + +static void check_sum_dep(nghttp2_stream *stream) { + nghttp2_stream *si; + int32_t n = 0; + for (si = stream->dep_next; si; si = si->sib_next) { + n += si->weight; + } + if (n != stream->sum_dep_weight) { + fprintf(stderr, "stream(%p)=%d, sum_dep_weight = %d; want %d\n", stream, + stream->stream_id, n, stream->sum_dep_weight); + assert(0); + } + for (si = stream->dep_next; si; si = si->sib_next) { + check_sum_dep(si); + } +} + +static void check_dep_prev(nghttp2_stream *stream) { + nghttp2_stream *si; + for (si = stream->dep_next; si; si = si->sib_next) { + if (si->dep_prev != stream) { + fprintf(stderr, "si->dep_prev = %p; want %p\n", si->dep_prev, stream); + assert(0); + } + check_dep_prev(si); + } +} + +#endif /* STREAM_DEP_DEBUG */ + +#ifdef STREAM_DEP_DEBUG +static void validate_tree(nghttp2_stream *stream) { + nghttp2_stream *si; + + if (!stream) { + return; + } + + for (; stream->dep_prev; stream = stream->dep_prev) + ; + + assert(stream->stream_id == 0); + assert(!stream->queued); + + fprintf(stderr, "checking...\n"); + if (nghttp2_pq_empty(&stream->obq)) { + fprintf(stderr, "root obq empty\n"); + for (si = stream->dep_next; si; si = si->sib_next) { + ensure_inactive(si); + } + } else { + for (si = stream->dep_next; si; si = si->sib_next) { + check_queued(si); + } + } + + check_sum_dep(stream); + check_dep_prev(stream); +} +#else /* !STREAM_DEP_DEBUG */ +static void validate_tree(nghttp2_stream *stream) { (void)stream; } +#endif /* !STREAM_DEP_DEBUG*/ + +static int stream_update_dep_on_attach_item(nghttp2_stream *stream) { + int rv; + + rv = stream_obq_push(stream->dep_prev, stream); + if (rv != 0) { + return rv; + } + + validate_tree(stream); + return 0; +} + +static void stream_update_dep_on_detach_item(nghttp2_stream *stream) { + if (nghttp2_pq_empty(&stream->obq)) { + stream_obq_remove(stream); + } + + validate_tree(stream); +} + +int nghttp2_stream_attach_item(nghttp2_stream *stream, + nghttp2_outbound_item *item) { + int rv; + + assert((stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) == 0); + assert(stream->item == NULL); + + DEBUGF("stream: stream=%d attach item=%p\n", stream->stream_id, item); + + stream->item = item; + + if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) { + return 0; + } + + rv = stream_update_dep_on_attach_item(stream); + if (rv != 0) { + /* This may relave stream->queued == 1, but stream->item == NULL. + But only consequence of this error is fatal one, and session + destruction. In that execution path, these inconsistency does + not matter. */ + stream->item = NULL; + return rv; + } + + return 0; +} + +void nghttp2_stream_detach_item(nghttp2_stream *stream) { + DEBUGF("stream: stream=%d detach item=%p\n", stream->stream_id, stream->item); + + stream->item = NULL; + stream->flags = (uint8_t)(stream->flags & ~NGHTTP2_STREAM_FLAG_DEFERRED_ALL); + + if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) { + return; + } + + stream_update_dep_on_detach_item(stream); +} + +void nghttp2_stream_defer_item(nghttp2_stream *stream, uint8_t flags) { + assert(stream->item); + + DEBUGF("stream: stream=%d defer item=%p cause=%02x\n", stream->stream_id, + stream->item, flags); + + stream->flags |= flags; + + if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) { + return; + } + + stream_update_dep_on_detach_item(stream); +} + +int nghttp2_stream_resume_deferred_item(nghttp2_stream *stream, uint8_t flags) { + assert(stream->item); + + DEBUGF("stream: stream=%d resume item=%p flags=%02x\n", stream->stream_id, + stream->item, flags); + + stream->flags = (uint8_t)(stream->flags & ~flags); + + if (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) { + return 0; + } + + if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) { + return 0; + } + + return stream_update_dep_on_attach_item(stream); +} + +int nghttp2_stream_check_deferred_item(nghttp2_stream *stream) { + return stream->item && (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL); +} + +int nghttp2_stream_check_deferred_by_flow_control(nghttp2_stream *stream) { + return stream->item && + (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); +} + +static int update_initial_window_size(int32_t *window_size_ptr, + int32_t new_initial_window_size, + int32_t old_initial_window_size) { + int64_t new_window_size = (int64_t)(*window_size_ptr) + + new_initial_window_size - old_initial_window_size; + if (INT32_MIN > new_window_size || + new_window_size > NGHTTP2_MAX_WINDOW_SIZE) { + return -1; + } + *window_size_ptr = (int32_t)new_window_size; + return 0; +} + +int nghttp2_stream_update_remote_initial_window_size( + nghttp2_stream *stream, int32_t new_initial_window_size, + int32_t old_initial_window_size) { + return update_initial_window_size(&stream->remote_window_size, + new_initial_window_size, + old_initial_window_size); +} + +int nghttp2_stream_update_local_initial_window_size( + nghttp2_stream *stream, int32_t new_initial_window_size, + int32_t old_initial_window_size) { + return update_initial_window_size(&stream->local_window_size, + new_initial_window_size, + old_initial_window_size); +} + +void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream) { + stream->state = NGHTTP2_STREAM_OPENED; + stream->flags = (uint8_t)(stream->flags & ~NGHTTP2_STREAM_FLAG_PUSH); +} + +int nghttp2_stream_dep_find_ancestor(nghttp2_stream *stream, + nghttp2_stream *target) { + for (; stream; stream = stream->dep_prev) { + if (stream == target) { + return 1; + } + } + return 0; +} + +int nghttp2_stream_dep_insert(nghttp2_stream *dep_stream, + nghttp2_stream *stream) { + nghttp2_stream *si; + int rv; + + DEBUGF("stream: dep_insert dep_stream(%p)=%d, stream(%p)=%d\n", dep_stream, + dep_stream->stream_id, stream, stream->stream_id); + + stream->sum_dep_weight = dep_stream->sum_dep_weight; + dep_stream->sum_dep_weight = stream->weight; + + if (dep_stream->dep_next) { + for (si = dep_stream->dep_next; si; si = si->sib_next) { + si->dep_prev = stream; + if (si->queued) { + rv = stream_obq_move(stream, dep_stream, si); + if (rv != 0) { + return rv; + } + } + } + + if (stream_subtree_active(stream)) { + rv = stream_obq_push(dep_stream, stream); + if (rv != 0) { + return rv; + } + } + + stream->dep_next = dep_stream->dep_next; + } + + dep_stream->dep_next = stream; + stream->dep_prev = dep_stream; + + validate_tree(stream); + + return 0; +} + +static void set_dep_prev(nghttp2_stream *stream, nghttp2_stream *dep) { + for (; stream; stream = stream->sib_next) { + stream->dep_prev = dep; + } +} + +static void link_dep(nghttp2_stream *dep_stream, nghttp2_stream *stream) { + dep_stream->dep_next = stream; + if (stream) { + stream->dep_prev = dep_stream; + } +} + +static void link_sib(nghttp2_stream *a, nghttp2_stream *b) { + a->sib_next = b; + if (b) { + b->sib_prev = a; + } +} + +static void insert_link_dep(nghttp2_stream *dep_stream, + nghttp2_stream *stream) { + nghttp2_stream *sib_next; + + assert(stream->sib_prev == NULL); + + sib_next = dep_stream->dep_next; + + link_sib(stream, sib_next); + + link_dep(dep_stream, stream); +} + +static void unlink_sib(nghttp2_stream *stream) { + nghttp2_stream *prev, *next, *dep_next; + + prev = stream->sib_prev; + dep_next = stream->dep_next; + + assert(prev); + + if (dep_next) { + /* + * prev--stream(--sib_next--...) + * | + * dep_next + */ + + link_sib(prev, dep_next); + + set_dep_prev(dep_next, stream->dep_prev); + + if (stream->sib_next) { + link_sib(stream_last_sib(dep_next), stream->sib_next); + } + } else { + /* + * prev--stream(--sib_next--...) + */ + next = stream->sib_next; + + prev->sib_next = next; + + if (next) { + next->sib_prev = prev; + } + } +} + +static void unlink_dep(nghttp2_stream *stream) { + nghttp2_stream *prev, *next, *dep_next; + + prev = stream->dep_prev; + dep_next = stream->dep_next; + + assert(prev); + + if (dep_next) { + /* + * prev + * | + * stream(--sib_next--...) + * | + * dep_next + */ + link_dep(prev, dep_next); + + set_dep_prev(dep_next, stream->dep_prev); + + if (stream->sib_next) { + link_sib(stream_last_sib(dep_next), stream->sib_next); + } + + } else if (stream->sib_next) { + /* + * prev + * | + * stream--sib_next + */ + next = stream->sib_next; + + next->sib_prev = NULL; + + link_dep(prev, next); + } else { + prev->dep_next = NULL; + } +} + +void nghttp2_stream_dep_add(nghttp2_stream *dep_stream, + nghttp2_stream *stream) { + DEBUGF("stream: dep_add dep_stream(%p)=%d, stream(%p)=%d\n", dep_stream, + dep_stream->stream_id, stream, stream->stream_id); + + dep_stream->sum_dep_weight += stream->weight; + + if (dep_stream->dep_next == NULL) { + link_dep(dep_stream, stream); + } else { + insert_link_dep(dep_stream, stream); + } + + validate_tree(stream); +} + +int nghttp2_stream_dep_remove(nghttp2_stream *stream) { + nghttp2_stream *dep_prev, *si; + int32_t sum_dep_weight_delta; + int rv; + + DEBUGF("stream: dep_remove stream(%p)=%d\n", stream, stream->stream_id); + + /* Distribute weight of |stream| to direct descendants */ + sum_dep_weight_delta = -stream->weight; + + for (si = stream->dep_next; si; si = si->sib_next) { + si->weight = nghttp2_stream_dep_distributed_weight(stream, si->weight); + + sum_dep_weight_delta += si->weight; + + if (si->queued) { + rv = stream_obq_move(stream->dep_prev, stream, si); + if (rv != 0) { + return rv; + } + } + } + + assert(stream->dep_prev); + + dep_prev = stream->dep_prev; + + dep_prev->sum_dep_weight += sum_dep_weight_delta; + + if (stream->queued) { + stream_obq_remove(stream); + } + + if (stream->sib_prev) { + unlink_sib(stream); + } else { + unlink_dep(stream); + } + + stream->sum_dep_weight = 0; + + stream->dep_prev = NULL; + stream->dep_next = NULL; + stream->sib_prev = NULL; + stream->sib_next = NULL; + + validate_tree(dep_prev); + + return 0; +} + +int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream, + nghttp2_stream *stream) { + nghttp2_stream *last_sib; + nghttp2_stream *dep_next; + nghttp2_stream *si; + int rv; + + DEBUGF("stream: dep_insert_subtree dep_stream(%p)=%d stream(%p)=%d\n", + dep_stream, dep_stream->stream_id, stream, stream->stream_id); + + stream->sum_dep_weight += dep_stream->sum_dep_weight; + dep_stream->sum_dep_weight = stream->weight; + + if (dep_stream->dep_next) { + dep_next = dep_stream->dep_next; + + link_dep(dep_stream, stream); + + if (stream->dep_next) { + last_sib = stream_last_sib(stream->dep_next); + + link_sib(last_sib, dep_next); + } else { + link_dep(stream, dep_next); + } + + for (si = dep_next; si; si = si->sib_next) { + si->dep_prev = stream; + if (si->queued) { + rv = stream_obq_move(stream, dep_stream, si); + if (rv != 0) { + return rv; + } + } + } + } else { + link_dep(dep_stream, stream); + } + + if (stream_subtree_active(stream)) { + rv = stream_obq_push(dep_stream, stream); + if (rv != 0) { + return rv; + } + } + + validate_tree(dep_stream); + + return 0; +} + +int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream, + nghttp2_stream *stream) { + int rv; + + DEBUGF("stream: dep_add_subtree dep_stream(%p)=%d stream(%p)=%d\n", + dep_stream, dep_stream->stream_id, stream, stream->stream_id); + + dep_stream->sum_dep_weight += stream->weight; + + if (dep_stream->dep_next) { + insert_link_dep(dep_stream, stream); + } else { + link_dep(dep_stream, stream); + } + + if (stream_subtree_active(stream)) { + rv = stream_obq_push(dep_stream, stream); + if (rv != 0) { + return rv; + } + } + + validate_tree(dep_stream); + + return 0; +} + +void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream) { + nghttp2_stream *next, *dep_prev; + + DEBUGF("stream: dep_remove_subtree stream(%p)=%d\n", stream, + stream->stream_id); + + assert(stream->dep_prev); + + dep_prev = stream->dep_prev; + + if (stream->sib_prev) { + link_sib(stream->sib_prev, stream->sib_next); + } else { + next = stream->sib_next; + + link_dep(dep_prev, next); + + if (next) { + next->sib_prev = NULL; + } + } + + dep_prev->sum_dep_weight -= stream->weight; + + if (stream->queued) { + stream_obq_remove(stream); + } + + validate_tree(dep_prev); + + stream->sib_prev = NULL; + stream->sib_next = NULL; + stream->dep_prev = NULL; +} + +int nghttp2_stream_in_dep_tree(nghttp2_stream *stream) { + return stream->dep_prev || stream->dep_next || stream->sib_prev || + stream->sib_next; +} + +nghttp2_outbound_item * +nghttp2_stream_next_outbound_item(nghttp2_stream *stream) { + nghttp2_pq_entry *ent; + nghttp2_stream *si; + + for (;;) { + if (stream_active(stream)) { + /* Update ascendant's descendant_last_cycle here, so that we can + assure that new stream is scheduled based on it. */ + for (si = stream; si->dep_prev; si = si->dep_prev) { + si->dep_prev->descendant_last_cycle = si->cycle; + } + return stream->item; + } + ent = nghttp2_pq_top(&stream->obq); + if (!ent) { + return NULL; + } + stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry); + } +} + +nghttp2_stream_proto_state nghttp2_stream_get_state(nghttp2_stream *stream) { + if (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) { + return NGHTTP2_STREAM_STATE_CLOSED; + } + + if (stream->flags & NGHTTP2_STREAM_FLAG_PUSH) { + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return NGHTTP2_STREAM_STATE_RESERVED_LOCAL; + } + + if (stream->shut_flags & NGHTTP2_SHUT_WR) { + return NGHTTP2_STREAM_STATE_RESERVED_REMOTE; + } + } + + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + } + + if (stream->shut_flags & NGHTTP2_SHUT_WR) { + return NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; + } + + if (stream->state == NGHTTP2_STREAM_IDLE) { + return NGHTTP2_STREAM_STATE_IDLE; + } + + return NGHTTP2_STREAM_STATE_OPEN; +} + +nghttp2_stream *nghttp2_stream_get_parent(nghttp2_stream *stream) { + return stream->dep_prev; +} + +nghttp2_stream *nghttp2_stream_get_next_sibling(nghttp2_stream *stream) { + return stream->sib_next; +} + +nghttp2_stream *nghttp2_stream_get_previous_sibling(nghttp2_stream *stream) { + return stream->sib_prev; +} + +nghttp2_stream *nghttp2_stream_get_first_child(nghttp2_stream *stream) { + return stream->dep_next; +} + +int32_t nghttp2_stream_get_weight(nghttp2_stream *stream) { + return stream->weight; +} + +int32_t nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream) { + return stream->sum_dep_weight; +} + +int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream) { + return stream->stream_id; +} diff --git a/lib/nghttp2/lib/nghttp2_stream.h b/lib/nghttp2/lib/nghttp2_stream.h new file mode 100644 index 00000000000..71b9fb1140c --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_stream.h @@ -0,0 +1,441 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_STREAM_H +#define NGHTTP2_STREAM_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include +#include "nghttp2_outbound_item.h" +#include "nghttp2_map.h" +#include "nghttp2_pq.h" +#include "nghttp2_int.h" + +/* + * If local peer is stream initiator: + * NGHTTP2_STREAM_OPENING : upon sending request HEADERS + * NGHTTP2_STREAM_OPENED : upon receiving response HEADERS + * NGHTTP2_STREAM_CLOSING : upon queuing RST_STREAM + * + * If remote peer is stream initiator: + * NGHTTP2_STREAM_OPENING : upon receiving request HEADERS + * NGHTTP2_STREAM_OPENED : upon sending response HEADERS + * NGHTTP2_STREAM_CLOSING : upon queuing RST_STREAM + */ +typedef enum { + /* Initial state */ + NGHTTP2_STREAM_INITIAL, + /* For stream initiator: request HEADERS has been sent, but response + HEADERS has not been received yet. For receiver: request HEADERS + has been received, but it does not send response HEADERS yet. */ + NGHTTP2_STREAM_OPENING, + /* For stream initiator: response HEADERS is received. For receiver: + response HEADERS is sent. */ + NGHTTP2_STREAM_OPENED, + /* RST_STREAM is received, but somehow we need to keep stream in + memory. */ + NGHTTP2_STREAM_CLOSING, + /* PUSH_PROMISE is received or sent */ + NGHTTP2_STREAM_RESERVED, + /* Stream is created in this state if it is used as anchor in + dependency tree. */ + NGHTTP2_STREAM_IDLE +} nghttp2_stream_state; + +typedef enum { + NGHTTP2_SHUT_NONE = 0, + /* Indicates further receptions will be disallowed. */ + NGHTTP2_SHUT_RD = 0x01, + /* Indicates further transmissions will be disallowed. */ + NGHTTP2_SHUT_WR = 0x02, + /* Indicates both further receptions and transmissions will be + disallowed. */ + NGHTTP2_SHUT_RDWR = NGHTTP2_SHUT_RD | NGHTTP2_SHUT_WR +} nghttp2_shut_flag; + +typedef enum { + NGHTTP2_STREAM_FLAG_NONE = 0, + /* Indicates that this stream is pushed stream and not opened + yet. */ + NGHTTP2_STREAM_FLAG_PUSH = 0x01, + /* Indicates that this stream was closed */ + NGHTTP2_STREAM_FLAG_CLOSED = 0x02, + /* Indicates the item is deferred due to flow control. */ + NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL = 0x04, + /* Indicates the item is deferred by user callback */ + NGHTTP2_STREAM_FLAG_DEFERRED_USER = 0x08, + /* bitwise OR of NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL and + NGHTTP2_STREAM_FLAG_DEFERRED_USER. */ + NGHTTP2_STREAM_FLAG_DEFERRED_ALL = 0x0c, + /* Indicates that this stream is not subject to RFC7540 + priorities scheme. */ + NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES = 0x10, + /* Ignore client RFC 9218 priority signal. */ + NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES = 0x20, + /* Indicates that RFC 9113 leading and trailing white spaces + validation against a field value is not performed. */ + NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 0x40, +} nghttp2_stream_flag; + +/* HTTP related flags to enforce HTTP semantics */ +typedef enum { + NGHTTP2_HTTP_FLAG_NONE = 0, + /* header field seen so far */ + NGHTTP2_HTTP_FLAG__AUTHORITY = 1, + NGHTTP2_HTTP_FLAG__PATH = 1 << 1, + NGHTTP2_HTTP_FLAG__METHOD = 1 << 2, + NGHTTP2_HTTP_FLAG__SCHEME = 1 << 3, + /* host is not pseudo header, but we require either host or + :authority */ + NGHTTP2_HTTP_FLAG_HOST = 1 << 4, + NGHTTP2_HTTP_FLAG__STATUS = 1 << 5, + /* required header fields for HTTP request except for CONNECT + method. */ + NGHTTP2_HTTP_FLAG_REQ_HEADERS = NGHTTP2_HTTP_FLAG__METHOD | + NGHTTP2_HTTP_FLAG__PATH | + NGHTTP2_HTTP_FLAG__SCHEME, + NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED = 1 << 6, + /* HTTP method flags */ + NGHTTP2_HTTP_FLAG_METH_CONNECT = 1 << 7, + NGHTTP2_HTTP_FLAG_METH_HEAD = 1 << 8, + NGHTTP2_HTTP_FLAG_METH_OPTIONS = 1 << 9, + NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND = 1 << 10, + NGHTTP2_HTTP_FLAG_METH_ALL = NGHTTP2_HTTP_FLAG_METH_CONNECT | + NGHTTP2_HTTP_FLAG_METH_HEAD | + NGHTTP2_HTTP_FLAG_METH_OPTIONS | + NGHTTP2_HTTP_FLAG_METH_UPGRADE_WORKAROUND, + /* :path category */ + /* path starts with "/" */ + NGHTTP2_HTTP_FLAG_PATH_REGULAR = 1 << 11, + /* path "*" */ + NGHTTP2_HTTP_FLAG_PATH_ASTERISK = 1 << 12, + /* scheme */ + /* "http" or "https" scheme */ + NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 13, + /* set if final response is expected */ + NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 14, + NGHTTP2_HTTP_FLAG__PROTOCOL = 1 << 15, + /* set if priority header field is received */ + NGHTTP2_HTTP_FLAG_PRIORITY = 1 << 16, + /* set if an error is encountered while parsing priority header + field */ + NGHTTP2_HTTP_FLAG_BAD_PRIORITY = 1 << 17, +} nghttp2_http_flag; + +struct nghttp2_stream { + /* Entry for dep_prev->obq */ + nghttp2_pq_entry pq_entry; + /* Priority Queue storing direct descendant (nghttp2_stream). Only + streams which itself has some data to send, or has a descendant + which has some data to sent. */ + nghttp2_pq obq; + /* Content-Length of request/response body. -1 if unknown. */ + int64_t content_length; + /* Received body so far */ + int64_t recv_content_length; + /* Base last_cycle for direct descendent streams. */ + uint64_t descendant_last_cycle; + /* Next scheduled time to sent item */ + uint64_t cycle; + /* Next seq used for direct descendant streams */ + uint64_t descendant_next_seq; + /* Secondary key for prioritization to break a tie for cycle. This + value is monotonically increased for single parent stream. */ + uint64_t seq; + /* pointers to form dependency tree. If multiple streams depend on + a stream, only one stream (left most) has non-NULL dep_prev which + points to the stream it depends on. The remaining streams are + linked using sib_prev and sib_next. The stream which has + non-NULL dep_prev always NULL sib_prev. The right most stream + has NULL sib_next. If this stream is a root of dependency tree, + dep_prev and sib_prev are NULL. */ + nghttp2_stream *dep_prev, *dep_next; + nghttp2_stream *sib_prev, *sib_next; + /* When stream is kept after closure, it may be kept in doubly + linked list pointed by nghttp2_session closed_stream_head. + closed_next points to the next stream object if it is the element + of the list. */ + nghttp2_stream *closed_prev, *closed_next; + /* The arbitrary data provided by user for this stream. */ + void *stream_user_data; + /* Item to send */ + nghttp2_outbound_item *item; + /* Last written length of frame payload */ + size_t last_writelen; + /* stream ID */ + int32_t stream_id; + /* Current remote window size. This value is computed against the + current initial window size of remote endpoint. */ + int32_t remote_window_size; + /* Keep track of the number of bytes received without + WINDOW_UPDATE. This could be negative after submitting negative + value to WINDOW_UPDATE */ + int32_t recv_window_size; + /* The number of bytes consumed by the application and now is + subject to WINDOW_UPDATE. This is only used when auto + WINDOW_UPDATE is turned off. */ + int32_t consumed_size; + /* The amount of recv_window_size cut using submitting negative + value to WINDOW_UPDATE */ + int32_t recv_reduction; + /* window size for local flow control. It is initially set to + NGHTTP2_INITIAL_WINDOW_SIZE and could be increased/decreased by + submitting WINDOW_UPDATE. See nghttp2_submit_window_update(). */ + int32_t local_window_size; + /* weight of this stream */ + int32_t weight; + /* This is unpaid penalty (offset) when calculating cycle. */ + uint32_t pending_penalty; + /* sum of weight of direct descendants */ + int32_t sum_dep_weight; + nghttp2_stream_state state; + /* status code from remote server */ + int16_t status_code; + /* Bitwise OR of zero or more nghttp2_http_flag values */ + uint32_t http_flags; + /* This is bitwise-OR of 0 or more of nghttp2_stream_flag. */ + uint8_t flags; + /* Bitwise OR of zero or more nghttp2_shut_flag values */ + uint8_t shut_flags; + /* Nonzero if this stream has been queued to stream pointed by + dep_prev. We maintain the invariant that if a stream is queued, + then its ancestors, except for root, are also queued. This + invariant may break in fatal error condition. */ + uint8_t queued; + /* This flag is used to reduce excessive queuing of WINDOW_UPDATE to + this stream. The nonzero does not necessarily mean WINDOW_UPDATE + is not queued. */ + uint8_t window_update_queued; + /* extpri is a stream priority produced by nghttp2_extpri_to_uint8 + used by RFC 9218 extensible priorities. */ + uint8_t extpri; + /* http_extpri is a stream priority received in HTTP request header + fields and produced by nghttp2_extpri_to_uint8. */ + uint8_t http_extpri; +}; + +void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, + uint8_t flags, nghttp2_stream_state initial_state, + int32_t weight, int32_t remote_initial_window_size, + int32_t local_initial_window_size, + void *stream_user_data, nghttp2_mem *mem); + +void nghttp2_stream_free(nghttp2_stream *stream); + +/* + * Disallow either further receptions or transmissions, or both. + * |flag| is bitwise OR of one or more of nghttp2_shut_flag. + */ +void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag); + +/* + * Defer |stream->item|. We won't call this function in the situation + * where |stream->item| == NULL. The |flags| is bitwise OR of zero or + * more of NGHTTP2_STREAM_FLAG_DEFERRED_USER and + * NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL. The |flags| indicates + * the reason of this action. + */ +void nghttp2_stream_defer_item(nghttp2_stream *stream, uint8_t flags); + +/* + * Put back deferred data in this stream to active state. The |flags| + * are one or more of bitwise OR of the following values: + * NGHTTP2_STREAM_FLAG_DEFERRED_USER and + * NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL and given masks are + * cleared if they are set. So even if this function is called, if + * one of flag is still set, data does not become active. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_resume_deferred_item(nghttp2_stream *stream, uint8_t flags); + +/* + * Returns nonzero if item is deferred by whatever reason. + */ +int nghttp2_stream_check_deferred_item(nghttp2_stream *stream); + +/* + * Returns nonzero if item is deferred by flow control. + */ +int nghttp2_stream_check_deferred_by_flow_control(nghttp2_stream *stream); + +/* + * Updates the remote window size with the new value + * |new_initial_window_size|. The |old_initial_window_size| is used to + * calculate the current window size. + * + * This function returns 0 if it succeeds or -1. The failure is due to + * overflow. + */ +int nghttp2_stream_update_remote_initial_window_size( + nghttp2_stream *stream, int32_t new_initial_window_size, + int32_t old_initial_window_size); + +/* + * Updates the local window size with the new value + * |new_initial_window_size|. The |old_initial_window_size| is used to + * calculate the current window size. + * + * This function returns 0 if it succeeds or -1. The failure is due to + * overflow. + */ +int nghttp2_stream_update_local_initial_window_size( + nghttp2_stream *stream, int32_t new_initial_window_size, + int32_t old_initial_window_size); + +/* + * Call this function if promised stream |stream| is replied with + * HEADERS. This function makes the state of the |stream| to + * NGHTTP2_STREAM_OPENED. + */ +void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream); + +/* + * Returns nonzero if |target| is an ancestor of |stream|. + */ +int nghttp2_stream_dep_find_ancestor(nghttp2_stream *stream, + nghttp2_stream *target); + +/* + * Computes distributed weight of a stream of the |weight| under the + * |stream| if |stream| is removed from a dependency tree. + */ +int32_t nghttp2_stream_dep_distributed_weight(nghttp2_stream *stream, + int32_t weight); + +/* + * Makes the |stream| depend on the |dep_stream|. This dependency is + * exclusive. All existing direct descendants of |dep_stream| become + * the descendants of the |stream|. This function assumes + * |stream->item| is NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_dep_insert(nghttp2_stream *dep_stream, + nghttp2_stream *stream); + +/* + * Makes the |stream| depend on the |dep_stream|. This dependency is + * not exclusive. This function assumes |stream->item| is NULL. + */ +void nghttp2_stream_dep_add(nghttp2_stream *dep_stream, nghttp2_stream *stream); + +/* + * Removes the |stream| from the current dependency tree. This + * function assumes |stream->item| is NULL. + */ +int nghttp2_stream_dep_remove(nghttp2_stream *stream); + +/* + * Attaches |item| to |stream|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_attach_item(nghttp2_stream *stream, + nghttp2_outbound_item *item); + +/* + * Detaches |stream->item|. This function does not free + * |stream->item|. The caller must free it. + */ +void nghttp2_stream_detach_item(nghttp2_stream *stream); + +/* + * Makes the |stream| depend on the |dep_stream|. This dependency is + * exclusive. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream, + nghttp2_stream *stream); + +/* + * Makes the |stream| depend on the |dep_stream|. This dependency is + * not exclusive. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream, + nghttp2_stream *stream); + +/* + * Removes subtree whose root stream is |stream|. The + * effective_weight of streams in removed subtree is not updated. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory + */ +void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream); + +/* + * Returns nonzero if |stream| is in any dependency tree. + */ +int nghttp2_stream_in_dep_tree(nghttp2_stream *stream); + +/* + * Schedules transmission of |stream|'s item, assuming stream->item is + * attached, and stream->last_writelen was updated. + */ +void nghttp2_stream_reschedule(nghttp2_stream *stream); + +/* + * Changes |stream|'s weight to |weight|. If |stream| is queued, it + * will be rescheduled based on new weight. + */ +void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight); + +/* + * Returns a stream which has highest priority, updating + * descendant_last_cycle of selected stream's ancestors. + */ +nghttp2_outbound_item * +nghttp2_stream_next_outbound_item(nghttp2_stream *stream); + +#endif /* NGHTTP2_STREAM */ diff --git a/lib/nghttp2/lib/nghttp2_submit.c b/lib/nghttp2/lib/nghttp2_submit.c new file mode 100644 index 00000000000..f5554eb5649 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_submit.c @@ -0,0 +1,900 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_submit.h" + +#include +#include + +#include "nghttp2_session.h" +#include "nghttp2_frame.h" +#include "nghttp2_helper.h" +#include "nghttp2_priority_spec.h" + +/* + * Detects the dependency error, that is stream attempted to depend on + * itself. If |stream_id| is -1, we use session->next_stream_id as + * stream ID. + * + * This function returns 0 if it succeeds, or one of the following + * error codes: + * + * NGHTTP2_ERR_INVALID_ARGUMENT + * Stream attempted to depend on itself. + */ +static int detect_self_dependency(nghttp2_session *session, int32_t stream_id, + const nghttp2_priority_spec *pri_spec) { + assert(pri_spec); + + if (stream_id == -1) { + if ((int32_t)session->next_stream_id == pri_spec->stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + return 0; + } + + if (stream_id == pri_spec->stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + return 0; +} + +/* This function takes ownership of |nva_copy|. Regardless of the + return value, the caller must not free |nva_copy| after this + function returns. */ +static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec, + nghttp2_nv *nva_copy, size_t nvlen, + const nghttp2_data_provider *data_prd, + void *stream_user_data) { + int rv; + uint8_t flags_copy; + nghttp2_outbound_item *item = NULL; + nghttp2_frame *frame = NULL; + nghttp2_headers_category hcat; + nghttp2_mem *mem; + + mem = &session->mem; + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + rv = NGHTTP2_ERR_NOMEM; + goto fail; + } + + nghttp2_outbound_item_init(item); + + if (data_prd != NULL && data_prd->read_callback != NULL) { + item->aux_data.headers.data_prd = *data_prd; + } + + item->aux_data.headers.stream_user_data = stream_user_data; + + flags_copy = + (uint8_t)((flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) | + NGHTTP2_FLAG_END_HEADERS); + + if (stream_id == -1) { + if (session->next_stream_id > INT32_MAX) { + rv = NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE; + goto fail; + } + + stream_id = (int32_t)session->next_stream_id; + session->next_stream_id += 2; + + hcat = NGHTTP2_HCAT_REQUEST; + } else { + /* More specific categorization will be done later. */ + hcat = NGHTTP2_HCAT_HEADERS; + } + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, flags_copy, stream_id, hcat, + pri_spec, nva_copy, nvlen); + + rv = nghttp2_session_add_item(session, item); + + if (rv != 0) { + nghttp2_frame_headers_free(&frame->headers, mem); + goto fail2; + } + + if (hcat == NGHTTP2_HCAT_REQUEST) { + return stream_id; + } + + return 0; + +fail: + /* nghttp2_frame_headers_init() takes ownership of nva_copy. */ + nghttp2_nv_array_del(nva_copy, mem); +fail2: + nghttp2_mem_free(mem, item); + + return rv; +} + +static int32_t submit_headers_shared_nva(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + const nghttp2_priority_spec *pri_spec, + const nghttp2_nv *nva, size_t nvlen, + const nghttp2_data_provider *data_prd, + void *stream_user_data) { + int rv; + nghttp2_nv *nva_copy; + nghttp2_priority_spec copy_pri_spec; + nghttp2_mem *mem; + + mem = &session->mem; + + if (pri_spec) { + copy_pri_spec = *pri_spec; + nghttp2_priority_spec_normalize_weight(©_pri_spec); + } else { + nghttp2_priority_spec_default_init(©_pri_spec); + } + + rv = nghttp2_nv_array_copy(&nva_copy, nva, nvlen, mem); + if (rv < 0) { + return rv; + } + + return submit_headers_shared(session, flags, stream_id, ©_pri_spec, + nva_copy, nvlen, data_prd, stream_user_data); +} + +int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen) { + if (stream_id <= 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM, + stream_id, NULL, nva, nvlen, NULL, + NULL); +} + +int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec, + const nghttp2_nv *nva, size_t nvlen, + void *stream_user_data) { + int rv; + + if (stream_id == -1) { + if (session->server) { + return NGHTTP2_ERR_PROTO; + } + } else if (stream_id <= 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + flags &= NGHTTP2_FLAG_END_STREAM; + + if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec) && + session->remote_settings.no_rfc7540_priorities != 1) { + rv = detect_self_dependency(session, stream_id, pri_spec); + if (rv != 0) { + return rv; + } + + flags |= NGHTTP2_FLAG_PRIORITY; + } else { + pri_spec = NULL; + } + + return submit_headers_shared_nva(session, flags, stream_id, pri_spec, nva, + nvlen, NULL, stream_user_data); +} + +int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags, + const uint8_t *opaque_data) { + flags &= NGHTTP2_FLAG_ACK; + return nghttp2_session_add_ping(session, flags, opaque_data); +} + +int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_priority_spec copy_pri_spec; + nghttp2_mem *mem; + (void)flags; + + mem = &session->mem; + + if (session->remote_settings.no_rfc7540_priorities == 1) { + return 0; + } + + if (stream_id == 0 || pri_spec == NULL) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (stream_id == pri_spec->stream_id) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + copy_pri_spec = *pri_spec; + + nghttp2_priority_spec_normalize_weight(©_pri_spec); + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_priority_init(&frame->priority, stream_id, ©_pri_spec); + + rv = nghttp2_session_add_item(session, item); + + if (rv != 0) { + nghttp2_frame_priority_free(&frame->priority); + nghttp2_mem_free(mem, item); + + return rv; + } + + return 0; +} + +int nghttp2_submit_rst_stream(nghttp2_session *session, uint8_t flags, + int32_t stream_id, uint32_t error_code) { + (void)flags; + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + return nghttp2_session_add_rst_stream(session, stream_id, error_code); +} + +int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags, + int32_t last_stream_id, uint32_t error_code, + const uint8_t *opaque_data, size_t opaque_data_len) { + (void)flags; + + if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) { + return 0; + } + return nghttp2_session_add_goaway(session, last_stream_id, error_code, + opaque_data, opaque_data_len, + NGHTTP2_GOAWAY_AUX_NONE); +} + +int nghttp2_submit_shutdown_notice(nghttp2_session *session) { + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + if (session->goaway_flags) { + return 0; + } + return nghttp2_session_add_goaway(session, (1u << 31) - 1, NGHTTP2_NO_ERROR, + NULL, 0, + NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE); +} + +int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags, + const nghttp2_settings_entry *iv, size_t niv) { + (void)flags; + return nghttp2_session_add_settings(session, NGHTTP2_FLAG_NONE, iv, niv); +} + +int32_t nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const nghttp2_nv *nva, + size_t nvlen, + void *promised_stream_user_data) { + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_nv *nva_copy; + uint8_t flags_copy; + int32_t promised_stream_id; + int rv; + nghttp2_mem *mem; + (void)flags; + + mem = &session->mem; + + if (stream_id <= 0 || nghttp2_session_is_my_stream_id(session, stream_id)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!session->server) { + return NGHTTP2_ERR_PROTO; + } + + /* All 32bit signed stream IDs are spent. */ + if (session->next_stream_id > INT32_MAX) { + return NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + item->aux_data.headers.stream_user_data = promised_stream_user_data; + + frame = &item->frame; + + rv = nghttp2_nv_array_copy(&nva_copy, nva, nvlen, mem); + if (rv < 0) { + nghttp2_mem_free(mem, item); + return rv; + } + + flags_copy = NGHTTP2_FLAG_END_HEADERS; + + promised_stream_id = (int32_t)session->next_stream_id; + session->next_stream_id += 2; + + nghttp2_frame_push_promise_init(&frame->push_promise, flags_copy, stream_id, + promised_stream_id, nva_copy, nvlen); + + rv = nghttp2_session_add_item(session, item); + + if (rv != 0) { + nghttp2_frame_push_promise_free(&frame->push_promise, mem); + nghttp2_mem_free(mem, item); + + return rv; + } + + return promised_stream_id; +} + +int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + int32_t window_size_increment) { + int rv; + nghttp2_stream *stream = 0; + (void)flags; + + if (window_size_increment == 0) { + return 0; + } + if (stream_id == 0) { + rv = nghttp2_adjust_local_window_size( + &session->local_window_size, &session->recv_window_size, + &session->recv_reduction, &window_size_increment); + if (rv != 0) { + return rv; + } + } else { + stream = nghttp2_session_get_stream(session, stream_id); + if (!stream) { + return 0; + } + + rv = nghttp2_adjust_local_window_size( + &stream->local_window_size, &stream->recv_window_size, + &stream->recv_reduction, &window_size_increment); + if (rv != 0) { + return rv; + } + } + + if (window_size_increment > 0) { + if (stream_id == 0) { + session->consumed_size = + nghttp2_max(0, session->consumed_size - window_size_increment); + } else { + stream->consumed_size = + nghttp2_max(0, stream->consumed_size - window_size_increment); + } + + return nghttp2_session_add_window_update(session, 0, stream_id, + window_size_increment); + } + return 0; +} + +int nghttp2_session_set_local_window_size(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + int32_t window_size) { + int32_t window_size_increment; + nghttp2_stream *stream; + int rv; + (void)flags; + + if (window_size < 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (stream_id == 0) { + window_size_increment = window_size - session->local_window_size; + + if (window_size_increment == 0) { + return 0; + } + + if (window_size_increment < 0) { + return nghttp2_adjust_local_window_size( + &session->local_window_size, &session->recv_window_size, + &session->recv_reduction, &window_size_increment); + } + + rv = nghttp2_increase_local_window_size( + &session->local_window_size, &session->recv_window_size, + &session->recv_reduction, &window_size_increment); + + if (rv != 0) { + return rv; + } + + if (window_size_increment > 0) { + return nghttp2_session_add_window_update(session, 0, stream_id, + window_size_increment); + } + + return nghttp2_session_update_recv_connection_window_size(session, 0); + } else { + stream = nghttp2_session_get_stream(session, stream_id); + + if (stream == NULL) { + return 0; + } + + window_size_increment = window_size - stream->local_window_size; + + if (window_size_increment == 0) { + return 0; + } + + if (window_size_increment < 0) { + return nghttp2_adjust_local_window_size( + &stream->local_window_size, &stream->recv_window_size, + &stream->recv_reduction, &window_size_increment); + } + + rv = nghttp2_increase_local_window_size( + &stream->local_window_size, &stream->recv_window_size, + &stream->recv_reduction, &window_size_increment); + + if (rv != 0) { + return rv; + } + + if (window_size_increment > 0) { + return nghttp2_session_add_window_update(session, 0, stream_id, + window_size_increment); + } + + return nghttp2_session_update_recv_stream_window_size(session, stream, 0, + 1); + } +} + +int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *origin, + size_t origin_len, const uint8_t *field_value, + size_t field_value_len) { + nghttp2_mem *mem; + uint8_t *buf, *p; + uint8_t *origin_copy; + uint8_t *field_value_copy; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_ext_altsvc *altsvc; + int rv; + (void)flags; + + mem = &session->mem; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (2 + origin_len + field_value_len > NGHTTP2_MAX_PAYLOADLEN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (stream_id == 0) { + if (origin_len == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + } else if (origin_len != 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + buf = nghttp2_mem_malloc(mem, origin_len + field_value_len + 2); + if (buf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + p = buf; + + origin_copy = p; + if (origin_len) { + p = nghttp2_cpymem(p, origin, origin_len); + } + *p++ = '\0'; + + field_value_copy = p; + if (field_value_len) { + p = nghttp2_cpymem(p, field_value, field_value_len); + } + *p++ = '\0'; + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + rv = NGHTTP2_ERR_NOMEM; + goto fail_item_malloc; + } + + nghttp2_outbound_item_init(item); + + item->aux_data.ext.builtin = 1; + + altsvc = &item->ext_frame_payload.altsvc; + + frame = &item->frame; + frame->ext.payload = altsvc; + + nghttp2_frame_altsvc_init(&frame->ext, stream_id, origin_copy, origin_len, + field_value_copy, field_value_len); + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_altsvc_free(&frame->ext, mem); + nghttp2_mem_free(mem, item); + + return rv; + } + + return 0; + +fail_item_malloc: + free(buf); + + return rv; +} + +int nghttp2_submit_origin(nghttp2_session *session, uint8_t flags, + const nghttp2_origin_entry *ov, size_t nov) { + nghttp2_mem *mem; + uint8_t *p; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_ext_origin *origin; + nghttp2_origin_entry *ov_copy; + size_t len = 0; + size_t i; + int rv; + (void)flags; + + mem = &session->mem; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (nov) { + for (i = 0; i < nov; ++i) { + len += ov[i].origin_len; + } + + if (2 * nov + len > NGHTTP2_MAX_PAYLOADLEN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + /* The last nov is added for terminal NULL character. */ + ov_copy = + nghttp2_mem_malloc(mem, nov * sizeof(nghttp2_origin_entry) + len + nov); + if (ov_copy == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + p = (uint8_t *)ov_copy + nov * sizeof(nghttp2_origin_entry); + + for (i = 0; i < nov; ++i) { + ov_copy[i].origin = p; + ov_copy[i].origin_len = ov[i].origin_len; + p = nghttp2_cpymem(p, ov[i].origin, ov[i].origin_len); + *p++ = '\0'; + } + + assert((size_t)(p - (uint8_t *)ov_copy) == + nov * sizeof(nghttp2_origin_entry) + len + nov); + } else { + ov_copy = NULL; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + rv = NGHTTP2_ERR_NOMEM; + goto fail_item_malloc; + } + + nghttp2_outbound_item_init(item); + + item->aux_data.ext.builtin = 1; + + origin = &item->ext_frame_payload.origin; + + frame = &item->frame; + frame->ext.payload = origin; + + nghttp2_frame_origin_init(&frame->ext, ov_copy, nov); + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_origin_free(&frame->ext, mem); + nghttp2_mem_free(mem, item); + + return rv; + } + + return 0; + +fail_item_malloc: + free(ov_copy); + + return rv; +} + +int nghttp2_submit_priority_update(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const uint8_t *field_value, + size_t field_value_len) { + nghttp2_mem *mem; + uint8_t *buf, *p; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_ext_priority_update *priority_update; + int rv; + (void)flags; + + mem = &session->mem; + + if (session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (session->remote_settings.no_rfc7540_priorities == 0) { + return 0; + } + + if (stream_id == 0 || 4 + field_value_len > NGHTTP2_MAX_PAYLOADLEN) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (field_value_len) { + buf = nghttp2_mem_malloc(mem, field_value_len + 1); + if (buf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + p = nghttp2_cpymem(buf, field_value, field_value_len); + *p = '\0'; + } else { + buf = NULL; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + rv = NGHTTP2_ERR_NOMEM; + goto fail_item_malloc; + } + + nghttp2_outbound_item_init(item); + + item->aux_data.ext.builtin = 1; + + priority_update = &item->ext_frame_payload.priority_update; + + frame = &item->frame; + frame->ext.payload = priority_update; + + nghttp2_frame_priority_update_init(&frame->ext, stream_id, buf, + field_value_len); + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_priority_update_free(&frame->ext, mem); + nghttp2_mem_free(mem, item); + + return rv; + } + + return 0; + +fail_item_malloc: + free(buf); + + return rv; +} + +static uint8_t set_request_flags(const nghttp2_priority_spec *pri_spec, + const nghttp2_data_provider *data_prd) { + uint8_t flags = NGHTTP2_FLAG_NONE; + if (data_prd == NULL || data_prd->read_callback == NULL) { + flags |= NGHTTP2_FLAG_END_STREAM; + } + + if (pri_spec) { + flags |= NGHTTP2_FLAG_PRIORITY; + } + + return flags; +} + +int32_t nghttp2_submit_request(nghttp2_session *session, + const nghttp2_priority_spec *pri_spec, + const nghttp2_nv *nva, size_t nvlen, + const nghttp2_data_provider *data_prd, + void *stream_user_data) { + uint8_t flags; + int rv; + + if (session->server) { + return NGHTTP2_ERR_PROTO; + } + + if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec) && + session->remote_settings.no_rfc7540_priorities != 1) { + rv = detect_self_dependency(session, -1, pri_spec); + if (rv != 0) { + return rv; + } + } else { + pri_spec = NULL; + } + + flags = set_request_flags(pri_spec, data_prd); + + return submit_headers_shared_nva(session, flags, -1, pri_spec, nva, nvlen, + data_prd, stream_user_data); +} + +static uint8_t set_response_flags(const nghttp2_data_provider *data_prd) { + uint8_t flags = NGHTTP2_FLAG_NONE; + if (data_prd == NULL || data_prd->read_callback == NULL) { + flags |= NGHTTP2_FLAG_END_STREAM; + } + return flags; +} + +int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen, + const nghttp2_data_provider *data_prd) { + uint8_t flags; + + if (stream_id <= 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!session->server) { + return NGHTTP2_ERR_PROTO; + } + + flags = set_response_flags(data_prd); + return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen, + data_prd, NULL); +} + +int nghttp2_submit_data(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_data_provider *data_prd) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_data_aux_data *aux_data; + uint8_t nflags = flags & NGHTTP2_FLAG_END_STREAM; + nghttp2_mem *mem; + + mem = &session->mem; + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + aux_data = &item->aux_data.data; + aux_data->data_prd = *data_prd; + aux_data->eof = 0; + aux_data->flags = nflags; + + /* flags are sent on transmission */ + nghttp2_frame_data_init(&frame->data, NGHTTP2_FLAG_NONE, stream_id); + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_data_free(&frame->data); + nghttp2_mem_free(mem, item); + return rv; + } + return 0; +} + +ssize_t nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen, + const nghttp2_settings_entry *iv, + size_t niv) { + if (!nghttp2_iv_check(iv, niv)) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (buflen < (niv * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH)) { + return NGHTTP2_ERR_INSUFF_BUFSIZE; + } + + return (ssize_t)nghttp2_frame_pack_settings_payload(buf, iv, niv); +} + +int nghttp2_submit_extension(nghttp2_session *session, uint8_t type, + uint8_t flags, int32_t stream_id, void *payload) { + int rv; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = &session->mem; + + if (type <= NGHTTP2_CONTINUATION) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!session->callbacks.pack_extension_callback) { + return NGHTTP2_ERR_INVALID_STATE; + } + + item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); + if (item == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + nghttp2_frame_extension_init(&frame->ext, type, flags, stream_id, payload); + + rv = nghttp2_session_add_item(session, item); + if (rv != 0) { + nghttp2_frame_extension_free(&frame->ext); + nghttp2_mem_free(mem, item); + return rv; + } + + return 0; +} diff --git a/lib/nghttp2/lib/nghttp2_submit.h b/lib/nghttp2/lib/nghttp2_submit.h new file mode 100644 index 00000000000..74d702fbcf0 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_submit.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_SUBMIT_H +#define NGHTTP2_SUBMIT_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#endif /* NGHTTP2_SUBMIT_H */ diff --git a/lib/nghttp2/lib/nghttp2_time.c b/lib/nghttp2/lib/nghttp2_time.c new file mode 100644 index 00000000000..bdc83d2ec7f --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_time.c @@ -0,0 +1,65 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 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 "nghttp2_time.h" + +#ifdef HAVE_TIME_H +# include +#endif /* HAVE_TIME_H */ + +#ifdef HAVE_WINDOWS_H +# include +#endif /* HAVE_WINDOWS_H */ + +#if !defined(HAVE_GETTICKCOUNT64) || defined(__CYGWIN__) +static uint64_t time_now_sec(void) { + time_t t = time(NULL); + + if (t == -1) { + return 0; + } + + return (uint64_t)t; +} +#endif /* !HAVE_GETTICKCOUNT64 || __CYGWIN__ */ + +#if defined(HAVE_GETTICKCOUNT64) && !defined(__CYGWIN__) +uint64_t nghttp2_time_now_sec(void) { return GetTickCount64() / 1000; } +#elif defined(HAVE_CLOCK_GETTIME) && defined(HAVE_DECL_CLOCK_MONOTONIC) && \ + HAVE_DECL_CLOCK_MONOTONIC +uint64_t nghttp2_time_now_sec(void) { + struct timespec tp; + int rv = clock_gettime(CLOCK_MONOTONIC, &tp); + + if (rv == -1) { + return time_now_sec(); + } + + return (uint64_t)tp.tv_sec; +} +#else /* (!HAVE_CLOCK_GETTIME || !HAVE_DECL_CLOCK_MONOTONIC) && \ + (!HAVE_GETTICKCOUNT64 || __CYGWIN__)) */ +uint64_t nghttp2_time_now_sec(void) { return time_now_sec(); } +#endif /* (!HAVE_CLOCK_GETTIME || !HAVE_DECL_CLOCK_MONOTONIC) && \ + (!HAVE_GETTICKCOUNT64 || __CYGWIN__)) */ diff --git a/lib/nghttp2/lib/nghttp2_time.h b/lib/nghttp2/lib/nghttp2_time.h new file mode 100644 index 00000000000..03c0bbe944e --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_time.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 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 NGHTTP2_TIME_H +#define NGHTTP2_TIME_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +/* nghttp2_time_now_sec returns seconds from implementation-specific + timepoint. If it is unable to get seconds, it returns 0. */ +uint64_t nghttp2_time_now_sec(void); + +#endif /* NGHTTP2_TIME_H */ diff --git a/lib/nghttp2/lib/nghttp2_version.c b/lib/nghttp2/lib/nghttp2_version.c new file mode 100644 index 00000000000..4211f2cf8f6 --- /dev/null +++ b/lib/nghttp2/lib/nghttp2_version.c @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + * + * 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 +#endif /* HAVE_CONFIG_H */ + +#include + +static nghttp2_info version = {NGHTTP2_VERSION_AGE, NGHTTP2_VERSION_NUM, + NGHTTP2_VERSION, NGHTTP2_PROTO_VERSION_ID}; + +nghttp2_info *nghttp2_version(int least_version) { + if (least_version > NGHTTP2_VERSION_NUM) + return NULL; + return &version; +} diff --git a/lib/nghttp2/lib/sfparse.c b/lib/nghttp2/lib/sfparse.c new file mode 100644 index 00000000000..efa2850c9d6 --- /dev/null +++ b/lib/nghttp2/lib/sfparse.c @@ -0,0 +1,1146 @@ +/* + * sfparse + * + * Copyright (c) 2023 sfparse contributors + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2015 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 "sfparse.h" + +#include +#include +#include + +#define SF_STATE_DICT 0x08u +#define SF_STATE_LIST 0x10u +#define SF_STATE_ITEM 0x18u + +#define SF_STATE_INNER_LIST 0x04u + +#define SF_STATE_BEFORE 0x00u +#define SF_STATE_BEFORE_PARAMS 0x01u +#define SF_STATE_PARAMS 0x02u +#define SF_STATE_AFTER 0x03u + +#define SF_STATE_OP_MASK 0x03u + +#define SF_SET_STATE_AFTER(NAME) (SF_STATE_##NAME | SF_STATE_AFTER) +#define SF_SET_STATE_BEFORE_PARAMS(NAME) \ + (SF_STATE_##NAME | SF_STATE_BEFORE_PARAMS) +#define SF_SET_STATE_INNER_LIST_BEFORE(NAME) \ + (SF_STATE_##NAME | SF_STATE_INNER_LIST | SF_STATE_BEFORE) + +#define SF_STATE_DICT_AFTER SF_SET_STATE_AFTER(DICT) +#define SF_STATE_DICT_BEFORE_PARAMS SF_SET_STATE_BEFORE_PARAMS(DICT) +#define SF_STATE_DICT_INNER_LIST_BEFORE SF_SET_STATE_INNER_LIST_BEFORE(DICT) + +#define SF_STATE_LIST_AFTER SF_SET_STATE_AFTER(LIST) +#define SF_STATE_LIST_BEFORE_PARAMS SF_SET_STATE_BEFORE_PARAMS(LIST) +#define SF_STATE_LIST_INNER_LIST_BEFORE SF_SET_STATE_INNER_LIST_BEFORE(LIST) + +#define SF_STATE_ITEM_AFTER SF_SET_STATE_AFTER(ITEM) +#define SF_STATE_ITEM_BEFORE_PARAMS SF_SET_STATE_BEFORE_PARAMS(ITEM) +#define SF_STATE_ITEM_INNER_LIST_BEFORE SF_SET_STATE_INNER_LIST_BEFORE(ITEM) + +#define SF_STATE_INITIAL 0x00u + +#define DIGIT_CASES \ + case '0': \ + case '1': \ + case '2': \ + case '3': \ + case '4': \ + case '5': \ + case '6': \ + case '7': \ + case '8': \ + case '9' + +#define LCALPHA_CASES \ + case 'a': \ + case 'b': \ + case 'c': \ + case 'd': \ + case 'e': \ + case 'f': \ + case 'g': \ + case 'h': \ + case 'i': \ + case 'j': \ + case 'k': \ + case 'l': \ + case 'm': \ + case 'n': \ + case 'o': \ + case 'p': \ + case 'q': \ + case 'r': \ + case 's': \ + case 't': \ + case 'u': \ + case 'v': \ + case 'w': \ + case 'x': \ + case 'y': \ + case 'z' + +#define UCALPHA_CASES \ + case 'A': \ + case 'B': \ + case 'C': \ + case 'D': \ + case 'E': \ + case 'F': \ + case 'G': \ + case 'H': \ + case 'I': \ + case 'J': \ + case 'K': \ + case 'L': \ + case 'M': \ + case 'N': \ + case 'O': \ + case 'P': \ + case 'Q': \ + case 'R': \ + case 'S': \ + case 'T': \ + case 'U': \ + case 'V': \ + case 'W': \ + case 'X': \ + case 'Y': \ + case 'Z' + +#define ALPHA_CASES \ + UCALPHA_CASES: \ + LCALPHA_CASES + +#define X20_21_CASES \ + case ' ': \ + case '!' + +#define X23_5B_CASES \ + case '#': \ + case '$': \ + case '%': \ + case '&': \ + case '\'': \ + case '(': \ + case ')': \ + case '*': \ + case '+': \ + case ',': \ + case '-': \ + case '.': \ + case '/': \ + DIGIT_CASES: \ + case ':': \ + case ';': \ + case '<': \ + case '=': \ + case '>': \ + case '?': \ + case '@': \ + UCALPHA_CASES: \ + case '[' + +#define X5D_7E_CASES \ + case ']': \ + case '^': \ + case '_': \ + case '`': \ + LCALPHA_CASES: \ + case '{': \ + case '|': \ + case '}': \ + case '~' + +static int is_ws(uint8_t c) { + switch (c) { + case ' ': + case '\t': + return 1; + default: + return 0; + } +} + +static int parser_eof(sf_parser *sfp) { return sfp->pos == sfp->end; } + +static void parser_discard_ows(sf_parser *sfp) { + for (; !parser_eof(sfp) && is_ws(*sfp->pos); ++sfp->pos) + ; +} + +static void parser_discard_sp(sf_parser *sfp) { + for (; !parser_eof(sfp) && *sfp->pos == ' '; ++sfp->pos) + ; +} + +static void parser_set_op_state(sf_parser *sfp, uint32_t op) { + sfp->state &= ~SF_STATE_OP_MASK; + sfp->state |= op; +} + +static void parser_unset_inner_list_state(sf_parser *sfp) { + sfp->state &= ~SF_STATE_INNER_LIST; +} + +static int parser_key(sf_parser *sfp, sf_vec *dest) { + const uint8_t *base; + + switch (*sfp->pos) { + case '*': + LCALPHA_CASES: + break; + default: + return SF_ERR_PARSE_ERROR; + } + + base = sfp->pos++; + + for (; !parser_eof(sfp); ++sfp->pos) { + switch (*sfp->pos) { + case '_': + case '-': + case '.': + case '*': + DIGIT_CASES: + LCALPHA_CASES: + continue; + } + + break; + } + + if (dest) { + dest->base = (uint8_t *)base; + dest->len = (size_t)(sfp->pos - dest->base); + } + + return 0; +} + +static int parser_number(sf_parser *sfp, sf_value *dest) { + int sign = 1; + int64_t value = 0; + size_t len = 0; + size_t fpos = 0; + + if (*sfp->pos == '-') { + ++sfp->pos; + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + sign = -1; + } + + assert(!parser_eof(sfp)); + + for (; !parser_eof(sfp); ++sfp->pos) { + switch (*sfp->pos) { + DIGIT_CASES: + if (++len > 15) { + return SF_ERR_PARSE_ERROR; + } + + value *= 10; + value += *sfp->pos - '0'; + + continue; + } + + break; + } + + if (len == 0) { + return SF_ERR_PARSE_ERROR; + } + + if (parser_eof(sfp) || *sfp->pos != '.') { + if (dest) { + dest->type = SF_TYPE_INTEGER; + dest->flags = SF_VALUE_FLAG_NONE; + dest->integer = value * sign; + } + + return 0; + } + + /* decimal */ + + if (len > 12) { + return SF_ERR_PARSE_ERROR; + } + + fpos = len; + + ++sfp->pos; + + for (; !parser_eof(sfp); ++sfp->pos) { + switch (*sfp->pos) { + DIGIT_CASES: + if (++len > 15) { + return SF_ERR_PARSE_ERROR; + } + + value *= 10; + value += *sfp->pos - '0'; + + continue; + } + + break; + } + + if (fpos == len || len - fpos > 3) { + return SF_ERR_PARSE_ERROR; + } + + if (dest) { + dest->type = SF_TYPE_DECIMAL; + dest->flags = SF_VALUE_FLAG_NONE; + dest->decimal.numer = value * sign; + + switch (len - fpos) { + case 1: + dest->decimal.denom = 10; + + break; + case 2: + dest->decimal.denom = 100; + + break; + case 3: + dest->decimal.denom = 1000; + + break; + } + } + + return 0; +} + +static int parser_date(sf_parser *sfp, sf_value *dest) { + int rv; + sf_value val; + + /* The first byte has already been validated by the caller. */ + assert('@' == *sfp->pos); + + ++sfp->pos; + + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + rv = parser_number(sfp, &val); + if (rv != 0) { + return rv; + } + + if (val.type != SF_TYPE_INTEGER) { + return SF_ERR_PARSE_ERROR; + } + + if (dest) { + *dest = val; + dest->type = SF_TYPE_DATE; + } + + return 0; +} + +static int parser_string(sf_parser *sfp, sf_value *dest) { + const uint8_t *base; + uint32_t flags = SF_VALUE_FLAG_NONE; + + /* The first byte has already been validated by the caller. */ + assert('"' == *sfp->pos); + + base = ++sfp->pos; + + for (; !parser_eof(sfp); ++sfp->pos) { + switch (*sfp->pos) { + X20_21_CASES: + X23_5B_CASES: + X5D_7E_CASES: + break; + case '\\': + ++sfp->pos; + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + switch (*sfp->pos) { + case '"': + case '\\': + flags = SF_VALUE_FLAG_ESCAPED_STRING; + + break; + default: + return SF_ERR_PARSE_ERROR; + } + + break; + case '"': + if (dest) { + dest->type = SF_TYPE_STRING; + dest->flags = flags; + dest->vec.len = (size_t)(sfp->pos - base); + dest->vec.base = dest->vec.len == 0 ? NULL : (uint8_t *)base; + } + + ++sfp->pos; + + return 0; + default: + return SF_ERR_PARSE_ERROR; + } + } + + return SF_ERR_PARSE_ERROR; +} + +static int parser_token(sf_parser *sfp, sf_value *dest) { + const uint8_t *base; + + /* The first byte has already been validated by the caller. */ + base = sfp->pos++; + + for (; !parser_eof(sfp); ++sfp->pos) { + switch (*sfp->pos) { + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '*': + case '+': + case '-': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + case ':': + case '/': + DIGIT_CASES: + ALPHA_CASES: + continue; + } + + break; + } + + if (dest) { + dest->type = SF_TYPE_TOKEN; + dest->flags = SF_VALUE_FLAG_NONE; + dest->vec.base = (uint8_t *)base; + dest->vec.len = (size_t)(sfp->pos - base); + } + + return 0; +} + +static int parser_byteseq(sf_parser *sfp, sf_value *dest) { + const uint8_t *base; + + /* The first byte has already been validated by the caller. */ + assert(':' == *sfp->pos); + + base = ++sfp->pos; + + for (; !parser_eof(sfp); ++sfp->pos) { + switch (*sfp->pos) { + case '+': + case '/': + DIGIT_CASES: + ALPHA_CASES: + continue; + case '=': + switch ((sfp->pos - base) & 0x3) { + case 0: + case 1: + return SF_ERR_PARSE_ERROR; + case 2: + switch (*(sfp->pos - 1)) { + case 'A': + case 'Q': + case 'g': + case 'w': + break; + default: + return SF_ERR_PARSE_ERROR; + } + + ++sfp->pos; + + if (parser_eof(sfp) || *sfp->pos != '=') { + return SF_ERR_PARSE_ERROR; + } + + break; + case 3: + switch (*(sfp->pos - 1)) { + case 'A': + case 'E': + case 'I': + case 'M': + case 'Q': + case 'U': + case 'Y': + case 'c': + case 'g': + case 'k': + case 'o': + case 's': + case 'w': + case '0': + case '4': + case '8': + break; + default: + return SF_ERR_PARSE_ERROR; + } + + break; + } + + ++sfp->pos; + + if (parser_eof(sfp) || *sfp->pos != ':') { + return SF_ERR_PARSE_ERROR; + } + + goto fin; + case ':': + if ((sfp->pos - base) & 0x3) { + return SF_ERR_PARSE_ERROR; + } + + goto fin; + default: + return SF_ERR_PARSE_ERROR; + } + } + + return SF_ERR_PARSE_ERROR; + +fin: + if (dest) { + dest->type = SF_TYPE_BYTESEQ; + dest->flags = SF_VALUE_FLAG_NONE; + dest->vec.len = (size_t)(sfp->pos - base); + dest->vec.base = dest->vec.len == 0 ? NULL : (uint8_t *)base; + } + + ++sfp->pos; + + return 0; +} + +static int parser_boolean(sf_parser *sfp, sf_value *dest) { + int b; + + /* The first byte has already been validated by the caller. */ + assert('?' == *sfp->pos); + + ++sfp->pos; + + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + switch (*sfp->pos) { + case '0': + b = 0; + + break; + case '1': + b = 1; + + break; + default: + return SF_ERR_PARSE_ERROR; + } + + ++sfp->pos; + + if (dest) { + dest->type = SF_TYPE_BOOLEAN; + dest->flags = SF_VALUE_FLAG_NONE; + dest->boolean = b; + } + + return 0; +} + +static int parser_bare_item(sf_parser *sfp, sf_value *dest) { + switch (*sfp->pos) { + case '"': + return parser_string(sfp, dest); + case '-': + DIGIT_CASES: + return parser_number(sfp, dest); + case '@': + return parser_date(sfp, dest); + case ':': + return parser_byteseq(sfp, dest); + case '?': + return parser_boolean(sfp, dest); + case '*': + ALPHA_CASES: + return parser_token(sfp, dest); + default: + return SF_ERR_PARSE_ERROR; + } +} + +static int parser_skip_inner_list(sf_parser *sfp); + +int sf_parser_param(sf_parser *sfp, sf_vec *dest_key, sf_value *dest_value) { + int rv; + + switch (sfp->state & SF_STATE_OP_MASK) { + case SF_STATE_BEFORE: + rv = parser_skip_inner_list(sfp); + if (rv != 0) { + return rv; + } + + /* fall through */ + case SF_STATE_BEFORE_PARAMS: + parser_set_op_state(sfp, SF_STATE_PARAMS); + + break; + case SF_STATE_PARAMS: + break; + default: + assert(0); + abort(); + } + + if (parser_eof(sfp) || *sfp->pos != ';') { + parser_set_op_state(sfp, SF_STATE_AFTER); + + return SF_ERR_EOF; + } + + ++sfp->pos; + + parser_discard_sp(sfp); + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + rv = parser_key(sfp, dest_key); + if (rv != 0) { + return rv; + } + + if (parser_eof(sfp) || *sfp->pos != '=') { + if (dest_value) { + dest_value->type = SF_TYPE_BOOLEAN; + dest_value->flags = SF_VALUE_FLAG_NONE; + dest_value->boolean = 1; + } + + return 0; + } + + ++sfp->pos; + + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + return parser_bare_item(sfp, dest_value); +} + +static int parser_skip_params(sf_parser *sfp) { + int rv; + + for (;;) { + rv = sf_parser_param(sfp, NULL, NULL); + switch (rv) { + case 0: + break; + case SF_ERR_EOF: + return 0; + case SF_ERR_PARSE_ERROR: + return rv; + default: + assert(0); + abort(); + } + } +} + +int sf_parser_inner_list(sf_parser *sfp, sf_value *dest) { + int rv; + + switch (sfp->state & SF_STATE_OP_MASK) { + case SF_STATE_BEFORE: + parser_discard_sp(sfp); + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + break; + case SF_STATE_BEFORE_PARAMS: + rv = parser_skip_params(sfp); + if (rv != 0) { + return rv; + } + + /* Technically, we are entering SF_STATE_AFTER, but we will set + another state without reading the state. */ + /* parser_set_op_state(sfp, SF_STATE_AFTER); */ + + /* fall through */ + case SF_STATE_AFTER: + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + switch (*sfp->pos) { + case ' ': + parser_discard_sp(sfp); + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + break; + case ')': + break; + default: + return SF_ERR_PARSE_ERROR; + } + + break; + default: + assert(0); + abort(); + } + + if (*sfp->pos == ')') { + ++sfp->pos; + + parser_unset_inner_list_state(sfp); + parser_set_op_state(sfp, SF_STATE_BEFORE_PARAMS); + + return SF_ERR_EOF; + } + + rv = parser_bare_item(sfp, dest); + if (rv != 0) { + return rv; + } + + parser_set_op_state(sfp, SF_STATE_BEFORE_PARAMS); + + return 0; +} + +static int parser_skip_inner_list(sf_parser *sfp) { + int rv; + + for (;;) { + rv = sf_parser_inner_list(sfp, NULL); + switch (rv) { + case 0: + break; + case SF_ERR_EOF: + return 0; + case SF_ERR_PARSE_ERROR: + return rv; + default: + assert(0); + abort(); + } + } +} + +static int parser_next_key_or_item(sf_parser *sfp) { + parser_discard_ows(sfp); + + if (parser_eof(sfp)) { + return SF_ERR_EOF; + } + + if (*sfp->pos != ',') { + return SF_ERR_PARSE_ERROR; + } + + ++sfp->pos; + + parser_discard_ows(sfp); + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + return 0; +} + +static int parser_dict_value(sf_parser *sfp, sf_value *dest) { + int rv; + + if (parser_eof(sfp) || *(sfp->pos) != '=') { + /* Boolean true */ + if (dest) { + dest->type = SF_TYPE_BOOLEAN; + dest->flags = SF_VALUE_FLAG_NONE; + dest->boolean = 1; + } + + sfp->state = SF_STATE_DICT_BEFORE_PARAMS; + + return 0; + } + + ++sfp->pos; + + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + if (*sfp->pos == '(') { + if (dest) { + dest->type = SF_TYPE_INNER_LIST; + dest->flags = SF_VALUE_FLAG_NONE; + } + + ++sfp->pos; + + sfp->state = SF_STATE_DICT_INNER_LIST_BEFORE; + + return 0; + } + + rv = parser_bare_item(sfp, dest); + if (rv != 0) { + return rv; + } + + sfp->state = SF_STATE_DICT_BEFORE_PARAMS; + + return 0; +} + +int sf_parser_dict(sf_parser *sfp, sf_vec *dest_key, sf_value *dest_value) { + int rv; + + switch (sfp->state) { + case SF_STATE_DICT_INNER_LIST_BEFORE: + rv = parser_skip_inner_list(sfp); + if (rv != 0) { + return rv; + } + + /* fall through */ + case SF_STATE_DICT_BEFORE_PARAMS: + rv = parser_skip_params(sfp); + if (rv != 0) { + return rv; + } + + /* fall through */ + case SF_STATE_DICT_AFTER: + rv = parser_next_key_or_item(sfp); + if (rv != 0) { + return rv; + } + + break; + case SF_STATE_INITIAL: + parser_discard_sp(sfp); + + if (parser_eof(sfp)) { + return SF_ERR_EOF; + } + + break; + default: + assert(0); + abort(); + } + + rv = parser_key(sfp, dest_key); + if (rv != 0) { + return rv; + } + + return parser_dict_value(sfp, dest_value); +} + +int sf_parser_list(sf_parser *sfp, sf_value *dest) { + int rv; + + switch (sfp->state) { + case SF_STATE_LIST_INNER_LIST_BEFORE: + rv = parser_skip_inner_list(sfp); + if (rv != 0) { + return rv; + } + + /* fall through */ + case SF_STATE_LIST_BEFORE_PARAMS: + rv = parser_skip_params(sfp); + if (rv != 0) { + return rv; + } + + /* fall through */ + case SF_STATE_LIST_AFTER: + rv = parser_next_key_or_item(sfp); + if (rv != 0) { + return rv; + } + + break; + case SF_STATE_INITIAL: + parser_discard_sp(sfp); + + if (parser_eof(sfp)) { + return SF_ERR_EOF; + } + + break; + default: + assert(0); + abort(); + } + + if (*sfp->pos == '(') { + if (dest) { + dest->type = SF_TYPE_INNER_LIST; + dest->flags = SF_VALUE_FLAG_NONE; + } + + ++sfp->pos; + + sfp->state = SF_STATE_LIST_INNER_LIST_BEFORE; + + return 0; + } + + rv = parser_bare_item(sfp, dest); + if (rv != 0) { + return rv; + } + + sfp->state = SF_STATE_LIST_BEFORE_PARAMS; + + return 0; +} + +int sf_parser_item(sf_parser *sfp, sf_value *dest) { + int rv; + + switch (sfp->state) { + case SF_STATE_INITIAL: + parser_discard_sp(sfp); + + if (parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + break; + case SF_STATE_ITEM_INNER_LIST_BEFORE: + rv = parser_skip_inner_list(sfp); + if (rv != 0) { + return rv; + } + + /* fall through */ + case SF_STATE_ITEM_BEFORE_PARAMS: + rv = parser_skip_params(sfp); + if (rv != 0) { + return rv; + } + + /* fall through */ + case SF_STATE_ITEM_AFTER: + parser_discard_sp(sfp); + + if (!parser_eof(sfp)) { + return SF_ERR_PARSE_ERROR; + } + + return SF_ERR_EOF; + default: + assert(0); + abort(); + } + + if (*sfp->pos == '(') { + if (dest) { + dest->type = SF_TYPE_INNER_LIST; + dest->flags = SF_VALUE_FLAG_NONE; + } + + ++sfp->pos; + + sfp->state = SF_STATE_ITEM_INNER_LIST_BEFORE; + + return 0; + } + + rv = parser_bare_item(sfp, dest); + if (rv != 0) { + return rv; + } + + sfp->state = SF_STATE_ITEM_BEFORE_PARAMS; + + return 0; +} + +void sf_parser_init(sf_parser *sfp, const uint8_t *data, size_t datalen) { + if (datalen == 0) { + sfp->pos = sfp->end = NULL; + } else { + sfp->pos = data; + sfp->end = data + datalen; + } + + sfp->state = SF_STATE_INITIAL; +} + +void sf_unescape(sf_vec *dest, const sf_vec *src) { + const uint8_t *p, *q; + uint8_t *o; + size_t len, slen; + + if (src->len == 0) { + *dest = *src; + + return; + } + + o = dest->base; + p = src->base; + len = src->len; + + for (;;) { + q = memchr(p, '\\', len); + if (q == NULL) { + if (len == src->len) { + *dest = *src; + + return; + } + + memcpy(o, p, len); + o += len; + + break; + } + + slen = (size_t)(q - p); + memcpy(o, p, slen); + o += slen; + + p = q + 1; + *o++ = *p++; + len -= slen + 2; + } + + dest->len = (size_t)(o - dest->base); +} + +void sf_base64decode(sf_vec *dest, const sf_vec *src) { + static const int index_tbl[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1}; + uint8_t *o; + const uint8_t *p, *end; + uint32_t n; + size_t i; + int idx; + + assert((src->len & 0x3) == 0); + + if (src->len == 0) { + *dest = *src; + + return; + } + + o = dest->base; + p = src->base; + end = src->base + src->len; + + for (; p != end;) { + n = 0; + + for (i = 1; i <= 4; ++i, ++p) { + idx = index_tbl[*p]; + + if (idx == -1) { + assert(i > 2); + + if (i == 3) { + assert(*p == '=' && *(p + 1) == '=' && p + 2 == end); + + *o++ = (uint8_t)(n >> 16); + + goto fin; + } + + assert(*p == '=' && p + 1 == end); + + *o++ = (uint8_t)(n >> 16); + *o++ = (n >> 8) & 0xffu; + + goto fin; + } + + n += (uint32_t)(idx << (24 - i * 6)); + } + + *o++ = (uint8_t)(n >> 16); + *o++ = (n >> 8) & 0xffu; + *o++ = n & 0xffu; + } + +fin: + dest->len = (size_t)(o - dest->base); +} diff --git a/lib/nghttp2/lib/sfparse.h b/lib/nghttp2/lib/sfparse.h new file mode 100644 index 00000000000..1474db1429a --- /dev/null +++ b/lib/nghttp2/lib/sfparse.h @@ -0,0 +1,409 @@ +/* + * sfparse + * + * Copyright (c) 2023 sfparse contributors + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2015 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 SFPARSE_H +#define SFPARSE_H + +/* Define WIN32 when build target is Win32 API (borrowed from + libcurl) */ +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1800) +/* MSVC < 2013 does not have inttypes.h because it is not C99 + compliant. See compiler macros and version number in + https://sourceforge.net/p/predef/wiki/Compilers/ */ +# include +#else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +# include +#endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +#include +#include + +/** + * @enum + * + * :type:`sf_type` defines value type. + */ +typedef enum sf_type { + /** + * :enum:`SF_TYPE_BOOLEAN` indicates boolean type. + */ + SF_TYPE_BOOLEAN, + /** + * :enum:`SF_TYPE_INTEGER` indicates integer type. + */ + SF_TYPE_INTEGER, + /** + * :enum:`SF_TYPE_DECIMAL` indicates decimal type. + */ + SF_TYPE_DECIMAL, + /** + * :enum:`SF_TYPE_STRING` indicates string type. + */ + SF_TYPE_STRING, + /** + * :enum:`SF_TYPE_TOKEN` indicates token type. + */ + SF_TYPE_TOKEN, + /** + * :enum:`SF_TYPE_BYTESEQ` indicates byte sequence type. + */ + SF_TYPE_BYTESEQ, + /** + * :enum:`SF_TYPE_INNER_LIST` indicates inner list type. + */ + SF_TYPE_INNER_LIST, + /** + * :enum:`SF_TYPE_DATE` indicates date type. + */ + SF_TYPE_DATE +} sf_type; + +/** + * @macro + * + * :macro:`SF_ERR_PARSE_ERROR` indicates fatal parse error has + * occurred, and it is not possible to continue the processing. + */ +#define SF_ERR_PARSE_ERROR -1 + +/** + * @macro + * + * :macro:`SF_ERR_EOF` indicates that there is nothing left to read. + * The context of this error varies depending on the function that + * returns this error code. + */ +#define SF_ERR_EOF -2 + +/** + * @struct + * + * :type:`sf_vec` stores sequence of bytes. + */ +typedef struct sf_vec { + /** + * :member:`base` points to the beginning of the sequence of bytes. + */ + uint8_t *base; + /** + * :member:`len` is the number of bytes contained in this sequence. + */ + size_t len; +} sf_vec; + +/** + * @macro + * + * :macro:`SF_VALUE_FLAG_NONE` indicates no flag set. + */ +#define SF_VALUE_FLAG_NONE 0x0u + +/** + * @macro + * + * :macro:`SF_VALUE_FLAG_ESCAPED_STRING` indicates that a string + * contains escaped character(s). + */ +#define SF_VALUE_FLAG_ESCAPED_STRING 0x1u + +/** + * @struct + * + * :type:`sf_decimal` contains decimal value. + */ +typedef struct sf_decimal { + /** + * :member:`numer` contains numerator of the decimal value. + */ + int64_t numer; + /** + * :member:`denom` contains denominator of the decimal value. + */ + int64_t denom; +} sf_decimal; + +/** + * @struct + * + * :type:`sf_value` stores a Structured Field item. For Inner List, + * only type is set to :enum:`sf_type.SF_TYPE_INNER_LIST`. In order + * to read the items contained in an inner list, call + * `sf_parser_inner_list`. + */ +typedef struct sf_value { + /** + * :member:`type` is the type of the value contained in this + * particular object. + */ + sf_type type; + /** + * :member:`flags` is bitwise OR of one or more of + * :macro:`SF_VALUE_FLAG_* `. + */ + uint32_t flags; + /** + * @anonunion_start + * + * @sf_value_value + */ + union { + /** + * :member:`boolean` contains boolean value if :member:`type` == + * :enum:`sf_type.SF_TYPE_BOOLEAN`. 1 indicates true, and 0 + * indicates false. + */ + int boolean; + /** + * :member:`integer` contains integer value if :member:`type` is + * either :enum:`sf_type.SF_TYPE_INTEGER` or + * :enum:`sf_type.SF_TYPE_DATE`. + */ + int64_t integer; + /** + * :member:`decimal` contains decimal value if :member:`type` == + * :enum:`sf_type.SF_TYPE_DECIMAL`. + */ + sf_decimal decimal; + /** + * :member:`vec` contains sequence of bytes if :member:`type` is + * either :enum:`sf_type.SF_TYPE_STRING`, + * :enum:`sf_type.SF_TYPE_TOKEN`, or + * :enum:`sf_type.SF_TYPE_BYTESEQ`. + * + * For :enum:`sf_type.SF_TYPE_STRING`, this field contains one or + * more escaped characters if :member:`flags` has + * :macro:`SF_VALUE_FLAG_ESCAPED_STRING` set. To unescape the + * string, use `sf_unescape`. + * + * For :enum:`sf_type.SF_TYPE_BYTESEQ`, this field contains base64 + * encoded string. To decode this byte string, use + * `sf_base64decode`. + * + * If :member:`vec.len ` == 0, :member:`vec.base + * ` is guaranteed to be NULL. + */ + sf_vec vec; + /** + * @anonunion_end + */ + }; +} sf_value; + +/** + * @struct + * + * :type:`sf_parser` is the Structured Field Values parser. Use + * `sf_parser_init` to initialize it. + */ +typedef struct sf_parser { + /* all fields are private */ + const uint8_t *pos; + const uint8_t *end; + uint32_t state; +} sf_parser; + +/** + * @function + * + * `sf_parser_init` initializes |sfp| with the given buffer pointed by + * |data| of length |datalen|. + */ +void sf_parser_init(sf_parser *sfp, const uint8_t *data, size_t datalen); + +/** + * @function + * + * `sf_parser_param` reads a parameter. If this function returns 0, + * it stores parameter key and value in |dest_key| and |dest_value| + * respectively, if they are not NULL. + * + * This function does no effort to find duplicated keys. Same key may + * be reported more than once. + * + * Caller should keep calling this function until it returns negative + * error code. If it returns :macro:`SF_ERR_EOF`, all parameters have + * read, and caller can continue to read rest of the values. If it + * returns :macro:`SF_ERR_PARSE_ERROR`, it encountered fatal error + * while parsing field value. + */ +int sf_parser_param(sf_parser *sfp, sf_vec *dest_key, sf_value *dest_value); + +/** + * @function + * + * `sf_parser_dict` reads the next dictionary key and value pair. If + * this function returns 0, it stores the key and value in |dest_key| + * and |dest_value| respectively, if they are not NULL. + * + * Caller can optionally read parameters attached to the pair by + * calling `sf_parser_param`. + * + * This function does no effort to find duplicated keys. Same key may + * be reported more than once. + * + * Caller should keep calling this function until it returns negative + * error code. If it returns :macro:`SF_ERR_EOF`, all key and value + * pairs have been read, and there is nothing left to read. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`SF_ERR_EOF` + * All values in the dictionary have read. + * :macro:`SF_ERR_PARSE_ERROR` + * It encountered fatal error while parsing field value. + */ +int sf_parser_dict(sf_parser *sfp, sf_vec *dest_key, sf_value *dest_value); + +/** + * @function + * + * `sf_parser_list` reads the next list item. If this function + * returns 0, it stores the item in |dest| if it is not NULL. + * + * Caller can optionally read parameters attached to the item by + * calling `sf_parser_param`. + * + * Caller should keep calling this function until it returns negative + * error code. If it returns :macro:`SF_ERR_EOF`, all values in the + * list have been read, and there is nothing left to read. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`SF_ERR_EOF` + * All values in the list have read. + * :macro:`SF_ERR_PARSE_ERROR` + * It encountered fatal error while parsing field value. + */ +int sf_parser_list(sf_parser *sfp, sf_value *dest); + +/** + * @function + * + * `sf_parser_item` reads a single item. If this function returns 0, + * it stores the item in |dest| if it is not NULL. + * + * This function is only used for the field value that consists of a + * single item. + * + * Caller can optionally read parameters attached to the item by + * calling `sf_parser_param`. + * + * Caller should call this function again to make sure that there is + * nothing left to read. If this 2nd function call returns + * :macro:`SF_ERR_EOF`, all data have been processed successfully. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`SF_ERR_EOF` + * There is nothing left to read. + * :macro:`SF_ERR_PARSE_ERROR` + * It encountered fatal error while parsing field value. + */ +int sf_parser_item(sf_parser *sfp, sf_value *dest); + +/** + * @function + * + * `sf_parser_inner_list` reads the next inner list item. If this + * function returns 0, it stores the item in |dest| if it is not NULL. + * + * Caller can optionally read parameters attached to the item by + * calling `sf_parser_param`. + * + * Caller should keep calling this function until it returns negative + * error code. If it returns :macro:`SF_ERR_EOF`, all values in this + * inner list have been read, and caller can optionally read + * parameters attached to this inner list by calling + * `sf_parser_param`. Then caller can continue to read rest of the + * values. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`SF_ERR_EOF` + * All values in the inner list have read. + * :macro:`SF_ERR_PARSE_ERROR` + * It encountered fatal error while parsing field value. + */ +int sf_parser_inner_list(sf_parser *sfp, sf_value *dest); + +/** + * @function + * + * `sf_unescape` copies |src| to |dest| by removing escapes (``\``). + * |src| should be the pointer to :member:`sf_value.vec` of type + * :enum:`sf_type.SF_TYPE_STRING` produced by either `sf_parser_dict`, + * `sf_parser_list`, `sf_parser_inner_list`, `sf_parser_item`, or + * `sf_parser_param`, otherwise the behavior is undefined. + * + * :member:`dest->base ` must point to the buffer that + * has sufficient space to store the unescaped string. + * + * If there is no escape character in |src|, |*src| is assigned to + * |*dest|. This includes the case that :member:`src->len + * ` == 0. + * + * This function sets the length of unescaped string to + * :member:`dest->len `. + */ +void sf_unescape(sf_vec *dest, const sf_vec *src); + +/** + * @function + * + * `sf_base64decode` decodes Base64 encoded string |src| and writes + * the result into |dest|. |src| should be the pointer to + * :member:`sf_value.vec` of type :enum:`sf_type.SF_TYPE_BYTESEQ` + * produced by either `sf_parser_dict`, `sf_parser_list`, + * `sf_parser_inner_list`, `sf_parser_item`, or `sf_parser_param`, + * otherwise the behavior is undefined. + * + * :member:`dest->base ` must point to the buffer that + * has sufficient space to store the decoded byte string. + * + * If :member:`src->len ` == 0, |*src| is assigned to + * |*dest|. + * + * This function sets the length of decoded byte string to + * :member:`dest->len `. + */ +void sf_base64decode(sf_vec *dest, const sf_vec *src); + +#ifdef __cplusplus +} +#endif + +#endif /* SFPARSE_H */ diff --git a/lib/nghttp2/lib/version.rc.in b/lib/nghttp2/lib/version.rc.in new file mode 100644 index 00000000000..4edfa7a49f9 --- /dev/null +++ b/lib/nghttp2/lib/version.rc.in @@ -0,0 +1,40 @@ +#include + +VS_VERSION_INFO VERSIONINFO + +FILEVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0 +PRODUCTVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, 0 +FILEFLAGSMASK 0x3fL +FILEOS 0x40004L +FILETYPE 0x2L +FILESUBTYPE 0x0L +#ifdef _DEBUG + #define VER_STR "@PROJECT_VERSION@.0 (MSVC debug)" + #define DBG "d" + FILEFLAGS 0x1L +#else + #define VER_STR "@PROJECT_VERSION@.0 (MSVC release)" + #define DBG "" + FILEFLAGS 0x0L +#endif +BEGIN +BLOCK "StringFileInfo" +BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "https://nghttp2.org/" + VALUE "FileDescription", "nghttp2; HTTP/2 C library" + VALUE "FileVersion", VER_STR + VALUE "InternalName", "nghttp2" DBG + VALUE "LegalCopyright", "The MIT License" + VALUE "LegalTrademarks", "" + VALUE "OriginalFilename", "nghttp2" DBG ".dll" + VALUE "ProductName", "NGHTTP2." + VALUE "ProductVersion", VER_STR + END +END +BLOCK "VarFileInfo" +BEGIN +VALUE "Translation", 0x409, 1200 +END +END diff --git a/lib/nghttp2/m4/ax_check_compile_flag.m4 b/lib/nghttp2/m4/ax_check_compile_flag.m4 new file mode 100644 index 00000000000..ca3639715e7 --- /dev/null +++ b/lib/nghttp2/m4/ax_check_compile_flag.m4 @@ -0,0 +1,74 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 4 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/lib/nghttp2/m4/ax_cxx_compile_stdcxx.m4 b/lib/nghttp2/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 00000000000..8edf5152ec7 --- /dev/null +++ b/lib/nghttp2/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,1018 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for +# the respective C++ standard version. +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for no added switch, and then for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# Copyright (c) 2021 Jörn Heusipp +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 18 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [$1], [20], [ax_cxx_compile_alternatives="20"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + dnl MSVC needs -std:c++NN for C++17 and later (default is C++14) + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}" MSVC; do + if test x"$switch" = xMSVC; then + dnl AS_TR_SH maps both `:` and `=` to `_` so -std:c++17 would collide + dnl with -std=c++17. We suffix the cache variable name with _MSVC to + dnl avoid this. + switch=-std:c++${alternative} + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_${switch}_MSVC]) + else + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + fi + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +dnl Test body for checking C++17 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Test body for checking C++20 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 +) + + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +// MSVC always sets __cplusplus to 199711L in older versions; newer versions +// only set it correctly if /Zc:__cplusplus is specified as well as a +// /std:c++NN switch: +// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ +#elif __cplusplus < 201103L && !defined _MSC_VER + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L && !defined _MSC_VER + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L && !defined _MSC_VER + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L && !defined _MSC_VER + +]]) + + +dnl Tests for new features in C++20 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 202002L && !defined _MSC_VER + +#error "This is not a C++20 compiler" + +#else + +#include + +namespace cxx20 +{ + +// As C++20 supports feature test macros in the standard, there is no +// immediate need to actually test for feature availability on the +// Autoconf side. + +} // namespace cxx20 + +#endif // __cplusplus < 202002L && !defined _MSC_VER + +]]) diff --git a/lib/nghttp2/m4/libxml2.m4 b/lib/nghttp2/m4/libxml2.m4 new file mode 100644 index 00000000000..68cd8242fbc --- /dev/null +++ b/lib/nghttp2/m4/libxml2.m4 @@ -0,0 +1,188 @@ +# Configure paths for LIBXML2 +# Mike Hommey 2004-06-19 +# use CPPFLAGS instead of CFLAGS +# Toshio Kuratomi 2001-04-21 +# Adapted from: +# Configure paths for GLIB +# Owen Taylor 97-11-3 + +dnl AM_PATH_XML2([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for XML, and define XML_CPPFLAGS and XML_LIBS +dnl +AC_DEFUN([AM_PATH_XML2],[ +AC_ARG_WITH(xml-prefix, + [ --with-xml-prefix=PFX Prefix where libxml is installed (optional)], + xml_config_prefix="$withval", xml_config_prefix="") +AC_ARG_WITH(xml-exec-prefix, + [ --with-xml-exec-prefix=PFX Exec prefix where libxml is installed (optional)], + xml_config_exec_prefix="$withval", xml_config_exec_prefix="") +AC_ARG_ENABLE(xmltest, + [ --disable-xmltest Do not try to compile and run a test LIBXML program],, + enable_xmltest=yes) + + if test x$xml_config_exec_prefix != x ; then + xml_config_args="$xml_config_args" + if test x${XML2_CONFIG+set} != xset ; then + XML2_CONFIG=$xml_config_exec_prefix/bin/xml2-config + fi + fi + if test x$xml_config_prefix != x ; then + xml_config_args="$xml_config_args --prefix=$xml_config_prefix" + if test x${XML2_CONFIG+set} != xset ; then + XML2_CONFIG=$xml_config_prefix/bin/xml2-config + fi + fi + + AC_PATH_PROG(XML2_CONFIG, xml2-config, no) + min_xml_version=ifelse([$1], ,2.0.0,[$1]) + AC_MSG_CHECKING(for libxml - version >= $min_xml_version) + no_xml="" + if test "$XML2_CONFIG" = "no" ; then + no_xml=yes + else + XML_CPPFLAGS=`$XML2_CONFIG $xml_config_args --cflags` + XML_LIBS=`$XML2_CONFIG $xml_config_args --libs` + xml_config_major_version=`$XML2_CONFIG $xml_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` + xml_config_minor_version=`$XML2_CONFIG $xml_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` + xml_config_micro_version=`$XML2_CONFIG $xml_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` + if test "x$enable_xmltest" = "xyes" ; then + ac_save_CPPFLAGS="$CPPFLAGS" + ac_save_LIBS="$LIBS" + CPPFLAGS="$CPPFLAGS $XML_CPPFLAGS" + LIBS="$XML_LIBS $LIBS" +dnl +dnl Now check if the installed libxml is sufficiently new. +dnl (Also sanity checks the results of xml2-config to some extent) +dnl + rm -f conf.xmltest + AC_TRY_RUN([ +#include +#include +#include +#include + +int +main() +{ + int xml_major_version, xml_minor_version, xml_micro_version; + int major, minor, micro; + char *tmp_version; + + system("touch conf.xmltest"); + + /* Capture xml2-config output via autoconf/configure variables */ + /* HP/UX 9 (%@#!) writes to sscanf strings */ + tmp_version = (char *)strdup("$min_xml_version"); + if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { + printf("%s, bad version string from xml2-config\n", "$min_xml_version"); + exit(1); + } + free(tmp_version); + + /* Capture the version information from the header files */ + tmp_version = (char *)strdup(LIBXML_DOTTED_VERSION); + if (sscanf(tmp_version, "%d.%d.%d", &xml_major_version, &xml_minor_version, &xml_micro_version) != 3) { + printf("%s, bad version string from libxml includes\n", "LIBXML_DOTTED_VERSION"); + exit(1); + } + free(tmp_version); + + /* Compare xml2-config output to the libxml headers */ + if ((xml_major_version != $xml_config_major_version) || + (xml_minor_version != $xml_config_minor_version) || + (xml_micro_version != $xml_config_micro_version)) + { + printf("*** libxml header files (version %d.%d.%d) do not match\n", + xml_major_version, xml_minor_version, xml_micro_version); + printf("*** xml2-config (version %d.%d.%d)\n", + $xml_config_major_version, $xml_config_minor_version, $xml_config_micro_version); + return 1; + } +/* Compare the headers to the library to make sure we match */ + /* Less than ideal -- doesn't provide us with return value feedback, + * only exits if there's a serious mismatch between header and library. + */ + LIBXML_TEST_VERSION; + + /* Test that the library is greater than our minimum version */ + if ((xml_major_version > major) || + ((xml_major_version == major) && (xml_minor_version > minor)) || + ((xml_major_version == major) && (xml_minor_version == minor) && + (xml_micro_version >= micro))) + { + return 0; + } + else + { + printf("\n*** An old version of libxml (%d.%d.%d) was found.\n", + xml_major_version, xml_minor_version, xml_micro_version); + printf("*** You need a version of libxml newer than %d.%d.%d. The latest version of\n", + major, minor, micro); + printf("*** libxml is always available from ftp://ftp.xmlsoft.org.\n"); + printf("***\n"); + printf("*** If you have already installed a sufficiently new version, this error\n"); + printf("*** probably means that the wrong copy of the xml2-config shell script is\n"); + printf("*** being found. The easiest way to fix this is to remove the old version\n"); + printf("*** of LIBXML, but you can also set the XML2_CONFIG environment to point to the\n"); + printf("*** correct copy of xml2-config. (In this case, you will have to\n"); + printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n"); + printf("*** so that the correct libraries are found at run-time))\n"); + } + return 1; +} +],, no_xml=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + fi + fi + + if test "x$no_xml" = x ; then + AC_MSG_RESULT(yes (version $xml_config_major_version.$xml_config_minor_version.$xml_config_micro_version)) + ifelse([$2], , :, [$2]) + else + AC_MSG_RESULT(no) + if test "$XML2_CONFIG" = "no" ; then + echo "*** The xml2-config script installed by LIBXML could not be found" + echo "*** If libxml was installed in PREFIX, make sure PREFIX/bin is in" + echo "*** your path, or set the XML2_CONFIG environment variable to the" + echo "*** full path to xml2-config." + else + if test -f conf.xmltest ; then + : + else + echo "*** Could not run libxml test program, checking why..." + CPPFLAGS="$CPPFLAGS $XML_CPPFLAGS" + LIBS="$LIBS $XML_LIBS" + AC_TRY_LINK([ +#include +#include +], [ LIBXML_TEST_VERSION; return 0;], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding LIBXML or finding the wrong" + echo "*** version of LIBXML. If it is not finding LIBXML, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH" ], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means LIBXML was incorrectly installed" + echo "*** or that you have moved LIBXML since it was installed. In the latter case, you" + echo "*** may want to edit the xml2-config script: $XML2_CONFIG" ]) + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + fi + fi + + XML_CPPFLAGS="" + XML_LIBS="" + ifelse([$3], , :, [$3]) + fi + AC_SUBST(XML_CPPFLAGS) + AC_SUBST(XML_LIBS) + rm -f conf.xmltest +]) diff --git a/lib/nghttp2/makebashcompletion b/lib/nghttp2/makebashcompletion new file mode 100755 index 00000000000..9e88d9d4b6d --- /dev/null +++ b/lib/nghttp2/makebashcompletion @@ -0,0 +1,7 @@ +#!/bin/sh -e + +BCPATH=doc/bash_completion + +for prog in nghttp nghttpd nghttpx h2load; do + $BCPATH/make_bash_completion.py src/$prog > $BCPATH/$prog +done diff --git a/lib/nghttp2/makemanpages b/lib/nghttp2/makemanpages new file mode 100755 index 00000000000..0b7c54ea4dd --- /dev/null +++ b/lib/nghttp2/makemanpages @@ -0,0 +1,12 @@ +#!/bin/sh -e + +for prog in nghttp nghttpd nghttpx h2load; do + src/$prog -h | ./help2rst.py -i doc/$prog.h2r > doc/$prog.1.rst +done + +cd doc +make man + +for prog in nghttp nghttpd nghttpx h2load; do + cp manual/man/$prog.1 $prog.1 +done diff --git a/lib/nghttp2/makerelease.sh b/lib/nghttp2/makerelease.sh new file mode 100755 index 00000000000..b65e08f57f4 --- /dev/null +++ b/lib/nghttp2/makerelease.sh @@ -0,0 +1,23 @@ +#!/bin/sh -e + +TAG=$1 +PREV_TAG=$2 + +git checkout refs/tags/$TAG +git log --pretty=fuller --date=short refs/tags/$PREV_TAG..HEAD > ChangeLog + +git submodule update --init + +autoreconf -i +./configure --with-mruby && \ + make dist-bzip2 && make dist-gzip && make dist-xz || echo "error" + +rm -f checksums.txt + +VERSION=`echo -n $TAG | sed -E 's|^v([0-9]+\.[0-9]+\.[0-9]+)(-DEV)?$|\1|'` +for f in nghttp2-$VERSION.tar.bz2 nghttp2-$VERSION.tar.gz nghttp2-$VERSION.tar.xz; do + sha256sum $f >> checksums.txt + gpg --armor --detach-sign $f +done + +make distclean diff --git a/lib/nghttp2/mkcipherlist.py b/lib/nghttp2/mkcipherlist.py new file mode 100755 index 00000000000..e366f5e5bf8 --- /dev/null +++ b/lib/nghttp2/mkcipherlist.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# This script read cipher suite list csv file [1] and prints out id +# and name of black listed cipher suites. The output is used by +# src/ssl.cc. +# +# [1] http://www.iana.org/assignments/tls-parameters/tls-parameters-4.csv +# [2] http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml + +import re +import sys +import csv + +# From RFC 7540 +blacklist = [ + 'TLS_NULL_WITH_NULL_NULL', + 'TLS_RSA_WITH_NULL_MD5', + 'TLS_RSA_WITH_NULL_SHA', + 'TLS_RSA_EXPORT_WITH_RC4_40_MD5', + 'TLS_RSA_WITH_RC4_128_MD5', + 'TLS_RSA_WITH_RC4_128_SHA', + 'TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5', + 'TLS_RSA_WITH_IDEA_CBC_SHA', + 'TLS_RSA_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_RSA_WITH_DES_CBC_SHA', + 'TLS_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DH_DSS_WITH_DES_CBC_SHA', + 'TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA', + 'TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DH_RSA_WITH_DES_CBC_SHA', + 'TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DHE_DSS_WITH_DES_CBC_SHA', + 'TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA', + 'TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DHE_RSA_WITH_DES_CBC_SHA', + 'TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_DH_anon_EXPORT_WITH_RC4_40_MD5', + 'TLS_DH_anon_WITH_RC4_128_MD5', + 'TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA', + 'TLS_DH_anon_WITH_DES_CBC_SHA', + 'TLS_DH_anon_WITH_3DES_EDE_CBC_SHA', + 'TLS_KRB5_WITH_DES_CBC_SHA', + 'TLS_KRB5_WITH_3DES_EDE_CBC_SHA', + 'TLS_KRB5_WITH_RC4_128_SHA', + 'TLS_KRB5_WITH_IDEA_CBC_SHA', + 'TLS_KRB5_WITH_DES_CBC_MD5', + 'TLS_KRB5_WITH_3DES_EDE_CBC_MD5', + 'TLS_KRB5_WITH_RC4_128_MD5', + 'TLS_KRB5_WITH_IDEA_CBC_MD5', + 'TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA', + 'TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA', + 'TLS_KRB5_EXPORT_WITH_RC4_40_SHA', + 'TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5', + 'TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5', + 'TLS_KRB5_EXPORT_WITH_RC4_40_MD5', + 'TLS_PSK_WITH_NULL_SHA', + 'TLS_DHE_PSK_WITH_NULL_SHA', + 'TLS_RSA_PSK_WITH_NULL_SHA', + 'TLS_RSA_WITH_AES_128_CBC_SHA', + 'TLS_DH_DSS_WITH_AES_128_CBC_SHA', + 'TLS_DH_RSA_WITH_AES_128_CBC_SHA', + 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA', + 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA', + 'TLS_DH_anon_WITH_AES_128_CBC_SHA', + 'TLS_RSA_WITH_AES_256_CBC_SHA', + 'TLS_DH_DSS_WITH_AES_256_CBC_SHA', + 'TLS_DH_RSA_WITH_AES_256_CBC_SHA', + 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA', + 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA', + 'TLS_DH_anon_WITH_AES_256_CBC_SHA', + 'TLS_RSA_WITH_NULL_SHA256', + 'TLS_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_RSA_WITH_AES_256_CBC_SHA256', + 'TLS_DH_DSS_WITH_AES_128_CBC_SHA256', + 'TLS_DH_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA256', + 'TLS_RSA_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA', + 'TLS_DHE_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_DH_DSS_WITH_AES_256_CBC_SHA256', + 'TLS_DH_RSA_WITH_AES_256_CBC_SHA256', + 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA256', + 'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256', + 'TLS_DH_anon_WITH_AES_128_CBC_SHA256', + 'TLS_DH_anon_WITH_AES_256_CBC_SHA256', + 'TLS_RSA_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA', + 'TLS_PSK_WITH_RC4_128_SHA', + 'TLS_PSK_WITH_3DES_EDE_CBC_SHA', + 'TLS_PSK_WITH_AES_128_CBC_SHA', + 'TLS_PSK_WITH_AES_256_CBC_SHA', + 'TLS_DHE_PSK_WITH_RC4_128_SHA', + 'TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA', + 'TLS_DHE_PSK_WITH_AES_128_CBC_SHA', + 'TLS_DHE_PSK_WITH_AES_256_CBC_SHA', + 'TLS_RSA_PSK_WITH_RC4_128_SHA', + 'TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA', + 'TLS_RSA_PSK_WITH_AES_128_CBC_SHA', + 'TLS_RSA_PSK_WITH_AES_256_CBC_SHA', + 'TLS_RSA_WITH_SEED_CBC_SHA', + 'TLS_DH_DSS_WITH_SEED_CBC_SHA', + 'TLS_DH_RSA_WITH_SEED_CBC_SHA', + 'TLS_DHE_DSS_WITH_SEED_CBC_SHA', + 'TLS_DHE_RSA_WITH_SEED_CBC_SHA', + 'TLS_DH_anon_WITH_SEED_CBC_SHA', + 'TLS_RSA_WITH_AES_128_GCM_SHA256', + 'TLS_RSA_WITH_AES_256_GCM_SHA384', + 'TLS_DH_RSA_WITH_AES_128_GCM_SHA256', + 'TLS_DH_RSA_WITH_AES_256_GCM_SHA384', + 'TLS_DH_DSS_WITH_AES_128_GCM_SHA256', + 'TLS_DH_DSS_WITH_AES_256_GCM_SHA384', + 'TLS_DH_anon_WITH_AES_128_GCM_SHA256', + 'TLS_DH_anon_WITH_AES_256_GCM_SHA384', + 'TLS_PSK_WITH_AES_128_GCM_SHA256', + 'TLS_PSK_WITH_AES_256_GCM_SHA384', + 'TLS_RSA_PSK_WITH_AES_128_GCM_SHA256', + 'TLS_RSA_PSK_WITH_AES_256_GCM_SHA384', + 'TLS_PSK_WITH_AES_128_CBC_SHA256', + 'TLS_PSK_WITH_AES_256_CBC_SHA384', + 'TLS_PSK_WITH_NULL_SHA256', + 'TLS_PSK_WITH_NULL_SHA384', + 'TLS_DHE_PSK_WITH_AES_128_CBC_SHA256', + 'TLS_DHE_PSK_WITH_AES_256_CBC_SHA384', + 'TLS_DHE_PSK_WITH_NULL_SHA256', + 'TLS_DHE_PSK_WITH_NULL_SHA384', + 'TLS_RSA_PSK_WITH_AES_128_CBC_SHA256', + 'TLS_RSA_PSK_WITH_AES_256_CBC_SHA384', + 'TLS_RSA_PSK_WITH_NULL_SHA256', + 'TLS_RSA_PSK_WITH_NULL_SHA384', + 'TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256', + 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV', + 'TLS_ECDH_ECDSA_WITH_NULL_SHA', + 'TLS_ECDH_ECDSA_WITH_RC4_128_SHA', + 'TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA', + 'TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA', + 'TLS_ECDHE_ECDSA_WITH_NULL_SHA', + 'TLS_ECDHE_ECDSA_WITH_RC4_128_SHA', + 'TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA', + 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA', + 'TLS_ECDH_RSA_WITH_NULL_SHA', + 'TLS_ECDH_RSA_WITH_RC4_128_SHA', + 'TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDH_RSA_WITH_AES_128_CBC_SHA', + 'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA', + 'TLS_ECDHE_RSA_WITH_NULL_SHA', + 'TLS_ECDHE_RSA_WITH_RC4_128_SHA', + 'TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA', + 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA', + 'TLS_ECDH_anon_WITH_NULL_SHA', + 'TLS_ECDH_anon_WITH_RC4_128_SHA', + 'TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDH_anon_WITH_AES_128_CBC_SHA', + 'TLS_ECDH_anon_WITH_AES_256_CBC_SHA', + 'TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA', + 'TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA', + 'TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA', + 'TLS_SRP_SHA_WITH_AES_128_CBC_SHA', + 'TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA', + 'TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA', + 'TLS_SRP_SHA_WITH_AES_256_CBC_SHA', + 'TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA', + 'TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA', + 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256', + 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384', + 'TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256', + 'TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384', + 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384', + 'TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256', + 'TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384', + 'TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256', + 'TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384', + 'TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256', + 'TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384', + 'TLS_ECDHE_PSK_WITH_RC4_128_SHA', + 'TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA', + 'TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA', + 'TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA', + 'TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256', + 'TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384', + 'TLS_ECDHE_PSK_WITH_NULL_SHA', + 'TLS_ECDHE_PSK_WITH_NULL_SHA256', + 'TLS_ECDHE_PSK_WITH_NULL_SHA384', + 'TLS_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256', + 'TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384', + 'TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256', + 'TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384', + 'TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_DH_anon_WITH_ARIA_128_CBC_SHA256', + 'TLS_DH_anon_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384', + 'TLS_RSA_WITH_ARIA_128_GCM_SHA256', + 'TLS_RSA_WITH_ARIA_256_GCM_SHA384', + 'TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256', + 'TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384', + 'TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256', + 'TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384', + 'TLS_DH_anon_WITH_ARIA_128_GCM_SHA256', + 'TLS_DH_anon_WITH_ARIA_256_GCM_SHA384', + 'TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256', + 'TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384', + 'TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256', + 'TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384', + 'TLS_PSK_WITH_ARIA_128_CBC_SHA256', + 'TLS_PSK_WITH_ARIA_256_CBC_SHA384', + 'TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256', + 'TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384', + 'TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256', + 'TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384', + 'TLS_PSK_WITH_ARIA_128_GCM_SHA256', + 'TLS_PSK_WITH_ARIA_256_GCM_SHA384', + 'TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256', + 'TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384', + 'TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256', + 'TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384', + 'TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256', + 'TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384', + 'TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256', + 'TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384', + 'TLS_RSA_WITH_AES_128_CCM', + 'TLS_RSA_WITH_AES_256_CCM', + 'TLS_RSA_WITH_AES_128_CCM_8', + 'TLS_RSA_WITH_AES_256_CCM_8', + 'TLS_PSK_WITH_AES_128_CCM', + 'TLS_PSK_WITH_AES_256_CCM', + 'TLS_PSK_WITH_AES_128_CCM_8', + 'TLS_PSK_WITH_AES_256_CCM_8', +] + +ciphers = [] +found = set() +for hl, name, _, _, _ in csv.reader(sys.stdin): + if name not in blacklist: + continue + + found.add(name) + + high, low = hl.split(',') + + id = high + low[2:] + 'u' + ciphers.append((id, name)) + +print('''\ +enum {''') + +for id, name in ciphers: + print('{} = {},'.format(name, id)) + +print('''\ +}; +''') + +for id, name in ciphers: + print('''\ +case {}:'''.format(name)) + +if len(found) != len(blacklist): + print('{} found out of {}; not all cipher was found: {}'.format( + len(found), len(blacklist), + found.symmetric_difference(blacklist))) diff --git a/lib/nghttp2/mkhufftbl.py b/lib/nghttp2/mkhufftbl.py new file mode 100755 index 00000000000..a9f10d751e3 --- /dev/null +++ b/lib/nghttp2/mkhufftbl.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# This script reads Huffman Code table [1] and generates symbol table +# and decoding tables in C language. The resulting code is used in +# lib/nghttp2_hd_huffman.h and lib/nghttp2_hd_huffman_data.c +# +# [1] https://httpwg.org/specs/rfc7541.html + +import re +import sys +from io import StringIO + +# From [1] +HUFFMAN_CODE_TABLE = """\ + ( 0) |11111111|11000 1ff8 [13] + ( 1) |11111111|11111111|1011000 7fffd8 [23] + ( 2) |11111111|11111111|11111110|0010 fffffe2 [28] + ( 3) |11111111|11111111|11111110|0011 fffffe3 [28] + ( 4) |11111111|11111111|11111110|0100 fffffe4 [28] + ( 5) |11111111|11111111|11111110|0101 fffffe5 [28] + ( 6) |11111111|11111111|11111110|0110 fffffe6 [28] + ( 7) |11111111|11111111|11111110|0111 fffffe7 [28] + ( 8) |11111111|11111111|11111110|1000 fffffe8 [28] + ( 9) |11111111|11111111|11101010 ffffea [24] + ( 10) |11111111|11111111|11111111|111100 3ffffffc [30] + ( 11) |11111111|11111111|11111110|1001 fffffe9 [28] + ( 12) |11111111|11111111|11111110|1010 fffffea [28] + ( 13) |11111111|11111111|11111111|111101 3ffffffd [30] + ( 14) |11111111|11111111|11111110|1011 fffffeb [28] + ( 15) |11111111|11111111|11111110|1100 fffffec [28] + ( 16) |11111111|11111111|11111110|1101 fffffed [28] + ( 17) |11111111|11111111|11111110|1110 fffffee [28] + ( 18) |11111111|11111111|11111110|1111 fffffef [28] + ( 19) |11111111|11111111|11111111|0000 ffffff0 [28] + ( 20) |11111111|11111111|11111111|0001 ffffff1 [28] + ( 21) |11111111|11111111|11111111|0010 ffffff2 [28] + ( 22) |11111111|11111111|11111111|111110 3ffffffe [30] + ( 23) |11111111|11111111|11111111|0011 ffffff3 [28] + ( 24) |11111111|11111111|11111111|0100 ffffff4 [28] + ( 25) |11111111|11111111|11111111|0101 ffffff5 [28] + ( 26) |11111111|11111111|11111111|0110 ffffff6 [28] + ( 27) |11111111|11111111|11111111|0111 ffffff7 [28] + ( 28) |11111111|11111111|11111111|1000 ffffff8 [28] + ( 29) |11111111|11111111|11111111|1001 ffffff9 [28] + ( 30) |11111111|11111111|11111111|1010 ffffffa [28] + ( 31) |11111111|11111111|11111111|1011 ffffffb [28] +' ' ( 32) |010100 14 [ 6] +'!' ( 33) |11111110|00 3f8 [10] +'"' ( 34) |11111110|01 3f9 [10] +'#' ( 35) |11111111|1010 ffa [12] +'$' ( 36) |11111111|11001 1ff9 [13] +'%' ( 37) |010101 15 [ 6] +'&' ( 38) |11111000 f8 [ 8] +''' ( 39) |11111111|010 7fa [11] +'(' ( 40) |11111110|10 3fa [10] +')' ( 41) |11111110|11 3fb [10] +'*' ( 42) |11111001 f9 [ 8] +'+' ( 43) |11111111|011 7fb [11] +',' ( 44) |11111010 fa [ 8] +'-' ( 45) |010110 16 [ 6] +'.' ( 46) |010111 17 [ 6] +'/' ( 47) |011000 18 [ 6] +'0' ( 48) |00000 0 [ 5] +'1' ( 49) |00001 1 [ 5] +'2' ( 50) |00010 2 [ 5] +'3' ( 51) |011001 19 [ 6] +'4' ( 52) |011010 1a [ 6] +'5' ( 53) |011011 1b [ 6] +'6' ( 54) |011100 1c [ 6] +'7' ( 55) |011101 1d [ 6] +'8' ( 56) |011110 1e [ 6] +'9' ( 57) |011111 1f [ 6] +':' ( 58) |1011100 5c [ 7] +';' ( 59) |11111011 fb [ 8] +'<' ( 60) |11111111|1111100 7ffc [15] +'=' ( 61) |100000 20 [ 6] +'>' ( 62) |11111111|1011 ffb [12] +'?' ( 63) |11111111|00 3fc [10] +'@' ( 64) |11111111|11010 1ffa [13] +'A' ( 65) |100001 21 [ 6] +'B' ( 66) |1011101 5d [ 7] +'C' ( 67) |1011110 5e [ 7] +'D' ( 68) |1011111 5f [ 7] +'E' ( 69) |1100000 60 [ 7] +'F' ( 70) |1100001 61 [ 7] +'G' ( 71) |1100010 62 [ 7] +'H' ( 72) |1100011 63 [ 7] +'I' ( 73) |1100100 64 [ 7] +'J' ( 74) |1100101 65 [ 7] +'K' ( 75) |1100110 66 [ 7] +'L' ( 76) |1100111 67 [ 7] +'M' ( 77) |1101000 68 [ 7] +'N' ( 78) |1101001 69 [ 7] +'O' ( 79) |1101010 6a [ 7] +'P' ( 80) |1101011 6b [ 7] +'Q' ( 81) |1101100 6c [ 7] +'R' ( 82) |1101101 6d [ 7] +'S' ( 83) |1101110 6e [ 7] +'T' ( 84) |1101111 6f [ 7] +'U' ( 85) |1110000 70 [ 7] +'V' ( 86) |1110001 71 [ 7] +'W' ( 87) |1110010 72 [ 7] +'X' ( 88) |11111100 fc [ 8] +'Y' ( 89) |1110011 73 [ 7] +'Z' ( 90) |11111101 fd [ 8] +'[' ( 91) |11111111|11011 1ffb [13] +'\' ( 92) |11111111|11111110|000 7fff0 [19] +']' ( 93) |11111111|11100 1ffc [13] +'^' ( 94) |11111111|111100 3ffc [14] +'_' ( 95) |100010 22 [ 6] +'`' ( 96) |11111111|1111101 7ffd [15] +'a' ( 97) |00011 3 [ 5] +'b' ( 98) |100011 23 [ 6] +'c' ( 99) |00100 4 [ 5] +'d' (100) |100100 24 [ 6] +'e' (101) |00101 5 [ 5] +'f' (102) |100101 25 [ 6] +'g' (103) |100110 26 [ 6] +'h' (104) |100111 27 [ 6] +'i' (105) |00110 6 [ 5] +'j' (106) |1110100 74 [ 7] +'k' (107) |1110101 75 [ 7] +'l' (108) |101000 28 [ 6] +'m' (109) |101001 29 [ 6] +'n' (110) |101010 2a [ 6] +'o' (111) |00111 7 [ 5] +'p' (112) |101011 2b [ 6] +'q' (113) |1110110 76 [ 7] +'r' (114) |101100 2c [ 6] +'s' (115) |01000 8 [ 5] +'t' (116) |01001 9 [ 5] +'u' (117) |101101 2d [ 6] +'v' (118) |1110111 77 [ 7] +'w' (119) |1111000 78 [ 7] +'x' (120) |1111001 79 [ 7] +'y' (121) |1111010 7a [ 7] +'z' (122) |1111011 7b [ 7] +'{' (123) |11111111|1111110 7ffe [15] +'|' (124) |11111111|100 7fc [11] +'}' (125) |11111111|111101 3ffd [14] +'~' (126) |11111111|11101 1ffd [13] + (127) |11111111|11111111|11111111|1100 ffffffc [28] + (128) |11111111|11111110|0110 fffe6 [20] + (129) |11111111|11111111|010010 3fffd2 [22] + (130) |11111111|11111110|0111 fffe7 [20] + (131) |11111111|11111110|1000 fffe8 [20] + (132) |11111111|11111111|010011 3fffd3 [22] + (133) |11111111|11111111|010100 3fffd4 [22] + (134) |11111111|11111111|010101 3fffd5 [22] + (135) |11111111|11111111|1011001 7fffd9 [23] + (136) |11111111|11111111|010110 3fffd6 [22] + (137) |11111111|11111111|1011010 7fffda [23] + (138) |11111111|11111111|1011011 7fffdb [23] + (139) |11111111|11111111|1011100 7fffdc [23] + (140) |11111111|11111111|1011101 7fffdd [23] + (141) |11111111|11111111|1011110 7fffde [23] + (142) |11111111|11111111|11101011 ffffeb [24] + (143) |11111111|11111111|1011111 7fffdf [23] + (144) |11111111|11111111|11101100 ffffec [24] + (145) |11111111|11111111|11101101 ffffed [24] + (146) |11111111|11111111|010111 3fffd7 [22] + (147) |11111111|11111111|1100000 7fffe0 [23] + (148) |11111111|11111111|11101110 ffffee [24] + (149) |11111111|11111111|1100001 7fffe1 [23] + (150) |11111111|11111111|1100010 7fffe2 [23] + (151) |11111111|11111111|1100011 7fffe3 [23] + (152) |11111111|11111111|1100100 7fffe4 [23] + (153) |11111111|11111110|11100 1fffdc [21] + (154) |11111111|11111111|011000 3fffd8 [22] + (155) |11111111|11111111|1100101 7fffe5 [23] + (156) |11111111|11111111|011001 3fffd9 [22] + (157) |11111111|11111111|1100110 7fffe6 [23] + (158) |11111111|11111111|1100111 7fffe7 [23] + (159) |11111111|11111111|11101111 ffffef [24] + (160) |11111111|11111111|011010 3fffda [22] + (161) |11111111|11111110|11101 1fffdd [21] + (162) |11111111|11111110|1001 fffe9 [20] + (163) |11111111|11111111|011011 3fffdb [22] + (164) |11111111|11111111|011100 3fffdc [22] + (165) |11111111|11111111|1101000 7fffe8 [23] + (166) |11111111|11111111|1101001 7fffe9 [23] + (167) |11111111|11111110|11110 1fffde [21] + (168) |11111111|11111111|1101010 7fffea [23] + (169) |11111111|11111111|011101 3fffdd [22] + (170) |11111111|11111111|011110 3fffde [22] + (171) |11111111|11111111|11110000 fffff0 [24] + (172) |11111111|11111110|11111 1fffdf [21] + (173) |11111111|11111111|011111 3fffdf [22] + (174) |11111111|11111111|1101011 7fffeb [23] + (175) |11111111|11111111|1101100 7fffec [23] + (176) |11111111|11111111|00000 1fffe0 [21] + (177) |11111111|11111111|00001 1fffe1 [21] + (178) |11111111|11111111|100000 3fffe0 [22] + (179) |11111111|11111111|00010 1fffe2 [21] + (180) |11111111|11111111|1101101 7fffed [23] + (181) |11111111|11111111|100001 3fffe1 [22] + (182) |11111111|11111111|1101110 7fffee [23] + (183) |11111111|11111111|1101111 7fffef [23] + (184) |11111111|11111110|1010 fffea [20] + (185) |11111111|11111111|100010 3fffe2 [22] + (186) |11111111|11111111|100011 3fffe3 [22] + (187) |11111111|11111111|100100 3fffe4 [22] + (188) |11111111|11111111|1110000 7ffff0 [23] + (189) |11111111|11111111|100101 3fffe5 [22] + (190) |11111111|11111111|100110 3fffe6 [22] + (191) |11111111|11111111|1110001 7ffff1 [23] + (192) |11111111|11111111|11111000|00 3ffffe0 [26] + (193) |11111111|11111111|11111000|01 3ffffe1 [26] + (194) |11111111|11111110|1011 fffeb [20] + (195) |11111111|11111110|001 7fff1 [19] + (196) |11111111|11111111|100111 3fffe7 [22] + (197) |11111111|11111111|1110010 7ffff2 [23] + (198) |11111111|11111111|101000 3fffe8 [22] + (199) |11111111|11111111|11110110|0 1ffffec [25] + (200) |11111111|11111111|11111000|10 3ffffe2 [26] + (201) |11111111|11111111|11111000|11 3ffffe3 [26] + (202) |11111111|11111111|11111001|00 3ffffe4 [26] + (203) |11111111|11111111|11111011|110 7ffffde [27] + (204) |11111111|11111111|11111011|111 7ffffdf [27] + (205) |11111111|11111111|11111001|01 3ffffe5 [26] + (206) |11111111|11111111|11110001 fffff1 [24] + (207) |11111111|11111111|11110110|1 1ffffed [25] + (208) |11111111|11111110|010 7fff2 [19] + (209) |11111111|11111111|00011 1fffe3 [21] + (210) |11111111|11111111|11111001|10 3ffffe6 [26] + (211) |11111111|11111111|11111100|000 7ffffe0 [27] + (212) |11111111|11111111|11111100|001 7ffffe1 [27] + (213) |11111111|11111111|11111001|11 3ffffe7 [26] + (214) |11111111|11111111|11111100|010 7ffffe2 [27] + (215) |11111111|11111111|11110010 fffff2 [24] + (216) |11111111|11111111|00100 1fffe4 [21] + (217) |11111111|11111111|00101 1fffe5 [21] + (218) |11111111|11111111|11111010|00 3ffffe8 [26] + (219) |11111111|11111111|11111010|01 3ffffe9 [26] + (220) |11111111|11111111|11111111|1101 ffffffd [28] + (221) |11111111|11111111|11111100|011 7ffffe3 [27] + (222) |11111111|11111111|11111100|100 7ffffe4 [27] + (223) |11111111|11111111|11111100|101 7ffffe5 [27] + (224) |11111111|11111110|1100 fffec [20] + (225) |11111111|11111111|11110011 fffff3 [24] + (226) |11111111|11111110|1101 fffed [20] + (227) |11111111|11111111|00110 1fffe6 [21] + (228) |11111111|11111111|101001 3fffe9 [22] + (229) |11111111|11111111|00111 1fffe7 [21] + (230) |11111111|11111111|01000 1fffe8 [21] + (231) |11111111|11111111|1110011 7ffff3 [23] + (232) |11111111|11111111|101010 3fffea [22] + (233) |11111111|11111111|101011 3fffeb [22] + (234) |11111111|11111111|11110111|0 1ffffee [25] + (235) |11111111|11111111|11110111|1 1ffffef [25] + (236) |11111111|11111111|11110100 fffff4 [24] + (237) |11111111|11111111|11110101 fffff5 [24] + (238) |11111111|11111111|11111010|10 3ffffea [26] + (239) |11111111|11111111|1110100 7ffff4 [23] + (240) |11111111|11111111|11111010|11 3ffffeb [26] + (241) |11111111|11111111|11111100|110 7ffffe6 [27] + (242) |11111111|11111111|11111011|00 3ffffec [26] + (243) |11111111|11111111|11111011|01 3ffffed [26] + (244) |11111111|11111111|11111100|111 7ffffe7 [27] + (245) |11111111|11111111|11111101|000 7ffffe8 [27] + (246) |11111111|11111111|11111101|001 7ffffe9 [27] + (247) |11111111|11111111|11111101|010 7ffffea [27] + (248) |11111111|11111111|11111101|011 7ffffeb [27] + (249) |11111111|11111111|11111111|1110 ffffffe [28] + (250) |11111111|11111111|11111101|100 7ffffec [27] + (251) |11111111|11111111|11111101|101 7ffffed [27] + (252) |11111111|11111111|11111101|110 7ffffee [27] + (253) |11111111|11111111|11111101|111 7ffffef [27] + (254) |11111111|11111111|11111110|000 7fffff0 [27] + (255) |11111111|11111111|11111011|10 3ffffee [26] +EOS (256) |11111111|11111111|11111111|111111 3fffffff [30] +""" + +class Node: + + def __init__(self, term = None): + self.term = term + self.left = None + self.right = None + self.trans = [] + self.id = None + self.accept = False + +class Context: + + def __init__(self): + self.next_id_ = 0 + self.root = Node() + + def next_id(self): + id = self.next_id_ + self.next_id_ += 1 + return id + +def _add(node, sym, bits): + if len(bits) == 0: + node.term = sym + return + else: + if bits[0] == '0': + if node.left is None: + node.left = Node() + child = node.left + else: + if node.right is None: + node.right = Node() + child = node.right + _add(child, sym, bits[1:]) + +def huffman_tree_add(ctx, sym, bits): + _add(ctx.root, sym, bits) + +def _set_node_id(ctx, node, prefix): + if node.term is not None: + return + if len(prefix) <= 7 and [1] * len(prefix) == prefix: + node.accept = True + node.id = ctx.next_id() + _set_node_id(ctx, node.left, prefix + [0]) + _set_node_id(ctx, node.right, prefix + [1]) + +def huffman_tree_set_node_id(ctx): + _set_node_id(ctx, ctx.root, []) + +def _traverse(node, sym, start_node, root, left): + if left == 0: + if sym == 256: + sym = None + node = None + start_node.trans.append((node, sym)) + return + + if node.term is not None: + node = root + + def go(node): + if node.term is not None: + assert sym is None + nsym = node.term + else: + nsym = sym + + _traverse(node, nsym, start_node, root, left - 1) + + go(node.left) + go(node.right) + +def _build_transition_table(ctx, node): + if node is None: + return + _traverse(node, None, node, ctx.root, 4) + _build_transition_table(ctx, node.left) + _build_transition_table(ctx, node.right) + +def huffman_tree_build_transition_table(ctx): + _build_transition_table(ctx, ctx.root) + +NGHTTP2_HUFF_ACCEPTED = 1 << 14 +NGHTTP2_HUFF_SYM = 1 << 15 + +def _print_transition_table(node): + if node.term is not None: + return + print('/* {} */'.format(node.id)) + print('{') + for nd, sym in node.trans: + flags = 0 + if sym is None: + out = 0 + else: + out = sym + flags |= NGHTTP2_HUFF_SYM + if nd is None: + id = 256 + else: + id = nd.id + if id is None: + # if nd.id is None, it is a leaf node + id = 0 + flags |= NGHTTP2_HUFF_ACCEPTED + elif nd.accept: + flags |= NGHTTP2_HUFF_ACCEPTED + print(' {{0x{:02x}, {}}},'.format(id | flags, out)) + print('},') + _print_transition_table(node.left) + _print_transition_table(node.right) + +def huffman_tree_print_transition_table(ctx): + _print_transition_table(ctx.root) + print('/* 256 */') + print('{') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print(' {0x100, 0},') + print('},') + +if __name__ == '__main__': + ctx = Context() + symbol_tbl = [(None, 0) for i in range(257)] + + for line in StringIO(HUFFMAN_CODE_TABLE): + m = re.match( + r'.*\(\s*(\d+)\)\s+([|01]+)\s+(\S+)\s+\[\s*(\d+)\].*', line) + if m: + sym = int(m.group(1)) + bits = re.sub(r'\|', '', m.group(2)) + code = m.group(3) + nbits = int(m.group(4)) + if len(code) > 8: + raise Error('Code is more than 4 bytes long') + assert(len(bits) == nbits) + symbol_tbl[sym] = (nbits, code) + huffman_tree_add(ctx, sym, bits) + + huffman_tree_set_node_id(ctx) + huffman_tree_build_transition_table(ctx) + + print('''\ +typedef struct { + uint32_t nbits; + uint32_t code; +} nghttp2_huff_sym; +''') + + print('''\ +const nghttp2_huff_sym huff_sym_table[] = {''') + for i in range(257): + nbits = symbol_tbl[i][0] + k = int(symbol_tbl[i][1], 16) + k = k << (32 - nbits) + print('''\ + {{ {}, 0x{}u }}{}\ +'''.format(symbol_tbl[i][0], hex(k)[2:], ',' if i < 256 else '')) + print('};') + print() + + print('''\ +enum {{ + NGHTTP2_HUFF_ACCEPTED = {}, + NGHTTP2_HUFF_SYM = {}, +}} nghttp2_huff_decode_flag; +'''.format(NGHTTP2_HUFF_ACCEPTED, NGHTTP2_HUFF_SYM)) + + print('''\ +typedef struct { + uint16_t fstate; + uint8_t sym; +} nghttp2_huff_decode; +''') + + print('''\ +const nghttp2_huff_decode huff_decode_table[][16] = {''') + huffman_tree_print_transition_table(ctx) + print('};') diff --git a/lib/nghttp2/mkstatichdtbl.py b/lib/nghttp2/mkstatichdtbl.py new file mode 100755 index 00000000000..20f0c153955 --- /dev/null +++ b/lib/nghttp2/mkstatichdtbl.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# This scripts reads static table entries [1] and generates +# nghttp2_hd_static_entry table. This table is used in +# lib/nghttp2_hd.c. +# +# [1] https://httpwg.org/specs/rfc7541.html + +import re, sys + +def hd_map_hash(name): + h = 2166136261 + + # FNV hash variant: http://isthe.com/chongo/tech/comp/fnv/ + for c in name: + h ^= ord(c) + h *= 16777619 + h &= 0xffffffff + + return h + +entries = [] +for line in sys.stdin: + m = re.match(r'(\d+)\s+(\S+)\s+(\S.*)?', line) + val = m.group(3).strip() if m.group(3) else '' + entries.append((int(m.group(1)), m.group(2), val)) + +print('static nghttp2_hd_entry static_table[] = {') +idx = 0 +for i, ent in enumerate(entries): + if entries[idx][1] != ent[1]: + idx = i + print('MAKE_STATIC_ENT("{}", "{}", {}, {}u),'\ + .format(ent[1], ent[2], entries[idx][0] - 1, hd_map_hash(ent[1]))) +print('};') diff --git a/lib/nghttp2/nghttp2-master.zip b/lib/nghttp2/nghttp2-master.zip new file mode 100644 index 00000000000..4b9bea30903 Binary files /dev/null and b/lib/nghttp2/nghttp2-master.zip differ diff --git a/lib/nghttp2/nghttp2-master/.github/dependabot.yml b/lib/nghttp2/nghttp2-master/.github/dependabot.yml new file mode 100644 index 00000000000..8c139c7bec2 --- /dev/null +++ b/lib/nghttp2/nghttp2-master/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/lib/nghttp2/nghttp2-master/.github/workflows/build.yml b/lib/nghttp2/nghttp2-master/.github/workflows/build.yml new file mode 100644 index 00000000000..f4106c85345 --- /dev/null +++ b/lib/nghttp2/nghttp2-master/.github/workflows/build.yml @@ -0,0 +1,482 @@ +name: build + +on: [push, pull_request] + +permissions: read-all + +env: + LIBBPF_VERSION: v1.3.0 + OPENSSL1_VERSION: 1_1_1w+quic + OPENSSL3_VERSION: 3.1.4+quic + BORINGSSL_VERSION: 6ca49385b168f47a50e7172d82a590b218f55e4d + NGHTTP3_VERSION: v1.1.0 + NGTCP2_VERSION: v1.1.0 + +jobs: + build-cache: + strategy: + matrix: + os: [ubuntu-22.04, macos-12] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - name: Restore libbpf cache + id: cache-libbpf + uses: actions/cache@v3 + if: runner.os == 'Linux' + with: + path: libbpf/build + key: ${{ runner.os }}-libbpf-${{ env.LIBBPF_VERSION }} + - name: Restore OpenSSL v1.1.1 cache + id: cache-openssl1 + uses: actions/cache@v3 + with: + path: openssl1/build + key: ${{ runner.os }}-openssl-${{ env.OPENSSL1_VERSION }} + - name: Restore OpenSSL v3.x cache + id: cache-openssl3 + uses: actions/cache@v3 + with: + path: openssl3/build + key: ${{ runner.os }}-openssl-${{ env.OPENSSL3_VERSION }} + - name: Restore BoringSSL cache + id: cache-boringssl + uses: actions/cache@v3 + with: + path: | + boringssl/build/crypto/libcrypto.a + boringssl/build/ssl/libssl.a + boringssl/include + key: ${{ runner.os }}-boringssl-${{ env.BORINGSSL_VERSION }} + - name: Restore nghttp3 cache + id: cache-nghttp3 + uses: actions/cache@v3 + with: + path: nghttp3/build + key: ${{ runner.os }}-nghttp3-${{ env.NGHTTP3_VERSION }} + - name: Restore ngtcp2 + quictls/openssl v1.1.1 cache + id: cache-ngtcp2-openssl1 + uses: actions/cache@v3 + with: + path: ngtcp2-openssl1/build + key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL1_VERSION }} + - name: Restore ngtcp2 + quictls/openssl v3.x cache + id: cache-ngtcp2-openssl3 + uses: actions/cache@v3 + with: + path: ngtcp2-openssl3/build + key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL3_VERSION }} + - id: settings + if: | + (steps.cache-libbpf.outputs.cache-hit != 'true' && runner.os == 'Linux') || + steps.cache-openssl1.outputs.cache-hit != 'true' || + steps.cache-openssl3.outputs.cache-hit != 'true' || + steps.cache-boringssl.outputs.cache-hit != 'true' || + steps.cache-nghttp3.outputs.cache-hit != 'true' || + steps.cache-ngtcp2-openssl1.outputs.cache-hit != 'true' || + steps.cache-ngtcp2-openssl3.outputs.cache-hit != 'true' + run: | + echo 'needs-build=true' >> $GITHUB_OUTPUT + - name: Linux setup + if: runner.os == 'Linux' && steps.settings.outputs.needs-build == 'true' + run: | + sudo apt-get install \ + g++-12 \ + clang-15 \ + autoconf \ + automake \ + autotools-dev \ + libtool \ + pkg-config \ + libelf-dev \ + cmake \ + cmake-data + - name: MacOS setup + if: runner.os == 'macOS' && steps.settings.outputs.needs-build == 'true' + run: | + brew install \ + autoconf \ + automake \ + pkg-config \ + libtool + - name: Build libbpf + if: steps.cache-libbpf.outputs.cache-hit != 'true' && runner.os == 'Linux' + run: | + git clone -b ${{ env.LIBBPF_VERSION }} https://github.com/libbpf/libbpf + cd libbpf + make -C src install PREFIX=$PWD/build + - name: Build quictls/openssl v1.1.1 + if: steps.cache-openssl1.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b OpenSSL_${{ env.OPENSSL1_VERSION }} https://github.com/quictls/openssl openssl1 + cd openssl1 + ./config --prefix=$PWD/build + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" + make install_sw + - name: Build quictls/openssl v3.x + if: steps.cache-openssl3.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b openssl-${{ env.OPENSSL3_VERSION }} https://github.com/quictls/openssl openssl3 + cd openssl3 + ./config enable-ktls --prefix=$PWD/build --libdir=$PWD/build/lib + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" + make install_sw + - name: Build BoringSSL + if: steps.cache-boringssl.outputs.cache-hit != 'true' + run: | + git clone https://boringssl.googlesource.com/boringssl + cd boringssl + git checkout ${{ env.BORINGSSL_VERSION }} + mkdir build + cd build + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" + - name: Build nghttp3 + if: steps.cache-nghttp3.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b ${{ env.NGHTTP3_VERSION}} https://github.com/ngtcp2/nghttp3 + cd nghttp3 + autoreconf -i + ./configure --prefix=$PWD/build --enable-lib-only + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check + make install + - name: Build ngtcp2 + quictls/openssl v1.1.1 + if: steps.cache-ngtcp2-openssl1.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b ${{ env.NGTCP2_VERSION }} https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl1 + cd ngtcp2-openssl1 + autoreconf -i + ./configure --prefix=$PWD/build --enable-lib-only \ + PKG_CONFIG_PATH="../openssl1/build/lib/pkgconfig" \ + BORINGSSL_CFLAGS="-I$PWD/../boringssl/include/" \ + BORINGSSL_LIBS="-L$PWD/../boringssl/build/ssl -lssl -L$PWD/../boringssl/build/crypto -lcrypto" \ + --with-boringssl + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check + make install + - name: Build ngtcp2 + quictls/openssl v3.x + if: steps.cache-ngtcp2-openssl3.outputs.cache-hit != 'true' + run: | + git clone --depth 1 -b ${{ env.NGTCP2_VERSION }} https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl3 + cd ngtcp2-openssl3 + autoreconf -i + ./configure --prefix=$PWD/build --enable-lib-only \ + PKG_CONFIG_PATH="../openssl3/build/lib/pkgconfig" \ + BORINGSSL_CFLAGS="-I$PWD/../boringssl/include/" \ + BORINGSSL_LIBS="-L$PWD/../boringssl/build/ssl -lssl -L$PWD/../boringssl/build/crypto -lcrypto" \ + --with-boringssl + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check + make install + + build: + needs: + - build-cache + + strategy: + matrix: + os: [ubuntu-22.04, macos-12] + compiler: [gcc, clang] + buildtool: [autotools, cmake] + http3: [http3, no-http3] + openssl: [openssl1, openssl3, boringssl] + exclude: + - os: macos-12 + openssl: openssl3 + - http3: no-http3 + openssl: openssl3 + - os: macos-12 + compiler: gcc + - # disable macos cmake because of include path issue + os: macos-12 + buildtool: cmake + - os: macos-12 + openssl: boringssl + - openssl: boringssl + buildtool: cmake + - openssl: boringssl + compiler: gcc + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + - name: Linux setup + if: runner.os == 'Linux' + run: | + sudo apt-get install \ + g++-12 \ + clang-15 \ + autoconf \ + automake \ + autotools-dev \ + libtool \ + pkg-config \ + zlib1g-dev \ + libcunit1-dev \ + libssl-dev \ + libxml2-dev \ + libev-dev \ + libevent-dev \ + libjansson-dev \ + libjemalloc-dev \ + libc-ares-dev \ + libelf-dev \ + cmake \ + cmake-data + echo 'CPPFLAGS=-fsanitize=address,undefined -fno-sanitize-recover=undefined -g' >> $GITHUB_ENV + echo 'LDFLAGS=-fsanitize=address,undefined -fno-sanitize-recover=undefined' >> $GITHUB_ENV + - name: MacOS setup + if: runner.os == 'macOS' + run: | + brew install \ + libev \ + libevent \ + c-ares \ + cunit \ + libressl \ + autoconf \ + automake \ + pkg-config \ + libtool + echo 'PKG_CONFIG_PATH=/usr/local/opt/libressl/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig' >> $GITHUB_ENV + - name: Setup clang (Linux) + if: runner.os == 'Linux' && matrix.compiler == 'clang' + run: | + echo 'CC=clang-15' >> $GITHUB_ENV + echo 'CXX=clang++-15' >> $GITHUB_ENV + - name: Setup clang (MacOS) + if: runner.os == 'macOS' && matrix.compiler == 'clang' + run: | + echo 'CC=clang' >> $GITHUB_ENV + echo 'CXX=clang++' >> $GITHUB_ENV + - name: Setup gcc (Linux) + if: runner.os == 'Linux' && matrix.compiler == 'gcc' + run: | + echo 'CC=gcc-12' >> $GITHUB_ENV + echo 'CXX=g++-12' >> $GITHUB_ENV + - name: Setup gcc (MacOS) + if: runner.os == 'macOS' && matrix.compiler == 'gcc' + run: | + echo 'CC=gcc' >> $GITHUB_ENV + echo 'CXX=g++' >> $GITHUB_ENV + - name: Restore libbpf cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && matrix.compiler == 'clang' && runner.os == 'Linux' + with: + path: libbpf/build + key: ${{ runner.os }}-libbpf-${{ env.LIBBPF_VERSION }} + fail-on-cache-miss: true + - name: Set libbpf variables + if: matrix.http3 == 'http3' && matrix.compiler == 'clang' && runner.os == 'Linux' + run: | + cd libbpf + + EXTRA_AUTOTOOLS_OPTS="--with-libbpf" + EXTRA_CMAKE_OPTS="-DWITH_LIBBPF=1" + + echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV + echo 'EXTRA_CMAKE_OPTS='"$EXTRA_CMAKE_OPTS" >> $GITHUB_ENV + - name: Restore quictls/openssl v1.1.1 cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && matrix.openssl == 'openssl1' + with: + path: openssl1/build + key: ${{ runner.os }}-openssl-${{ env.OPENSSL1_VERSION }} + fail-on-cache-miss: true + - name: Restore quictls/openssl v3.x cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && matrix.openssl == 'openssl3' + with: + path: openssl3/build + key: ${{ runner.os }}-openssl-${{ env.OPENSSL3_VERSION }} + fail-on-cache-miss: true + - name: Restore BoringSSL cache + uses: actions/cache/restore@v3 + if: matrix.openssl == 'boringssl' + with: + path: | + boringssl/build/crypto/libcrypto.a + boringssl/build/ssl/libssl.a + boringssl/include + key: ${{ runner.os }}-boringssl-${{ env.BORINGSSL_VERSION }} + fail-on-cache-miss: true + - name: Set BoringSSL variables + if: matrix.openssl == 'boringssl' + run: | + cd boringssl + + OPENSSL_CFLAGS="-I$PWD/include/" + OPENSSL_LIBS="-L$PWD/build/ssl -lssl -L$PWD/build/crypto -lcrypto -pthread" + EXTRA_AUTOTOOLS_OPTS="$EXTRA_AUTOTOOLS_OPTS --without-neverbleed --without-jemalloc" + + echo 'OPENSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV + echo 'OPENSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV + echo 'BORINGSSL_CFLAGS='"$OPENSSL_CFLAGS" >> $GITHUB_ENV + echo 'BORINGSSL_LIBS='"$OPENSSL_LIBS" >> $GITHUB_ENV + echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV + - name: Restore nghttp3 cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' + with: + path: nghttp3/build + key: ${{ runner.os }}-nghttp3-${{ env.NGHTTP3_VERSION }} + fail-on-cache-miss: true + - name: Restore ngtcp2 + quictls/openssl v1.1.1 cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && (matrix.openssl == 'openssl1' || matrix.openssl == 'boringssl') + with: + path: ngtcp2-openssl1/build + key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL1_VERSION }} + fail-on-cache-miss: true + - name: Restore ngtcp2 + quictls/openssl v3.x cache + uses: actions/cache/restore@v3 + if: matrix.http3 == 'http3' && matrix.openssl == 'openssl3' + with: + path: ngtcp2-openssl3/build + key: ${{ runner.os }}-ngtcp2-${{ env.NGTCP2_VERSION }}-openssl-${{ env.OPENSSL3_VERSION }} + fail-on-cache-miss: true + - name: Setup extra environment variables for HTTP/3 + if: matrix.http3 == 'http3' + run: | + PKG_CONFIG_PATH="$PWD/openssl1/build/lib/pkgconfig:$PWD/openssl3/build/lib/pkgconfig:$PWD/nghttp3/build/lib/pkgconfig:$PWD/ngtcp2-openssl1/build/lib/pkgconfig:$PWD/ngtcp2-openssl3/build/lib/pkgconfig:$PWD/libbpf/build/lib64/pkgconfig:$PKG_CONFIG_PATH" + LDFLAGS="$LDFLAGS -Wl,-rpath,$PWD/openssl1/build/lib -Wl,-rpath,$PWD/openssl3/build/lib -Wl,-rpath,$PWD/libbpf/build/lib64" + EXTRA_AUTOTOOLS_OPTS="--enable-http3 $EXTRA_AUTOTOOLS_OPTS" + EXTRA_CMAKE_OPTS="-DENABLE_HTTP3=1 $EXTRA_CMAKE_OPTS" + + echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV + echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV + echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV + echo 'EXTRA_CMAKE_OPTS='"$EXTRA_CMAKE_OPTS" >> $GITHUB_ENV + - name: Setup git submodules + run: | + git submodule update --init + - name: Configure autotools + run: | + autoreconf -i + ./configure + - name: Configure cmake (Linux) + if: matrix.buildtool == 'cmake' && runner.os == 'Linux' + run: | + make dist + VERSION=$(grep PACKAGE_VERSION config.h | cut -d' ' -f3 | tr -d '"') + tar xf nghttp2-$VERSION.tar.gz + cd nghttp2-$VERSION + echo 'NGHTTP2_CMAKE_DIR='"$PWD" >> $GITHUB_ENV + + cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DWITH_NEVERBLEED=1 -DENABLE_APP=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" . + - name: Configure cmake (MacOS) + if: matrix.buildtool == 'cmake' && runner.os == 'macOS' + run: | + make dist + VERSION=$(grep PACKAGE_VERSION config.h | cut -d' ' -f3 | tr -d '"') + tar xf nghttp2-$VERSION.tar.gz + cd nghttp2-$VERSION + echo 'NGHTTP2_CMAKE_DIR='"$PWD" >> $GITHUB_ENV + + # This fixes infamous 'stdio.h not found' error. + echo 'SDKROOT='"$(xcrun --sdk macosx --show-sdk-path)" >> $GITHUB_ENV + + cmake -DENABLE_WERROR=1 -DWITH_MRUBY=1 -DENABLE_APP=1 $EXTRA_CMAKE_OPTS -DCPPFLAGS="$CPPFLAGS" -DLDFLAGS="$LDFLAGS" . + - name: Build nghttp2 with autotools (Linux) + if: matrix.buildtool == 'autotools' && runner.os == 'Linux' + run: | + make -j"$(nproc)" distcheck \ + DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --with-libev --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\"" + - name: Build nghttp2 with autotools (MacOS) + if: matrix.buildtool == 'autotools' && runner.os == 'macOS' + run: | + make -j"$(sysctl -n hw.ncpu)" distcheck \ + DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-libev --enable-werror $EXTRA_AUTOTOOLS_OPTS CPPFLAGS=\"$CPPFLAGS\" LDFLAGS=\"$LDFLAGS\"" + - name: Build nghttp2 with cmake + if: matrix.buildtool == 'cmake' + run: | + cd $NGHTTP2_CMAKE_DIR + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" + make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check + - uses: actions/setup-go@v5 + if: matrix.buildtool == 'cmake' + with: + go-version-file: go.mod + - name: Integration test + # Integration tests for nghttpx; autotools erases build + # artifacts. + if: matrix.buildtool == 'cmake' + run: | + cd $NGHTTP2_CMAKE_DIR/integration-tests + make it + + build-cross: + strategy: + matrix: + host: [x86_64-w64-mingw32, i686-w64-mingw32] + + runs-on: ubuntu-22.04 + + env: + HOST: ${{ matrix.host }} + + steps: + - uses: actions/checkout@v4 + - name: Linux setup + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install \ + gcc-mingw-w64 \ + autoconf \ + automake \ + autotools-dev \ + libtool \ + pkg-config \ + wine + - name: Build CUnit + run: | + curl -LO https://jaist.dl.sourceforge.net/project/cunit/CUnit/2.1-3/CUnit-2.1-3.tar.bz2 + tar xf CUnit-2.1-3.tar.bz2 + cd CUnit-2.1-3 + ./bootstrap + ./configure --disable-shared --host="$HOST" --prefix="$PWD/build" + make -j$(nproc) install + - name: Configure autotools + run: | + autoreconf -i && \ + ./configure --enable-werror --enable-lib-only --with-cunit \ + --host="$HOST" PKG_CONFIG_PATH="$PWD/CUnit-2.1-3/build/lib/pkgconfig" \ + CFLAGS="-g -O2 -D_WIN32_WINNT=0x0600" + - name: Build nghttp2 + run: | + make -j$(nproc) + make -j$(nproc) check TESTS="" + - name: Run tests + if: matrix.host == 'x86_64-w64-mingw32' + run: | + cd tests + wine main.exe + + build-windows: + strategy: + matrix: + arch: [x86, x64] + include: + - arch: x86 + platform: Win32 + - arch: x64 + platform: x64 + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + - uses: microsoft/setup-msbuild@v1 + - run: | + vcpkg --triplet=${{ matrix.arch }}-windows install cunit + - name: Configure cmake + run: | + mkdir build + cd build + cmake -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_GENERATOR_PLATFORM=${{ matrix.platform }} -DVCPKG_TARGET_TRIPLET=${{ matrix.arch}}-windows .. + - name: Build nghttp2 + run: | + cmake --build build + cmake --build build --target check diff --git a/lib/nghttp2/nghttp2-master/.github/workflows/fuzz.yml b/lib/nghttp2/nghttp2-master/.github/workflows/fuzz.yml new file mode 100644 index 00000000000..720b25fff01 --- /dev/null +++ b/lib/nghttp2/nghttp2-master/.github/workflows/fuzz.yml @@ -0,0 +1,24 @@ +name: CIFuzz +on: [pull_request] +permissions: read-all +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'nghttp2' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'nghttp2' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v4 + if: failure() + with: + name: artifacts + path: ./out/artifacts diff --git a/lib/nghttp2/nghttp2-master/.gitignore b/lib/nghttp2/nghttp2-master/.gitignore new file mode 100644 index 00000000000..8c089da4031 --- /dev/null +++ b/lib/nghttp2/nghttp2-master/.gitignore @@ -0,0 +1,56 @@ +# emacs backup file +*~ + +# autotools +*.la +*.lo +*.m4 +*.o +*.pyc +.deps/ +.libs/ +INSTALL +Makefile +Makefile.in +autom4te.cache/ +compile +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +missing +stamp-h1 +test-driver + +# cmake +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt +CTestTestfile.cmake +build.ninja +rules.ninja +.ninja_deps +.ninja_log +lib*.so +lib*.so.* +lib*.a +# generated by "make test" with cmake +Testing/ + +# test logs generated by `make check` +*.log +*.trs + +lib/MSVC_obj/ +_VC_ROOT/ +.depend.MSVC +*.pyd +*.egg-info/ diff --git a/lib/nghttp2/nghttp2-master/.gitmodules b/lib/nghttp2/nghttp2-master/.gitmodules new file mode 100644 index 00000000000..393c80881d4 --- /dev/null +++ b/lib/nghttp2/nghttp2-master/.gitmodules @@ -0,0 +1,7 @@ +[submodule "third-party/mruby"] + path = third-party/mruby + url = https://github.com/mruby/mruby +[submodule "third-party/neverbleed"] + path = third-party/neverbleed + url = https://github.com/tatsuhiro-t/neverbleed.git + branch = nghttp2 diff --git a/lib/nghttp2/nghttpx.conf.sample b/lib/nghttp2/nghttpx.conf.sample new file mode 100644 index 00000000000..97d79543910 --- /dev/null +++ b/lib/nghttp2/nghttpx.conf.sample @@ -0,0 +1,29 @@ +# +# Sample configuration file for nghttpx. +# +# * Line staring '#' is treated as comment. +# +# * The option name in the configuration file is the long command-line +# option name with leading '--' stripped (e.g., frontend). Put '=' +# between option name and value. Don't put extra leading or trailing +# spaces. +# +# * The options which do not take argument in the command-line *take* +# argument in the configuration file. Specify 'yes' as argument +# (e.g., http2-proxy=yes). If other string is given, it disables the +# option. +# +# * To specify private key and certificate file, use private-key-file +# and certificate-file. See the examples below. +# +# * conf option cannot be used in the configuration file. It will be +# ignored. +# +# Examples: +# +# frontend=0.0.0.0,3000 +# backend=127.0.0.1,80 +# private-key-file=/path/to/server.key +# certificate-file=/path/to/server.crt +# http2-proxy=no +# workers=1 diff --git a/lib/nghttp2/pre-commit b/lib/nghttp2/pre-commit new file mode 100755 index 00000000000..1dfe1f548be --- /dev/null +++ b/lib/nghttp2/pre-commit @@ -0,0 +1,27 @@ +#!/bin/sh -e +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# + +CLANGFORMATDIFF=`git config --get clangformatdiff.binary` + +if [ -z "$CLANGFORMATDIFF" ]; then + CLANGFORMATDIFF=clang-format-diff.py +fi + +errors=`git diff-index --cached --diff-filter=ACMR -p HEAD lib src examples tests | $CLANGFORMATDIFF -p1` + +if [ -n "$errors" ]; then + echo "$errors" + echo "--" + echo "[ERROR] We have detected the difference between the code to commit" + echo "and clang-format style rules. Please fix this problem in either:" + echo "1) Apply patch above." + echo "2) Use clang-format to format lines." + echo "3) Reformat these lines manually." + echo "Aborting commit." + exit 1 +fi diff --git a/lib/nghttp2/proxy.pac.sample b/lib/nghttp2/proxy.pac.sample new file mode 100644 index 00000000000..9283920b7f0 --- /dev/null +++ b/lib/nghttp2/proxy.pac.sample @@ -0,0 +1,6 @@ +function FindProxyForURL(url, host) { + // For SPDY proxy + return "HTTPS localhost:3000"; + // For conventional HTTP proxy + // return "PROXY localhost:3000"; +} diff --git a/lib/nghttp2/releasechk b/lib/nghttp2/releasechk new file mode 100755 index 00000000000..0c05cca6895 --- /dev/null +++ b/lib/nghttp2/releasechk @@ -0,0 +1,6 @@ +#!/bin/sh -e + +autoreconf -i +git submodule update --init +./configure --with-mruby --with-neverbleed +make -j8 distcheck DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --enable-werror" diff --git a/lib/nghttp2/script/CMakeLists.txt b/lib/nghttp2/script/CMakeLists.txt new file mode 100644 index 00000000000..f89885857fe --- /dev/null +++ b/lib/nghttp2/script/CMakeLists.txt @@ -0,0 +1,5 @@ +# EXTRA_DIST = README.rst +install( + PROGRAMS fetch-ocsp-response + DESTINATION "${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}" +) diff --git a/lib/nghttp2/script/Makefile.am b/lib/nghttp2/script/Makefile.am new file mode 100644 index 00000000000..387f33c5e96 --- /dev/null +++ b/lib/nghttp2/script/Makefile.am @@ -0,0 +1,25 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2015 Tatsuhiro Tsujikawa + +# 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 = README.rst CMakeLists.txt +dist_pkgdata_SCRIPTS = fetch-ocsp-response diff --git a/lib/nghttp2/script/README.rst b/lib/nghttp2/script/README.rst new file mode 100644 index 00000000000..a9f773771ec --- /dev/null +++ b/lib/nghttp2/script/README.rst @@ -0,0 +1,10 @@ +fetch-ocsp-response is a Python script which performs OCSP query and +get response. It uses openssl command under the hood. nghttpx uses +it to enable OCSP stapling feature. + +fetch-ocsp-response is a translation from original fetch-ocsp-response +written in Perl and which has been developed as part of h2o project +(https://github.com/h2o/h2o). + +fetch-ocsp-response is usually installed under $(pkgdatadir), which is +$(prefix)/share/nghttp2. diff --git a/lib/nghttp2/script/fetch-ocsp-response b/lib/nghttp2/script/fetch-ocsp-response new file mode 100755 index 00000000000..0ff7461ee2e --- /dev/null +++ b/lib/nghttp2/script/fetch-ocsp-response @@ -0,0 +1,253 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2015 Tatsuhiro Tsujikawa + +# 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. + +# This program was translated from the program originally developed by +# h2o project (https://github.com/h2o/h2o), written in Perl. It had +# the following copyright notice: + +# Copyright (c) 2015 DeNA Co., Ltd. +# +# 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. + +from __future__ import unicode_literals +import argparse +import io +import os +import os.path +import re +import shutil +import subprocess +import sys +import tempfile + +# make this program work for both Python 3 and Python 2. +try: + from urllib.parse import urlparse + stdout_bwrite = sys.stdout.buffer.write +except ImportError: + from urlparse import urlparse + stdout_bwrite = sys.stdout.write + + +def die(msg): + sys.stderr.write(msg) + sys.stderr.write('\n') + sys.exit(255) + + +def tempfail(msg): + sys.stderr.write(msg) + sys.stderr.write('\n') + sys.exit(os.EX_TEMPFAIL) + + +def run_openssl(args, allow_tempfail=False): + buf = io.BytesIO() + try: + p = subprocess.Popen(args, stdout=subprocess.PIPE) + except Exception as e: + die('failed to invoke {}:{}'.format(args, e)) + try: + while True: + data = p.stdout.read() + if len(data) == 0: + break + buf.write(data) + if p.wait() != 0: + raise Exception('nonzero return code {}'.format(p.returncode)) + return buf.getvalue() + except Exception as e: + msg = 'OpenSSL exitted abnormally: {}:{}'.format(args, e) + tempfail(msg) if allow_tempfail else die(msg) + + +def read_file(path): + with open(path, 'rb') as f: + return f.read() + + +def write_file(path, data): + with open(path, 'wb') as f: + f.write(data) + + +def detect_openssl_version(cmd): + return run_openssl([cmd, 'version']).decode('utf-8').strip() + + +def extract_ocsp_uri(cmd, cert_fn): + # obtain ocsp uri + ocsp_uri = run_openssl( + [cmd, 'x509', '-in', cert_fn, '-noout', + '-ocsp_uri']).decode('utf-8').strip() + + if not re.match(r'^https?://', ocsp_uri): + die('failed to extract ocsp URI from {}'.format(cert_fn)) + + return ocsp_uri + + +def save_issuer_certificate(issuer_fn, cert_fn): + # save issuer certificate + chain = read_file(cert_fn).decode('utf-8') + m = re.match( + r'.*?-----END CERTIFICATE-----.*?(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----)', + chain, re.DOTALL) + if not m: + die('--issuer option was not used, and failed to extract issuer certificate from the certificate') + write_file(issuer_fn, (m.group(1) + '\n').encode('utf-8')) + + +def send_and_receive_ocsp(respder_fn, cmd, cert_fn, issuer_fn, ocsp_uri, + ocsp_host, openssl_version): + # obtain response (without verification) + sys.stderr.write('sending OCSP request to {}\n'.format(ocsp_uri)) + args = [ + cmd, 'ocsp', '-issuer', issuer_fn, '-cert', cert_fn, '-url', ocsp_uri, + '-noverify', '-respout', respder_fn + ] + ver = openssl_version.lower() + if ver.startswith('openssl 1.0.') or ver.startswith('libressl '): + args.extend(['-header', 'Host', ocsp_host]) + resp = run_openssl(args, allow_tempfail=True) + return resp.decode('utf-8') + + +def verify_response(cmd, tempdir, issuer_fn, respder_fn): + # verify the response + sys.stderr.write('verifying the response signature\n') + + verify_fn = os.path.join(tempdir, 'verify.out') + + # try from exotic options + allextra = [ + # for comodo + ['-VAfile', issuer_fn], + # these options are only available in OpenSSL >= 1.0.2 + ['-partial_chain', '-trusted_first', '-CAfile', issuer_fn], + # for OpenSSL <= 1.0.1 + ['-CAfile', issuer_fn], + ] + + for extra in allextra: + with open(verify_fn, 'w+b') as f: + args = [cmd, 'ocsp', '-respin', respder_fn] + args.extend(extra) + p = subprocess.Popen(args, stdout=f, stderr=f) + if p.wait() == 0: + # OpenSSL <= 1.0.1, openssl ocsp still returns exit + # code 0 even if verification was failed. So check + # the error message in stderr output. + f.seek(0) + if f.read().decode('utf-8').find( + 'Response Verify Failure') != -1: + continue + sys.stderr.write('verify OK (used: {})\n'.format(extra)) + return True + + sys.stderr.write(read_file(verify_fn).decode('utf-8')) + return False + + +def fetch_ocsp_response(cmd, cert_fn, tempdir, issuer_fn=None): + openssl_version = detect_openssl_version(cmd) + + sys.stderr.write( + 'fetch-ocsp-response (using {})\n'.format(openssl_version)) + + ocsp_uri = extract_ocsp_uri(cmd, cert_fn) + ocsp_host = urlparse(ocsp_uri).netloc + + if not issuer_fn: + issuer_fn = os.path.join(tempdir, 'issuer.crt') + save_issuer_certificate(issuer_fn, cert_fn) + + respder_fn = os.path.join(tempdir, 'resp.der') + resp = send_and_receive_ocsp( + respder_fn, cmd, cert_fn, issuer_fn, ocsp_uri, ocsp_host, + openssl_version) + + sys.stderr.write('{}\n'.format(resp)) + + # OpenSSL 1.0.2 still returns exit code 0 even if ocsp responder + # returned error status (e.g., trylater(3)) + if resp.find('Responder Error:') != -1: + raise Exception('responder returned error') + + if not verify_response(cmd, tempdir, issuer_fn, respder_fn): + tempfail('failed to verify the response') + + # success + res = read_file(respder_fn) + stdout_bwrite(res) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description= + '''The command issues an OCSP request for given server certificate, verifies the response and prints the resulting DER.''', + epilog= + '''The command exits 0 if successful, or 75 (EX_TEMPFAIL) on temporary error. Other exit codes may be returned in case of hard errors.''') + parser.add_argument( + '--issuer', + metavar='FILE', + help= + 'issuer certificate (if omitted, is extracted from the certificate chain)') + parser.add_argument('--openssl', + metavar='CMD', + help='openssl command to use (default: "openssl")', + default='openssl') + parser.add_argument('certificate', + help='path to certificate file to validate') + args = parser.parse_args() + + tempdir = None + try: + # Python3.2 has tempfile.TemporaryDirectory, which has nice + # feature to delete its tree by cleanup() function. We have + # to support Python2.7, so we have to do this manually. + tempdir = tempfile.mkdtemp() + fetch_ocsp_response(args.openssl, args.certificate, tempdir, + args.issuer) + finally: + if tempdir: + shutil.rmtree(tempdir) diff --git a/lib/nghttp2/src/.gitignore b/lib/nghttp2/src/.gitignore new file mode 100644 index 00000000000..27631a77d6a --- /dev/null +++ b/lib/nghttp2/src/.gitignore @@ -0,0 +1,13 @@ +# programs +deflatehd +h2load +inflatehd +nghttp +nghttpd +nghttpx + +# build +libnghttpx.a + +# tests +nghttpx-unittest diff --git a/lib/nghttp2/src/CMakeLists.txt b/lib/nghttp2/src/CMakeLists.txt new file mode 100644 index 00000000000..201c5a2d511 --- /dev/null +++ b/lib/nghttp2/src/CMakeLists.txt @@ -0,0 +1,243 @@ +file(GLOB c_sources *.c) +set_source_files_properties(${c_sources} PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}") +file(GLOB cxx_sources *.cc) +set_source_files_properties(${cxx_sources} PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} ${CXX1XCXXFLAGS}") + +include_directories( + "${CMAKE_CURRENT_SOURCE_DIR}/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../third-party" + "${CMAKE_CURRENT_SOURCE_DIR}/../third-party/llhttp/include" + + ${JEMALLOC_INCLUDE_DIRS} + ${LIBXML2_INCLUDE_DIRS} + ${LIBEV_INCLUDE_DIRS} + ${LIBNGHTTP3_INCLUDE_DIRS} + ${LIBNGTCP2_INCLUDE_DIRS} + ${LIBNGTCP2_CRYPTO_QUICTLS_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} + ${LIBCARES_INCLUDE_DIRS} + ${JANSSON_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIRS} + ${LIBBPF_INCLUDE_DIRS} +) + +# XXX per-target? +link_libraries( + nghttp2 + ${JEMALLOC_LIBRARIES} + ${LIBXML2_LIBRARIES} + ${LIBEV_LIBRARIES} + ${LIBNGHTTP3_LIBRARIES} + ${LIBNGTCP2_LIBRARIES} + ${LIBNGTCP2_CRYPTO_QUICTLS_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${LIBCARES_LIBRARIES} + ${JANSSON_LIBRARIES} + ${ZLIB_LIBRARIES} + ${APP_LIBRARIES} + ${LIBBPF_LIBRARIES} +) + +if(ENABLE_APP) + set(HELPER_OBJECTS + util.cc + http2.cc timegm.c app_helper.cc nghttp2_gzip.c + ) + + # nghttp client + set(NGHTTP_SOURCES + ${HELPER_OBJECTS} + nghttp.cc + tls.cc + ) + if(HAVE_LIBXML2) + list(APPEND NGHTTP_SOURCES HtmlParser.cc) + endif() + + # nghttpd + set(NGHTTPD_SOURCES + ${HELPER_OBJECTS} + nghttpd.cc + tls.cc + HttpServer.cc + ) + + # h2load + set(H2LOAD_SOURCES + util.cc + http2.cc h2load.cc + timegm.c + tls.cc + h2load_http2_session.cc + h2load_http1_session.cc + ) + if(ENABLE_HTTP3) + list(APPEND H2LOAD_SOURCES + h2load_http3_session.cc + h2load_quic.cc + quic.cc + ) + endif() + + # Common libnhttpx sources (used for nghttpx and unit tests) + set(NGHTTPX_SRCS + util.cc http2.cc timegm.c + app_helper.cc + tls.cc + shrpx_config.cc + shrpx_accept_handler.cc + shrpx_connection_handler.cc + shrpx_client_handler.cc + shrpx_http2_upstream.cc + shrpx_https_upstream.cc + shrpx_downstream.cc + shrpx_downstream_connection.cc + shrpx_http_downstream_connection.cc + shrpx_http2_downstream_connection.cc + shrpx_http2_session.cc + shrpx_downstream_queue.cc + shrpx_log.cc + shrpx_http.cc + shrpx_io_control.cc + shrpx_tls.cc + shrpx_worker.cc + shrpx_log_config.cc + shrpx_connect_blocker.cc + shrpx_live_check.cc + shrpx_downstream_connection_pool.cc + shrpx_rate_limit.cc + shrpx_connection.cc + shrpx_memcached_dispatcher.cc + shrpx_memcached_connection.cc + shrpx_worker_process.cc + shrpx_signal.cc + shrpx_router.cc + shrpx_api_downstream_connection.cc + shrpx_health_monitor_downstream_connection.cc + shrpx_null_downstream_connection.cc + shrpx_exec.cc + shrpx_dns_resolver.cc + shrpx_dual_dns_resolver.cc + shrpx_dns_tracker.cc + xsi_strerror.c + ) + if(HAVE_MRUBY) + list(APPEND NGHTTPX_SRCS + shrpx_mruby.cc + shrpx_mruby_module.cc + shrpx_mruby_module_env.cc + shrpx_mruby_module_request.cc + shrpx_mruby_module_response.cc + ) + endif() + if(ENABLE_HTTP3) + list(APPEND NGHTTPX_SRCS + shrpx_quic.cc + shrpx_quic_listener.cc + shrpx_quic_connection_handler.cc + shrpx_http3_upstream.cc + http3.cc + quic.cc + ) + endif() + add_library(nghttpx_static STATIC ${NGHTTPX_SRCS}) + set_target_properties(nghttpx_static PROPERTIES ARCHIVE_OUTPUT_NAME nghttpx) + + set(NGHTTPX-bin_SOURCES + shrpx.cc + ) + + if(HAVE_SYSTEMD) + target_link_libraries(nghttpx_static ${SYSTEMD_LIBRARIES}) + target_compile_definitions(nghttpx_static PUBLIC HAVE_LIBSYSTEMD) + target_include_directories(nghttpx_static PUBLIC ${SYSTEMD_INCLUDE_DIRS}) + endif() + + if(HAVE_MRUBY) + target_link_libraries(nghttpx_static mruby-lib) + endif() + + if(HAVE_NEVERBLEED) + target_link_libraries(nghttpx_static neverbleed) + endif() + + + if(HAVE_CUNIT) + set(NGHTTPX_UNITTEST_SOURCES + shrpx-unittest.cc + shrpx_tls_test.cc + shrpx_downstream_test.cc + shrpx_config_test.cc + shrpx_worker_test.cc + shrpx_http_test.cc + shrpx_router_test.cc + http2_test.cc + util_test.cc + nghttp2_gzip_test.c + nghttp2_gzip.c + buffer_test.cc + memchunk_test.cc + template_test.cc + base64_test.cc + ) + add_executable(nghttpx-unittest EXCLUDE_FROM_ALL + ${NGHTTPX_UNITTEST_SOURCES} + $ + $ + ) + target_include_directories(nghttpx-unittest PRIVATE ${CUNIT_INCLUDE_DIRS}) + target_compile_definitions(nghttpx-unittest + PRIVATE "-DNGHTTP2_SRC_DIR=\"${CMAKE_SOURCE_DIR}/src\"" + ) + target_link_libraries(nghttpx-unittest nghttpx_static ${CUNIT_LIBRARIES}) + if(HAVE_MRUBY) + target_link_libraries(nghttpx-unittest mruby-lib) + endif() + if(HAVE_NEVERBLEED) + target_link_libraries(nghttpx-unittest neverbleed) + endif() + + add_test(nghttpx-unittest nghttpx-unittest) + add_dependencies(check nghttpx-unittest) + endif() + + add_executable(nghttp ${NGHTTP_SOURCES} $ + $ + ) + add_executable(nghttpd ${NGHTTPD_SOURCES} $ + $ + ) + add_executable(nghttpx ${NGHTTPX-bin_SOURCES} $ + $ + ) + target_compile_definitions(nghttpx PRIVATE + "-DPKGDATADIR=\"${PKGDATADIR}\"" + "-DPKGLIBDIR=\"${PKGLIBDIR}\"" + ) + target_link_libraries(nghttpx nghttpx_static) + add_executable(h2load ${H2LOAD_SOURCES} $ + $ + ) + + install(TARGETS nghttp nghttpd nghttpx h2load + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() + +if(ENABLE_HPACK_TOOLS) + set(inflatehd_SOURCES + inflatehd.cc + comp_helper.c + ) + set(deflatehd_SOURCES + deflatehd.cc + comp_helper.c + util.cc + timegm.c + ) + add_executable(inflatehd ${inflatehd_SOURCES}) + add_executable(deflatehd ${deflatehd_SOURCES}) + install(TARGETS inflatehd deflatehd + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() diff --git a/lib/nghttp2/src/HtmlParser.cc b/lib/nghttp2/src/HtmlParser.cc new file mode 100644 index 00000000000..591c4c7e020 --- /dev/null +++ b/lib/nghttp2/src/HtmlParser.cc @@ -0,0 +1,217 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "HtmlParser.h" + +#include + +#include "util.h" + +namespace nghttp2 { + +ParserData::ParserData(const std::string &base_uri) + : base_uri(base_uri), inside_head(0) {} + +HtmlParser::HtmlParser(const std::string &base_uri) + : base_uri_(base_uri), parser_ctx_(nullptr), parser_data_(base_uri) {} + +HtmlParser::~HtmlParser() { htmlFreeParserCtxt(parser_ctx_); } + +namespace { +StringRef get_attr(const xmlChar **attrs, const StringRef &name) { + if (attrs == nullptr) { + return StringRef{}; + } + for (; *attrs; attrs += 2) { + if (util::strieq(StringRef{attrs[0], strlen(reinterpret_cast( + attrs[0]))}, + name)) { + return StringRef{attrs[1], + strlen(reinterpret_cast(attrs[1]))}; + } + } + return StringRef{}; +} +} // namespace + +namespace { +ResourceType +get_resource_type_for_preload_as(const StringRef &attribute_value) { + if (util::strieq_l("image", attribute_value)) { + return REQ_IMG; + } else if (util::strieq_l("style", attribute_value)) { + return REQ_CSS; + } else if (util::strieq_l("script", attribute_value)) { + return REQ_UNBLOCK_JS; + } else { + return REQ_OTHERS; + } +} +} // namespace + +namespace { +void add_link(ParserData *parser_data, const StringRef &uri, + ResourceType res_type) { + auto u = xmlBuildURI( + reinterpret_cast(uri.c_str()), + reinterpret_cast(parser_data->base_uri.c_str())); + if (u) { + parser_data->links.push_back( + std::make_pair(reinterpret_cast(u), res_type)); + free(u); + } +} +} // namespace + +namespace { +void start_element_func(void *user_data, const xmlChar *src_name, + const xmlChar **attrs) { + auto parser_data = static_cast(user_data); + auto name = + StringRef{src_name, strlen(reinterpret_cast(src_name))}; + if (util::strieq_l("head", name)) { + ++parser_data->inside_head; + } + if (util::strieq_l("link", name)) { + auto rel_attr = get_attr(attrs, StringRef::from_lit("rel")); + auto href_attr = get_attr(attrs, StringRef::from_lit("href")); + if (rel_attr.empty() || href_attr.empty()) { + return; + } + if (util::strieq_l("shortcut icon", rel_attr)) { + add_link(parser_data, href_attr, REQ_OTHERS); + } else if (util::strieq_l("stylesheet", rel_attr)) { + add_link(parser_data, href_attr, REQ_CSS); + } else if (util::strieq_l("preload", rel_attr)) { + auto as_attr = get_attr(attrs, StringRef::from_lit("as")); + if (as_attr.empty()) { + return; + } + add_link(parser_data, href_attr, + get_resource_type_for_preload_as(as_attr)); + } + } else if (util::strieq_l("img", name)) { + auto src_attr = get_attr(attrs, StringRef::from_lit("src")); + if (src_attr.empty()) { + return; + } + add_link(parser_data, src_attr, REQ_IMG); + } else if (util::strieq_l("script", name)) { + auto src_attr = get_attr(attrs, StringRef::from_lit("src")); + if (src_attr.empty()) { + return; + } + if (parser_data->inside_head) { + add_link(parser_data, src_attr, REQ_JS); + } else { + add_link(parser_data, src_attr, REQ_UNBLOCK_JS); + } + } +} +} // namespace + +namespace { +void end_element_func(void *user_data, const xmlChar *name) { + auto parser_data = static_cast(user_data); + if (util::strieq_l( + "head", + StringRef{name, strlen(reinterpret_cast(name))})) { + --parser_data->inside_head; + } +} +} // namespace + +namespace { +xmlSAXHandler saxHandler = { + nullptr, // internalSubsetSAXFunc + nullptr, // isStandaloneSAXFunc + nullptr, // hasInternalSubsetSAXFunc + nullptr, // hasExternalSubsetSAXFunc + nullptr, // resolveEntitySAXFunc + nullptr, // getEntitySAXFunc + nullptr, // entityDeclSAXFunc + nullptr, // notationDeclSAXFunc + nullptr, // attributeDeclSAXFunc + nullptr, // elementDeclSAXFunc + nullptr, // unparsedEntityDeclSAXFunc + nullptr, // setDocumentLocatorSAXFunc + nullptr, // startDocumentSAXFunc + nullptr, // endDocumentSAXFunc + &start_element_func, // startElementSAXFunc + &end_element_func, // endElementSAXFunc + nullptr, // referenceSAXFunc + nullptr, // charactersSAXFunc + nullptr, // ignorableWhitespaceSAXFunc + nullptr, // processingInstructionSAXFunc + nullptr, // commentSAXFunc + nullptr, // warningSAXFunc + nullptr, // errorSAXFunc + nullptr, // fatalErrorSAXFunc + nullptr, // getParameterEntitySAXFunc + nullptr, // cdataBlockSAXFunc + nullptr, // externalSubsetSAXFunc + 0, // unsigned int initialized + nullptr, // void * _private + nullptr, // startElementNsSAX2Func + nullptr, // endElementNsSAX2Func + nullptr, // xmlStructuredErrorFunc +}; +} // namespace + +int HtmlParser::parse_chunk(const char *chunk, size_t size, int fin) { + if (!parser_ctx_) { + parser_ctx_ = + htmlCreatePushParserCtxt(&saxHandler, &parser_data_, chunk, size, + base_uri_.c_str(), XML_CHAR_ENCODING_NONE); + if (!parser_ctx_) { + return -1; + } else { + if (fin) { + return parse_chunk_internal(nullptr, 0, fin); + } else { + return 0; + } + } + } else { + return parse_chunk_internal(chunk, size, fin); + } +} + +int HtmlParser::parse_chunk_internal(const char *chunk, size_t size, int fin) { + int rv = htmlParseChunk(parser_ctx_, chunk, size, fin); + if (rv == 0) { + return 0; + } else { + return -1; + } +} + +const std::vector> & +HtmlParser::get_links() const { + return parser_data_.links; +} + +void HtmlParser::clear_links() { parser_data_.links.clear(); } + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/HtmlParser.h b/lib/nghttp2/src/HtmlParser.h new file mode 100644 index 00000000000..1e846882cd0 --- /dev/null +++ b/lib/nghttp2/src/HtmlParser.h @@ -0,0 +1,94 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 HTML_PARSER_H +#define HTML_PARSER_H + +#include "nghttp2_config.h" + +#include +#include + +#ifdef HAVE_LIBXML2 + +# include + +#endif // HAVE_LIBXML2 + +namespace nghttp2 { + +enum ResourceType { + REQ_CSS = 1, + REQ_JS, + REQ_UNBLOCK_JS, + REQ_IMG, + REQ_OTHERS, +}; + +struct ParserData { + std::string base_uri; + std::vector> links; + // > 0 if we are inside "head" element. + int inside_head; + ParserData(const std::string &base_uri); +}; + +#ifdef HAVE_LIBXML2 + +class HtmlParser { +public: + HtmlParser(const std::string &base_uri); + ~HtmlParser(); + int parse_chunk(const char *chunk, size_t size, int fin); + const std::vector> &get_links() const; + void clear_links(); + +private: + int parse_chunk_internal(const char *chunk, size_t size, int fin); + + std::string base_uri_; + htmlParserCtxtPtr parser_ctx_; + ParserData parser_data_; +}; + +#else // !HAVE_LIBXML2 + +class HtmlParser { +public: + HtmlParser(const std::string &base_uri) {} + int parse_chunk(const char *chunk, size_t size, int fin) { return 0; } + const std::vector> &get_links() const { + return links_; + } + void clear_links() {} + +private: + std::vector> links_; +}; + +#endif // !HAVE_LIBXML2 + +} // namespace nghttp2 + +#endif // HTML_PARSER_H diff --git a/lib/nghttp2/src/HttpServer.cc b/lib/nghttp2/src/HttpServer.cc new file mode 100644 index 00000000000..23cba25d804 --- /dev/null +++ b/lib/nghttp2/src/HttpServer.cc @@ -0,0 +1,2286 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "HttpServer.h" + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H + +#include +#include +#include +#include +#include +#include + +#include "ssl_compat.h" + +#include +#include +#if OPENSSL_3_0_0_API +# include +#endif // OPENSSL_3_0_0_API + +#include + +#include "app_helper.h" +#include "http2.h" +#include "util.h" +#include "tls.h" +#include "template.h" + +#ifndef O_BINARY +# define O_BINARY (0) +#endif // O_BINARY + +using namespace std::chrono_literals; + +namespace nghttp2 { + +namespace { +// TODO could be constexpr +constexpr auto DEFAULT_HTML = StringRef::from_lit("index.html"); +constexpr auto NGHTTPD_SERVER = + StringRef::from_lit("nghttpd nghttp2/" NGHTTP2_VERSION); +} // namespace + +namespace { +void delete_handler(Http2Handler *handler) { + handler->remove_self(); + delete handler; +} +} // namespace + +namespace { +void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; } +} // namespace + +Config::Config() + : mime_types_file("/etc/mime.types"), + stream_read_timeout(1_min), + stream_write_timeout(1_min), + data_ptr(nullptr), + padding(0), + num_worker(1), + max_concurrent_streams(100), + header_table_size(-1), + encoder_header_table_size(-1), + window_bits(-1), + connection_window_bits(-1), + port(0), + verbose(false), + daemon(false), + verify_client(false), + no_tls(false), + error_gzip(false), + early_response(false), + hexdump(false), + echo_upload(false), + no_content_length(false), + ktls(false), + no_rfc7540_pri(false) {} + +Config::~Config() {} + +namespace { +void stream_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + int rv; + auto stream = static_cast(w->data); + auto hd = stream->handler; + auto config = hd->get_config(); + + ev_timer_stop(hd->get_loop(), &stream->rtimer); + ev_timer_stop(hd->get_loop(), &stream->wtimer); + + if (config->verbose) { + print_session_id(hd->session_id()); + print_timer(); + std::cout << " timeout stream_id=" << stream->stream_id << std::endl; + } + + hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR); + + rv = hd->on_write(); + if (rv == -1) { + delete_handler(hd); + } +} +} // namespace + +namespace { +void add_stream_read_timeout(Stream *stream) { + auto hd = stream->handler; + ev_timer_again(hd->get_loop(), &stream->rtimer); +} +} // namespace + +namespace { +void add_stream_read_timeout_if_pending(Stream *stream) { + auto hd = stream->handler; + if (ev_is_active(&stream->rtimer)) { + ev_timer_again(hd->get_loop(), &stream->rtimer); + } +} +} // namespace + +namespace { +void add_stream_write_timeout(Stream *stream) { + auto hd = stream->handler; + ev_timer_again(hd->get_loop(), &stream->wtimer); +} +} // namespace + +namespace { +void remove_stream_read_timeout(Stream *stream) { + auto hd = stream->handler; + ev_timer_stop(hd->get_loop(), &stream->rtimer); +} +} // namespace + +namespace { +void remove_stream_write_timeout(Stream *stream) { + auto hd = stream->handler; + ev_timer_stop(hd->get_loop(), &stream->wtimer); +} +} // namespace + +namespace { +void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config); +} // namespace + +namespace { +constexpr ev_tstamp RELEASE_FD_TIMEOUT = 2.; +} // namespace + +namespace { +void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents); +} // namespace + +namespace { +constexpr auto FILE_ENTRY_MAX_AGE = 10s; +} // namespace + +namespace { +constexpr size_t FILE_ENTRY_EVICT_THRES = 2048; +} // namespace + +namespace { +bool need_validation_file_entry( + const FileEntry *ent, const std::chrono::steady_clock::time_point &now) { + return ent->last_valid + FILE_ENTRY_MAX_AGE < now; +} +} // namespace + +namespace { +bool validate_file_entry(FileEntry *ent, + const std::chrono::steady_clock::time_point &now) { + struct stat stbuf; + int rv; + + rv = fstat(ent->fd, &stbuf); + if (rv != 0) { + ent->stale = true; + return false; + } + + if (stbuf.st_nlink == 0 || ent->mtime != stbuf.st_mtime) { + ent->stale = true; + return false; + } + + ent->mtime = stbuf.st_mtime; + ent->last_valid = now; + + return true; +} +} // namespace + +class Sessions { +public: + Sessions(HttpServer *sv, struct ev_loop *loop, const Config *config, + SSL_CTX *ssl_ctx) + : sv_(sv), + loop_(loop), + config_(config), + ssl_ctx_(ssl_ctx), + callbacks_(nullptr), + option_(nullptr), + next_session_id_(1), + tstamp_cached_(ev_now(loop)), + cached_date_(util::http_date(tstamp_cached_)) { + nghttp2_session_callbacks_new(&callbacks_); + + fill_callback(callbacks_, config_); + + nghttp2_option_new(&option_); + + if (config_->encoder_header_table_size != -1) { + nghttp2_option_set_max_deflate_dynamic_table_size( + option_, config_->encoder_header_table_size); + } + + ev_timer_init(&release_fd_timer_, release_fd_cb, 0., RELEASE_FD_TIMEOUT); + release_fd_timer_.data = this; + } + ~Sessions() { + ev_timer_stop(loop_, &release_fd_timer_); + for (auto handler : handlers_) { + delete handler; + } + nghttp2_option_del(option_); + nghttp2_session_callbacks_del(callbacks_); + } + void add_handler(Http2Handler *handler) { handlers_.insert(handler); } + void remove_handler(Http2Handler *handler) { + handlers_.erase(handler); + if (handlers_.empty() && !fd_cache_.empty()) { + ev_timer_again(loop_, &release_fd_timer_); + } + } + SSL_CTX *get_ssl_ctx() const { return ssl_ctx_; } + SSL *ssl_session_new(int fd) { + SSL *ssl = SSL_new(ssl_ctx_); + if (!ssl) { + std::cerr << "SSL_new() failed" << std::endl; + return nullptr; + } + if (SSL_set_fd(ssl, fd) == 0) { + std::cerr << "SSL_set_fd() failed" << std::endl; + SSL_free(ssl); + return nullptr; + } + return ssl; + } + const Config *get_config() const { return config_; } + struct ev_loop *get_loop() const { return loop_; } + int64_t get_next_session_id() { + auto session_id = next_session_id_; + if (next_session_id_ == std::numeric_limits::max()) { + next_session_id_ = 1; + } else { + ++next_session_id_; + } + return session_id; + } + const nghttp2_session_callbacks *get_callbacks() const { return callbacks_; } + const nghttp2_option *get_option() const { return option_; } + void accept_connection(int fd) { + util::make_socket_nodelay(fd); + SSL *ssl = nullptr; + if (ssl_ctx_) { + ssl = ssl_session_new(fd); + if (!ssl) { + close(fd); + return; + } + } + auto handler = + std::make_unique(this, fd, ssl, get_next_session_id()); + if (!ssl) { + if (handler->connection_made() != 0) { + return; + } + } + add_handler(handler.release()); + } + void update_cached_date() { cached_date_ = util::http_date(tstamp_cached_); } + const std::string &get_cached_date() { + auto t = ev_now(loop_); + if (t != tstamp_cached_) { + tstamp_cached_ = t; + update_cached_date(); + } + return cached_date_; + } + FileEntry *get_cached_fd(const std::string &path) { + auto range = fd_cache_.equal_range(path); + if (range.first == range.second) { + return nullptr; + } + + auto now = std::chrono::steady_clock::now(); + + for (auto it = range.first; it != range.second;) { + auto &ent = (*it).second; + if (ent->stale) { + ++it; + continue; + } + if (need_validation_file_entry(ent.get(), now) && + !validate_file_entry(ent.get(), now)) { + if (ent->usecount == 0) { + fd_cache_lru_.remove(ent.get()); + close(ent->fd); + it = fd_cache_.erase(it); + continue; + } + ++it; + continue; + } + + fd_cache_lru_.remove(ent.get()); + fd_cache_lru_.append(ent.get()); + + ++ent->usecount; + return ent.get(); + } + return nullptr; + } + FileEntry *cache_fd(const std::string &path, const FileEntry &ent) { +#ifdef HAVE_STD_MAP_EMPLACE + auto rv = fd_cache_.emplace(path, std::make_unique(ent)); +#else // !HAVE_STD_MAP_EMPLACE + // for gcc-4.7 + auto rv = fd_cache_.insert( + std::make_pair(path, std::make_unique(ent))); +#endif // !HAVE_STD_MAP_EMPLACE + auto &res = (*rv).second; + res->it = rv; + fd_cache_lru_.append(res.get()); + + while (fd_cache_.size() > FILE_ENTRY_EVICT_THRES) { + auto ent = fd_cache_lru_.head; + if (ent->usecount) { + break; + } + fd_cache_lru_.remove(ent); + close(ent->fd); + fd_cache_.erase(ent->it); + } + + return res.get(); + } + void release_fd(FileEntry *target) { + --target->usecount; + + if (target->usecount == 0 && target->stale) { + fd_cache_lru_.remove(target); + close(target->fd); + fd_cache_.erase(target->it); + return; + } + + // We use timer to close file descriptor and delete the entry from + // cache. The timer will be started when there is no handler. + } + void release_unused_fd() { + for (auto i = std::begin(fd_cache_); i != std::end(fd_cache_);) { + auto &ent = (*i).second; + if (ent->usecount != 0) { + ++i; + continue; + } + + fd_cache_lru_.remove(ent.get()); + close(ent->fd); + i = fd_cache_.erase(i); + } + } + const HttpServer *get_server() const { return sv_; } + bool handlers_empty() const { return handlers_.empty(); } + +private: + std::set handlers_; + // cache for file descriptors to read file. + std::multimap> fd_cache_; + DList fd_cache_lru_; + HttpServer *sv_; + struct ev_loop *loop_; + const Config *config_; + SSL_CTX *ssl_ctx_; + nghttp2_session_callbacks *callbacks_; + nghttp2_option *option_; + ev_timer release_fd_timer_; + int64_t next_session_id_; + ev_tstamp tstamp_cached_; + std::string cached_date_; +}; + +namespace { +void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto sessions = static_cast(w->data); + + ev_timer_stop(loop, w); + + if (!sessions->handlers_empty()) { + return; + } + + sessions->release_unused_fd(); +} +} // namespace + +Stream::Stream(Http2Handler *handler, int32_t stream_id) + : balloc(1024, 1024), + header{}, + handler(handler), + file_ent(nullptr), + body_length(0), + body_offset(0), + header_buffer_size(0), + stream_id(stream_id), + echo_upload(false) { + auto config = handler->get_config(); + ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout); + ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout); + rtimer.data = this; + wtimer.data = this; +} + +Stream::~Stream() { + if (file_ent != nullptr) { + auto sessions = handler->get_sessions(); + sessions->release_fd(file_ent); + } + + auto &rcbuf = header.rcbuf; + nghttp2_rcbuf_decref(rcbuf.method); + nghttp2_rcbuf_decref(rcbuf.scheme); + nghttp2_rcbuf_decref(rcbuf.authority); + nghttp2_rcbuf_decref(rcbuf.host); + nghttp2_rcbuf_decref(rcbuf.path); + nghttp2_rcbuf_decref(rcbuf.ims); + nghttp2_rcbuf_decref(rcbuf.expect); + + auto loop = handler->get_loop(); + ev_timer_stop(loop, &rtimer); + ev_timer_stop(loop, &wtimer); +} + +namespace { +void on_session_closed(Http2Handler *hd, int64_t session_id) { + if (hd->get_config()->verbose) { + print_session_id(session_id); + print_timer(); + std::cout << " closed" << std::endl; + } +} +} // namespace + +namespace { +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + int rv; + auto hd = static_cast(w->data); + hd->terminate_session(NGHTTP2_SETTINGS_TIMEOUT); + rv = hd->on_write(); + if (rv == -1) { + delete_handler(hd); + } +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto handler = static_cast(w->data); + + rv = handler->on_read(); + if (rv == -1) { + delete_handler(handler); + } +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto handler = static_cast(w->data); + + rv = handler->on_write(); + if (rv == -1) { + delete_handler(handler); + } +} +} // namespace + +Http2Handler::Http2Handler(Sessions *sessions, int fd, SSL *ssl, + int64_t session_id) + : session_id_(session_id), + session_(nullptr), + sessions_(sessions), + ssl_(ssl), + data_pending_(nullptr), + data_pendinglen_(0), + fd_(fd) { + ev_timer_init(&settings_timerev_, settings_timeout_cb, 10., 0.); + ev_io_init(&wev_, writecb, fd, EV_WRITE); + ev_io_init(&rev_, readcb, fd, EV_READ); + + settings_timerev_.data = this; + wev_.data = this; + rev_.data = this; + + auto loop = sessions_->get_loop(); + ev_io_start(loop, &rev_); + + if (ssl) { + SSL_set_accept_state(ssl); + read_ = &Http2Handler::tls_handshake; + write_ = &Http2Handler::tls_handshake; + } else { + read_ = &Http2Handler::read_clear; + write_ = &Http2Handler::write_clear; + } +} + +Http2Handler::~Http2Handler() { + on_session_closed(this, session_id_); + nghttp2_session_del(session_); + if (ssl_) { + SSL_set_shutdown(ssl_, SSL_get_shutdown(ssl_) | SSL_RECEIVED_SHUTDOWN); + ERR_clear_error(); + SSL_shutdown(ssl_); + } + auto loop = sessions_->get_loop(); + ev_timer_stop(loop, &settings_timerev_); + ev_io_stop(loop, &rev_); + ev_io_stop(loop, &wev_); + if (ssl_) { + SSL_free(ssl_); + } + shutdown(fd_, SHUT_WR); + close(fd_); +} + +void Http2Handler::remove_self() { sessions_->remove_handler(this); } + +struct ev_loop *Http2Handler::get_loop() const { return sessions_->get_loop(); } + +Http2Handler::WriteBuf *Http2Handler::get_wb() { return &wb_; } + +void Http2Handler::start_settings_timer() { + ev_timer_start(sessions_->get_loop(), &settings_timerev_); +} + +int Http2Handler::fill_wb() { + if (data_pending_) { + auto n = std::min(wb_.wleft(), data_pendinglen_); + wb_.write(data_pending_, n); + if (n < data_pendinglen_) { + data_pending_ += n; + data_pendinglen_ -= n; + return 0; + } + + data_pending_ = nullptr; + data_pendinglen_ = 0; + } + + for (;;) { + const uint8_t *data; + auto datalen = nghttp2_session_mem_send(session_, &data); + + if (datalen < 0) { + std::cerr << "nghttp2_session_mem_send() returned error: " + << nghttp2_strerror(datalen) << std::endl; + return -1; + } + if (datalen == 0) { + break; + } + auto n = wb_.write(data, datalen); + if (n < static_cast(datalen)) { + data_pending_ = data + n; + data_pendinglen_ = datalen - n; + break; + } + } + return 0; +} + +int Http2Handler::read_clear() { + int rv; + std::array buf; + + ssize_t nread; + while ((nread = read(fd_, buf.data(), buf.size())) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return write_(*this); + } + return -1; + } + if (nread == 0) { + return -1; + } + + if (get_config()->hexdump) { + util::hexdump(stdout, buf.data(), nread); + } + + rv = nghttp2_session_mem_recv(session_, buf.data(), nread); + if (rv < 0) { + if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) { + std::cerr << "nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv) << std::endl; + } + return -1; + } + + return write_(*this); +} + +int Http2Handler::write_clear() { + auto loop = sessions_->get_loop(); + for (;;) { + if (wb_.rleft() > 0) { + ssize_t nwrite; + while ((nwrite = write(fd_, wb_.pos, wb_.rleft())) == -1 && + errno == EINTR) + ; + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + ev_io_start(loop, &wev_); + return 0; + } + return -1; + } + wb_.drain(nwrite); + continue; + } + wb_.reset(); + if (fill_wb() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + break; + } + } + + if (wb_.rleft() == 0) { + ev_io_stop(loop, &wev_); + } else { + ev_io_start(loop, &wev_); + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + return -1; + } + + return 0; +} + +int Http2Handler::tls_handshake() { + ev_io_stop(sessions_->get_loop(), &wev_); + + ERR_clear_error(); + + auto rv = SSL_do_handshake(ssl_); + + if (rv <= 0) { + auto err = SSL_get_error(ssl_, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(sessions_->get_loop(), &wev_); + return 0; + default: + return -1; + } + } + + if (sessions_->get_config()->verbose) { + std::cerr << "SSL/TLS handshake completed" << std::endl; + } + + if (verify_npn_result() != 0) { + return -1; + } + + read_ = &Http2Handler::read_tls; + write_ = &Http2Handler::write_tls; + + if (connection_made() != 0) { + return -1; + } + + if (sessions_->get_config()->verbose) { + if (SSL_session_reused(ssl_)) { + std::cerr << "SSL/TLS session reused" << std::endl; + } + } + + return 0; +} + +int Http2Handler::read_tls() { + std::array buf; + + ERR_clear_error(); + + auto rv = SSL_read(ssl_, buf.data(), buf.size()); + + if (rv <= 0) { + auto err = SSL_get_error(ssl_, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + return write_(*this); + case SSL_ERROR_WANT_WRITE: + // renegotiation started + return -1; + default: + return -1; + } + } + + auto nread = rv; + + if (get_config()->hexdump) { + util::hexdump(stdout, buf.data(), nread); + } + + rv = nghttp2_session_mem_recv(session_, buf.data(), nread); + if (rv < 0) { + if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) { + std::cerr << "nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv) << std::endl; + } + return -1; + } + + return write_(*this); +} + +int Http2Handler::write_tls() { + auto loop = sessions_->get_loop(); + + ERR_clear_error(); + + for (;;) { + if (wb_.rleft() > 0) { + auto rv = SSL_write(ssl_, wb_.pos, wb_.rleft()); + + if (rv <= 0) { + auto err = SSL_get_error(ssl_, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + // renegotiation started + return -1; + case SSL_ERROR_WANT_WRITE: + ev_io_start(sessions_->get_loop(), &wev_); + return 0; + default: + return -1; + } + } + + wb_.drain(rv); + continue; + } + wb_.reset(); + if (fill_wb() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + break; + } + } + + if (wb_.rleft() == 0) { + ev_io_stop(loop, &wev_); + } else { + ev_io_start(loop, &wev_); + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + return -1; + } + + return 0; +} + +int Http2Handler::on_read() { return read_(*this); } + +int Http2Handler::on_write() { return write_(*this); } + +int Http2Handler::connection_made() { + int r; + + r = nghttp2_session_server_new2(&session_, sessions_->get_callbacks(), this, + sessions_->get_option()); + + if (r != 0) { + return r; + } + + auto config = sessions_->get_config(); + std::array entry; + size_t niv = 1; + + entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + entry[0].value = config->max_concurrent_streams; + + if (config->header_table_size >= 0) { + entry[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + entry[niv].value = config->header_table_size; + ++niv; + } + + if (config->window_bits != -1) { + entry[niv].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + entry[niv].value = (1 << config->window_bits) - 1; + ++niv; + } + + if (config->no_rfc7540_pri) { + entry[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + entry[niv].value = 1; + ++niv; + } + + r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv); + if (r != 0) { + return r; + } + + if (config->connection_window_bits != -1) { + r = nghttp2_session_set_local_window_size( + session_, NGHTTP2_FLAG_NONE, 0, + (1 << config->connection_window_bits) - 1); + if (r != 0) { + return r; + } + } + + if (ssl_ && !nghttp2::tls::check_http2_requirement(ssl_)) { + terminate_session(NGHTTP2_INADEQUATE_SECURITY); + } + + return on_write(); +} + +int Http2Handler::verify_npn_result() { + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + // Check the negotiated protocol in NPN or ALPN +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG + for (int i = 0; i < 2; ++i) { + if (next_proto) { + auto proto = StringRef{next_proto, next_proto_len}; + if (sessions_->get_config()->verbose) { + std::cout << "The negotiated protocol: " << proto << std::endl; + } + if (util::check_h2_is_selected(proto)) { + return 0; + } + break; + } else { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len); +#else // OPENSSL_VERSION_NUMBER < 0x10002000L + break; +#endif // OPENSSL_VERSION_NUMBER < 0x10002000L + } + } + if (sessions_->get_config()->verbose) { + std::cerr << "Client did not advertise HTTP/2 protocol." + << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")" + << std::endl; + } + return -1; +} + +int Http2Handler::submit_file_response(const StringRef &status, Stream *stream, + time_t last_modified, off_t file_length, + const std::string *content_type, + nghttp2_data_provider *data_prd) { + std::string last_modified_str; + auto nva = make_array(http2::make_nv_ls_nocopy(":status", status), + http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER), + http2::make_nv_ll("cache-control", "max-age=3600"), + http2::make_nv_ls("date", sessions_->get_cached_date()), + http2::make_nv_ll("", ""), http2::make_nv_ll("", ""), + http2::make_nv_ll("", ""), http2::make_nv_ll("", "")); + size_t nvlen = 4; + if (!get_config()->no_content_length) { + nva[nvlen++] = http2::make_nv_ls_nocopy( + "content-length", + util::make_string_ref_uint(stream->balloc, file_length)); + } + if (last_modified != 0) { + last_modified_str = util::http_date(last_modified); + nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str); + } + if (content_type) { + nva[nvlen++] = http2::make_nv_ls("content-type", *content_type); + } + auto &trailer_names = get_config()->trailer_names; + if (!trailer_names.empty()) { + nva[nvlen++] = http2::make_nv_ls_nocopy("trailer", trailer_names); + } + return nghttp2_submit_response(session_, stream->stream_id, nva.data(), nvlen, + data_prd); +} + +int Http2Handler::submit_response(const StringRef &status, int32_t stream_id, + const HeaderRefs &headers, + nghttp2_data_provider *data_prd) { + auto nva = std::vector(); + nva.reserve(4 + headers.size()); + nva.push_back(http2::make_nv_ls_nocopy(":status", status)); + nva.push_back(http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER)); + nva.push_back(http2::make_nv_ls("date", sessions_->get_cached_date())); + + if (data_prd) { + auto &trailer_names = get_config()->trailer_names; + if (!trailer_names.empty()) { + nva.push_back(http2::make_nv_ls_nocopy("trailer", trailer_names)); + } + } + + for (auto &nv : headers) { + nva.push_back(http2::make_nv_nocopy(nv.name, nv.value, nv.no_index)); + } + int r = nghttp2_submit_response(session_, stream_id, nva.data(), nva.size(), + data_prd); + return r; +} + +int Http2Handler::submit_response(const StringRef &status, int32_t stream_id, + nghttp2_data_provider *data_prd) { + auto nva = make_array(http2::make_nv_ls_nocopy(":status", status), + http2::make_nv_ls_nocopy("server", NGHTTPD_SERVER), + http2::make_nv_ls("date", sessions_->get_cached_date()), + http2::make_nv_ll("", "")); + size_t nvlen = 3; + + if (data_prd) { + auto &trailer_names = get_config()->trailer_names; + if (!trailer_names.empty()) { + nva[nvlen++] = http2::make_nv_ls_nocopy("trailer", trailer_names); + } + } + + return nghttp2_submit_response(session_, stream_id, nva.data(), nvlen, + data_prd); +} + +int Http2Handler::submit_non_final_response(const std::string &status, + int32_t stream_id) { + auto nva = make_array(http2::make_nv_ls(":status", status)); + return nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, stream_id, nullptr, + nva.data(), nva.size(), nullptr); +} + +int Http2Handler::submit_push_promise(Stream *stream, + const StringRef &push_path) { + auto authority = stream->header.authority; + + if (authority.empty()) { + authority = stream->header.host; + } + + auto scheme = get_config()->no_tls ? StringRef::from_lit("http") + : StringRef::from_lit("https"); + + auto nva = make_array(http2::make_nv_ll(":method", "GET"), + http2::make_nv_ls_nocopy(":path", push_path), + http2::make_nv_ls_nocopy(":scheme", scheme), + http2::make_nv_ls_nocopy(":authority", authority)); + + auto promised_stream_id = nghttp2_submit_push_promise( + session_, NGHTTP2_FLAG_END_HEADERS, stream->stream_id, nva.data(), + nva.size(), nullptr); + + if (promised_stream_id < 0) { + return promised_stream_id; + } + + auto promised_stream = std::make_unique(this, promised_stream_id); + + auto &promised_header = promised_stream->header; + promised_header.method = StringRef::from_lit("GET"); + promised_header.path = push_path; + promised_header.scheme = scheme; + promised_header.authority = + make_string_ref(promised_stream->balloc, authority); + + add_stream(promised_stream_id, std::move(promised_stream)); + + return 0; +} + +int Http2Handler::submit_rst_stream(Stream *stream, uint32_t error_code) { + remove_stream_read_timeout(stream); + remove_stream_write_timeout(stream); + + return nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, + stream->stream_id, error_code); +} + +void Http2Handler::add_stream(int32_t stream_id, + std::unique_ptr stream) { + id2stream_[stream_id] = std::move(stream); +} + +void Http2Handler::remove_stream(int32_t stream_id) { + id2stream_.erase(stream_id); +} + +Stream *Http2Handler::get_stream(int32_t stream_id) { + auto itr = id2stream_.find(stream_id); + if (itr == std::end(id2stream_)) { + return nullptr; + } else { + return (*itr).second.get(); + } +} + +int64_t Http2Handler::session_id() const { return session_id_; } + +Sessions *Http2Handler::get_sessions() const { return sessions_; } + +const Config *Http2Handler::get_config() const { + return sessions_->get_config(); +} + +void Http2Handler::remove_settings_timer() { + ev_timer_stop(sessions_->get_loop(), &settings_timerev_); +} + +void Http2Handler::terminate_session(uint32_t error_code) { + nghttp2_session_terminate_session(session_, error_code); +} + +ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, + uint8_t *buf, size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) { + int rv; + auto hd = static_cast(user_data); + auto stream = hd->get_stream(stream_id); + + auto nread = std::min(stream->body_length - stream->body_offset, + static_cast(length)); + + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + + if (nread == 0 || stream->body_length == stream->body_offset + nread) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + auto config = hd->get_config(); + if (!config->trailer.empty()) { + std::vector nva; + nva.reserve(config->trailer.size()); + for (auto &kv : config->trailer) { + nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); + } + rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size()); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } else { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + } + + if (nghttp2_session_get_stream_remote_close(session, stream_id) == 0) { + remove_stream_read_timeout(stream); + remove_stream_write_timeout(stream); + + hd->submit_rst_stream(stream, NGHTTP2_NO_ERROR); + } + } + + return nread; +} + +namespace { +void prepare_status_response(Stream *stream, Http2Handler *hd, int status) { + auto sessions = hd->get_sessions(); + auto status_page = sessions->get_server()->get_status_page(status); + auto file_ent = &status_page->file_ent; + + // we don't set stream->file_ent since we don't want to expire it. + stream->body_length = file_ent->length; + nghttp2_data_provider data_prd; + data_prd.source.fd = file_ent->fd; + data_prd.read_callback = file_read_callback; + + HeaderRefs headers; + headers.reserve(2); + headers.emplace_back(StringRef::from_lit("content-type"), + StringRef::from_lit("text/html; charset=UTF-8")); + headers.emplace_back( + StringRef::from_lit("content-length"), + util::make_string_ref_uint(stream->balloc, file_ent->length)); + hd->submit_response(StringRef{status_page->status}, stream->stream_id, + headers, &data_prd); +} +} // namespace + +namespace { +void prepare_echo_response(Stream *stream, Http2Handler *hd) { + auto length = lseek(stream->file_ent->fd, 0, SEEK_END); + if (length == -1) { + hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR); + return; + } + stream->body_length = length; + if (lseek(stream->file_ent->fd, 0, SEEK_SET) == -1) { + hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR); + return; + } + nghttp2_data_provider data_prd; + data_prd.source.fd = stream->file_ent->fd; + data_prd.read_callback = file_read_callback; + + HeaderRefs headers; + headers.emplace_back(StringRef::from_lit("nghttpd-response"), + StringRef::from_lit("echo")); + if (!hd->get_config()->no_content_length) { + headers.emplace_back(StringRef::from_lit("content-length"), + util::make_string_ref_uint(stream->balloc, length)); + } + + hd->submit_response(StringRef::from_lit("200"), stream->stream_id, headers, + &data_prd); +} +} // namespace + +namespace { +bool prepare_upload_temp_store(Stream *stream, Http2Handler *hd) { + auto sessions = hd->get_sessions(); + + char tempfn[] = "/tmp/nghttpd.temp.XXXXXX"; + auto fd = mkstemp(tempfn); + if (fd == -1) { + return false; + } + unlink(tempfn); + // Ordinary request never start with "echo:". The length is 0 for + // now. We will update it when we get whole request body. + auto path = std::string("echo:") + tempfn; + stream->file_ent = + sessions->cache_fd(path, FileEntry(path, 0, 0, fd, nullptr, {}, true)); + stream->echo_upload = true; + return true; +} +} // namespace + +namespace { +void prepare_redirect_response(Stream *stream, Http2Handler *hd, + const StringRef &path, int status) { + auto scheme = stream->header.scheme; + + auto authority = stream->header.authority; + if (authority.empty()) { + authority = stream->header.host; + } + + auto location = concat_string_ref( + stream->balloc, scheme, StringRef::from_lit("://"), authority, path); + + auto headers = HeaderRefs{{StringRef::from_lit("location"), location}}; + + auto sessions = hd->get_sessions(); + auto status_page = sessions->get_server()->get_status_page(status); + + hd->submit_response(StringRef{status_page->status}, stream->stream_id, + headers, nullptr); +} +} // namespace + +namespace { +void prepare_response(Stream *stream, Http2Handler *hd, + bool allow_push = true) { + int rv; + auto reqpath = stream->header.path; + if (reqpath.empty()) { + prepare_status_response(stream, hd, 405); + return; + } + + auto ims = stream->header.ims; + + time_t last_mod = 0; + bool last_mod_found = false; + if (!ims.empty()) { + last_mod_found = true; + last_mod = util::parse_http_date(ims); + } + + StringRef raw_path, raw_query; + auto query_pos = std::find(std::begin(reqpath), std::end(reqpath), '?'); + if (query_pos != std::end(reqpath)) { + // Do not response to this request to allow clients to test timeouts. + if (util::streq_l("nghttpd_do_not_respond_to_req=yes", + StringRef{query_pos, std::end(reqpath)})) { + return; + } + raw_path = StringRef{std::begin(reqpath), query_pos}; + raw_query = StringRef{query_pos, std::end(reqpath)}; + } else { + raw_path = reqpath; + } + + auto sessions = hd->get_sessions(); + + StringRef path; + if (std::find(std::begin(raw_path), std::end(raw_path), '%') == + std::end(raw_path)) { + path = raw_path; + } else { + path = util::percent_decode(stream->balloc, raw_path); + } + + path = http2::path_join(stream->balloc, StringRef{}, StringRef{}, path, + StringRef{}); + + if (std::find(std::begin(path), std::end(path), '\\') != std::end(path)) { + if (stream->file_ent) { + sessions->release_fd(stream->file_ent); + stream->file_ent = nullptr; + } + prepare_status_response(stream, hd, 404); + return; + } + + if (!hd->get_config()->push.empty()) { + auto push_itr = hd->get_config()->push.find(path.str()); + if (allow_push && push_itr != std::end(hd->get_config()->push)) { + for (auto &push_path : (*push_itr).second) { + rv = hd->submit_push_promise(stream, StringRef{push_path}); + if (rv != 0) { + std::cerr << "nghttp2_submit_push_promise() returned error: " + << nghttp2_strerror(rv) << std::endl; + } + } + } + } + + std::string file_path; + { + auto len = hd->get_config()->htdocs.size() + path.size(); + + auto trailing_slash = path[path.size() - 1] == '/'; + if (trailing_slash) { + len += DEFAULT_HTML.size(); + } + + file_path.resize(len); + + auto p = &file_path[0]; + + auto &htdocs = hd->get_config()->htdocs; + p = std::copy(std::begin(htdocs), std::end(htdocs), p); + p = std::copy(std::begin(path), std::end(path), p); + if (trailing_slash) { + std::copy(std::begin(DEFAULT_HTML), std::end(DEFAULT_HTML), p); + } + } + + if (stream->echo_upload) { + assert(stream->file_ent); + prepare_echo_response(stream, hd); + return; + } + + auto file_ent = sessions->get_cached_fd(file_path); + + if (file_ent == nullptr) { + int file = open(file_path.c_str(), O_RDONLY | O_BINARY); + if (file == -1) { + prepare_status_response(stream, hd, 404); + + return; + } + + struct stat buf; + + if (fstat(file, &buf) == -1) { + close(file); + prepare_status_response(stream, hd, 404); + + return; + } + + if (buf.st_mode & S_IFDIR) { + close(file); + + auto reqpath = concat_string_ref(stream->balloc, raw_path, + StringRef::from_lit("/"), raw_query); + + prepare_redirect_response(stream, hd, reqpath, 301); + + return; + } + + const std::string *content_type = nullptr; + + auto ext = file_path.c_str() + file_path.size() - 1; + for (; file_path.c_str() < ext && *ext != '.' && *ext != '/'; --ext) + ; + if (*ext == '.') { + ++ext; + + const auto &mime_types = hd->get_config()->mime_types; + auto content_type_itr = mime_types.find(ext); + if (content_type_itr != std::end(mime_types)) { + content_type = &(*content_type_itr).second; + } + } + + file_ent = sessions->cache_fd( + file_path, FileEntry(file_path, buf.st_size, buf.st_mtime, file, + content_type, std::chrono::steady_clock::now())); + } + + stream->file_ent = file_ent; + + if (last_mod_found && file_ent->mtime <= last_mod) { + hd->submit_response(StringRef::from_lit("304"), stream->stream_id, nullptr); + + return; + } + + auto method = stream->header.method; + if (method == StringRef::from_lit("HEAD")) { + hd->submit_file_response(StringRef::from_lit("200"), stream, + file_ent->mtime, file_ent->length, + file_ent->content_type, nullptr); + return; + } + + stream->body_length = file_ent->length; + + nghttp2_data_provider data_prd; + + data_prd.source.fd = file_ent->fd; + data_prd.read_callback = file_read_callback; + + hd->submit_file_response(StringRef::from_lit("200"), stream, file_ent->mtime, + file_ent->length, file_ent->content_type, &data_prd); +} +} // namespace + +namespace { +int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame, + nghttp2_rcbuf *name, nghttp2_rcbuf *value, + uint8_t flags, void *user_data) { + auto hd = static_cast(user_data); + + auto namebuf = nghttp2_rcbuf_get_buf(name); + auto valuebuf = nghttp2_rcbuf_get_buf(value); + + if (hd->get_config()->verbose) { + print_session_id(hd->session_id()); + verbose_on_header_callback(session, frame, namebuf.base, namebuf.len, + valuebuf.base, valuebuf.len, flags, user_data); + } + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + auto stream = hd->get_stream(frame->hd.stream_id); + if (!stream) { + return 0; + } + + if (stream->header_buffer_size + namebuf.len + valuebuf.len > 64_k) { + hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR); + return 0; + } + + stream->header_buffer_size += namebuf.len + valuebuf.len; + + auto token = http2::lookup_token(namebuf.base, namebuf.len); + + auto &header = stream->header; + + switch (token) { + case http2::HD__METHOD: + header.method = StringRef{valuebuf.base, valuebuf.len}; + header.rcbuf.method = value; + nghttp2_rcbuf_incref(value); + break; + case http2::HD__SCHEME: + header.scheme = StringRef{valuebuf.base, valuebuf.len}; + header.rcbuf.scheme = value; + nghttp2_rcbuf_incref(value); + break; + case http2::HD__AUTHORITY: + header.authority = StringRef{valuebuf.base, valuebuf.len}; + header.rcbuf.authority = value; + nghttp2_rcbuf_incref(value); + break; + case http2::HD_HOST: + header.host = StringRef{valuebuf.base, valuebuf.len}; + header.rcbuf.host = value; + nghttp2_rcbuf_incref(value); + break; + case http2::HD__PATH: + header.path = StringRef{valuebuf.base, valuebuf.len}; + header.rcbuf.path = value; + nghttp2_rcbuf_incref(value); + break; + case http2::HD_IF_MODIFIED_SINCE: + header.ims = StringRef{valuebuf.base, valuebuf.len}; + header.rcbuf.ims = value; + nghttp2_rcbuf_incref(value); + break; + case http2::HD_EXPECT: + header.expect = StringRef{valuebuf.base, valuebuf.len}; + header.rcbuf.expect = value; + nghttp2_rcbuf_incref(value); + break; + } + + return 0; +} +} // namespace + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + auto hd = static_cast(user_data); + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + auto stream = std::make_unique(hd, frame->hd.stream_id); + + add_stream_read_timeout(stream.get()); + + hd->add_stream(frame->hd.stream_id, std::move(stream)); + + return 0; +} +} // namespace + +namespace { +int hd_on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + auto hd = static_cast(user_data); + if (hd->get_config()->verbose) { + print_session_id(hd->session_id()); + verbose_on_frame_recv_callback(session, frame, user_data); + } + switch (frame->hd.type) { + case NGHTTP2_DATA: { + // TODO Handle POST + auto stream = hd->get_stream(frame->hd.stream_id); + if (!stream) { + return 0; + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + remove_stream_read_timeout(stream); + if (stream->echo_upload || !hd->get_config()->early_response) { + prepare_response(stream, hd); + } + } else { + add_stream_read_timeout(stream); + } + + break; + } + case NGHTTP2_HEADERS: { + auto stream = hd->get_stream(frame->hd.stream_id); + if (!stream) { + return 0; + } + + if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { + + auto expect100 = stream->header.expect; + + if (util::strieq_l("100-continue", expect100)) { + hd->submit_non_final_response("100", frame->hd.stream_id); + } + + auto method = stream->header.method; + if (hd->get_config()->echo_upload && + (method == StringRef::from_lit("POST") || + method == StringRef::from_lit("PUT"))) { + if (!prepare_upload_temp_store(stream, hd)) { + hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR); + return 0; + } + } else if (hd->get_config()->early_response) { + prepare_response(stream, hd); + } + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + remove_stream_read_timeout(stream); + if (stream->echo_upload || !hd->get_config()->early_response) { + prepare_response(stream, hd); + } + } else { + add_stream_read_timeout(stream); + } + + break; + } + case NGHTTP2_SETTINGS: + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + hd->remove_settings_timer(); + } + break; + default: + break; + } + return 0; +} +} // namespace + +namespace { +int hd_on_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + auto hd = static_cast(user_data); + + if (hd->get_config()->verbose) { + print_session_id(hd->session_id()); + verbose_on_frame_send_callback(session, frame, user_data); + } + + switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: { + auto stream = hd->get_stream(frame->hd.stream_id); + + if (!stream) { + return 0; + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + remove_stream_write_timeout(stream); + } else if (std::min(nghttp2_session_get_stream_remote_window_size( + session, frame->hd.stream_id), + nghttp2_session_get_remote_window_size(session)) <= 0) { + // If stream is blocked by flow control, enable write timeout. + add_stream_read_timeout_if_pending(stream); + add_stream_write_timeout(stream); + } else { + add_stream_read_timeout_if_pending(stream); + remove_stream_write_timeout(stream); + } + + break; + } + case NGHTTP2_SETTINGS: { + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + return 0; + } + + hd->start_settings_timer(); + + break; + } + case NGHTTP2_PUSH_PROMISE: { + auto promised_stream_id = frame->push_promise.promised_stream_id; + auto promised_stream = hd->get_stream(promised_stream_id); + auto stream = hd->get_stream(frame->hd.stream_id); + + if (!stream || !promised_stream) { + return 0; + } + + add_stream_read_timeout_if_pending(stream); + add_stream_write_timeout(stream); + + prepare_response(promised_stream, hd, /*allow_push */ false); + } + } + return 0; +} +} // namespace + +namespace { +int send_data_callback(nghttp2_session *session, nghttp2_frame *frame, + const uint8_t *framehd, size_t length, + nghttp2_data_source *source, void *user_data) { + auto hd = static_cast(user_data); + auto wb = hd->get_wb(); + auto padlen = frame->data.padlen; + auto stream = hd->get_stream(frame->hd.stream_id); + + if (wb->wleft() < 9 + length + padlen) { + return NGHTTP2_ERR_WOULDBLOCK; + } + + int fd = source->fd; + + auto p = wb->last; + + p = std::copy_n(framehd, 9, p); + + if (padlen) { + *p++ = padlen - 1; + } + + while (length) { + ssize_t nread; + while ((nread = pread(fd, p, length, stream->body_offset)) == -1 && + errno == EINTR) + ; + + if (nread == -1) { + remove_stream_read_timeout(stream); + remove_stream_write_timeout(stream); + + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + stream->body_offset += nread; + length -= nread; + p += nread; + } + + if (padlen) { + std::fill(p, p + padlen - 1, 0); + p += padlen - 1; + } + + wb->last = p; + + return 0; +} +} // namespace + +namespace { +ssize_t select_padding_callback(nghttp2_session *session, + const nghttp2_frame *frame, size_t max_payload, + void *user_data) { + auto hd = static_cast(user_data); + return std::min(max_payload, frame->hd.length + hd->get_config()->padding); +} +} // namespace + +namespace { +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + auto hd = static_cast(user_data); + auto stream = hd->get_stream(stream_id); + + if (!stream) { + return 0; + } + + if (stream->echo_upload) { + assert(stream->file_ent); + while (len) { + ssize_t n; + while ((n = write(stream->file_ent->fd, data, len)) == -1 && + errno == EINTR) + ; + if (n == -1) { + hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR); + return 0; + } + len -= n; + data += n; + } + } + // TODO Handle POST + + add_stream_read_timeout(stream); + + return 0; +} +} // namespace + +namespace { +int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + auto hd = static_cast(user_data); + hd->remove_stream(stream_id); + if (hd->get_config()->verbose) { + print_session_id(hd->session_id()); + print_timer(); + printf(" stream_id=%d closed\n", stream_id); + fflush(stdout); + } + return 0; +} +} // namespace + +namespace { +void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) { + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback( + callbacks, hd_on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_frame_send_callback( + callbacks, hd_on_frame_send_callback); + + if (config->verbose) { + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( + callbacks, verbose_on_invalid_frame_recv_callback); + + nghttp2_session_callbacks_set_error_callback2(callbacks, + verbose_error_callback); + } + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_header_callback2(callbacks, + on_header_callback2); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_callbacks_set_send_data_callback(callbacks, + send_data_callback); + + if (config->padding) { + nghttp2_session_callbacks_set_select_padding_callback( + callbacks, select_padding_callback); + } +} +} // namespace + +struct ClientInfo { + int fd; +}; + +struct Worker { + std::unique_ptr sessions; + ev_async w; + // protects q + std::mutex m; + std::deque q; +}; + +namespace { +void worker_acceptcb(struct ev_loop *loop, ev_async *w, int revents) { + auto worker = static_cast(w->data); + auto &sessions = worker->sessions; + + std::deque q; + { + std::lock_guard lock(worker->m); + q.swap(worker->q); + } + + for (const auto &c : q) { + sessions->accept_connection(c.fd); + } +} +} // namespace + +namespace { +void run_worker(Worker *worker) { + auto loop = worker->sessions->get_loop(); + + ev_run(loop, 0); +} +} // namespace + +namespace { +int get_ev_loop_flags() { + if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) { + return ev_recommended_backends() | EVBACKEND_KQUEUE; + } + + return 0; +} +} // namespace + +class AcceptHandler { +public: + AcceptHandler(HttpServer *sv, Sessions *sessions, const Config *config) + : sessions_(sessions), config_(config), next_worker_(0) { + if (config_->num_worker == 1) { + return; + } + for (size_t i = 0; i < config_->num_worker; ++i) { + if (config_->verbose) { + std::cerr << "spawning thread #" << i << std::endl; + } + auto worker = std::make_unique(); + auto loop = ev_loop_new(get_ev_loop_flags()); + worker->sessions = std::make_unique(sv, loop, config_, + sessions_->get_ssl_ctx()); + ev_async_init(&worker->w, worker_acceptcb); + worker->w.data = worker.get(); + ev_async_start(loop, &worker->w); + + auto t = std::thread(run_worker, worker.get()); + t.detach(); + workers_.push_back(std::move(worker)); + } + } + void accept_connection(int fd) { + if (config_->num_worker == 1) { + sessions_->accept_connection(fd); + return; + } + + // Dispatch client to the one of the worker threads, in a round + // robin manner. + auto &worker = workers_[next_worker_]; + if (next_worker_ == config_->num_worker - 1) { + next_worker_ = 0; + } else { + ++next_worker_; + } + { + std::lock_guard lock(worker->m); + worker->q.push_back({fd}); + } + ev_async_send(worker->sessions->get_loop(), &worker->w); + } + +private: + std::vector> workers_; + Sessions *sessions_; + const Config *config_; + // In multi threading mode, this points to the next thread that + // client will be dispatched. + size_t next_worker_; +}; + +namespace { +void acceptcb(struct ev_loop *loop, ev_io *w, int revents); +} // namespace + +class ListenEventHandler { +public: + ListenEventHandler(Sessions *sessions, int fd, + std::shared_ptr acceptor) + : acceptor_(std::move(acceptor)), sessions_(sessions), fd_(fd) { + ev_io_init(&w_, acceptcb, fd, EV_READ); + w_.data = this; + ev_io_start(sessions_->get_loop(), &w_); + } + void accept_connection() { + for (;;) { +#ifdef HAVE_ACCEPT4 + auto fd = accept4(fd_, nullptr, nullptr, SOCK_NONBLOCK); +#else // !HAVE_ACCEPT4 + auto fd = accept(fd_, nullptr, nullptr); +#endif // !HAVE_ACCEPT4 + if (fd == -1) { + break; + } +#ifndef HAVE_ACCEPT4 + util::make_socket_nonblocking(fd); +#endif // !HAVE_ACCEPT4 + acceptor_->accept_connection(fd); + } + } + +private: + ev_io w_; + std::shared_ptr acceptor_; + Sessions *sessions_; + int fd_; +}; + +namespace { +void acceptcb(struct ev_loop *loop, ev_io *w, int revents) { + auto handler = static_cast(w->data); + handler->accept_connection(); +} +} // namespace + +namespace { +FileEntry make_status_body(int status, uint16_t port) { + BlockAllocator balloc(1024, 1024); + + auto status_string = http2::stringify_status(balloc, status); + auto reason_pharase = http2::get_reason_phrase(status); + + std::string body; + body = ""; + body += status_string; + body += ' '; + body += reason_pharase; + body += "

"; + body += status_string; + body += ' '; + body += reason_pharase; + body += "


"; + body += NGHTTPD_SERVER; + body += " at port "; + body += util::utos(port); + body += "
"; + body += ""; + + char tempfn[] = "/tmp/nghttpd.temp.XXXXXX"; + int fd = mkstemp(tempfn); + if (fd == -1) { + auto error = errno; + std::cerr << "Could not open status response body file: errno=" << error; + assert(0); + } + unlink(tempfn); + ssize_t nwrite; + while ((nwrite = write(fd, body.c_str(), body.size())) == -1 && + errno == EINTR) + ; + if (nwrite == -1) { + auto error = errno; + std::cerr << "Could not write status response body into file: errno=" + << error; + assert(0); + } + + return FileEntry(util::utos(status), nwrite, 0, fd, nullptr, {}); +} +} // namespace + +// index into HttpServer::status_pages_ +enum { + IDX_200, + IDX_301, + IDX_400, + IDX_404, + IDX_405, +}; + +HttpServer::HttpServer(const Config *config) : config_(config) { + status_pages_ = std::vector{ + {"200", make_status_body(200, config_->port)}, + {"301", make_status_body(301, config_->port)}, + {"400", make_status_body(400, config_->port)}, + {"404", make_status_body(404, config_->port)}, + {"405", make_status_body(405, config_->port)}, + }; +} + +#ifndef OPENSSL_NO_NEXTPROTONEG +namespace { +int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, + void *arg) { + auto next_proto = static_cast *>(arg); + *data = next_proto->data(); + *len = next_proto->size(); + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // !OPENSSL_NO_NEXTPROTONEG + +namespace { +int verify_callback(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 start_listen(HttpServer *sv, struct ev_loop *loop, Sessions *sessions, + const Config *config) { + int r; + bool ok = false; + const char *addr = nullptr; + + std::shared_ptr acceptor; + auto service = util::utos(config->port); + + addrinfo hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif // AI_ADDRCONFIG + + if (!config->address.empty()) { + addr = config->address.c_str(); + } + + addrinfo *res, *rp; + r = getaddrinfo(addr, service.c_str(), &hints, &res); + if (r != 0) { + std::cerr << "getaddrinfo() failed: " << gai_strerror(r) << std::endl; + return -1; + } + + for (rp = res; rp; rp = rp->ai_next) { + int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + continue; + } + int val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast(sizeof(val))) == -1) { + close(fd); + continue; + } + (void)util::make_socket_nonblocking(fd); +#ifdef IPV6_V6ONLY + if (rp->ai_family == AF_INET6) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast(sizeof(val))) == -1) { + close(fd); + continue; + } + } +#endif // IPV6_V6ONLY + if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0 && listen(fd, 1000) == 0) { + if (!acceptor) { + acceptor = std::make_shared(sv, sessions, config); + } + new ListenEventHandler(sessions, fd, acceptor); + + if (config->verbose) { + std::string s = util::numeric_name(rp->ai_addr, rp->ai_addrlen); + std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6") << ": listen " + << s << ":" << config->port << std::endl; + } + ok = true; + continue; + } else { + std::cerr << strerror(errno) << std::endl; + } + close(fd); + } + freeaddrinfo(res); + + if (!ok) { + return -1; + } + return 0; +} +} // namespace + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +namespace { +int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto config = static_cast(arg)->get_config(); + if (config->verbose) { + std::cout << "[ALPN] client offers:" << std::endl; + } + if (config->verbose) { + for (unsigned int i = 0; i < inlen; i += in[i] + 1) { + std::cout << " * "; + std::cout.write(reinterpret_cast(&in[i + 1]), in[i]); + std::cout << std::endl; + } + } + if (!util::select_h2(out, outlen, in, inlen)) { + return SSL_TLSEXT_ERR_NOACK; + } + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + +int HttpServer::run() { + SSL_CTX *ssl_ctx = nullptr; + std::vector next_proto; + + if (!config_->no_tls) { + ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | + SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_TICKET | + SSL_OP_CIPHER_SERVER_PREFERENCE; + +#ifdef SSL_OP_ENABLE_KTLS + if (config_->ktls) { + ssl_opts |= SSL_OP_ENABLE_KTLS; + } +#endif // SSL_OP_ENABLE_KTLS + + SSL_CTX_set_options(ssl_ctx, ssl_opts); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, + nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { + std::cerr << "Could not set TLS versions" << std::endl; + return -1; + } + + if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST) == 0) { + std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + const unsigned char sid_ctx[] = "nghttpd"; + SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER); + +#ifndef OPENSSL_NO_EC +# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { + std::cerr << "SSL_CTX_set1_curves_list failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return -1; + } +# else // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh == nullptr) { + std::cerr << "EC_KEY_new_by_curv_name failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return -1; + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); +# endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) +#endif // OPENSSL_NO_EC + + if (!config_->dh_param_file.empty()) { + // Read DH parameters from file + auto bio = BIO_new_file(config_->dh_param_file.c_str(), "rb"); + if (bio == nullptr) { + std::cerr << "BIO_new_file() failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + +#if OPENSSL_3_0_0_API + EVP_PKEY *dh = nullptr; + auto dctx = OSSL_DECODER_CTX_new_for_pkey( + &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + nullptr, nullptr); + + if (!OSSL_DECODER_from_bio(dctx, bio)) { + std::cerr << "OSSL_DECODER_from_bio() failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) { + std::cerr << "SSL_CTX_set0_tmp_dh_pkey failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } +#else // !OPENSSL_3_0_0_API + auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + + if (dh == nullptr) { + std::cerr << "PEM_read_bio_DHparams() failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + SSL_CTX_set_tmp_dh(ssl_ctx, dh); + DH_free(dh); +#endif // !OPENSSL_3_0_0_API + BIO_free(bio); + } + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config_->private_key_file.c_str(), + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl; + return -1; + } + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, + config_->cert_file.c_str()) != 1) { + std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl; + return -1; + } + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + std::cerr << "SSL_CTX_check_private_key failed." << std::endl; + return -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_callback); + } + + next_proto = util::get_default_alpn(); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, this); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + } + + auto loop = EV_DEFAULT; + + Sessions sessions(this, loop, config_, ssl_ctx); + if (start_listen(this, loop, &sessions, config_) != 0) { + std::cerr << "Could not listen" << std::endl; + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + } + return -1; + } + + ev_run(loop, 0); + return 0; +} + +const Config *HttpServer::get_config() const { return config_; } + +const StatusPage *HttpServer::get_status_page(int status) const { + switch (status) { + case 200: + return &status_pages_[IDX_200]; + case 301: + return &status_pages_[IDX_301]; + case 400: + return &status_pages_[IDX_400]; + case 404: + return &status_pages_[IDX_404]; + case 405: + return &status_pages_[IDX_405]; + default: + assert(0); + } + return nullptr; +} + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/HttpServer.h b/lib/nghttp2/src/HttpServer.h new file mode 100644 index 00000000000..f825b83e338 --- /dev/null +++ b/lib/nghttp2/src/HttpServer.h @@ -0,0 +1,253 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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_SERVER_H +#define HTTP_SERVER_H + +#include "nghttp2_config.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "http2.h" +#include "buffer.h" +#include "template.h" +#include "allocator.h" + +namespace nghttp2 { + +struct Config { + std::map> push; + std::map mime_types; + Headers trailer; + std::string trailer_names; + std::string htdocs; + std::string host; + std::string private_key_file; + std::string cert_file; + std::string dh_param_file; + std::string address; + std::string mime_types_file; + ev_tstamp stream_read_timeout; + ev_tstamp stream_write_timeout; + void *data_ptr; + size_t padding; + size_t num_worker; + size_t max_concurrent_streams; + ssize_t header_table_size; + ssize_t encoder_header_table_size; + int window_bits; + int connection_window_bits; + uint16_t port; + bool verbose; + bool daemon; + bool verify_client; + bool no_tls; + bool error_gzip; + bool early_response; + bool hexdump; + bool echo_upload; + bool no_content_length; + bool ktls; + bool no_rfc7540_pri; + Config(); + ~Config(); +}; + +class Http2Handler; + +struct FileEntry { + FileEntry(std::string path, int64_t length, int64_t mtime, int fd, + const std::string *content_type, + const std::chrono::steady_clock::time_point &last_valid, + bool stale = false) + : path(std::move(path)), + length(length), + mtime(mtime), + last_valid(last_valid), + content_type(content_type), + dlnext(nullptr), + dlprev(nullptr), + fd(fd), + usecount(1), + stale(stale) {} + std::string path; + std::multimap>::iterator it; + int64_t length; + int64_t mtime; + std::chrono::steady_clock::time_point last_valid; + const std::string *content_type; + FileEntry *dlnext, *dlprev; + int fd; + int usecount; + bool stale; +}; + +struct RequestHeader { + StringRef method; + StringRef scheme; + StringRef authority; + StringRef host; + StringRef path; + StringRef ims; + StringRef expect; + + struct { + nghttp2_rcbuf *method; + nghttp2_rcbuf *scheme; + nghttp2_rcbuf *authority; + nghttp2_rcbuf *host; + nghttp2_rcbuf *path; + nghttp2_rcbuf *ims; + nghttp2_rcbuf *expect; + } rcbuf; +}; + +struct Stream { + BlockAllocator balloc; + RequestHeader header; + Http2Handler *handler; + FileEntry *file_ent; + ev_timer rtimer; + ev_timer wtimer; + int64_t body_length; + int64_t body_offset; + // Total amount of bytes (sum of name and value length) used in + // headers. + size_t header_buffer_size; + int32_t stream_id; + bool echo_upload; + Stream(Http2Handler *handler, int32_t stream_id); + ~Stream(); +}; + +class Sessions; + +class Http2Handler { +public: + Http2Handler(Sessions *sessions, int fd, SSL *ssl, int64_t session_id); + ~Http2Handler(); + + void remove_self(); + void start_settings_timer(); + int on_read(); + int on_write(); + int connection_made(); + int verify_npn_result(); + + int submit_file_response(const StringRef &status, Stream *stream, + time_t last_modified, off_t file_length, + const std::string *content_type, + nghttp2_data_provider *data_prd); + + int submit_response(const StringRef &status, int32_t stream_id, + nghttp2_data_provider *data_prd); + + int submit_response(const StringRef &status, int32_t stream_id, + const HeaderRefs &headers, + nghttp2_data_provider *data_prd); + + int submit_non_final_response(const std::string &status, int32_t stream_id); + + int submit_push_promise(Stream *stream, const StringRef &push_path); + + int submit_rst_stream(Stream *stream, uint32_t error_code); + + void add_stream(int32_t stream_id, std::unique_ptr stream); + void remove_stream(int32_t stream_id); + Stream *get_stream(int32_t stream_id); + int64_t session_id() const; + Sessions *get_sessions() const; + const Config *get_config() const; + void remove_settings_timer(); + void terminate_session(uint32_t error_code); + + int fill_wb(); + + int read_clear(); + int write_clear(); + int tls_handshake(); + int read_tls(); + int write_tls(); + + struct ev_loop *get_loop() const; + + using WriteBuf = Buffer<64_k>; + + WriteBuf *get_wb(); + +private: + ev_io wev_; + ev_io rev_; + ev_timer settings_timerev_; + std::map> id2stream_; + WriteBuf wb_; + std::function read_, write_; + int64_t session_id_; + nghttp2_session *session_; + Sessions *sessions_; + SSL *ssl_; + const uint8_t *data_pending_; + size_t data_pendinglen_; + int fd_; +}; + +struct StatusPage { + std::string status; + FileEntry file_ent; +}; + +class HttpServer { +public: + HttpServer(const Config *config); + int listen(); + int run(); + const Config *get_config() const; + const StatusPage *get_status_page(int status) const; + +private: + std::vector status_pages_; + const Config *config_; +}; + +ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, + uint8_t *buf, size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data); + +} // namespace nghttp2 + +#endif // HTTP_SERVER_H diff --git a/lib/nghttp2/src/Makefile.am b/lib/nghttp2/src/Makefile.am new file mode 100644 index 00000000000..f112ac2cbcb --- /dev/null +++ b/lib/nghttp2/src/Makefile.am @@ -0,0 +1,257 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = testdata + +EXTRA_DIST = \ + CMakeLists.txt \ + test.example.com.pem \ + test.nghttp2.org.pem + +bin_PROGRAMS = +check_PROGRAMS = +TESTS = + +AM_CFLAGS = $(WARNCFLAGS) +AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS) +AM_CPPFLAGS = \ + -DPKGDATADIR='"$(pkgdatadir)"' \ + -DPKGLIBDIR='"$(pkglibdir)"' \ + -I$(top_srcdir)/lib/includes \ + -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/third-party \ + -I$(top_srcdir)/third-party/llhttp/include \ + @JEMALLOC_CFLAGS@ \ + @LIBXML2_CFLAGS@ \ + @LIBEV_CFLAGS@ \ + @LIBNGHTTP3_CFLAGS@ \ + @LIBNGTCP2_CRYPTO_QUICTLS_CFLAGS@ \ + @LIBNGTCP2_CRYPTO_BORINGSSL_CFLAGS@ \ + @LIBNGTCP2_CFLAGS@ \ + @OPENSSL_CFLAGS@ \ + @LIBCARES_CFLAGS@ \ + @JANSSON_CFLAGS@ \ + @LIBBPF_CFLAGS@ \ + @ZLIB_CFLAGS@ \ + @EXTRA_DEFS@ \ + @DEFS@ +AM_LDFLAGS = @LIBTOOL_LDFLAGS@ + +LDADD = $(top_builddir)/lib/libnghttp2.la \ + $(top_builddir)/third-party/liburl-parser.la \ + $(top_builddir)/third-party/libllhttp.la \ + @JEMALLOC_LIBS@ \ + @LIBXML2_LIBS@ \ + @LIBEV_LIBS@ \ + @LIBNGHTTP3_LIBS@ \ + @LIBNGTCP2_CRYPTO_QUICTLS_LIBS@ \ + @LIBNGTCP2_CRYPTO_BORINGSSL_LIBS@ \ + @LIBNGTCP2_LIBS@ \ + @OPENSSL_LIBS@ \ + @LIBCARES_LIBS@ \ + @SYSTEMD_LIBS@ \ + @JANSSON_LIBS@ \ + @LIBBPF_LIBS@ \ + @ZLIB_LIBS@ \ + @APPLDFLAGS@ + +if ENABLE_APP + +bin_PROGRAMS += nghttp nghttpd nghttpx + +HELPER_OBJECTS = util.cc \ + http2.cc timegm.c app_helper.cc nghttp2_gzip.c +HELPER_HFILES = util.h \ + http2.h timegm.h app_helper.h nghttp2_config.h \ + nghttp2_gzip.h network.h + +HTML_PARSER_OBJECTS = +HTML_PARSER_HFILES = HtmlParser.h + +if HAVE_LIBXML2 +HTML_PARSER_OBJECTS += HtmlParser.cc +endif # HAVE_LIBXML2 + +nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc nghttp.h \ + ${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES} \ + tls.cc tls.h ssl_compat.h + +nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \ + tls.cc tls.h ssl_compat.h \ + HttpServer.cc HttpServer.h + +bin_PROGRAMS += h2load + +h2load_SOURCES = util.cc util.h \ + http2.cc http2.h h2load.cc h2load.h \ + timegm.c timegm.h \ + tls.cc tls.h ssl_compat.h \ + h2load_session.h \ + h2load_http2_session.cc h2load_http2_session.h \ + h2load_http1_session.cc h2load_http1_session.h + +if ENABLE_HTTP3 +h2load_SOURCES += \ + h2load_http3_session.cc h2load_http3_session.h \ + h2load_quic.cc h2load_quic.h \ + quic.cc quic.h +endif # ENABLE_HTTP3 + +NGHTTPX_SRCS = \ + util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \ + app_helper.cc app_helper.h \ + tls.cc tls.h ssl_compat.h \ + shrpx_config.cc shrpx_config.h \ + shrpx_error.h \ + shrpx_accept_handler.cc shrpx_accept_handler.h \ + shrpx_connection_handler.cc shrpx_connection_handler.h \ + shrpx_client_handler.cc shrpx_client_handler.h \ + shrpx_upstream.h \ + shrpx_http2_upstream.cc shrpx_http2_upstream.h \ + shrpx_https_upstream.cc shrpx_https_upstream.h \ + shrpx_downstream.cc shrpx_downstream.h \ + shrpx_downstream_connection.cc shrpx_downstream_connection.h \ + shrpx_http_downstream_connection.cc shrpx_http_downstream_connection.h \ + shrpx_http2_downstream_connection.cc shrpx_http2_downstream_connection.h \ + shrpx_http2_session.cc shrpx_http2_session.h \ + shrpx_downstream_queue.cc shrpx_downstream_queue.h \ + shrpx_log.cc shrpx_log.h \ + shrpx_http.cc shrpx_http.h \ + shrpx_io_control.cc shrpx_io_control.h \ + shrpx_tls.cc shrpx_tls.h \ + shrpx_worker.cc shrpx_worker.h \ + shrpx_log_config.cc shrpx_log_config.h \ + shrpx_connect_blocker.cc shrpx_connect_blocker.h \ + shrpx_live_check.cc shrpx_live_check.h \ + shrpx_downstream_connection_pool.cc shrpx_downstream_connection_pool.h \ + shrpx_rate_limit.cc shrpx_rate_limit.h \ + shrpx_connection.cc shrpx_connection.h \ + shrpx_memcached_dispatcher.cc shrpx_memcached_dispatcher.h \ + shrpx_memcached_connection.cc shrpx_memcached_connection.h \ + shrpx_memcached_request.h \ + shrpx_memcached_result.h \ + shrpx_worker_process.cc shrpx_worker_process.h \ + shrpx_process.h \ + shrpx_signal.cc shrpx_signal.h \ + shrpx_router.cc shrpx_router.h \ + shrpx_api_downstream_connection.cc shrpx_api_downstream_connection.h \ + shrpx_health_monitor_downstream_connection.cc \ + shrpx_health_monitor_downstream_connection.h \ + shrpx_null_downstream_connection.cc shrpx_null_downstream_connection.h \ + shrpx_exec.cc shrpx_exec.h \ + shrpx_dns_resolver.cc shrpx_dns_resolver.h \ + shrpx_dual_dns_resolver.cc shrpx_dual_dns_resolver.h \ + shrpx_dns_tracker.cc shrpx_dns_tracker.h \ + buffer.h memchunk.h template.h allocator.h \ + xsi_strerror.c xsi_strerror.h + +if HAVE_MRUBY +NGHTTPX_SRCS += \ + shrpx_mruby.cc shrpx_mruby.h \ + shrpx_mruby_module.cc shrpx_mruby_module.h \ + shrpx_mruby_module_env.cc shrpx_mruby_module_env.h \ + shrpx_mruby_module_request.cc shrpx_mruby_module_request.h \ + shrpx_mruby_module_response.cc shrpx_mruby_module_response.h +endif # HAVE_MRUBY + +if ENABLE_HTTP3 +NGHTTPX_SRCS += \ + shrpx_quic.cc shrpx_quic.h \ + shrpx_quic_listener.cc shrpx_quic_listener.h \ + shrpx_quic_connection_handler.cc shrpx_quic_connection_handler.h \ + shrpx_http3_upstream.cc shrpx_http3_upstream.h \ + http3.cc http3.h \ + quic.cc quic.h +endif # ENABLE_HTTP3 + +noinst_LIBRARIES = libnghttpx.a +libnghttpx_a_SOURCES = ${NGHTTPX_SRCS} +libnghttpx_a_CPPFLAGS = ${AM_CPPFLAGS} + +nghttpx_SOURCES = shrpx.cc shrpx.h +nghttpx_CPPFLAGS = ${libnghttpx_a_CPPFLAGS} +nghttpx_LDADD = libnghttpx.a ${LDADD} + +if HAVE_MRUBY +libnghttpx_a_CPPFLAGS += \ + -I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@ +nghttpx_LDADD += -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@ +endif # HAVE_MRUBY + +if HAVE_NEVERBLEED +libnghttpx_a_CPPFLAGS += -I${top_srcdir}/third-party/neverbleed +nghttpx_LDADD += ${top_builddir}/third-party/libneverbleed.la +endif # HAVE_NEVERBLEED + +if HAVE_CUNIT +check_PROGRAMS += nghttpx-unittest +nghttpx_unittest_SOURCES = shrpx-unittest.cc \ + shrpx_tls_test.cc shrpx_tls_test.h \ + shrpx_downstream_test.cc shrpx_downstream_test.h \ + shrpx_config_test.cc shrpx_config_test.h \ + shrpx_worker_test.cc shrpx_worker_test.h \ + shrpx_http_test.cc shrpx_http_test.h \ + shrpx_router_test.cc shrpx_router_test.h \ + http2_test.cc http2_test.h \ + util_test.cc util_test.h \ + nghttp2_gzip_test.c nghttp2_gzip_test.h \ + nghttp2_gzip.c nghttp2_gzip.h \ + buffer_test.cc buffer_test.h \ + memchunk_test.cc memchunk_test.h \ + template_test.cc template_test.h \ + base64_test.cc base64_test.h +nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \ + -DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\" +nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@ + +if HAVE_MRUBY +nghttpx_unittest_CPPFLAGS += \ + -I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@ +nghttpx_unittest_LDADD += \ + -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@ +endif # HAVE_MRUBY + +if HAVE_NEVERBLEED +nghttpx_unittest_CPPFLAGS += -I${top_srcdir}/third-party/neverbleed +nghttpx_unittest_LDADD += ${top_builddir}/third-party/libneverbleed.la +endif # HAVE_NEVERBLEED + +TESTS += nghttpx-unittest +endif # HAVE_CUNIT + +endif # ENABLE_APP + +if ENABLE_HPACK_TOOLS + +bin_PROGRAMS += inflatehd deflatehd + +HPACK_TOOLS_COMMON_SRCS = \ + comp_helper.c comp_helper.h \ + util.cc util.h \ + timegm.c timegm.h + +inflatehd_SOURCES = inflatehd.cc $(HPACK_TOOLS_COMMON_SRCS) + +deflatehd_SOURCES = deflatehd.cc $(HPACK_TOOLS_COMMON_SRCS) + +endif # ENABLE_HPACK_TOOLS diff --git a/lib/nghttp2/src/allocator.h b/lib/nghttp2/src/allocator.h new file mode 100644 index 00000000000..97b9a418182 --- /dev/null +++ b/lib/nghttp2/src/allocator.h @@ -0,0 +1,273 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 ALLOCATOR_H +#define ALLOCATOR_H + +#include "nghttp2_config.h" + +#ifndef _WIN32 +# include +#endif // !_WIN32 + +#include +#include + +#include "template.h" + +namespace nghttp2 { + +struct MemBlock { + // The next MemBlock to chain them. This is for book keeping + // purpose to free them later. + MemBlock *next; + // begin is the pointer to the beginning of buffer. last is the + // location of next write. end is the one beyond of the end of the + // buffer. + uint8_t *begin, *last, *end; +}; + +// BlockAllocator allocates memory block with given size at once, and +// cuts the region from it when allocation is requested. If the +// requested size is larger than given threshold (plus small internal +// overhead), it will be allocated in a distinct buffer on demand. +// The |isolation_threshold| must be less than or equal to +// |block_size|. +struct BlockAllocator { + BlockAllocator(size_t block_size, size_t isolation_threshold) + : retain(nullptr), + head(nullptr), + block_size(block_size), + isolation_threshold(std::min(block_size, isolation_threshold)) { + assert(isolation_threshold <= block_size); + } + + ~BlockAllocator() { reset(); } + + BlockAllocator(BlockAllocator &&other) noexcept + : retain{std::exchange(other.retain, nullptr)}, + head{std::exchange(other.head, nullptr)}, + block_size(other.block_size), + isolation_threshold(other.isolation_threshold) {} + + BlockAllocator &operator=(BlockAllocator &&other) noexcept { + reset(); + + retain = std::exchange(other.retain, nullptr); + head = std::exchange(other.head, nullptr); + block_size = other.block_size; + isolation_threshold = other.isolation_threshold; + + return *this; + } + + BlockAllocator(const BlockAllocator &) = delete; + BlockAllocator &operator=(const BlockAllocator &) = delete; + + void reset() { + for (auto mb = retain; mb;) { + auto next = mb->next; + delete[] reinterpret_cast(mb); + mb = next; + } + + retain = nullptr; + head = nullptr; + } + + MemBlock *alloc_mem_block(size_t size) { + auto block = new uint8_t[sizeof(MemBlock) + size]; + auto mb = reinterpret_cast(block); + + mb->next = retain; + mb->begin = mb->last = block + sizeof(MemBlock); + mb->end = mb->begin + size; + retain = mb; + return mb; + } + + void *alloc(size_t size) { + if (size + sizeof(size_t) >= isolation_threshold) { + auto len = std::max(static_cast(16), size); + // We will store the allocated size in size_t field. + auto mb = alloc_mem_block(len + sizeof(size_t)); + auto sp = reinterpret_cast(mb->begin); + *sp = len; + mb->last = mb->end; + return mb->begin + sizeof(size_t); + } + + if (!head || + head->end - head->last < static_cast(size + sizeof(size_t))) { + head = alloc_mem_block(block_size); + } + + // We will store the allocated size in size_t field. + auto res = head->last + sizeof(size_t); + auto sp = reinterpret_cast(head->last); + *sp = size; + + head->last = reinterpret_cast( + (reinterpret_cast(res + size) + 0xf) & ~0xf); + + return res; + } + + // Returns allocated size for memory pointed by |ptr|. We assume + // that |ptr| was returned from alloc() or realloc(). + size_t get_alloc_length(void *ptr) { + return *reinterpret_cast(static_cast(ptr) - + sizeof(size_t)); + } + + // Allocates memory of at least |size| bytes. If |ptr| is nullptr, + // this is equivalent to alloc(size). If |ptr| is not nullptr, + // obtain the allocated size for |ptr|, assuming that |ptr| was + // returned from alloc() or realloc(). If the allocated size is + // greater than or equal to size, |ptr| is returned. Otherwise, + // allocates at least |size| bytes of memory, and the original + // content pointed by |ptr| is copied to the newly allocated memory. + void *realloc(void *ptr, size_t size) { + if (!ptr) { + return alloc(size); + } + auto alloclen = get_alloc_length(ptr); + auto p = reinterpret_cast(ptr); + if (size <= alloclen) { + return ptr; + } + + auto nalloclen = std::max(size + 1, alloclen * 2); + + auto res = alloc(nalloclen); + std::copy_n(p, alloclen, static_cast(res)); + + return res; + } + + // This holds live memory block to free them in dtor. + MemBlock *retain; + // Current memory block to use. + MemBlock *head; + // size of single memory block + size_t block_size; + // if allocation greater or equal to isolation_threshold bytes is + // requested, allocate dedicated block. + size_t isolation_threshold; +}; + +// Makes a copy of |src|. The resulting string will be +// NULL-terminated. +template +StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) { + auto dst = static_cast(alloc.alloc(src.size() + 1)); + auto p = dst; + p = std::copy(std::begin(src), std::end(src), p); + *p = '\0'; + return StringRef{dst, src.size()}; +} + +// private function used in concat_string_ref. this is the base +// function of concat_string_ref_count(). +inline constexpr size_t concat_string_ref_count(size_t acc) { return acc; } + +// private function used in concat_string_ref. This function counts +// the sum of length of given arguments. The calculated length is +// accumulated, and passed to the next function. +template +constexpr size_t concat_string_ref_count(size_t acc, const StringRef &value, + Args &&...args) { + return concat_string_ref_count(acc + value.size(), + std::forward(args)...); +} + +// private function used in concat_string_ref. this is the base +// function of concat_string_ref_copy(). +inline uint8_t *concat_string_ref_copy(uint8_t *p) { return p; } + +// private function used in concat_string_ref. This function copies +// given strings into |p|. |p| is incremented by the copied length, +// and returned. In the end, return value points to the location one +// beyond the last byte written. +template +uint8_t *concat_string_ref_copy(uint8_t *p, const StringRef &value, + Args &&...args) { + p = std::copy(std::begin(value), std::end(value), p); + return concat_string_ref_copy(p, std::forward(args)...); +} + +// Returns the string which is the concatenation of |args| in the +// given order. The resulting string will be NULL-terminated. +template +StringRef concat_string_ref(BlockAllocator &alloc, Args &&...args) { + size_t len = concat_string_ref_count(0, std::forward(args)...); + auto dst = static_cast(alloc.alloc(len + 1)); + auto p = dst; + p = concat_string_ref_copy(p, std::forward(args)...); + *p = '\0'; + return StringRef{dst, len}; +} + +// Returns the string which is the concatenation of |value| and |args| +// in the given order. The resulting string will be NULL-terminated. +// This function assumes that the pointer value value.c_str() was +// obtained from alloc.alloc() or alloc.realloc(), and attempts to use +// unused memory region by using alloc.realloc(). If value is empty, +// then just call concat_string_ref(). +template +StringRef realloc_concat_string_ref(BlockAllocator &alloc, + const StringRef &value, Args &&...args) { + if (value.empty()) { + return concat_string_ref(alloc, std::forward(args)...); + } + + auto len = + value.size() + concat_string_ref_count(0, std::forward(args)...); + auto dst = static_cast( + alloc.realloc(const_cast(value.byte()), len + 1)); + auto p = dst + value.size(); + p = concat_string_ref_copy(p, std::forward(args)...); + *p = '\0'; + + return StringRef{dst, len}; +} + +struct ByteRef { + // The pointer to the beginning of the buffer. + uint8_t *base; + // The length of the buffer. + size_t len; +}; + +// Makes a buffer with given size. The resulting byte string might +// not be NULL-terminated. +template +ByteRef make_byte_ref(BlockAllocator &alloc, size_t size) { + auto dst = static_cast(alloc.alloc(size)); + return {dst, size}; +} + +} // namespace nghttp2 + +#endif // ALLOCATOR_H diff --git a/lib/nghttp2/src/app_helper.cc b/lib/nghttp2/src/app_helper.cc new file mode 100644 index 00000000000..ef9276285a7 --- /dev/null +++ b/lib/nghttp2/src/app_helper.cc @@ -0,0 +1,518 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "app_helper.h" +#include "util.h" +#include "http2.h" +#include "template.h" + +namespace nghttp2 { + +namespace { +const char *strsettingsid(int32_t id) { + switch (id) { + case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: + return "SETTINGS_HEADER_TABLE_SIZE"; + case NGHTTP2_SETTINGS_ENABLE_PUSH: + return "SETTINGS_ENABLE_PUSH"; + case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + return "SETTINGS_MAX_CONCURRENT_STREAMS"; + case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + return "SETTINGS_INITIAL_WINDOW_SIZE"; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + return "SETTINGS_MAX_FRAME_SIZE"; + case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + return "SETTINGS_MAX_HEADER_LIST_SIZE"; + case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: + return "SETTINGS_ENABLE_CONNECT_PROTOCOL"; + case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES: + return "SETTINGS_NO_RFC7540_PRIORITIES"; + default: + return "UNKNOWN"; + } +} +} // namespace + +namespace { +std::string strframetype(uint8_t type) { + switch (type) { + case NGHTTP2_DATA: + return "DATA"; + case NGHTTP2_HEADERS: + return "HEADERS"; + case NGHTTP2_PRIORITY: + return "PRIORITY"; + case NGHTTP2_RST_STREAM: + return "RST_STREAM"; + case NGHTTP2_SETTINGS: + return "SETTINGS"; + case NGHTTP2_PUSH_PROMISE: + return "PUSH_PROMISE"; + case NGHTTP2_PING: + return "PING"; + case NGHTTP2_GOAWAY: + return "GOAWAY"; + case NGHTTP2_WINDOW_UPDATE: + return "WINDOW_UPDATE"; + case NGHTTP2_ALTSVC: + return "ALTSVC"; + case NGHTTP2_ORIGIN: + return "ORIGIN"; + case NGHTTP2_PRIORITY_UPDATE: + return "PRIORITY_UPDATE"; + } + + std::string s = "extension(0x"; + s += util::format_hex(&type, 1); + s += ')'; + + return s; +}; +} // namespace + +namespace { +bool color_output = false; +} // namespace + +void set_color_output(bool f) { color_output = f; } + +namespace { +FILE *outfile = stdout; +} // namespace + +void set_output(FILE *file) { outfile = file; } + +namespace { +void print_frame_attr_indent() { fprintf(outfile, " "); } +} // namespace + +namespace { +const char *ansi_esc(const char *code) { return color_output ? code : ""; } +} // namespace + +namespace { +const char *ansi_escend() { return color_output ? "\033[0m" : ""; } +} // namespace + +namespace { +void print_nv(nghttp2_nv *nv) { + fprintf(outfile, "%s%s%s: %s\n", ansi_esc("\033[1;34m"), nv->name, + ansi_escend(), nv->value); +} +} // namespace +namespace { +void print_nv(nghttp2_nv *nva, size_t nvlen) { + auto end = nva + nvlen; + for (; nva != end; ++nva) { + print_frame_attr_indent(); + + print_nv(nva); + } +} +} // namespace + +void print_timer() { + auto millis = get_timer(); + fprintf(outfile, "%s[%3ld.%03ld]%s", ansi_esc("\033[33m"), + (long int)(millis.count() / 1000), (long int)(millis.count() % 1000), + ansi_escend()); +} + +namespace { +void print_frame_hd(const nghttp2_frame_hd &hd) { + fprintf(outfile, "\n", hd.length, + hd.flags, hd.stream_id); +} +} // namespace + +namespace { +void print_flags(const nghttp2_frame_hd &hd) { + std::string s; + switch (hd.type) { + case NGHTTP2_DATA: + if (hd.flags & NGHTTP2_FLAG_END_STREAM) { + s += "END_STREAM"; + } + if (hd.flags & NGHTTP2_FLAG_PADDED) { + if (!s.empty()) { + s += " | "; + } + s += "PADDED"; + } + break; + case NGHTTP2_HEADERS: + if (hd.flags & NGHTTP2_FLAG_END_STREAM) { + s += "END_STREAM"; + } + if (hd.flags & NGHTTP2_FLAG_END_HEADERS) { + if (!s.empty()) { + s += " | "; + } + s += "END_HEADERS"; + } + if (hd.flags & NGHTTP2_FLAG_PADDED) { + if (!s.empty()) { + s += " | "; + } + s += "PADDED"; + } + if (hd.flags & NGHTTP2_FLAG_PRIORITY) { + if (!s.empty()) { + s += " | "; + } + s += "PRIORITY"; + } + + break; + case NGHTTP2_PRIORITY: + break; + case NGHTTP2_SETTINGS: + if (hd.flags & NGHTTP2_FLAG_ACK) { + s += "ACK"; + } + break; + case NGHTTP2_PUSH_PROMISE: + if (hd.flags & NGHTTP2_FLAG_END_HEADERS) { + s += "END_HEADERS"; + } + if (hd.flags & NGHTTP2_FLAG_PADDED) { + if (!s.empty()) { + s += " | "; + } + s += "PADDED"; + } + break; + case NGHTTP2_PING: + if (hd.flags & NGHTTP2_FLAG_ACK) { + s += "ACK"; + } + break; + } + fprintf(outfile, "; %s\n", s.c_str()); +} +} // namespace + +enum print_type { PRINT_SEND, PRINT_RECV }; + +namespace { +const char *frame_name_ansi_esc(print_type ptype) { + return ansi_esc(ptype == PRINT_SEND ? "\033[1;35m" : "\033[1;36m"); +} +} // namespace + +namespace { +void print_frame(print_type ptype, const nghttp2_frame *frame) { + fprintf(outfile, "%s%s%s frame ", frame_name_ansi_esc(ptype), + strframetype(frame->hd.type).c_str(), ansi_escend()); + print_frame_hd(frame->hd); + if (frame->hd.flags) { + print_frame_attr_indent(); + print_flags(frame->hd); + } + switch (frame->hd.type) { + case NGHTTP2_DATA: + if (frame->data.padlen > 0) { + print_frame_attr_indent(); + fprintf(outfile, "(padlen=%zu)\n", frame->data.padlen); + } + break; + case NGHTTP2_HEADERS: + print_frame_attr_indent(); + fprintf(outfile, "(padlen=%zu", frame->headers.padlen); + if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) { + fprintf(outfile, ", dep_stream_id=%d, weight=%u, exclusive=%d", + frame->headers.pri_spec.stream_id, frame->headers.pri_spec.weight, + frame->headers.pri_spec.exclusive); + } + fprintf(outfile, ")\n"); + switch (frame->headers.cat) { + case NGHTTP2_HCAT_REQUEST: + print_frame_attr_indent(); + fprintf(outfile, "; Open new stream\n"); + break; + case NGHTTP2_HCAT_RESPONSE: + print_frame_attr_indent(); + fprintf(outfile, "; First response header\n"); + break; + case NGHTTP2_HCAT_PUSH_RESPONSE: + print_frame_attr_indent(); + fprintf(outfile, "; First push response header\n"); + break; + default: + break; + } + print_nv(frame->headers.nva, frame->headers.nvlen); + break; + case NGHTTP2_PRIORITY: + print_frame_attr_indent(); + + fprintf(outfile, "(dep_stream_id=%d, weight=%u, exclusive=%d)\n", + frame->priority.pri_spec.stream_id, frame->priority.pri_spec.weight, + frame->priority.pri_spec.exclusive); + + break; + case NGHTTP2_RST_STREAM: + print_frame_attr_indent(); + fprintf(outfile, "(error_code=%s(0x%02x))\n", + nghttp2_http2_strerror(frame->rst_stream.error_code), + frame->rst_stream.error_code); + break; + case NGHTTP2_SETTINGS: + print_frame_attr_indent(); + fprintf(outfile, "(niv=%lu)\n", + static_cast(frame->settings.niv)); + for (size_t i = 0; i < frame->settings.niv; ++i) { + print_frame_attr_indent(); + fprintf(outfile, "[%s(0x%02x):%u]\n", + strsettingsid(frame->settings.iv[i].settings_id), + frame->settings.iv[i].settings_id, frame->settings.iv[i].value); + } + break; + case NGHTTP2_PUSH_PROMISE: + print_frame_attr_indent(); + fprintf(outfile, "(padlen=%zu, promised_stream_id=%d)\n", + frame->push_promise.padlen, frame->push_promise.promised_stream_id); + print_nv(frame->push_promise.nva, frame->push_promise.nvlen); + break; + case NGHTTP2_PING: + print_frame_attr_indent(); + fprintf(outfile, "(opaque_data=%s)\n", + util::format_hex(frame->ping.opaque_data, 8).c_str()); + break; + case NGHTTP2_GOAWAY: + print_frame_attr_indent(); + fprintf(outfile, + "(last_stream_id=%d, error_code=%s(0x%02x), " + "opaque_data(%u)=[%s])\n", + frame->goaway.last_stream_id, + nghttp2_http2_strerror(frame->goaway.error_code), + frame->goaway.error_code, + static_cast(frame->goaway.opaque_data_len), + util::ascii_dump(frame->goaway.opaque_data, + frame->goaway.opaque_data_len) + .c_str()); + break; + case NGHTTP2_WINDOW_UPDATE: + print_frame_attr_indent(); + fprintf(outfile, "(window_size_increment=%d)\n", + frame->window_update.window_size_increment); + break; + case NGHTTP2_ALTSVC: { + auto altsvc = static_cast(frame->ext.payload); + print_frame_attr_indent(); + fprintf(outfile, "(origin=[%.*s], altsvc_field_value=[%.*s])\n", + static_cast(altsvc->origin_len), altsvc->origin, + static_cast(altsvc->field_value_len), altsvc->field_value); + break; + } + case NGHTTP2_ORIGIN: { + auto origin = static_cast(frame->ext.payload); + for (size_t i = 0; i < origin->nov; ++i) { + auto ent = &origin->ov[i]; + print_frame_attr_indent(); + fprintf(outfile, "[%.*s]\n", (int)ent->origin_len, ent->origin); + } + break; + } + case NGHTTP2_PRIORITY_UPDATE: { + auto priority_update = + static_cast(frame->ext.payload); + print_frame_attr_indent(); + fprintf(outfile, + "(prioritized_stream_id=%d, priority_field_value=[%.*s])\n", + priority_update->stream_id, + static_cast(priority_update->field_value_len), + priority_update->field_value); + break; + } + default: + break; + } +} +} // namespace + +int verbose_on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags, + void *user_data) { + nghttp2_nv nv = {const_cast(name), const_cast(value), + namelen, valuelen}; + + print_timer(); + fprintf(outfile, " recv (stream_id=%d", frame->hd.stream_id); + if (flags & NGHTTP2_NV_FLAG_NO_INDEX) { + fprintf(outfile, ", sensitive"); + } + fprintf(outfile, ") "); + + print_nv(&nv); + fflush(outfile); + + return 0; +} + +int verbose_on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + print_timer(); + fprintf(outfile, " recv "); + print_frame(PRINT_RECV, frame); + fflush(outfile); + return 0; +} + +int verbose_on_invalid_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, + void *user_data) { + print_timer(); + fprintf(outfile, " [INVALID; error=%s] recv ", + nghttp2_strerror(lib_error_code)); + print_frame(PRINT_RECV, frame); + fflush(outfile); + return 0; +} + +int verbose_on_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + print_timer(); + fprintf(outfile, " send "); + print_frame(PRINT_SEND, frame); + fflush(outfile); + return 0; +} + +int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + print_timer(); + auto srecv = + nghttp2_session_get_stream_effective_recv_data_length(session, stream_id); + auto crecv = nghttp2_session_get_effective_recv_data_length(session); + + fprintf(outfile, + " recv (stream_id=%d, length=%zu, srecv=%d, crecv=%d) DATA\n", + stream_id, len, srecv, crecv); + fflush(outfile); + + return 0; +} + +int verbose_error_callback(nghttp2_session *session, int lib_error_code, + const char *msg, size_t len, void *user_data) { + print_timer(); + fprintf(outfile, " [ERROR] %.*s\n", (int)len, msg); + fflush(outfile); + + return 0; +} + +namespace { +std::chrono::steady_clock::time_point base_tv; +} // namespace + +void reset_timer() { base_tv = std::chrono::steady_clock::now(); } + +std::chrono::milliseconds get_timer() { + return time_delta(std::chrono::steady_clock::now(), base_tv); +} + +std::chrono::steady_clock::time_point get_time() { + return std::chrono::steady_clock::now(); +} + +ssize_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in, + size_t inlen) { + int rv; + z_stream zst{}; + uint8_t temp_out[8_k]; + auto temp_outlen = sizeof(temp_out); + + rv = deflateInit2(&zst, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 9, + Z_DEFAULT_STRATEGY); + + if (rv != Z_OK) { + return -1; + } + + zst.avail_in = inlen; + zst.next_in = (uint8_t *)in; + zst.avail_out = temp_outlen; + zst.next_out = temp_out; + + rv = deflate(&zst, Z_FINISH); + + deflateEnd(&zst); + + if (rv != Z_STREAM_END) { + return -1; + } + + temp_outlen -= zst.avail_out; + + if (temp_outlen > outlen) { + return -1; + } + + memcpy(out, temp_out, temp_outlen); + + return temp_outlen; +} + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/app_helper.h b/lib/nghttp2/src/app_helper.h new file mode 100644 index 00000000000..5424054ffa0 --- /dev/null +++ b/lib/nghttp2/src/app_helper.h @@ -0,0 +1,98 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 APP_HELPER_H +#define APP_HELPER_H + +#include "nghttp2_config.h" + +#include +#include +#ifdef HAVE_SYS_TIME_H +# include +#endif // HAVE_SYS_TIME_H +#include + +#include +#include + +#include + +namespace nghttp2 { + +int verbose_on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags, void *user_data); + +int verbose_on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data); + +int verbose_on_invalid_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, void *user_data); + +int verbose_on_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data); + +int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data); + +int verbose_error_callback(nghttp2_session *session, int lib_error_code, + const char *msg, size_t len, void *user_data); + +// Returns difference between |a| and |b| in milliseconds, assuming +// |a| is more recent than |b|. +template +std::chrono::milliseconds time_delta(const TimePoint &a, const TimePoint &b) { + return std::chrono::duration_cast(a - b); +} + +// Resets timer +void reset_timer(); + +// Returns the duration since timer reset. +std::chrono::milliseconds get_timer(); + +// Returns current time point. +std::chrono::steady_clock::time_point get_time(); + +void print_timer(); + +// Setting true will print characters with ANSI color escape codes +// when printing HTTP2 frames. This function changes a static +// variable. +void set_color_output(bool f); + +// Set output file when printing HTTP2 frames. By default, stdout is +// used. +void set_output(FILE *file); + +ssize_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in, + size_t inlen); + +} // namespace nghttp2 + +#endif // APP_HELPER_H diff --git a/lib/nghttp2/src/base64.h b/lib/nghttp2/src/base64.h new file mode 100644 index 00000000000..1bd51afa1dd --- /dev/null +++ b/lib/nghttp2/src/base64.h @@ -0,0 +1,225 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 BASE64_H +#define BASE64_H + +#include "nghttp2_config.h" + +#include + +#include "template.h" +#include "allocator.h" + +namespace nghttp2 { + +namespace base64 { + +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 std::string encode(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(*first++) << 16; + n += static_cast(*first++) << 8; + n += static_cast(*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(*first++) << 16; + n += static_cast(*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(*first++) << 16; + *p++ = B64_CHARS[n >> 18]; + *p++ = B64_CHARS[(n >> 12) & 0x3fu]; + *p++ = '='; + *p++ = '='; + } + return res; +} + +constexpr size_t encode_length(size_t n) { return (n + 2) / 3 * 4; } + +template +OutputIt encode(InputIt first, InputIt last, OutputIt d_first) { + size_t len = last - first; + if (len == 0) { + return d_first; + } + auto r = len % 3; + auto j = last - r; + auto p = d_first; + while (first != j) { + uint32_t n = static_cast(*first++) << 16; + n += static_cast(*first++) << 8; + n += static_cast(*first++); + *p++ = B64_CHARS[n >> 18]; + *p++ = B64_CHARS[(n >> 12) & 0x3fu]; + *p++ = B64_CHARS[(n >> 6) & 0x3fu]; + *p++ = B64_CHARS[n & 0x3fu]; + } + + switch (r) { + case 2: { + uint32_t n = static_cast(*first++) << 16; + n += static_cast(*first++) << 8; + *p++ = B64_CHARS[n >> 18]; + *p++ = B64_CHARS[(n >> 12) & 0x3fu]; + *p++ = B64_CHARS[(n >> 6) & 0x3fu]; + *p++ = '='; + break; + } + case 1: { + uint32_t n = static_cast(*first++) << 16; + *p++ = B64_CHARS[n >> 18]; + *p++ = B64_CHARS[(n >> 12) & 0x3fu]; + *p++ = '='; + *p++ = '='; + break; + } + } + return p; +} + +template +InputIt next_decode_input(InputIt first, InputIt last, const int *tbl) { + for (; first != last; ++first) { + if (tbl[static_cast(*first)] != -1 || *first == '=') { + break; + } + } + return first; +} + +template +OutputIt decode(InputIt first, InputIt last, OutputIt d_first) { + static constexpr int INDEX_TABLE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1}; + assert(std::distance(first, last) % 4 == 0); + auto p = d_first; + for (; first != last;) { + uint32_t n = 0; + for (int i = 1; i <= 4; ++i, ++first) { + auto idx = INDEX_TABLE[static_cast(*first)]; + if (idx == -1) { + if (i <= 2) { + return d_first; + } + if (i == 3) { + if (*first == '=' && *(first + 1) == '=' && first + 2 == last) { + *p++ = n >> 16; + return p; + } + return d_first; + } + if (*first == '=' && first + 1 == last) { + *p++ = n >> 16; + *p++ = n >> 8 & 0xffu; + return p; + } + return d_first; + } + + n += idx << (24 - i * 6); + } + + *p++ = n >> 16; + *p++ = n >> 8 & 0xffu; + *p++ = n & 0xffu; + } + + return p; +} + +template std::string decode(InputIt first, InputIt last) { + auto len = std::distance(first, last); + if (len % 4 != 0) { + return ""; + } + std::string res; + res.resize(len / 4 * 3); + + res.erase(decode(first, last, std::begin(res)), std::end(res)); + + return res; +} + +template +StringRef decode(BlockAllocator &balloc, InputIt first, InputIt last) { + auto len = std::distance(first, last); + if (len % 4 != 0) { + return StringRef::from_lit(""); + } + auto iov = make_byte_ref(balloc, len / 4 * 3 + 1); + auto p = iov.base; + + p = decode(first, last, p); + *p = '\0'; + + return StringRef{iov.base, p}; +} + +} // namespace base64 + +} // namespace nghttp2 + +#endif // BASE64_H diff --git a/lib/nghttp2/src/base64_test.cc b/lib/nghttp2/src/base64_test.cc new file mode 100644 index 00000000000..4324bd744ca --- /dev/null +++ b/lib/nghttp2/src/base64_test.cc @@ -0,0 +1,121 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "base64_test.h" + +#include +#include + +#include + +#include + +#include "base64.h" + +namespace nghttp2 { + +void test_base64_encode(void) { + { + std::string in = "\xff"; + auto out = base64::encode(std::begin(in), std::end(in)); + CU_ASSERT("/w==" == out); + } + { + std::string in = "\xff\xfe"; + auto out = base64::encode(std::begin(in), std::end(in)); + CU_ASSERT("//4=" == out); + } + { + std::string in = "\xff\xfe\xfd"; + auto out = base64::encode(std::begin(in), std::end(in)); + CU_ASSERT("//79" == out); + } + { + std::string in = "\xff\xfe\xfd\xfc"; + auto out = base64::encode(std::begin(in), std::end(in)); + CU_ASSERT("//79/A==" == out); + } +} + +void test_base64_decode(void) { + BlockAllocator balloc(4096, 4096); + { + std::string in = "/w=="; + auto out = base64::decode(std::begin(in), std::end(in)); + CU_ASSERT("\xff" == out); + CU_ASSERT("\xff" == base64::decode(balloc, std::begin(in), std::end(in))); + } + { + std::string in = "//4="; + auto out = base64::decode(std::begin(in), std::end(in)); + CU_ASSERT("\xff\xfe" == out); + CU_ASSERT("\xff\xfe" == + base64::decode(balloc, std::begin(in), std::end(in))); + } + { + std::string in = "//79"; + auto out = base64::decode(std::begin(in), std::end(in)); + CU_ASSERT("\xff\xfe\xfd" == out); + CU_ASSERT("\xff\xfe\xfd" == + base64::decode(balloc, std::begin(in), std::end(in))); + } + { + std::string in = "//79/A=="; + auto out = base64::decode(std::begin(in), std::end(in)); + CU_ASSERT("\xff\xfe\xfd\xfc" == out); + CU_ASSERT("\xff\xfe\xfd\xfc" == + base64::decode(balloc, std::begin(in), std::end(in))); + } + { + // we check the number of valid input must be multiples of 4 + std::string in = "//79="; + auto out = base64::decode(std::begin(in), std::end(in)); + CU_ASSERT("" == out); + CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in))); + } + { + // ending invalid character at the boundary of multiples of 4 is + // bad + std::string in = "bmdodHRw\n"; + auto out = base64::decode(std::begin(in), std::end(in)); + CU_ASSERT("" == out); + CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in))); + } + { + // after seeing '=', subsequent input must be also '='. + std::string in = "//79/A=A"; + auto out = base64::decode(std::begin(in), std::end(in)); + CU_ASSERT("" == out); + CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in))); + } + { + // additional '=' at the end is bad + std::string in = "//79/A======"; + auto out = base64::decode(std::begin(in), std::end(in)); + CU_ASSERT("" == out); + CU_ASSERT("" == base64::decode(balloc, std::begin(in), std::end(in))); + } +} + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/base64_test.h b/lib/nghttp2/src/base64_test.h new file mode 100644 index 00000000000..8bdb84f8ec2 --- /dev/null +++ b/lib/nghttp2/src/base64_test.h @@ -0,0 +1,39 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 BASE64_TEST_H +#define BASE64_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_base64_encode(void); +void test_base64_decode(void); + +} // namespace nghttp2 + +#endif // BASE64_TEST_H diff --git a/lib/nghttp2/src/buffer.h b/lib/nghttp2/src/buffer.h new file mode 100644 index 00000000000..1921edf19a2 --- /dev/null +++ b/lib/nghttp2/src/buffer.h @@ -0,0 +1,78 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 BUFFER_H +#define BUFFER_H + +#include "nghttp2_config.h" + +#include +#include +#include + +namespace nghttp2 { + +template struct Buffer { + Buffer() : pos(std::begin(buf)), last(pos) {} + // Returns the number of bytes to read. + size_t rleft() const { return last - pos; } + // Returns the number of bytes this buffer can store. + size_t wleft() const { return std::end(buf) - last; } + // Writes up to min(wleft(), |count|) bytes from buffer pointed by + // |src|. Returns number of bytes written. + size_t write(const void *src, size_t count) { + count = std::min(count, wleft()); + auto p = static_cast(src); + last = std::copy_n(p, count, last); + return count; + } + size_t write(size_t count) { + count = std::min(count, wleft()); + last += count; + return count; + } + // Drains min(rleft(), |count|) bytes from start of the buffer. + size_t drain(size_t count) { + count = std::min(count, rleft()); + pos += count; + return count; + } + size_t drain_reset(size_t count) { + count = std::min(count, rleft()); + std::copy(pos + count, last, std::begin(buf)); + last = std::begin(buf) + (last - (pos + count)); + pos = std::begin(buf); + return count; + } + void reset() { pos = last = std::begin(buf); } + uint8_t *begin() { return std::begin(buf); } + uint8_t &operator[](size_t n) { return buf[n]; } + const uint8_t &operator[](size_t n) const { return buf[n]; } + std::array buf; + uint8_t *pos, *last; +}; + +} // namespace nghttp2 + +#endif // BUFFER_H diff --git a/lib/nghttp2/src/buffer_test.cc b/lib/nghttp2/src/buffer_test.cc new file mode 100644 index 00000000000..38688edc7ad --- /dev/null +++ b/lib/nghttp2/src/buffer_test.cc @@ -0,0 +1,78 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "buffer_test.h" + +#include +#include +#include + +#include + +#include + +#include "buffer.h" + +namespace nghttp2 { + +void test_buffer_write(void) { + Buffer<16> b; + CU_ASSERT(0 == b.rleft()); + CU_ASSERT(16 == b.wleft()); + + b.write("012", 3); + + CU_ASSERT(3 == b.rleft()); + CU_ASSERT(13 == b.wleft()); + CU_ASSERT(b.pos == std::begin(b.buf)); + + b.drain(3); + + CU_ASSERT(0 == b.rleft()); + CU_ASSERT(13 == b.wleft()); + CU_ASSERT(3 == b.pos - std::begin(b.buf)); + + auto n = b.write("0123456789ABCDEF", 16); + + CU_ASSERT(n == 13); + + CU_ASSERT(13 == b.rleft()); + CU_ASSERT(0 == b.wleft()); + CU_ASSERT(3 == b.pos - std::begin(b.buf)); + CU_ASSERT(0 == memcmp(b.pos, "0123456789ABC", 13)); + + b.reset(); + + CU_ASSERT(0 == b.rleft()); + CU_ASSERT(16 == b.wleft()); + CU_ASSERT(b.pos == std::begin(b.buf)); + + b.write(5); + + CU_ASSERT(5 == b.rleft()); + CU_ASSERT(11 == b.wleft()); + CU_ASSERT(b.pos == std::begin(b.buf)); +} + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/buffer_test.h b/lib/nghttp2/src/buffer_test.h new file mode 100644 index 00000000000..6789aa39bc5 --- /dev/null +++ b/lib/nghttp2/src/buffer_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 BUFFER_TEST_H +#define BUFFER_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_buffer_write(void); + +} // namespace nghttp2 + +#endif // BUFFER_TEST_H diff --git a/lib/nghttp2/src/ca-config.json b/lib/nghttp2/src/ca-config.json new file mode 100644 index 00000000000..5788d0739b0 --- /dev/null +++ b/lib/nghttp2/src/ca-config.json @@ -0,0 +1,17 @@ +{ + "signing": { + "default": { + "expiry": "87600h" + }, + "profiles": { + "server": { + "expiry": "87600h", + "usages": [ + "signing", + "key encipherment", + "server auth" + ] + } + } + } +} diff --git a/lib/nghttp2/src/ca.nghttp2.org-key.pem b/lib/nghttp2/src/ca.nghttp2.org-key.pem new file mode 100644 index 00000000000..6ce8707f4dd --- /dev/null +++ b/lib/nghttp2/src/ca.nghttp2.org-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA1kVkF8QSUwW/HV9EFRPSMoiOVmYwB8vqKDtT0d6MFiKAM8/Y +JFUq2uKlUydgT4IPE7PATvVcIj3GtL9XzPhscqYO/S0Y7scyTE2VAPmtz+StPWf2 +wZ1IQR09HrnDTc44KvYGZpefBZkD9UjbmJ9a1ZmJjJiMr3hTnKE/sxZ2+dMsnMZX +N822cfaHyTN+T0+Tyw5vBBboCDsZzxmf+9FFIDJNs3NL34cR8EZRhpfaegapH8bt +OJ+D+RZ2kg7E/YYkGcS6NodvTjSUFCFHpWjHCfTFhn/owBIAooCdWorh6dc8Q72l +AodwNLXS8uuPgPqM5s4Cz57m7Zgs4OilNmIdawIDAQABAoIBAQCwqLtygLye6KD+ +RXorapEmCsJX5553/x6Klwdvg+25ni5XCWjp47IWj0DBQzi7tL5bfxrxvod8z7QR +d6SbIMLA77px8Ima7G7CzEAqcrBkM+TFOP8P+G4HCWVH/N5SOtDCUt9KHH4Grna9 +95jdx5yreRAX8/oh/bHp9GRBcicbpwYMVWOnjTE2seEUYQOpdpYdP4bOPUvAju0l +mwmy2/dDGmbibktN3sdHEhDodKu+Znv7nFZo0jzhlyoXse653WcvaQeZZYuojvSe +Sr92DvPp7UaYrb4KvT7ujXiPavSV2m/4EmGtyqevUf2dZ6sfMXZjmXsjWz9txhWp +4BgbHyHRAoGBAPqyuNj2CDD3FE7N3Hxyba8d+ZtsVUNawjq2gwOvT9NLsMstOGyH +OCc1v4W6Sq4w1wo4nIJyY8kNZwtReaTHOPZlDgBhVvk/x8eLBu+QTMRyocRt1LoD +8HyKxWSAnYTtCh/GUEQ37amIqvOJ5GNL+25WDzevLa5kMYWG743uxEupAoGBANrN +c/fVxepvP0GISlLpL3aZCFGAjMrq3xUYcf/w4wPoMq6AdpIPeRVBmJ1/Uqw1FkV8 +NRKJNPE2YcMuv8iMeQlacoPd34KT9ob80EYVlMwAkeC0NK+FfiM/UteR0wB49gmi +ugX9YlJytOP9aUgPvEGT6l+XtgGC44W1TQWe62zzAoGBAKZenNU+0UjNb6isbToZ +Jjkkh1Vhm2PLg0I7hM6ZNTxf6r+rDtrXEajTvnocmxrmRo796r+W8immv09/jl6P +53l8rsIJ1xIqBYai+MNa29cyy6/zw0x++MVtwnlj8SUZubJEhVgAVbRAglKEnBBZ +iE48xnSJyKMG0uZuGePzJEmhAoGBAIOHJcNBumum3DuklikpC+MbMyjrQbdpYRjp +TP4x7AWZO34ysxQyQPNKL1feBfCHKRA0DiNKX4zwx+vw2lDQQKIiwNwMMCPqljOn +HfxDVOMdJJQTP+iTMrQ1iLMVceXC0QQR0glvu/8b/SlgWD19WAmDxUwZgst9xw/F +YLuUQKmJAoGAREeTugd4hc0U/YV/BQQjSCLhl11EtVry/oQMHj8KZpIJhP7tj8lw +hSE0+z04oMhiTeq55PYKQkTo5l6V4PW0zfpEwlKEEm0erab1G9Ddh7us47XFcKLl +Rmk192EVZ0lQuzftsYv7dzRLiAR7yDFXwD1ELIK/uPkwBtu7wtHlq+M= +-----END RSA PRIVATE KEY----- diff --git a/lib/nghttp2/src/ca.nghttp2.org.csr b/lib/nghttp2/src/ca.nghttp2.org.csr new file mode 100644 index 00000000000..37ee5600000 --- /dev/null +++ b/lib/nghttp2/src/ca.nghttp2.org.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICwjCCAaoCAQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx +ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2Eu +bmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWRWQX +xBJTBb8dX0QVE9IyiI5WZjAHy+ooO1PR3owWIoAzz9gkVSra4qVTJ2BPgg8Ts8BO +9VwiPca0v1fM+Gxypg79LRjuxzJMTZUA+a3P5K09Z/bBnUhBHT0eucNNzjgq9gZm +l58FmQP1SNuYn1rVmYmMmIyveFOcoT+zFnb50yycxlc3zbZx9ofJM35PT5PLDm8E +FugIOxnPGZ/70UUgMk2zc0vfhxHwRlGGl9p6Bqkfxu04n4P5FnaSDsT9hiQZxLo2 +h29ONJQUIUelaMcJ9MWGf+jAEgCigJ1aiuHp1zxDvaUCh3A0tdLy64+A+ozmzgLP +nubtmCzg6KU2Yh1rAgMBAAGgHzAdBgkqhkiG9w0BCQ4xEDAOMAwGA1UdEwQFMAMB +Af8wDQYJKoZIhvcNAQELBQADggEBACI5v8GbOXKv38h9/tuGEwJ9uxpYEljgGt8h +QL5lwfEifh/7A8b39b9JEzWk5hnMRCOb8J6Jc3/6nmVgtKkQ+Mceupqpwsp1gT/v +uUoAkJE03Iuja9zLhHmy74oZ7LWOQrZ1T7Z0eGQ+5u+LBZiPKnKxmkLCQoUPTbc4 +NQ9BbKhr8OaoJ4DDvJnszcL7to6kih7SkdoNZsq4zB0/ai/cPhvoVgkYfbLH2++D +Tcs7TqU2L7gKzqXUtHeAKM2y81ewL7QTrcYzgiW86s3NmquxZG5pq0mjD+P4BYLc +MOdnCxKbBuE/1R29pa6+JKgc46jOa2yRgv5+8rXkkpu53Ke3FGc= +-----END CERTIFICATE REQUEST----- diff --git a/lib/nghttp2/src/ca.nghttp2.org.csr.json b/lib/nghttp2/src/ca.nghttp2.org.csr.json new file mode 100644 index 00000000000..69d9c6a4d5e --- /dev/null +++ b/lib/nghttp2/src/ca.nghttp2.org.csr.json @@ -0,0 +1,17 @@ +{ + "CN": "ca.nghttp2.org", + "key": { + "algo": "rsa", + "size": 2048 + }, + "ca": { + "expiry": "87600h" + }, + "names": [ + { + "C": "AU", + "ST": "Some-State", + "O": "Internet Widgits Pty Ltd" + } + ] +} diff --git a/lib/nghttp2/src/ca.nghttp2.org.pem b/lib/nghttp2/src/ca.nghttp2.org.pem new file mode 100644 index 00000000000..e50acfc9a4f --- /dev/null +++ b/lib/nghttp2/src/ca.nghttp2.org.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrTCCApWgAwIBAgIUe4dvx8haIjsT3ZpNCMrl62Xk6E0wDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v +cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBeMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRcwFQYDVQQDEw5jYS5uZ2h0dHAyLm9yZzCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANZFZBfEElMFvx1fRBUT0jKIjlZmMAfL6ig7U9He +jBYigDPP2CRVKtripVMnYE+CDxOzwE71XCI9xrS/V8z4bHKmDv0tGO7HMkxNlQD5 +rc/krT1n9sGdSEEdPR65w03OOCr2BmaXnwWZA/VI25ifWtWZiYyYjK94U5yhP7MW +dvnTLJzGVzfNtnH2h8kzfk9Pk8sObwQW6Ag7Gc8Zn/vRRSAyTbNzS9+HEfBGUYaX +2noGqR/G7Tifg/kWdpIOxP2GJBnEujaHb040lBQhR6Voxwn0xYZ/6MASAKKAnVqK +4enXPEO9pQKHcDS10vLrj4D6jObOAs+e5u2YLODopTZiHWsCAwEAAaNjMGEwDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNA5xVR1Zcax +RJL9VC6pzuLmvduGMB8GA1UdIwQYMBaAFNA5xVR1ZcaxRJL9VC6pzuLmvduGMA0G +CSqGSIb3DQEBCwUAA4IBAQCmdVfn/hUyEdvkKG7svg5d8o6BENOj8695KtWmzJjK +zxH8J5Vy3mn89XrHQ+BOYXCDPyhs0aDS8aq3Z+HY0n9z1oAicyGzlVwZQQNX3YId +Y2vcf7qu/2ATm/1S+mebE1/EXMUlWISKKUYXjggCwFgjDhH87Ai+A8MKScVdmqgL +Hf+fRSzH3ToW7BCXlRl5bPAq2g+v1ALYc8wU9cT1MYm4dqAXh870LGFyUpaSWmFr +TtX1DXBTgLp62syNlDthAvGigYFDtCa4cDM2vdTD9wpec2V9EKpfVqiRDDuYjUVX +UXl27MvkNWnEBKCIoNv5abWXpZVG2zQdEMmUOkVuAXUC +-----END CERTIFICATE----- diff --git a/lib/nghttp2/src/comp_helper.c b/lib/nghttp2/src/comp_helper.c new file mode 100644 index 00000000000..98db08a487a --- /dev/null +++ b/lib/nghttp2/src/comp_helper.c @@ -0,0 +1,133 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "comp_helper.h" +#include + +static void dump_val(json_t *jent, const char *key, uint8_t *val, size_t len) { + json_object_set_new(jent, key, json_pack("s#", val, len)); +} + +#define NGHTTP2_HD_ENTRY_OVERHEAD 32 + +json_t *dump_deflate_header_table(nghttp2_hd_deflater *deflater) { + json_t *obj, *entries; + size_t i; + size_t len = nghttp2_hd_deflate_get_num_table_entries(deflater); + + obj = json_object(); + entries = json_array(); + /* The first index of dynamic table is 62 */ + for (i = 62; i <= len; ++i) { + const nghttp2_nv *nv = nghttp2_hd_deflate_get_table_entry(deflater, i); + json_t *outent = json_object(); + json_object_set_new(outent, "index", json_integer((json_int_t)i)); + dump_val(outent, "name", nv->name, nv->namelen); + dump_val(outent, "value", nv->value, nv->valuelen); + json_object_set_new(outent, "size", + json_integer((json_int_t)(nv->namelen + nv->valuelen + + NGHTTP2_HD_ENTRY_OVERHEAD))); + json_array_append_new(entries, outent); + } + json_object_set_new(obj, "entries", entries); + json_object_set_new( + obj, "size", + json_integer( + (json_int_t)nghttp2_hd_deflate_get_dynamic_table_size(deflater))); + json_object_set_new( + obj, "max_size", + json_integer( + (json_int_t)nghttp2_hd_deflate_get_max_dynamic_table_size(deflater))); + + return obj; +} + +json_t *dump_inflate_header_table(nghttp2_hd_inflater *inflater) { + json_t *obj, *entries; + size_t i; + size_t len = nghttp2_hd_inflate_get_num_table_entries(inflater); + + obj = json_object(); + entries = json_array(); + /* The first index of dynamic table is 62 */ + for (i = 62; i <= len; ++i) { + const nghttp2_nv *nv = nghttp2_hd_inflate_get_table_entry(inflater, i); + json_t *outent = json_object(); + json_object_set_new(outent, "index", json_integer((json_int_t)i)); + dump_val(outent, "name", nv->name, nv->namelen); + dump_val(outent, "value", nv->value, nv->valuelen); + json_object_set_new(outent, "size", + json_integer((json_int_t)(nv->namelen + nv->valuelen + + NGHTTP2_HD_ENTRY_OVERHEAD))); + json_array_append_new(entries, outent); + } + json_object_set_new(obj, "entries", entries); + json_object_set_new( + obj, "size", + json_integer( + (json_int_t)nghttp2_hd_inflate_get_dynamic_table_size(inflater))); + json_object_set_new( + obj, "max_size", + json_integer( + (json_int_t)nghttp2_hd_inflate_get_max_dynamic_table_size(inflater))); + + return obj; +} + +json_t *dump_header(const uint8_t *name, size_t namelen, const uint8_t *value, + size_t valuelen) { + json_t *nv_pair = json_object(); + char *cname = malloc(namelen + 1); + if (cname == NULL) { + return NULL; + } + memcpy(cname, name, namelen); + cname[namelen] = '\0'; + json_object_set_new(nv_pair, cname, json_pack("s#", value, valuelen)); + free(cname); + return nv_pair; +} + +json_t *dump_headers(const nghttp2_nv *nva, size_t nvlen) { + json_t *headers; + size_t i; + + headers = json_array(); + for (i = 0; i < nvlen; ++i) { + json_array_append_new(headers, dump_header(nva[i].name, nva[i].namelen, + nva[i].value, nva[i].valuelen)); + } + return headers; +} + +void output_json_header(void) { + printf("{\n" + " \"cases\":\n" + " [\n"); +} + +void output_json_footer(void) { + printf(" ]\n" + "}\n"); +} diff --git a/lib/nghttp2/src/comp_helper.h b/lib/nghttp2/src/comp_helper.h new file mode 100644 index 00000000000..131ed21b637 --- /dev/null +++ b/lib/nghttp2/src/comp_helper.h @@ -0,0 +1,57 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_COMP_HELPER_H +#define NGHTTP2_COMP_HELPER_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +json_t *dump_deflate_header_table(nghttp2_hd_deflater *deflater); + +json_t *dump_inflate_header_table(nghttp2_hd_inflater *inflater); + +json_t *dump_header(const uint8_t *name, size_t namelen, const uint8_t *value, + size_t vlauelen); + +json_t *dump_headers(const nghttp2_nv *nva, size_t nvlen); + +void output_json_header(void); + +void output_json_footer(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NGHTTP2_COMP_HELPER_H */ diff --git a/lib/nghttp2/src/deflatehd.cc b/lib/nghttp2/src/deflatehd.cc new file mode 100644 index 00000000000..7dcfccffcf7 --- /dev/null +++ b/lib/nghttp2/src/deflatehd.cc @@ -0,0 +1,450 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 +#endif // HAVE_CONFIG_H + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "template.h" +#include "comp_helper.h" +#include "util.h" + +namespace nghttp2 { + +typedef struct { + size_t table_size; + size_t deflate_table_size; + int http1text; + int dump_header_table; +} deflate_config; + +static deflate_config config; + +static size_t input_sum; +static size_t output_sum; + +static char to_hex_digit(uint8_t n) { + if (n > 9) { + return n - 10 + 'a'; + } + return n + '0'; +} + +static void to_hex(char *dest, const uint8_t *src, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + *dest++ = to_hex_digit(src[i] >> 4); + *dest++ = to_hex_digit(src[i] & 0xf); + } +} + +static void output_to_json(nghttp2_hd_deflater *deflater, const uint8_t *buf, + size_t buflen, size_t inputlen, + const std::vector &nva, int seq) { + auto hex = std::vector(buflen * 2); + auto obj = json_object(); + auto comp_ratio = inputlen == 0 ? 0.0 : (double)buflen / inputlen * 100; + + json_object_set_new(obj, "seq", json_integer(seq)); + json_object_set_new(obj, "input_length", json_integer(inputlen)); + json_object_set_new(obj, "output_length", json_integer(buflen)); + json_object_set_new(obj, "percentage_of_original_size", + json_real(comp_ratio)); + + if (buflen == 0) { + json_object_set_new(obj, "wire", json_string("")); + } else { + to_hex(hex.data(), buf, buflen); + json_object_set_new(obj, "wire", json_pack("s#", hex.data(), hex.size())); + } + json_object_set_new(obj, "headers", dump_headers(nva.data(), nva.size())); + if (seq == 0) { + // We only change the header table size only once at the beginning + json_object_set_new(obj, "header_table_size", + json_integer(config.table_size)); + } + if (config.dump_header_table) { + json_object_set_new(obj, "header_table", + dump_deflate_header_table(deflater)); + } + json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2)); + printf("\n"); + json_decref(obj); +} + +static void deflate_hd(nghttp2_hd_deflater *deflater, + const std::vector &nva, size_t inputlen, + int seq) { + ssize_t rv; + std::array buf; + + rv = nghttp2_hd_deflate_hd(deflater, buf.data(), buf.size(), + (nghttp2_nv *)nva.data(), nva.size()); + if (rv < 0) { + fprintf(stderr, "deflate failed with error code %zd at %d\n", rv, seq); + exit(EXIT_FAILURE); + } + + input_sum += inputlen; + output_sum += rv; + + output_to_json(deflater, buf.data(), rv, inputlen, nva, seq); +} + +static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater, + int seq) { + size_t inputlen = 0; + + auto js = json_object_get(obj, "headers"); + if (js == nullptr) { + fprintf(stderr, "'headers' key is missing at %d\n", seq); + return -1; + } + if (!json_is_array(js)) { + fprintf(stderr, "The value of 'headers' key must be an array at %d\n", seq); + return -1; + } + + auto len = json_array_size(js); + auto nva = std::vector(len); + + for (size_t i = 0; i < len; ++i) { + auto nv_pair = json_array_get(js, i); + const char *name; + json_t *value; + + if (!json_is_object(nv_pair) || json_object_size(nv_pair) != 1) { + fprintf(stderr, "bad formatted name/value pair object at %d\n", seq); + return -1; + } + + json_object_foreach(nv_pair, name, value) { + nva[i].name = (uint8_t *)name; + nva[i].namelen = strlen(name); + + if (!json_is_string(value)) { + fprintf(stderr, "value is not string at %d\n", seq); + return -1; + } + + nva[i].value = (uint8_t *)json_string_value(value); + nva[i].valuelen = strlen(json_string_value(value)); + + nva[i].flags = NGHTTP2_NV_FLAG_NONE; + } + + inputlen += nva[i].namelen + nva[i].valuelen; + } + + deflate_hd(deflater, nva, inputlen, seq); + + return 0; +} + +static nghttp2_hd_deflater *init_deflater() { + nghttp2_hd_deflater *deflater; + nghttp2_hd_deflate_new(&deflater, config.deflate_table_size); + if (config.table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { + nghttp2_hd_deflate_change_table_size(deflater, config.table_size); + } + return deflater; +} + +static void deinit_deflater(nghttp2_hd_deflater *deflater) { + nghttp2_hd_deflate_del(deflater); +} + +static int perform(void) { + json_error_t error; + + auto json = json_loadf(stdin, 0, &error); + + if (json == nullptr) { + fprintf(stderr, "JSON loading failed\n"); + exit(EXIT_FAILURE); + } + + auto cases = json_object_get(json, "cases"); + + if (cases == nullptr) { + fprintf(stderr, "Missing 'cases' key in root object\n"); + exit(EXIT_FAILURE); + } + + if (!json_is_array(cases)) { + fprintf(stderr, "'cases' must be JSON array\n"); + exit(EXIT_FAILURE); + } + + auto deflater = init_deflater(); + output_json_header(); + auto len = json_array_size(cases); + + for (size_t i = 0; i < len; ++i) { + auto obj = json_array_get(cases, i); + if (!json_is_object(obj)) { + fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i); + continue; + } + if (deflate_hd_json(obj, deflater, i) != 0) { + continue; + } + if (i + 1 < len) { + printf(",\n"); + } + } + output_json_footer(); + deinit_deflater(deflater); + json_decref(json); + return 0; +} + +static int perform_from_http1text(void) { + char line[1 << 14]; + int seq = 0; + + auto deflater = init_deflater(); + output_json_header(); + for (;;) { + std::vector nva; + int end = 0; + size_t inputlen = 0; + + for (;;) { + char *rv = fgets(line, sizeof(line), stdin); + char *val, *val_end; + if (rv == nullptr) { + end = 1; + break; + } else if (line[0] == '\n') { + break; + } + + nva.emplace_back(); + auto &nv = nva.back(); + + val = strchr(line + 1, ':'); + if (val == nullptr) { + fprintf(stderr, "Bad HTTP/1 header field format at %d.\n", seq); + exit(EXIT_FAILURE); + } + *val = '\0'; + ++val; + for (; *val && (*val == ' ' || *val == '\t'); ++val) + ; + for (val_end = val; *val_end && (*val_end != '\r' && *val_end != '\n'); + ++val_end) + ; + *val_end = '\0'; + + nv.namelen = strlen(line); + nv.valuelen = strlen(val); + nv.name = (uint8_t *)strdup(line); + nv.value = (uint8_t *)strdup(val); + nv.flags = NGHTTP2_NV_FLAG_NONE; + + inputlen += nv.namelen + nv.valuelen; + } + + if (!end) { + if (seq > 0) { + printf(",\n"); + } + deflate_hd(deflater, nva, inputlen, seq); + } + + for (auto &nv : nva) { + free(nv.name); + free(nv.value); + } + + if (end) + break; + ++seq; + } + output_json_footer(); + deinit_deflater(deflater); + return 0; +} + +static void print_help(void) { + std::cout << R"(HPACK HTTP/2 header encoder +Usage: deflatehd [OPTIONS] < INPUT + +Reads JSON data or HTTP/1-style header fields from stdin and outputs +deflated header block in JSON array. + +For the JSON input, the root JSON object must contain "context" key, +which indicates which compression context is used. If it is +"request", request compression context is used. Otherwise, response +compression context is used. The value of "cases" key contains the +sequence of input header set. They share the same compression context +and are processed in the order they appear. Each item in the sequence +is a JSON object and it must have at least "headers" key. Its value +is an array of a JSON object containing exactly one name/value pair. + +Example: +{ + "context": "request", + "cases": + [ + { + "headers": [ + { ":method": "GET" }, + { ":path": "/" } + ] + }, + { + "headers": [ + { ":method": "POST" }, + { ":path": "/" } + ] + } + ] +} + +With -t option, the program can accept more familiar HTTP/1 style +header field block. Each header set must be followed by one empty +line: + +Example: + +:method: GET +:scheme: https +:path: / + +:method: POST +user-agent: nghttp2 + +The output of this program can be used as input for inflatehd. + +OPTIONS: + -t, --http1text Use HTTP/1 style header field text as input. + Each header set is delimited by single empty + line. + -s, --table-size= + Set dynamic table size. In the HPACK + specification, this value is denoted by + SETTINGS_HEADER_TABLE_SIZE. + Default: 4096 + -S, --deflate-table-size= + Use first N bytes of dynamic header table + buffer. + Default: 4096 + -d, --dump-header-table + Output dynamic header table.)" + << std::endl; +} + +constexpr static struct option long_options[] = { + {"http1text", no_argument, nullptr, 't'}, + {"table-size", required_argument, nullptr, 's'}, + {"deflate-table-size", required_argument, nullptr, 'S'}, + {"dump-header-table", no_argument, nullptr, 'd'}, + {nullptr, 0, nullptr, 0}}; + +int main(int argc, char **argv) { + config.table_size = 4_k; + config.deflate_table_size = 4_k; + config.http1text = 0; + config.dump_header_table = 0; + while (1) { + int option_index = 0; + int c = getopt_long(argc, argv, "S:dhs:t", long_options, &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'h': + print_help(); + exit(EXIT_SUCCESS); + case 't': + // --http1text + config.http1text = 1; + break; + case 's': { + // --table-size + auto n = util::parse_uint(optarg); + if (n == -1) { + fprintf(stderr, "-s: Bad option value\n"); + exit(EXIT_FAILURE); + } + config.table_size = n; + break; + } + case 'S': { + // --deflate-table-size + auto n = util::parse_uint(optarg); + if (n == -1) { + fprintf(stderr, "-S: Bad option value\n"); + exit(EXIT_FAILURE); + } + config.deflate_table_size = n; + break; + } + case 'd': + // --dump-header-table + config.dump_header_table = 1; + break; + case '?': + exit(EXIT_FAILURE); + default: + break; + } + } + if (config.http1text) { + perform_from_http1text(); + } else { + perform(); + } + + auto comp_ratio = input_sum == 0 ? 0.0 : (double)output_sum / input_sum; + + fprintf(stderr, "Overall: input=%zu output=%zu ratio=%.02f\n", input_sum, + output_sum, comp_ratio); + return 0; +} + +} // namespace nghttp2 + +int main(int argc, char **argv) { + return nghttp2::run_app(nghttp2::main, argc, argv); +} diff --git a/lib/nghttp2/src/h2load.cc b/lib/nghttp2/src/h2load.cc new file mode 100644 index 00000000000..1c0fbc3b7db --- /dev/null +++ b/lib/nghttp2/src/h2load.cc @@ -0,0 +1,3343 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "h2load.h" + +#include +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#include +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef ENABLE_HTTP3 +# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# include +# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +# include +# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +#endif // ENABLE_HTTP3 + +#include "url-parser/url_parser.h" + +#include "h2load_http1_session.h" +#include "h2load_http2_session.h" +#ifdef ENABLE_HTTP3 +# include "h2load_http3_session.h" +# include "h2load_quic.h" +#endif // ENABLE_HTTP3 +#include "tls.h" +#include "http2.h" +#include "util.h" +#include "template.h" +#include "ssl_compat.h" + +#ifndef O_BINARY +# define O_BINARY (0) +#endif // O_BINARY + +using namespace nghttp2; + +namespace h2load { + +namespace { +bool recorded(const std::chrono::steady_clock::time_point &t) { + return std::chrono::steady_clock::duration::zero() != t.time_since_epoch(); +} +} // namespace + +#if OPENSSL_1_1_1_API +namespace { +std::ofstream keylog_file; +void keylog_callback(const SSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace +#endif // OPENSSL_1_1_1_API + +Config::Config() + : ciphers(tls::DEFAULT_CIPHER_LIST), + tls13_ciphers("TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_" + "CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256"), + groups("X25519:P-256:P-384:P-521"), + data_length(-1), + data(nullptr), + addrs(nullptr), + nreqs(1), + nclients(1), + nthreads(1), + max_concurrent_streams(1), + window_bits(30), + connection_window_bits(30), + max_frame_size(16_k), + rate(0), + rate_period(1.0), + duration(0.0), + warm_up_time(0.0), + conn_active_timeout(0.), + conn_inactivity_timeout(0.), + no_tls_proto(PROTO_HTTP2), + header_table_size(4_k), + encoder_header_table_size(4_k), + data_fd(-1), + log_fd(-1), + qlog_file_base(), + port(0), + default_port(0), + connect_to_port(0), + verbose(false), + timing_script(false), + base_uri_unix(false), + unix_addr{}, + rps(0.), + no_udp_gso(false), + max_udp_payload_size(0), + ktls(false) {} + +Config::~Config() { + if (addrs) { + if (base_uri_unix) { + delete addrs; + } else { + freeaddrinfo(addrs); + } + } + + if (data_fd != -1) { + close(data_fd); + } +} + +bool Config::is_rate_mode() const { return (this->rate != 0); } +bool Config::is_timing_based_mode() const { return (this->duration > 0); } +bool Config::has_base_uri() const { return (!this->base_uri.empty()); } +bool Config::rps_enabled() const { return this->rps > 0.0; } +bool Config::is_quic() const { +#ifdef ENABLE_HTTP3 + return !npn_list.empty() && + (npn_list[0] == NGHTTP3_ALPN_H3 || npn_list[0] == "\x5h3-29"); +#else // !ENABLE_HTTP3 + return false; +#endif // !ENABLE_HTTP3 +} +Config config; + +namespace { +constexpr size_t MAX_SAMPLES = 1000000; +} // namespace + +Stats::Stats(size_t req_todo, size_t nclients) + : req_todo(req_todo), + req_started(0), + req_done(0), + req_success(0), + req_status_success(0), + req_failed(0), + req_error(0), + req_timedout(0), + bytes_total(0), + bytes_head(0), + bytes_head_decomp(0), + bytes_body(0), + status(), + udp_dgram_recv(0), + udp_dgram_sent(0) {} + +Stream::Stream() : req_stat{}, status_success(-1) {} + +namespace { +std::random_device rd; +} // namespace + +namespace { +std::mt19937 gen(rd()); +} // namespace + +namespace { +void sampling_init(Sampling &smp, size_t max_samples) { + smp.n = 0; + smp.max_samples = max_samples; +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto client = static_cast(w->data); + client->restart_timeout(); + auto rv = client->do_write(); + if (rv == Client::ERR_CONNECT_FAIL) { + client->disconnect(); + // Try next address + client->current_addr = nullptr; + rv = client->connect(); + if (rv != 0) { + client->fail(); + client->worker->free_client(client); + delete client; + return; + } + return; + } + if (rv != 0) { + client->fail(); + client->worker->free_client(client); + delete client; + } +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto client = static_cast(w->data); + client->restart_timeout(); + if (client->do_read() != 0) { + if (client->try_again_or_fail() == 0) { + return; + } + client->worker->free_client(client); + delete client; + return; + } + client->signal_write(); +} +} // namespace + +namespace { +// Called every rate_period when rate mode is being used +void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto worker = static_cast(w->data); + auto nclients_per_second = worker->rate; + auto conns_remaining = worker->nclients - worker->nconns_made; + auto nclients = std::min(nclients_per_second, conns_remaining); + + for (size_t i = 0; i < nclients; ++i) { + auto req_todo = worker->nreqs_per_client; + if (worker->nreqs_rem > 0) { + ++req_todo; + --worker->nreqs_rem; + } + auto client = + std::make_unique(worker->next_client_id++, worker, req_todo); + + ++worker->nconns_made; + + if (client->connect() != 0) { + std::cerr << "client could not connect to host" << std::endl; + client->fail(); + } else { + if (worker->config->is_timing_based_mode()) { + worker->clients.push_back(client.release()); + } else { + client.release(); + } + } + worker->report_rate_progress(); + } + if (!worker->config->is_timing_based_mode()) { + if (worker->nconns_made >= worker->nclients) { + ev_timer_stop(worker->loop, w); + } + } else { + // To check whether all created clients are pushed correctly + assert(worker->nclients == worker->clients.size()); + } +} +} // namespace + +namespace { +// Called when the duration for infinite number of requests are over +void duration_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto worker = static_cast(w->data); + + worker->current_phase = Phase::DURATION_OVER; + + std::cout << "Main benchmark duration is over for thread #" << worker->id + << ". Stopping all clients." << std::endl; + worker->stop_all_clients(); + std::cout << "Stopped all clients for thread #" << worker->id << std::endl; +} +} // namespace + +namespace { +// Called when the warmup duration for infinite number of requests are over +void warmup_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto worker = static_cast(w->data); + + std::cout << "Warm-up phase is over for thread #" << worker->id << "." + << std::endl; + std::cout << "Main benchmark duration is started for thread #" << worker->id + << "." << std::endl; + assert(worker->stats.req_started == 0); + assert(worker->stats.req_done == 0); + + for (auto client : worker->clients) { + if (client) { + assert(client->req_todo == 0); + assert(client->req_left == 1); + assert(client->req_inflight == 0); + assert(client->req_started == 0); + assert(client->req_done == 0); + + client->record_client_start_time(); + client->clear_connect_times(); + client->record_connect_start_time(); + } + } + + worker->current_phase = Phase::MAIN_DURATION; + + ev_timer_start(worker->loop, &worker->duration_watcher); +} +} // namespace + +namespace { +void rps_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto client = static_cast(w->data); + auto &session = client->session; + + assert(!config.timing_script); + + if (client->req_left == 0) { + ev_timer_stop(loop, w); + return; + } + + auto now = std::chrono::steady_clock::now(); + auto d = now - client->rps_duration_started; + auto n = static_cast( + round(std::chrono::duration(d).count() * config.rps)); + client->rps_req_pending += n; + client->rps_duration_started += + util::duration_from(static_cast(n) / config.rps); + + if (client->rps_req_pending == 0) { + return; + } + + auto nreq = session->max_concurrent_streams() - client->rps_req_inflight; + if (nreq == 0) { + return; + } + + nreq = config.is_timing_based_mode() ? std::max(nreq, client->req_left) + : std::min(nreq, client->req_left); + nreq = std::min(nreq, client->rps_req_pending); + + client->rps_req_inflight += nreq; + client->rps_req_pending -= nreq; + + for (; nreq > 0; --nreq) { + if (client->submit_request() != 0) { + client->process_request_failure(); + break; + } + } + + client->signal_write(); +} +} // namespace + +namespace { +// Called when an a connection has been inactive for a set period of time +// or a fixed amount of time after all requests have been made on a +// connection +void conn_timeout_cb(EV_P_ ev_timer *w, int revents) { + auto client = static_cast(w->data); + + ev_timer_stop(client->worker->loop, &client->conn_inactivity_watcher); + ev_timer_stop(client->worker->loop, &client->conn_active_watcher); + + if (util::check_socket_connected(client->fd)) { + client->timeout(); + } +} +} // namespace + +namespace { +bool check_stop_client_request_timeout(Client *client, ev_timer *w) { + if (client->req_left == 0) { + // no more requests to make, stop timer + ev_timer_stop(client->worker->loop, w); + return true; + } + + return false; +} +} // namespace + +namespace { +void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto client = static_cast(w->data); + + if (client->streams.size() >= (size_t)config.max_concurrent_streams) { + ev_timer_stop(client->worker->loop, w); + return; + } + + if (client->submit_request() != 0) { + ev_timer_stop(client->worker->loop, w); + client->process_request_failure(); + return; + } + client->signal_write(); + + if (check_stop_client_request_timeout(client, w)) { + return; + } + + auto duration = + config.timings[client->reqidx] - config.timings[client->reqidx - 1]; + + while (duration < std::chrono::duration(1e-9)) { + if (client->submit_request() != 0) { + ev_timer_stop(client->worker->loop, w); + client->process_request_failure(); + return; + } + client->signal_write(); + if (check_stop_client_request_timeout(client, w)) { + return; + } + + duration = + config.timings[client->reqidx] - config.timings[client->reqidx - 1]; + } + + client->request_timeout_watcher.repeat = util::ev_tstamp_from(duration); + ev_timer_again(client->worker->loop, &client->request_timeout_watcher); +} +} // namespace + +Client::Client(uint32_t id, Worker *worker, size_t req_todo) + : wb(&worker->mcpool), + cstat{}, + worker(worker), + ssl(nullptr), +#ifdef ENABLE_HTTP3 + quic{}, +#endif // ENABLE_HTTP3 + next_addr(config.addrs), + current_addr(nullptr), + reqidx(0), + state(CLIENT_IDLE), + req_todo(req_todo), + req_left(req_todo), + req_inflight(0), + req_started(0), + req_done(0), + id(id), + fd(-1), + local_addr{}, + new_connection_requested(false), + final(false), + rps_req_pending(0), + rps_req_inflight(0) { + if (req_todo == 0) { // this means infinite number of requests are to be made + // This ensures that number of requests are unbounded + // Just a positive number is fine, we chose the first positive number + req_left = 1; + } + ev_io_init(&wev, writecb, 0, EV_WRITE); + ev_io_init(&rev, readcb, 0, EV_READ); + + wev.data = this; + rev.data = this; + + ev_timer_init(&conn_inactivity_watcher, conn_timeout_cb, 0., + worker->config->conn_inactivity_timeout); + conn_inactivity_watcher.data = this; + + ev_timer_init(&conn_active_watcher, conn_timeout_cb, + worker->config->conn_active_timeout, 0.); + conn_active_watcher.data = this; + + ev_timer_init(&request_timeout_watcher, client_request_timeout_cb, 0., 0.); + request_timeout_watcher.data = this; + + ev_timer_init(&rps_watcher, rps_cb, 0., 0.); + rps_watcher.data = this; + +#ifdef ENABLE_HTTP3 + ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.); + quic.pkt_timer.data = this; + + if (config.is_quic()) { + quic.tx.data = std::make_unique(64_k); + } + + ngtcp2_ccerr_default(&quic.last_error); +#endif // ENABLE_HTTP3 +} + +Client::~Client() { + disconnect(); + +#ifdef ENABLE_HTTP3 + if (config.is_quic()) { + quic_free(); + } +#endif // ENABLE_HTTP3 + + if (ssl) { + SSL_free(ssl); + } + + worker->sample_client_stat(&cstat); + ++worker->client_smp.n; +} + +int Client::do_read() { return readfn(*this); } +int Client::do_write() { return writefn(*this); } + +int Client::make_socket(addrinfo *addr) { + int rv; + + if (config.is_quic()) { +#ifdef ENABLE_HTTP3 + fd = util::create_nonblock_udp_socket(addr->ai_family); + if (fd == -1) { + return -1; + } + +# ifdef UDP_GRO + int val = 1; + if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val)) != 0) { + std::cerr << "setsockopt UDP_GRO failed" << std::endl; + return -1; + } +# endif // UDP_GRO + + rv = util::bind_any_addr_udp(fd, addr->ai_family); + if (rv != 0) { + close(fd); + fd = -1; + return -1; + } + + socklen_t addrlen = sizeof(local_addr.su.storage); + rv = getsockname(fd, &local_addr.su.sa, &addrlen); + if (rv == -1) { + return -1; + } + local_addr.len = addrlen; + + if (quic_init(&local_addr.su.sa, local_addr.len, addr->ai_addr, + addr->ai_addrlen) != 0) { + std::cerr << "quic_init failed" << std::endl; + return -1; + } +#endif // ENABLE_HTTP3 + } else { + fd = util::create_nonblock_socket(addr->ai_family); + if (fd == -1) { + return -1; + } + if (config.scheme == "https") { + if (!ssl) { + ssl = SSL_new(worker->ssl_ctx); + } + + SSL_set_connect_state(ssl); + } + } + + if (ssl && !util::numeric_host(config.host.c_str())) { + SSL_set_tlsext_host_name(ssl, config.host.c_str()); + } + + if (config.is_quic()) { + return 0; + } + + rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen); + if (rv != 0 && errno != EINPROGRESS) { + if (ssl) { + SSL_free(ssl); + ssl = nullptr; + } + close(fd); + fd = -1; + return -1; + } + return 0; +} + +int Client::connect() { + int rv; + + if (!worker->config->is_timing_based_mode() || + worker->current_phase == Phase::MAIN_DURATION) { + record_client_start_time(); + clear_connect_times(); + record_connect_start_time(); + } else if (worker->current_phase == Phase::INITIAL_IDLE) { + worker->current_phase = Phase::WARM_UP; + std::cout << "Warm-up started for thread #" << worker->id << "." + << std::endl; + ev_timer_start(worker->loop, &worker->warmup_watcher); + } + + if (worker->config->conn_inactivity_timeout > 0.) { + ev_timer_again(worker->loop, &conn_inactivity_watcher); + } + + if (current_addr) { + rv = make_socket(current_addr); + if (rv == -1) { + return -1; + } + } else { + addrinfo *addr = nullptr; + while (next_addr) { + addr = next_addr; + next_addr = next_addr->ai_next; + rv = make_socket(addr); + if (rv == 0) { + break; + } + } + + if (fd == -1) { + return -1; + } + + assert(addr); + + current_addr = addr; + } + + ev_io_set(&rev, fd, EV_READ); + ev_io_set(&wev, fd, EV_WRITE); + + ev_io_start(worker->loop, &wev); + + if (config.is_quic()) { +#ifdef ENABLE_HTTP3 + ev_io_start(worker->loop, &rev); + + readfn = &Client::read_quic; + writefn = &Client::write_quic; +#endif // ENABLE_HTTP3 + } else { + writefn = &Client::connected; + } + + return 0; +} + +void Client::timeout() { + process_timedout_streams(); + + disconnect(); +} + +void Client::restart_timeout() { + if (worker->config->conn_inactivity_timeout > 0.) { + ev_timer_again(worker->loop, &conn_inactivity_watcher); + } +} + +int Client::try_again_or_fail() { + disconnect(); + + if (new_connection_requested) { + new_connection_requested = false; + + if (req_left) { + + if (worker->current_phase == Phase::MAIN_DURATION) { + // At the moment, we don't have a facility to re-start request + // already in in-flight. Make them fail. + worker->stats.req_failed += req_inflight; + worker->stats.req_error += req_inflight; + + req_inflight = 0; + } + + // Keep using current address + if (connect() == 0) { + return 0; + } + std::cerr << "client could not connect to host" << std::endl; + } + } + + process_abandoned_streams(); + + return -1; +} + +void Client::fail() { + disconnect(); + + process_abandoned_streams(); +} + +void Client::disconnect() { + record_client_end_time(); + +#ifdef ENABLE_HTTP3 + if (config.is_quic()) { + quic_close_connection(); + } +#endif // ENABLE_HTTP3 + +#ifdef ENABLE_HTTP3 + ev_timer_stop(worker->loop, &quic.pkt_timer); +#endif // ENABLE_HTTP3 + ev_timer_stop(worker->loop, &conn_inactivity_watcher); + ev_timer_stop(worker->loop, &conn_active_watcher); + ev_timer_stop(worker->loop, &rps_watcher); + ev_timer_stop(worker->loop, &request_timeout_watcher); + streams.clear(); + session.reset(); + wb.reset(); + state = CLIENT_IDLE; + ev_io_stop(worker->loop, &wev); + ev_io_stop(worker->loop, &rev); + if (ssl) { + if (config.is_quic()) { + SSL_free(ssl); + ssl = nullptr; + } else { + SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN); + ERR_clear_error(); + + if (SSL_shutdown(ssl) != 1) { + SSL_free(ssl); + ssl = nullptr; + } + } + } + if (fd != -1) { + shutdown(fd, SHUT_WR); + close(fd); + fd = -1; + } + + final = false; +} + +int Client::submit_request() { + if (session->submit_request() != 0) { + return -1; + } + + if (worker->current_phase != Phase::MAIN_DURATION) { + return 0; + } + + ++worker->stats.req_started; + ++req_started; + ++req_inflight; + if (!worker->config->is_timing_based_mode()) { + --req_left; + } + // if an active timeout is set and this is the last request to be submitted + // on this connection, start the active timeout. + if (worker->config->conn_active_timeout > 0. && req_left == 0) { + ev_timer_start(worker->loop, &conn_active_watcher); + } + + return 0; +} + +void Client::process_timedout_streams() { + if (worker->current_phase != Phase::MAIN_DURATION) { + return; + } + + for (auto &p : streams) { + auto &req_stat = p.second.req_stat; + if (!req_stat.completed) { + req_stat.stream_close_time = std::chrono::steady_clock::now(); + } + } + + worker->stats.req_timedout += req_inflight; + + process_abandoned_streams(); +} + +void Client::process_abandoned_streams() { + if (worker->current_phase != Phase::MAIN_DURATION) { + return; + } + + auto req_abandoned = req_inflight + req_left; + + worker->stats.req_failed += req_abandoned; + worker->stats.req_error += req_abandoned; + + req_inflight = 0; + req_left = 0; +} + +void Client::process_request_failure() { + if (worker->current_phase != Phase::MAIN_DURATION) { + return; + } + + worker->stats.req_failed += req_left; + worker->stats.req_error += req_left; + + req_left = 0; + + if (req_inflight == 0) { + terminate_session(); + } + std::cout << "Process Request Failure:" << worker->stats.req_failed + << std::endl; +} + +namespace { +void print_server_tmp_key(SSL *ssl) { +// libressl does not have SSL_get_server_tmp_key +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && defined(SSL_get_server_tmp_key) + EVP_PKEY *key; + + if (!SSL_get_server_tmp_key(ssl, &key)) { + return; + } + + auto key_del = defer(EVP_PKEY_free, key); + + std::cout << "Server Temp Key: "; + + auto pkey_id = EVP_PKEY_id(key); + switch (pkey_id) { + case EVP_PKEY_RSA: + std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl; + break; + case EVP_PKEY_DH: + std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl; + break; + case EVP_PKEY_EC: { +# if OPENSSL_3_0_0_API + std::array curve_name; + const char *cname; + if (!EVP_PKEY_get_utf8_string_param(key, "group", curve_name.data(), + curve_name.size(), nullptr)) { + cname = ""; + } else { + cname = curve_name.data(); + } +# else // !OPENSSL_3_0_0_API + auto ec = EVP_PKEY_get1_EC_KEY(key); + auto ec_del = defer(EC_KEY_free, ec); + auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); + auto cname = EC_curve_nid2nist(nid); + if (!cname) { + cname = OBJ_nid2sn(nid); + } +# endif // !OPENSSL_3_0_0_API + + std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits" + << std::endl; + break; + } + default: + std::cout << OBJ_nid2sn(pkey_id) << " " << EVP_PKEY_bits(key) << " bits" + << std::endl; + break; + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} +} // namespace + +void Client::report_tls_info() { + if (worker->id == 0 && !worker->tls_info_report_done) { + worker->tls_info_report_done = true; + auto cipher = SSL_get_current_cipher(ssl); + std::cout << "TLS Protocol: " << tls::get_tls_protocol(ssl) << "\n" + << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl; + print_server_tmp_key(ssl); + } +} + +void Client::report_app_info() { + if (worker->id == 0 && !worker->app_info_report_done) { + worker->app_info_report_done = true; + std::cout << "Application protocol: " << selected_proto << std::endl; + } +} + +void Client::terminate_session() { +#ifdef ENABLE_HTTP3 + if (config.is_quic()) { + quic.close_requested = true; + } +#endif // ENABLE_HTTP3 + if (session) { + session->terminate(); + } + // http1 session needs writecb to tear down session. + signal_write(); +} + +void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); } + +void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen) { + auto itr = streams.find(stream_id); + if (itr == std::end(streams)) { + return; + } + auto &stream = (*itr).second; + + if (worker->current_phase != Phase::MAIN_DURATION) { + // If the stream is for warm-up phase, then mark as a success + // But we do not update the count for 2xx, 3xx, etc status codes + // Same has been done in on_status_code function + stream.status_success = 1; + return; + } + + if (stream.status_success == -1 && namelen == 7 && + util::streq_l(":status", name, namelen)) { + int status = 0; + for (size_t i = 0; i < valuelen; ++i) { + if ('0' <= value[i] && value[i] <= '9') { + status *= 10; + status += value[i] - '0'; + if (status > 999) { + stream.status_success = 0; + return; + } + } else { + break; + } + } + + stream.req_stat.status = status; + if (status >= 200 && status < 300) { + ++worker->stats.status[2]; + stream.status_success = 1; + } else if (status < 400) { + ++worker->stats.status[3]; + stream.status_success = 1; + } else if (status < 600) { + ++worker->stats.status[status / 100]; + stream.status_success = 0; + } else { + stream.status_success = 0; + } + } +} + +void Client::on_status_code(int32_t stream_id, uint16_t status) { + auto itr = streams.find(stream_id); + if (itr == std::end(streams)) { + return; + } + auto &stream = (*itr).second; + + if (worker->current_phase != Phase::MAIN_DURATION) { + stream.status_success = 1; + return; + } + + stream.req_stat.status = status; + if (status >= 200 && status < 300) { + ++worker->stats.status[2]; + stream.status_success = 1; + } else if (status < 400) { + ++worker->stats.status[3]; + stream.status_success = 1; + } else if (status < 600) { + ++worker->stats.status[status / 100]; + stream.status_success = 0; + } else { + stream.status_success = 0; + } +} + +void Client::on_stream_close(int32_t stream_id, bool success, bool final) { + if (worker->current_phase == Phase::MAIN_DURATION) { + if (req_inflight > 0) { + --req_inflight; + } + auto req_stat = get_req_stat(stream_id); + if (!req_stat) { + return; + } + + req_stat->stream_close_time = std::chrono::steady_clock::now(); + if (success) { + req_stat->completed = true; + ++worker->stats.req_success; + ++cstat.req_success; + + if (streams[stream_id].status_success == 1) { + ++worker->stats.req_status_success; + } else { + ++worker->stats.req_failed; + } + + worker->sample_req_stat(req_stat); + + // Count up in successful cases only + ++worker->request_times_smp.n; + } else { + ++worker->stats.req_failed; + ++worker->stats.req_error; + } + ++worker->stats.req_done; + ++req_done; + + if (worker->config->log_fd != -1) { + auto start = std::chrono::duration_cast( + req_stat->request_wall_time.time_since_epoch()); + auto delta = std::chrono::duration_cast( + req_stat->stream_close_time - req_stat->request_time); + + std::array buf; + auto p = std::begin(buf); + p = util::utos(p, start.count()); + *p++ = '\t'; + if (success) { + p = util::utos(p, req_stat->status); + } else { + *p++ = '-'; + *p++ = '1'; + } + *p++ = '\t'; + p = util::utos(p, delta.count()); + *p++ = '\n'; + + auto nwrite = static_cast(std::distance(std::begin(buf), p)); + assert(nwrite <= buf.size()); + while (write(worker->config->log_fd, buf.data(), nwrite) == -1 && + errno == EINTR) + ; + } + } + + worker->report_progress(); + streams.erase(stream_id); + if (req_left == 0 && req_inflight == 0) { + terminate_session(); + return; + } + + if (!final && req_left > 0) { + if (config.timing_script) { + if (!ev_is_active(&request_timeout_watcher)) { + ev_feed_event(worker->loop, &request_timeout_watcher, EV_TIMER); + } + } else if (!config.rps_enabled()) { + if (submit_request() != 0) { + process_request_failure(); + } + } else if (rps_req_pending) { + --rps_req_pending; + if (submit_request() != 0) { + process_request_failure(); + } + } else { + assert(rps_req_inflight); + --rps_req_inflight; + } + } +} + +RequestStat *Client::get_req_stat(int32_t stream_id) { + auto it = streams.find(stream_id); + if (it == std::end(streams)) { + return nullptr; + } + + return &(*it).second.req_stat; +} + +int Client::connection_made() { + if (ssl) { + report_tls_info(); + + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + if (next_proto) { + auto proto = StringRef{next_proto, next_proto_len}; + if (config.is_quic()) { +#ifdef ENABLE_HTTP3 + assert(session); + if (!util::streq(StringRef{&NGHTTP3_ALPN_H3[1]}, proto) && + !util::streq_l("h3-29", proto)) { + return -1; + } +#endif // ENABLE_HTTP3 + } else if (util::check_h2_is_selected(proto)) { + session = std::make_unique(this); + } else if (util::streq(NGHTTP2_H1_1, proto)) { + session = std::make_unique(this); + } + + // Just assign next_proto to selected_proto anyway to show the + // negotiation result. + selected_proto = proto.str(); + } else if (config.is_quic()) { + std::cerr << "QUIC requires ALPN negotiation" << std::endl; + return -1; + } else { + std::cout << "No protocol negotiated. Fallback behaviour may be activated" + << std::endl; + + for (const auto &proto : config.npn_list) { + if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{proto})) { + std::cout + << "Server does not support NPN/ALPN. Falling back to HTTP/1.1." + << std::endl; + session = std::make_unique(this); + selected_proto = NGHTTP2_H1_1.str(); + break; + } + } + } + + if (!selected_proto.empty()) { + report_app_info(); + } + + if (!session) { + std::cout + << "No supported protocol was negotiated. Supported protocols were:" + << std::endl; + for (const auto &proto : config.npn_list) { + std::cout << proto.substr(1) << std::endl; + } + disconnect(); + return -1; + } + } else { + switch (config.no_tls_proto) { + case Config::PROTO_HTTP2: + session = std::make_unique(this); + selected_proto = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID; + break; + case Config::PROTO_HTTP1_1: + session = std::make_unique(this); + selected_proto = NGHTTP2_H1_1.str(); + break; + default: + // unreachable + assert(0); + } + + report_app_info(); + } + + state = CLIENT_CONNECTED; + + session->on_connect(); + + record_connect_time(); + + if (config.rps_enabled()) { + rps_watcher.repeat = std::max(0.01, 1. / config.rps); + ev_timer_again(worker->loop, &rps_watcher); + rps_duration_started = std::chrono::steady_clock::now(); + } + + if (config.rps_enabled()) { + assert(req_left); + + ++rps_req_inflight; + + if (submit_request() != 0) { + process_request_failure(); + } + } else if (!config.timing_script) { + auto nreq = config.is_timing_based_mode() + ? std::max(req_left, session->max_concurrent_streams()) + : std::min(req_left, session->max_concurrent_streams()); + + for (; nreq > 0; --nreq) { + if (submit_request() != 0) { + process_request_failure(); + break; + } + } + } else { + + auto duration = config.timings[reqidx]; + + while (duration < std::chrono::duration(1e-9)) { + if (submit_request() != 0) { + process_request_failure(); + break; + } + duration = config.timings[reqidx]; + if (reqidx == 0) { + // if reqidx wraps around back to 0, we uses up all lines and + // should break + break; + } + } + + if (duration >= std::chrono::duration(1e-9)) { + // double check since we may have break due to reqidx wraps + // around back to 0 + request_timeout_watcher.repeat = util::ev_tstamp_from(duration); + ev_timer_again(worker->loop, &request_timeout_watcher); + } + } + signal_write(); + + return 0; +} + +int Client::on_read(const uint8_t *data, size_t len) { + auto rv = session->on_read(data, len); + if (rv != 0) { + return -1; + } + if (worker->current_phase == Phase::MAIN_DURATION) { + worker->stats.bytes_total += len; + } + signal_write(); + return 0; +} + +int Client::on_write() { + if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) { + return 0; + } + + if (session->on_write() != 0) { + return -1; + } + return 0; +} + +int Client::read_clear() { + uint8_t buf[8_k]; + + for (;;) { + ssize_t nread; + while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return -1; + } + + if (nread == 0) { + return -1; + } + + if (on_read(buf, nread) != 0) { + return -1; + } + } + + return 0; +} + +int Client::write_clear() { + std::array iov; + + for (;;) { + if (on_write() != 0) { + return -1; + } + + auto iovcnt = wb.riovec(iov.data(), iov.size()); + + if (iovcnt == 0) { + break; + } + + ssize_t nwrite; + while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR) + ; + + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + ev_io_start(worker->loop, &wev); + return 0; + } + return -1; + } + + wb.drain(nwrite); + } + + ev_io_stop(worker->loop, &wev); + + return 0; +} + +int Client::connected() { + if (!util::check_socket_connected(fd)) { + return ERR_CONNECT_FAIL; + } + ev_io_start(worker->loop, &rev); + ev_io_stop(worker->loop, &wev); + + if (ssl) { + SSL_set_fd(ssl, fd); + + readfn = &Client::tls_handshake; + writefn = &Client::tls_handshake; + + return do_write(); + } + + readfn = &Client::read_clear; + writefn = &Client::write_clear; + + if (connection_made() != 0) { + return -1; + } + + return 0; +} + +int Client::tls_handshake() { + ERR_clear_error(); + + auto rv = SSL_do_handshake(ssl); + + if (rv <= 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + ev_io_stop(worker->loop, &wev); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(worker->loop, &wev); + return 0; + default: + return -1; + } + } + + ev_io_stop(worker->loop, &wev); + + readfn = &Client::read_tls; + writefn = &Client::write_tls; + + if (connection_made() != 0) { + return -1; + } + + return 0; +} + +int Client::read_tls() { + uint8_t buf[8_k]; + + ERR_clear_error(); + + for (;;) { + auto rv = SSL_read(ssl, buf, sizeof(buf)); + + if (rv <= 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + return 0; + case SSL_ERROR_WANT_WRITE: + // renegotiation started + return -1; + default: + return -1; + } + } + + if (on_read(buf, rv) != 0) { + return -1; + } + } +} + +int Client::write_tls() { + ERR_clear_error(); + + struct iovec iov; + + for (;;) { + if (on_write() != 0) { + return -1; + } + + auto iovcnt = wb.riovec(&iov, 1); + + if (iovcnt == 0) { + break; + } + + auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len); + + if (rv <= 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + // renegotiation started + return -1; + case SSL_ERROR_WANT_WRITE: + ev_io_start(worker->loop, &wev); + return 0; + default: + return -1; + } + } + + wb.drain(rv); + } + + ev_io_stop(worker->loop, &wev); + + return 0; +} + +#ifdef ENABLE_HTTP3 +// Returns 1 if sendmsg is blocked. +int Client::write_udp(const sockaddr *addr, socklen_t addrlen, + const uint8_t *data, size_t datalen, size_t gso_size) { + iovec msg_iov; + msg_iov.iov_base = const_cast(data); + msg_iov.iov_len = datalen; + + msghdr msg{}; + msg.msg_name = const_cast(addr); + msg.msg_namelen = addrlen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + +# ifdef UDP_SEGMENT + std::array msg_ctrl{}; + if (gso_size && datalen > gso_size) { + msg.msg_control = msg_ctrl.data(); + msg.msg_controllen = msg_ctrl.size(); + + auto cm = CMSG_FIRSTHDR(&msg); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + uint16_t n = gso_size; + memcpy(CMSG_DATA(cm), &n, sizeof(n)); + } +# endif // UDP_SEGMENT + + auto nwrite = sendmsg(fd, &msg, 0); + if (nwrite < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 1; + } + + std::cerr << "sendmsg: errno=" << errno << std::endl; + } else { + ++worker->stats.udp_dgram_sent; + } + + ev_io_stop(worker->loop, &wev); + + return 0; +} +#endif // ENABLE_HTTP3 + +void Client::record_request_time(RequestStat *req_stat) { + req_stat->request_time = std::chrono::steady_clock::now(); + req_stat->request_wall_time = std::chrono::system_clock::now(); +} + +void Client::record_connect_start_time() { + cstat.connect_start_time = std::chrono::steady_clock::now(); +} + +void Client::record_connect_time() { + cstat.connect_time = std::chrono::steady_clock::now(); +} + +void Client::record_ttfb() { + if (recorded(cstat.ttfb)) { + return; + } + + cstat.ttfb = std::chrono::steady_clock::now(); +} + +void Client::clear_connect_times() { + cstat.connect_start_time = std::chrono::steady_clock::time_point(); + cstat.connect_time = std::chrono::steady_clock::time_point(); + cstat.ttfb = std::chrono::steady_clock::time_point(); +} + +void Client::record_client_start_time() { + // Record start time only once at the very first connection is going + // to be made. + if (recorded(cstat.client_start_time)) { + return; + } + + cstat.client_start_time = std::chrono::steady_clock::now(); +} + +void Client::record_client_end_time() { + // Unlike client_start_time, we overwrite client_end_time. This + // handles multiple connect/disconnect for HTTP/1.1 benchmark. + cstat.client_end_time = std::chrono::steady_clock::now(); +} + +void Client::signal_write() { ev_io_start(worker->loop, &wev); } + +void Client::try_new_connection() { new_connection_requested = true; } + +namespace { +int get_ev_loop_flags() { + if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) { + return ev_recommended_backends() | EVBACKEND_KQUEUE; + } + + return 0; +} +} // namespace + +Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, + size_t rate, size_t max_samples, Config *config) + : randgen(util::make_mt19937()), + stats(req_todo, nclients), + loop(ev_loop_new(get_ev_loop_flags())), + ssl_ctx(ssl_ctx), + config(config), + id(id), + tls_info_report_done(false), + app_info_report_done(false), + nconns_made(0), + nclients(nclients), + nreqs_per_client(req_todo / nclients), + nreqs_rem(req_todo % nclients), + rate(rate), + max_samples(max_samples), + next_client_id(0) { + if (!config->is_rate_mode() && !config->is_timing_based_mode()) { + progress_interval = std::max(static_cast(1), req_todo / 10); + } else { + progress_interval = std::max(static_cast(1), nclients / 10); + } + + // Below timeout is not needed in case of timing-based benchmarking + // create timer that will go off every rate_period + ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0., + config->rate_period); + timeout_watcher.data = this; + + if (config->is_timing_based_mode()) { + stats.req_stats.reserve(std::max(req_todo, max_samples)); + stats.client_stats.reserve(std::max(nclients, max_samples)); + } else { + stats.req_stats.reserve(std::min(req_todo, max_samples)); + stats.client_stats.reserve(std::min(nclients, max_samples)); + } + + sampling_init(request_times_smp, max_samples); + sampling_init(client_smp, max_samples); + + ev_timer_init(&duration_watcher, duration_timeout_cb, config->duration, 0.); + duration_watcher.data = this; + + ev_timer_init(&warmup_watcher, warmup_timeout_cb, config->warm_up_time, 0.); + warmup_watcher.data = this; + + if (config->is_timing_based_mode()) { + current_phase = Phase::INITIAL_IDLE; + } else { + current_phase = Phase::MAIN_DURATION; + } +} + +Worker::~Worker() { + ev_timer_stop(loop, &timeout_watcher); + ev_timer_stop(loop, &duration_watcher); + ev_timer_stop(loop, &warmup_watcher); + ev_loop_destroy(loop); +} + +void Worker::stop_all_clients() { + for (auto client : clients) { + if (client) { + client->terminate_session(); + } + } +} + +void Worker::free_client(Client *deleted_client) { + for (auto &client : clients) { + if (client == deleted_client) { + client->req_todo = client->req_done; + stats.req_todo += client->req_todo; + auto index = &client - &clients[0]; + clients[index] = nullptr; + return; + } + } +} + +void Worker::run() { + if (!config->is_rate_mode() && !config->is_timing_based_mode()) { + for (size_t i = 0; i < nclients; ++i) { + auto req_todo = nreqs_per_client; + if (nreqs_rem > 0) { + ++req_todo; + --nreqs_rem; + } + + auto client = std::make_unique(next_client_id++, this, req_todo); + if (client->connect() != 0) { + std::cerr << "client could not connect to host" << std::endl; + client->fail(); + } else { + client.release(); + } + } + } else if (config->is_rate_mode()) { + ev_timer_again(loop, &timeout_watcher); + + // call callback so that we don't waste the first rate_period + rate_period_timeout_w_cb(loop, &timeout_watcher, 0); + } else { + // call the callback to start for one single time + rate_period_timeout_w_cb(loop, &timeout_watcher, 0); + } + ev_run(loop, 0); +} + +namespace { +template +void sample(Sampling &smp, Stats &stats, Stat *s) { + ++smp.n; + if (stats.size() < smp.max_samples) { + stats.push_back(*s); + return; + } + auto d = std::uniform_int_distribution(0, smp.n - 1); + auto i = d(gen); + if (i < smp.max_samples) { + stats[i] = *s; + } +} +} // namespace + +void Worker::sample_req_stat(RequestStat *req_stat) { + sample(request_times_smp, stats.req_stats, req_stat); +} + +void Worker::sample_client_stat(ClientStat *cstat) { + sample(client_smp, stats.client_stats, cstat); +} + +void Worker::report_progress() { + if (id != 0 || config->is_rate_mode() || stats.req_done % progress_interval || + config->is_timing_based_mode()) { + return; + } + + std::cout << "progress: " << stats.req_done * 100 / stats.req_todo << "% done" + << std::endl; +} + +void Worker::report_rate_progress() { + if (id != 0 || nconns_made % progress_interval) { + return; + } + + std::cout << "progress: " << nconns_made * 100 / nclients + << "% of clients started" << std::endl; +} + +namespace { +// Returns percentage of number of samples within mean +/- sd. +double within_sd(const std::vector &samples, double mean, double sd) { + if (samples.size() == 0) { + return 0.0; + } + auto lower = mean - sd; + auto upper = mean + sd; + auto m = std::count_if( + std::begin(samples), std::end(samples), + [&lower, &upper](double t) { return lower <= t && t <= upper; }); + return (m / static_cast(samples.size())) * 100; +} +} // namespace + +namespace { +// Computes statistics using |samples|. The min, max, mean, sd, and +// percentage of number of samples within mean +/- sd are computed. +// If |sampling| is true, this computes sample variance. Otherwise, +// population variance. +SDStat compute_time_stat(const std::vector &samples, + bool sampling = false) { + if (samples.empty()) { + return {0.0, 0.0, 0.0, 0.0, 0.0}; + } + // standard deviation calculated using Rapid calculation method: + // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods + double a = 0, q = 0; + size_t n = 0; + double sum = 0; + auto res = SDStat{std::numeric_limits::max(), + std::numeric_limits::min()}; + for (const auto &t : samples) { + ++n; + res.min = std::min(res.min, t); + res.max = std::max(res.max, t); + sum += t; + + auto na = a + (t - a) / n; + q += (t - a) * (t - na); + a = na; + } + + assert(n > 0); + res.mean = sum / n; + res.sd = sqrt(q / (sampling && n > 1 ? n - 1 : n)); + res.within_sd = within_sd(samples, res.mean, res.sd); + + return res; +} +} // namespace + +namespace { +SDStats +process_time_stats(const std::vector> &workers) { + auto request_times_sampling = false; + auto client_times_sampling = false; + size_t nrequest_times = 0; + size_t nclient_times = 0; + for (const auto &w : workers) { + nrequest_times += w->stats.req_stats.size(); + request_times_sampling = w->request_times_smp.n > w->stats.req_stats.size(); + + nclient_times += w->stats.client_stats.size(); + client_times_sampling = w->client_smp.n > w->stats.client_stats.size(); + } + + std::vector request_times; + request_times.reserve(nrequest_times); + + std::vector connect_times, ttfb_times, rps_values; + connect_times.reserve(nclient_times); + ttfb_times.reserve(nclient_times); + rps_values.reserve(nclient_times); + + for (const auto &w : workers) { + for (const auto &req_stat : w->stats.req_stats) { + if (!req_stat.completed) { + continue; + } + request_times.push_back( + std::chrono::duration_cast>( + req_stat.stream_close_time - req_stat.request_time) + .count()); + } + + const auto &stat = w->stats; + + for (const auto &cstat : stat.client_stats) { + if (recorded(cstat.client_start_time) && + recorded(cstat.client_end_time)) { + auto t = std::chrono::duration_cast>( + cstat.client_end_time - cstat.client_start_time) + .count(); + if (t > 1e-9) { + rps_values.push_back(cstat.req_success / t); + } + } + + // We will get connect event before FFTB. + if (!recorded(cstat.connect_start_time) || + !recorded(cstat.connect_time)) { + continue; + } + + connect_times.push_back( + std::chrono::duration_cast>( + cstat.connect_time - cstat.connect_start_time) + .count()); + + if (!recorded(cstat.ttfb)) { + continue; + } + + ttfb_times.push_back( + std::chrono::duration_cast>( + cstat.ttfb - cstat.connect_start_time) + .count()); + } + } + + return {compute_time_stat(request_times, request_times_sampling), + compute_time_stat(connect_times, client_times_sampling), + compute_time_stat(ttfb_times, client_times_sampling), + compute_time_stat(rps_values, client_times_sampling)}; +} +} // namespace + +namespace { +void resolve_host() { + if (config.base_uri_unix) { + auto res = std::make_unique(); + res->ai_family = config.unix_addr.sun_family; + res->ai_socktype = SOCK_STREAM; + res->ai_addrlen = sizeof(config.unix_addr); + res->ai_addr = + static_cast(static_cast(&config.unix_addr)); + + config.addrs = res.release(); + return; + }; + + int rv; + addrinfo hints{}, *res; + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_ADDRCONFIG; + + const auto &resolve_host = + config.connect_to_host.empty() ? config.host : config.connect_to_host; + auto port = + config.connect_to_port == 0 ? config.port : config.connect_to_port; + + rv = + getaddrinfo(resolve_host.c_str(), util::utos(port).c_str(), &hints, &res); + if (rv != 0) { + std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl; + exit(EXIT_FAILURE); + } + if (res == nullptr) { + std::cerr << "No address returned" << std::endl; + exit(EXIT_FAILURE); + } + config.addrs = res; +} +} // namespace + +namespace { +std::string get_reqline(const char *uri, const http_parser_url &u) { + std::string reqline; + + if (util::has_uri_field(u, UF_PATH)) { + reqline = util::get_uri_field(uri, u, UF_PATH).str(); + } else { + reqline = "/"; + } + + if (util::has_uri_field(u, UF_QUERY)) { + reqline += '?'; + reqline += util::get_uri_field(uri, u, UF_QUERY); + } + + return reqline; +} +} // namespace + +#ifndef OPENSSL_NO_NEXTPROTONEG +namespace { +int client_select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + if (util::select_protocol(const_cast(out), outlen, in, + inlen, config.npn_list)) { + return SSL_TLSEXT_ERR_OK; + } + + // OpenSSL will terminate handshake with fatal alert if we return + // NOACK. So there is no way to fallback. + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace +#endif // !OPENSSL_NO_NEXTPROTONEG + +namespace { +constexpr char UNIX_PATH_PREFIX[] = "unix:"; +} // namespace + +namespace { +bool parse_base_uri(const StringRef &base_uri) { + http_parser_url u{}; + if (http_parser_parse_url(base_uri.c_str(), base_uri.size(), 0, &u) != 0 || + !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) { + return false; + } + + config.scheme = util::get_uri_field(base_uri.c_str(), u, UF_SCHEMA).str(); + config.host = util::get_uri_field(base_uri.c_str(), u, UF_HOST).str(); + config.default_port = util::get_default_port(base_uri.c_str(), u); + if (util::has_uri_field(u, UF_PORT)) { + config.port = u.port; + } else { + config.port = config.default_port; + } + + return true; +} +} // namespace +namespace { +// Use std::vector::iterator explicitly, without that, +// http_parser_url u{} fails with clang-3.4. +std::vector parse_uris(std::vector::iterator first, + std::vector::iterator last) { + std::vector reqlines; + + if (first == last) { + std::cerr << "no URI available" << std::endl; + exit(EXIT_FAILURE); + } + + if (!config.has_base_uri()) { + + if (!parse_base_uri(StringRef{*first})) { + std::cerr << "invalid URI: " << *first << std::endl; + exit(EXIT_FAILURE); + } + + config.base_uri = *first; + } + + for (; first != last; ++first) { + http_parser_url u{}; + + auto uri = (*first).c_str(); + + if (http_parser_parse_url(uri, (*first).size(), 0, &u) != 0) { + std::cerr << "invalid URI: " << uri << std::endl; + exit(EXIT_FAILURE); + } + + reqlines.push_back(get_reqline(uri, u)); + } + + return reqlines; +} +} // namespace + +namespace { +std::vector read_uri_from_file(std::istream &infile) { + std::vector uris; + std::string line_uri; + while (std::getline(infile, line_uri)) { + uris.push_back(line_uri); + } + + return uris; +} +} // namespace + +namespace { +void read_script_from_file( + std::istream &infile, + std::vector &timings, + std::vector &uris) { + std::string script_line; + int line_count = 0; + while (std::getline(infile, script_line)) { + line_count++; + if (script_line.empty()) { + std::cerr << "Empty line detected at line " << line_count + << ". Ignoring and continuing." << std::endl; + continue; + } + + std::size_t pos = script_line.find("\t"); + if (pos == std::string::npos) { + std::cerr << "Invalid line format detected, no tab character at line " + << line_count << ". \n\t" << script_line << std::endl; + exit(EXIT_FAILURE); + } + + const char *start = script_line.c_str(); + char *end; + auto v = std::strtod(start, &end); + + errno = 0; + if (v < 0.0 || !std::isfinite(v) || end == start || errno != 0) { + auto error = errno; + std::cerr << "Time value error at line " << line_count << ". \n\t" + << "value = " << script_line.substr(0, pos) << std::endl; + if (error != 0) { + std::cerr << "\t" << strerror(error) << std::endl; + } + exit(EXIT_FAILURE); + } + + timings.emplace_back( + std::chrono::duration_cast( + std::chrono::duration(v))); + uris.push_back(script_line.substr(pos + 1, script_line.size())); + } +} +} // namespace + +namespace { +std::unique_ptr create_worker(uint32_t id, SSL_CTX *ssl_ctx, + size_t nreqs, size_t nclients, + size_t rate, size_t max_samples) { + std::stringstream rate_report; + if (config.is_rate_mode() && nclients > rate) { + rate_report << "Up to " << rate << " client(s) will be created every " + << util::duration_str(config.rate_period) << " "; + } + + if (config.is_timing_based_mode()) { + std::cout << "spawning thread #" << id << ": " << nclients + << " total client(s). Timing-based test with " + << config.warm_up_time << "s of warm-up time and " + << config.duration << "s of main duration for measurements." + << std::endl; + } else { + std::cout << "spawning thread #" << id << ": " << nclients + << " total client(s). " << rate_report.str() << nreqs + << " total requests" << std::endl; + } + + if (config.is_rate_mode()) { + return std::make_unique(id, ssl_ctx, nreqs, nclients, rate, + max_samples, &config); + } else { + // Here rate is same as client because the rate_timeout callback + // will be called only once + return std::make_unique(id, ssl_ctx, nreqs, nclients, nclients, + max_samples, &config); + } +} +} // namespace + +namespace { +int parse_header_table_size(uint32_t &dst, const char *opt, + const char *optarg) { + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "--" << opt << ": Bad option value: " << optarg << std::endl; + return -1; + } + if (n > std::numeric_limits::max()) { + std::cerr << "--" << opt + << ": Value too large. It should be less than or equal to " + << std::numeric_limits::max() << std::endl; + return -1; + } + + dst = n; + + return 0; +} +} // namespace + +namespace { +std::string make_http_authority(const Config &config) { + std::string host; + + if (util::numeric_host(config.host.c_str(), AF_INET6)) { + host += '['; + host += config.host; + host += ']'; + } else { + host = config.host; + } + + if (config.port != config.default_port) { + host += ':'; + host += util::utos(config.port); + } + + return host; +} +} // namespace + +namespace { +void print_version(std::ostream &out) { + out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl; +} +} // namespace + +namespace { +void print_usage(std::ostream &out) { + out << R"(Usage: h2load [OPTIONS]... [URI]... +benchmarking tool for HTTP/2 server)" + << std::endl; +} +} // namespace + +namespace { +constexpr char DEFAULT_NPN_LIST[] = "h2,h2-16,h2-14,http/1.1"; +} // namespace + +namespace { +void print_help(std::ostream &out) { + print_usage(out); + + auto config = Config(); + + out << R"( + Specify URI to access. Multiple URIs can be specified. + URIs are used in this order for each client. All URIs + are used, then first URI is used and then 2nd URI, and + so on. The scheme, host and port in the subsequent + URIs, if present, are ignored. Those in the first URI + are used solely. Definition of a base URI overrides all + scheme, host or port values. +Options: + -n, --requests= + Number of requests across all clients. If it is used + with --timing-script-file option, this option specifies + the number of requests each client performs rather than + the number of requests across all clients. This option + is ignored if timing-based benchmarking is enabled (see + --duration option). + Default: )" + << config.nreqs << R"( + -c, --clients= + Number of concurrent clients. With -r option, this + specifies the maximum number of connections to be made. + Default: )" + << config.nclients << R"( + -t, --threads= + Number of native threads. + Default: )" + << config.nthreads << R"( + -i, --input-file= + Path of a file with multiple URIs are separated by EOLs. + This option will disable URIs getting from command-line. + If '-' is given as , URIs will be read from stdin. + URIs are used in this order for each client. All URIs + are used, then first URI is used and then 2nd URI, and + so on. The scheme, host and port in the subsequent + URIs, if present, are ignored. Those in the first URI + are used solely. Definition of a base URI overrides all + scheme, host or port values. + -m, --max-concurrent-streams= + Max concurrent streams to issue per session. When + http/1.1 is used, this specifies the number of HTTP + pipelining requests in-flight. + Default: 1 + -f, --max-frame-size= + Maximum frame size that the local endpoint is willing to + receive. + Default: )" + << util::utos_unit(config.max_frame_size) << R"( + -w, --window-bits= + Sets the stream level initial window size to (2**)-1. + For QUIC, is capped to 26 (roughly 64MiB). + Default: )" + << config.window_bits << R"( + -W, --connection-window-bits= + Sets the connection level initial window size to + (2**)-1. + Default: )" + << config.connection_window_bits << R"( + -H, --header=
+ Add/Override a header to the requests. + --ciphers= + Set allowed cipher list for TLSv1.2 or earlier. The + format of the string is described in OpenSSL ciphers(1). + Default: )" + << config.ciphers << R"( + --tls13-ciphers= + Set allowed cipher list for TLSv1.3. The format of the + string is described in OpenSSL ciphers(1). + Default: )" + << config.tls13_ciphers << R"( + -p, --no-tls-proto= + Specify ALPN identifier of the protocol to be used when + accessing http URI without SSL/TLS. + Available protocols: )" + << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( and )" << NGHTTP2_H1_1 << R"( + Default: )" + << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( + -d, --data= + Post FILE to server. The request method is changed to + POST. For http/1.1 connection, if -d is used, the + maximum number of in-flight pipelined requests is set to + 1. + -r, --rate= + Specifies the fixed rate at which connections are + created. The rate must be a positive integer, + representing the number of connections to be made per + rate period. The maximum number of connections to be + made is given in -c option. This rate will be + distributed among threads as evenly as possible. For + example, with -t2 and -r4, each thread gets 2 + connections per period. When the rate is 0, the program + will run as it normally does, creating connections at + whatever variable rate it wants. The default value for + this option is 0. -r and -D are mutually exclusive. + --rate-period= + Specifies the time period between creating connections. + The period must be a positive number, representing the + length of the period in time. This option is ignored if + the rate option is not used. The default value for this + option is 1s. + -D, --duration= + Specifies the main duration for the measurements in case + of timing-based benchmarking. -D and -r are mutually + exclusive. + --warm-up-time= + Specifies the time period before starting the actual + measurements, in case of timing-based benchmarking. + Needs to provided along with -D option. + -T, --connection-active-timeout= + Specifies the maximum time that h2load is willing to + keep a connection open, regardless of the activity on + said connection. must be a positive integer, + specifying the amount of time to wait. When no timeout + value is set (either active or inactive), h2load will + keep a connection open indefinitely, waiting for a + response. + -N, --connection-inactivity-timeout= + Specifies the amount of time that h2load is willing to + wait to see activity on a given connection. + must be a positive integer, specifying the amount of + time to wait. When no timeout value is set (either + active or inactive), h2load will keep a connection open + indefinitely, waiting for a response. + --timing-script-file= + Path of a file containing one or more lines separated by + EOLs. Each script line is composed of two tab-separated + fields. The first field represents the time offset from + the start of execution, expressed as a positive value of + milliseconds with microsecond resolution. The second + field represents the URI. This option will disable URIs + getting from command-line. If '-' is given as , + script lines will be read from stdin. Script lines are + used in order for each client. If -n is given, it must + be less than or equal to the number of script lines, + larger values are clamped to the number of script lines. + If -n is not given, the number of requests will default + to the number of script lines. The scheme, host and + port defined in the first URI are used solely. Values + contained in other URIs, if present, are ignored. + Definition of a base URI overrides all scheme, host or + port values. --timing-script-file and --rps are + mutually exclusive. + -B, --base-uri=(|unix:) + Specify URI from which the scheme, host and port will be + used for all requests. The base URI overrides all + values defined either at the command line or inside + input files. If argument starts with "unix:", then the + rest of the argument will be treated as UNIX domain + socket path. The connection is made through that path + instead of TCP. In this case, scheme is inferred from + the first URI appeared in the command line or inside + input files as usual. + --npn-list= + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. This is used in both ALPN and + NPN. The parameter must be delimited by a single comma + only and any white spaces are treated as a part of + protocol string. + Default: )" + << DEFAULT_NPN_LIST << R"( + --h1 Short hand for --npn-list=http/1.1 + --no-tls-proto=http/1.1, which effectively force + http/1.1 for both http and https URI. + --header-table-size= + Specify decoder header table size. + Default: )" + << util::utos_unit(config.header_table_size) << R"( + --encoder-header-table-size= + Specify encoder header table size. The decoder (server) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which server specified. + Default: )" + << util::utos_unit(config.encoder_header_table_size) << R"( + --log-file= + Write per-request information to a file as tab-separated + columns: start time as microseconds since epoch; HTTP + status code; microseconds until end of response. More + columns may be added later. Rows are ordered by end-of- + response time when using one worker thread, but may + appear slightly out of order with multiple threads due + to buffering. Status code is -1 for failed streams. + --qlog-file-base= + Enable qlog output and specify base file name for qlogs. + Qlog is emitted for each connection. For a given base + name "base", each output file name becomes + "base.M.N.sqlog" where M is worker ID and N is client ID + (e.g. "base.0.3.sqlog"). Only effective in QUIC runs. + --connect-to=[:] + Host and port to connect instead of using the authority + in . + --rps= Specify request per second for each client. --rps and + --timing-script-file are mutually exclusive. + --groups= + Specify the supported groups. + Default: )" + << config.groups << R"( + --no-udp-gso + Disable UDP GSO. + --max-udp-payload-size= + Specify the maximum outgoing UDP datagram payload size. + --ktls Enable ktls. + -v, --verbose + Output debug information. + --version Display version information and exit. + -h, --help Display this help and exit. + +-- + + The argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The argument is an integer and an optional unit (e.g., 1s + is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms + (hours, minutes, seconds and milliseconds, respectively). If a unit + is omitted, a second is used as unit.)" + << std::endl; +} +} // namespace + +int main(int argc, char **argv) { + tls::libssl_init(); + +#ifndef NOTHREADS + tls::LibsslGlobalLock lock; +#endif // NOTHREADS + + std::string datafile; + std::string logfile; + std::string qlog_base; + bool nreqs_set_manually = false; + while (1) { + static int flag = 0; + constexpr static option long_options[] = { + {"requests", required_argument, nullptr, 'n'}, + {"clients", required_argument, nullptr, 'c'}, + {"data", required_argument, nullptr, 'd'}, + {"threads", required_argument, nullptr, 't'}, + {"max-concurrent-streams", required_argument, nullptr, 'm'}, + {"window-bits", required_argument, nullptr, 'w'}, + {"max-frame-size", required_argument, nullptr, 'f'}, + {"connection-window-bits", required_argument, nullptr, 'W'}, + {"input-file", required_argument, nullptr, 'i'}, + {"header", required_argument, nullptr, 'H'}, + {"no-tls-proto", required_argument, nullptr, 'p'}, + {"verbose", no_argument, nullptr, 'v'}, + {"help", no_argument, nullptr, 'h'}, + {"version", no_argument, &flag, 1}, + {"ciphers", required_argument, &flag, 2}, + {"rate", required_argument, nullptr, 'r'}, + {"connection-active-timeout", required_argument, nullptr, 'T'}, + {"connection-inactivity-timeout", required_argument, nullptr, 'N'}, + {"duration", required_argument, nullptr, 'D'}, + {"timing-script-file", required_argument, &flag, 3}, + {"base-uri", required_argument, nullptr, 'B'}, + {"npn-list", required_argument, &flag, 4}, + {"rate-period", required_argument, &flag, 5}, + {"h1", no_argument, &flag, 6}, + {"header-table-size", required_argument, &flag, 7}, + {"encoder-header-table-size", required_argument, &flag, 8}, + {"warm-up-time", required_argument, &flag, 9}, + {"log-file", required_argument, &flag, 10}, + {"connect-to", required_argument, &flag, 11}, + {"rps", required_argument, &flag, 12}, + {"groups", required_argument, &flag, 13}, + {"tls13-ciphers", required_argument, &flag, 14}, + {"no-udp-gso", no_argument, &flag, 15}, + {"qlog-file-base", required_argument, &flag, 16}, + {"max-udp-payload-size", required_argument, &flag, 17}, + {"ktls", no_argument, &flag, 18}, + {nullptr, 0, nullptr, 0}}; + int option_index = 0; + auto c = getopt_long(argc, argv, + "hvW:c:d:m:n:p:t:w:f:H:i:r:T:N:D:B:", long_options, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'n': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-n: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.nreqs = n; + nreqs_set_manually = true; + break; + } + case 'c': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-c: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.nclients = n; + break; + } + case 'd': + datafile = optarg; + break; + case 't': { +#ifdef NOTHREADS + std::cerr << "-t: WARNING: Threading disabled at build time, " + << "no threads created." << std::endl; +#else + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-t: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.nthreads = n; +#endif // NOTHREADS + break; + } + case 'm': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-m: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.max_concurrent_streams = n; + break; + } + case 'w': + case 'W': { + auto n = util::parse_uint(optarg); + if (n == -1 || n > 30) { + std::cerr << "-" << static_cast(c) + << ": specify the integer in the range [0, 30], inclusive" + << std::endl; + exit(EXIT_FAILURE); + } + if (c == 'w') { + config.window_bits = n; + } else { + config.connection_window_bits = n; + } + break; + } + case 'f': { + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "--max-frame-size: bad option value: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + if (static_cast(n) < 16_k) { + std::cerr << "--max-frame-size: minimum 16384" << std::endl; + exit(EXIT_FAILURE); + } + if (static_cast(n) > 16_m - 1) { + std::cerr << "--max-frame-size: maximum 16777215" << std::endl; + exit(EXIT_FAILURE); + } + config.max_frame_size = n; + break; + } + case 'H': { + char *header = optarg; + // Skip first possible ':' in the header name + char *value = strchr(optarg + 1, ':'); + if (!value || (header[0] == ':' && header + 1 == value)) { + std::cerr << "-H: invalid header: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + *value = 0; + value++; + while (isspace(*value)) { + value++; + } + if (*value == 0) { + // This could also be a valid case for suppressing a header + // similar to curl + std::cerr << "-H: invalid header - value missing: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + // Note that there is no processing currently to handle multiple + // message-header fields with the same field name + config.custom_headers.emplace_back(header, value); + util::inp_strlower(config.custom_headers.back().name); + break; + } + case 'i': + config.ifile = optarg; + break; + case 'p': { + auto proto = StringRef{optarg}; + if (util::strieq(StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID), + proto)) { + config.no_tls_proto = Config::PROTO_HTTP2; + } else if (util::strieq(NGHTTP2_H1_1, proto)) { + config.no_tls_proto = Config::PROTO_HTTP1_1; + } else { + std::cerr << "-p: unsupported protocol " << proto << std::endl; + exit(EXIT_FAILURE); + } + break; + } + case 'r': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-r: bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + if (n == 0) { + std::cerr << "-r: the rate at which connections are made " + << "must be positive." << std::endl; + exit(EXIT_FAILURE); + } + config.rate = n; + break; + } + case 'T': + config.conn_active_timeout = util::parse_duration_with_unit(optarg); + if (!std::isfinite(config.conn_active_timeout)) { + std::cerr << "-T: bad value for the conn_active_timeout wait time: " + << optarg << std::endl; + exit(EXIT_FAILURE); + } + break; + case 'N': + config.conn_inactivity_timeout = util::parse_duration_with_unit(optarg); + if (!std::isfinite(config.conn_inactivity_timeout)) { + std::cerr << "-N: bad value for the conn_inactivity_timeout wait time: " + << optarg << std::endl; + exit(EXIT_FAILURE); + } + break; + case 'B': { + auto arg = StringRef{optarg}; + config.base_uri = ""; + config.base_uri_unix = false; + + if (util::istarts_with_l(arg, UNIX_PATH_PREFIX)) { + // UNIX domain socket path + sockaddr_un un; + + auto path = StringRef{std::begin(arg) + str_size(UNIX_PATH_PREFIX), + std::end(arg)}; + + if (path.size() == 0 || path.size() + 1 > sizeof(un.sun_path)) { + std::cerr << "--base-uri: invalid UNIX domain socket path: " << arg + << std::endl; + exit(EXIT_FAILURE); + } + + config.base_uri_unix = true; + + auto &unix_addr = config.unix_addr; + std::copy(std::begin(path), std::end(path), unix_addr.sun_path); + unix_addr.sun_path[path.size()] = '\0'; + unix_addr.sun_family = AF_UNIX; + + break; + } + + if (!parse_base_uri(arg)) { + std::cerr << "--base-uri: invalid base URI: " << arg << std::endl; + exit(EXIT_FAILURE); + } + + config.base_uri = arg.str(); + break; + } + case 'D': + config.duration = util::parse_duration_with_unit(optarg); + if (!std::isfinite(config.duration)) { + std::cerr << "-D: value error " << optarg << std::endl; + exit(EXIT_FAILURE); + } + break; + case 'v': + config.verbose = true; + break; + case 'h': + print_help(std::cout); + exit(EXIT_SUCCESS); + case '?': + util::show_candidates(argv[optind - 1], long_options); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // version option + print_version(std::cout); + exit(EXIT_SUCCESS); + case 2: + // ciphers option + config.ciphers = optarg; + break; + case 3: + // timing-script option + config.ifile = optarg; + config.timing_script = true; + break; + case 4: + // npn-list option + config.npn_list = util::parse_config_str_list(StringRef{optarg}); + break; + case 5: + // rate-period + config.rate_period = util::parse_duration_with_unit(optarg); + if (!std::isfinite(config.rate_period)) { + std::cerr << "--rate-period: value error " << optarg << std::endl; + exit(EXIT_FAILURE); + } + break; + case 6: + // --h1 + config.npn_list = + util::parse_config_str_list(StringRef::from_lit("http/1.1")); + config.no_tls_proto = Config::PROTO_HTTP1_1; + break; + case 7: + // --header-table-size + if (parse_header_table_size(config.header_table_size, + "header-table-size", optarg) != 0) { + exit(EXIT_FAILURE); + } + break; + case 8: + // --encoder-header-table-size + if (parse_header_table_size(config.encoder_header_table_size, + "encoder-header-table-size", optarg) != 0) { + exit(EXIT_FAILURE); + } + break; + case 9: + // --warm-up-time + config.warm_up_time = util::parse_duration_with_unit(optarg); + if (!std::isfinite(config.warm_up_time)) { + std::cerr << "--warm-up-time: value error " << optarg << std::endl; + exit(EXIT_FAILURE); + } + break; + case 10: + // --log-file + logfile = optarg; + break; + case 11: { + // --connect-to + auto p = util::split_hostport(StringRef{optarg}); + int64_t port = 0; + if (p.first.empty() || + (!p.second.empty() && (port = util::parse_uint(p.second)) == -1)) { + std::cerr << "--connect-to: Invalid value " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.connect_to_host = p.first.str(); + config.connect_to_port = port; + break; + } + case 12: { + char *end; + auto v = std::strtod(optarg, &end); + if (end == optarg || *end != '\0' || !std::isfinite(v) || + 1. / v < 1e-6) { + std::cerr << "--rps: Invalid value " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.rps = v; + break; + } + case 13: + // --groups + config.groups = optarg; + break; + case 14: + // --tls13-ciphers + config.tls13_ciphers = optarg; + break; + case 15: + // --no-udp-gso + config.no_udp_gso = true; + break; + case 16: + // --qlog-file-base + qlog_base = optarg; + break; + case 17: { + // --max-udp-payload-size + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "--max-udp-payload-size: bad option value: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + if (static_cast(n) > 64_k) { + std::cerr << "--max-udp-payload-size: must not exceed 65536" + << std::endl; + exit(EXIT_FAILURE); + } + config.max_udp_payload_size = n; + break; + } + case 18: + // --ktls + config.ktls = true; + break; + } + break; + default: + break; + } + } + + if (argc == optind) { + if (config.ifile.empty()) { + std::cerr << "no URI or input file given" << std::endl; + exit(EXIT_FAILURE); + } + } + + if (config.nclients == 0) { + std::cerr << "-c: the number of clients must be strictly greater than 0." + << std::endl; + exit(EXIT_FAILURE); + } + + if (config.npn_list.empty()) { + config.npn_list = + util::parse_config_str_list(StringRef::from_lit(DEFAULT_NPN_LIST)); + } + + // serialize the APLN tokens + for (auto &proto : config.npn_list) { + proto.insert(proto.begin(), static_cast(proto.size())); + } + + std::vector reqlines; + + if (config.ifile.empty()) { + std::vector uris; + std::copy(&argv[optind], &argv[argc], std::back_inserter(uris)); + reqlines = parse_uris(std::begin(uris), std::end(uris)); + } else { + std::vector uris; + if (!config.timing_script) { + if (config.ifile == "-") { + uris = read_uri_from_file(std::cin); + } else { + std::ifstream infile(config.ifile); + if (!infile) { + std::cerr << "cannot read input file: " << config.ifile << std::endl; + exit(EXIT_FAILURE); + } + + uris = read_uri_from_file(infile); + } + } else { + if (config.ifile == "-") { + read_script_from_file(std::cin, config.timings, uris); + } else { + std::ifstream infile(config.ifile); + if (!infile) { + std::cerr << "cannot read input file: " << config.ifile << std::endl; + exit(EXIT_FAILURE); + } + + read_script_from_file(infile, config.timings, uris); + } + + if (nreqs_set_manually) { + if (config.nreqs > uris.size()) { + std::cerr << "-n: the number of requests must be less than or equal " + "to the number of timing script entries. Setting number " + "of requests to " + << uris.size() << std::endl; + + config.nreqs = uris.size(); + } + } else { + config.nreqs = uris.size(); + } + } + + reqlines = parse_uris(std::begin(uris), std::end(uris)); + } + + if (reqlines.empty()) { + std::cerr << "No URI given" << std::endl; + exit(EXIT_FAILURE); + } + + if (config.is_timing_based_mode() && config.is_rate_mode()) { + std::cerr << "-r, -D: they are mutually exclusive." << std::endl; + exit(EXIT_FAILURE); + } + + if (config.timing_script && config.rps_enabled()) { + std::cerr << "--timing-script-file, --rps: they are mutually exclusive." + << std::endl; + exit(EXIT_FAILURE); + } + + if (config.nreqs == 0 && !config.is_timing_based_mode()) { + std::cerr << "-n: the number of requests must be strictly greater than 0 " + "if timing-based test is not being run." + << std::endl; + exit(EXIT_FAILURE); + } + + if (config.max_concurrent_streams == 0) { + std::cerr << "-m: the max concurrent streams must be strictly greater " + << "than 0." << std::endl; + exit(EXIT_FAILURE); + } + + if (config.nthreads == 0) { + std::cerr << "-t: the number of threads must be strictly greater than 0." + << std::endl; + exit(EXIT_FAILURE); + } + + if (config.nthreads > std::thread::hardware_concurrency()) { + std::cerr << "-t: warning: the number of threads is greater than hardware " + << "cores." << std::endl; + } + + // With timing script, we don't distribute config.nreqs to each + // client or thread. + if (!config.timing_script && config.nreqs < config.nclients && + !config.is_timing_based_mode()) { + std::cerr << "-n, -c: the number of requests must be greater than or " + << "equal to the clients." << std::endl; + exit(EXIT_FAILURE); + } + + if (config.nclients < config.nthreads) { + std::cerr << "-c, -t: the number of clients must be greater than or equal " + << "to the number of threads." << std::endl; + exit(EXIT_FAILURE); + } + + if (config.is_timing_based_mode()) { + config.nreqs = 0; + } + + if (config.is_rate_mode()) { + if (config.rate < config.nthreads) { + std::cerr << "-r, -t: the connection rate must be greater than or equal " + << "to the number of threads." << std::endl; + exit(EXIT_FAILURE); + } + + if (config.rate > config.nclients) { + std::cerr << "-r, -c: the connection rate must be smaller than or equal " + "to the number of clients." + << std::endl; + exit(EXIT_FAILURE); + } + } + + if (!datafile.empty()) { + config.data_fd = open(datafile.c_str(), O_RDONLY | O_BINARY); + if (config.data_fd == -1) { + std::cerr << "-d: Could not open file " << datafile << std::endl; + exit(EXIT_FAILURE); + } + struct stat data_stat; + if (fstat(config.data_fd, &data_stat) == -1) { + std::cerr << "-d: Could not stat file " << datafile << std::endl; + exit(EXIT_FAILURE); + } + config.data_length = data_stat.st_size; + auto addr = mmap(nullptr, config.data_length, PROT_READ, MAP_SHARED, + config.data_fd, 0); + if (addr == MAP_FAILED) { + std::cerr << "-d: Could not mmap file " << datafile << std::endl; + exit(EXIT_FAILURE); + } + config.data = static_cast(addr); + } + + if (!logfile.empty()) { + config.log_fd = open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP); + if (config.log_fd == -1) { + std::cerr << "--log-file: Could not open file " << logfile << std::endl; + exit(EXIT_FAILURE); + } + } + + if (!qlog_base.empty()) { + if (!config.is_quic()) { + std::cerr + << "Warning: --qlog-file-base: only effective in quic, ignoring." + << std::endl; + } else { +#ifdef ENABLE_HTTP3 + config.qlog_file_base = qlog_base; +#endif // ENABLE_HTTP3 + } + } + + struct sigaction act {}; + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, nullptr); + + auto ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + std::cerr << "Failed to create SSL_CTX: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + exit(EXIT_FAILURE); + } + + auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; + +#ifdef SSL_OP_ENABLE_KTLS + if (config.ktls) { + ssl_opts |= SSL_OP_ENABLE_KTLS; + } +#endif // SSL_OP_ENABLE_KTLS + + SSL_CTX_set_options(ssl_ctx, ssl_opts); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (config.is_quic()) { +#ifdef ENABLE_HTTP3 +# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS + if (ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) { + std::cerr << "ngtcp2_crypto_quictls_configure_client_context failed" + << std::endl; + exit(EXIT_FAILURE); + } +# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL + if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { + std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed" + << std::endl; + exit(EXIT_FAILURE); + } +# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +#endif // ENABLE_HTTP3 + } else if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, + nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { + std::cerr << "Could not set TLS versions" << std::endl; + exit(EXIT_FAILURE); + } + + if (SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers.c_str()) == 0) { + std::cerr << "SSL_CTX_set_cipher_list with " << config.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + exit(EXIT_FAILURE); + } + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (SSL_CTX_set_ciphersuites(ssl_ctx, config.tls13_ciphers.c_str()) == 0) { + std::cerr << "SSL_CTX_set_ciphersuites with " << config.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + exit(EXIT_FAILURE); + } +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#if OPENSSL_1_1_1_API + if (SSL_CTX_set1_groups_list(ssl_ctx, config.groups.c_str()) != 1) { + std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; + exit(EXIT_FAILURE); + } +#else // !OPENSSL_1_1_1_API + if (SSL_CTX_set1_curves_list(ssl_ctx, config.groups.c_str()) != 1) { + std::cerr << "SSL_CTX_set1_curves_list failed" << std::endl; + exit(EXIT_FAILURE); + } +#endif // !OPENSSL_1_1_1_API + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, + nullptr); +#endif // !OPENSSL_NO_NEXTPROTONEG + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + std::vector proto_list; + for (const auto &proto : config.npn_list) { + std::copy_n(proto.c_str(), proto.size(), std::back_inserter(proto_list)); + } + + SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + +#if OPENSSL_1_1_1_API + auto keylog_filename = getenv("SSLKEYLOGFILE"); + if (keylog_filename) { + keylog_file.open(keylog_filename, std::ios_base::app); + if (keylog_file) { + SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); + } + } +#endif // OPENSSL_1_1_1_API + + std::string user_agent = "h2load nghttp2/" NGHTTP2_VERSION; + Headers shared_nva; + shared_nva.emplace_back(":scheme", config.scheme); + shared_nva.emplace_back(":authority", make_http_authority(config)); + shared_nva.emplace_back(":method", config.data_fd == -1 ? "GET" : "POST"); + shared_nva.emplace_back("user-agent", user_agent); + + // list header fields that can be overridden. + auto override_hdrs = make_array(":authority", ":host", ":method", + ":scheme", "user-agent"); + + for (auto &kv : config.custom_headers) { + if (std::find(std::begin(override_hdrs), std::end(override_hdrs), + kv.name) != std::end(override_hdrs)) { + // override header + for (auto &nv : shared_nva) { + if ((nv.name == ":authority" && kv.name == ":host") || + (nv.name == kv.name)) { + nv.value = kv.value; + } + } + } else { + // add additional headers + shared_nva.push_back(kv); + } + } + + std::string content_length_str; + if (config.data_fd != -1) { + content_length_str = util::utos(config.data_length); + } + + auto method_it = + std::find_if(std::begin(shared_nva), std::end(shared_nva), + [](const Header &nv) { return nv.name == ":method"; }); + assert(method_it != std::end(shared_nva)); + + config.h1reqs.reserve(reqlines.size()); + config.nva.reserve(reqlines.size()); + + for (auto &req : reqlines) { + // For HTTP/1.1 + auto h1req = (*method_it).value; + h1req += ' '; + h1req += req; + h1req += " HTTP/1.1\r\n"; + for (auto &nv : shared_nva) { + if (nv.name == ":authority") { + h1req += "Host: "; + h1req += nv.value; + h1req += "\r\n"; + continue; + } + if (nv.name[0] == ':') { + continue; + } + h1req += nv.name; + h1req += ": "; + h1req += nv.value; + h1req += "\r\n"; + } + + if (!content_length_str.empty()) { + h1req += "Content-Length: "; + h1req += content_length_str; + h1req += "\r\n"; + } + h1req += "\r\n"; + + config.h1reqs.push_back(std::move(h1req)); + + // For nghttp2 + std::vector nva; + // 2 for :path, and possible content-length + nva.reserve(2 + shared_nva.size()); + + nva.push_back(http2::make_nv_ls(":path", req)); + + for (auto &nv : shared_nva) { + nva.push_back(http2::make_nv(nv.name, nv.value, false)); + } + + if (!content_length_str.empty()) { + nva.push_back(http2::make_nv(StringRef::from_lit("content-length"), + StringRef{content_length_str})); + } + + config.nva.push_back(std::move(nva)); + } + + // Don't DOS our server! + if (config.host == "nghttp2.org") { + std::cerr << "Using h2load against public server " << config.host + << " should be prohibited." << std::endl; + exit(EXIT_FAILURE); + } + + resolve_host(); + + std::cout << "starting benchmark..." << std::endl; + + std::vector> workers; + workers.reserve(config.nthreads); + +#ifndef NOTHREADS + size_t nreqs_per_thread = 0; + ssize_t nreqs_rem = 0; + + if (!config.timing_script) { + nreqs_per_thread = config.nreqs / config.nthreads; + nreqs_rem = config.nreqs % config.nthreads; + } + + size_t nclients_per_thread = config.nclients / config.nthreads; + ssize_t nclients_rem = config.nclients % config.nthreads; + + size_t rate_per_thread = config.rate / config.nthreads; + ssize_t rate_per_thread_rem = config.rate % config.nthreads; + + size_t max_samples_per_thread = + std::max(static_cast(256), MAX_SAMPLES / config.nthreads); + + std::mutex mu; + std::condition_variable cv; + auto ready = false; + + std::vector> futures; + for (size_t i = 0; i < config.nthreads; ++i) { + auto rate = rate_per_thread; + if (rate_per_thread_rem > 0) { + --rate_per_thread_rem; + ++rate; + } + auto nclients = nclients_per_thread; + if (nclients_rem > 0) { + --nclients_rem; + ++nclients; + } + + size_t nreqs; + if (config.timing_script) { + // With timing script, each client issues config.nreqs requests. + // We divide nreqs by number of clients in Worker ctor to + // distribute requests to those clients evenly, so multiply + // config.nreqs here by config.nclients. + nreqs = config.nreqs * nclients; + } else { + nreqs = nreqs_per_thread; + if (nreqs_rem > 0) { + --nreqs_rem; + ++nreqs; + } + } + + workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate, + max_samples_per_thread)); + auto &worker = workers.back(); + futures.push_back( + std::async(std::launch::async, [&worker, &mu, &cv, &ready]() { + { + std::unique_lock ulk(mu); + cv.wait(ulk, [&ready] { return ready; }); + } + worker->run(); + })); + } + + { + std::lock_guard lg(mu); + ready = true; + cv.notify_all(); + } + + auto start = std::chrono::steady_clock::now(); + + for (auto &fut : futures) { + fut.get(); + } + +#else // NOTHREADS + auto rate = config.rate; + auto nclients = config.nclients; + auto nreqs = + config.timing_script ? config.nreqs * config.nclients : config.nreqs; + + workers.push_back( + create_worker(0, ssl_ctx, nreqs, nclients, rate, MAX_SAMPLES)); + + auto start = std::chrono::steady_clock::now(); + + workers.back()->run(); +#endif // NOTHREADS + + auto end = std::chrono::steady_clock::now(); + auto duration = + std::chrono::duration_cast(end - start); + + Stats stats(0, 0); + for (const auto &w : workers) { + const auto &s = w->stats; + + stats.req_todo += s.req_todo; + stats.req_started += s.req_started; + stats.req_done += s.req_done; + stats.req_timedout += s.req_timedout; + stats.req_success += s.req_success; + stats.req_status_success += s.req_status_success; + stats.req_failed += s.req_failed; + stats.req_error += s.req_error; + stats.bytes_total += s.bytes_total; + stats.bytes_head += s.bytes_head; + stats.bytes_head_decomp += s.bytes_head_decomp; + stats.bytes_body += s.bytes_body; + stats.udp_dgram_recv += s.udp_dgram_recv; + stats.udp_dgram_sent += s.udp_dgram_sent; + + for (size_t i = 0; i < stats.status.size(); ++i) { + stats.status[i] += s.status[i]; + } + } + + auto ts = process_time_stats(workers); + + // Requests which have not been issued due to connection errors, are + // counted towards req_failed and req_error. + auto req_not_issued = + (stats.req_todo - stats.req_status_success - stats.req_failed); + stats.req_failed += req_not_issued; + stats.req_error += req_not_issued; + + // UI is heavily inspired by weighttp[1] and wrk[2] + // + // [1] https://github.com/lighttpd/weighttp + // [2] https://github.com/wg/wrk + double rps = 0; + int64_t bps = 0; + if (duration.count() > 0) { + if (config.is_timing_based_mode()) { + // we only want to consider the main duration if warm-up is given + rps = stats.req_success / config.duration; + bps = stats.bytes_total / config.duration; + } else { + auto secd = std::chrono::duration_cast< + std::chrono::duration>( + duration); + rps = stats.req_success / secd.count(); + bps = stats.bytes_total / secd.count(); + } + } + + double header_space_savings = 0.; + if (stats.bytes_head_decomp > 0) { + header_space_savings = + 1. - static_cast(stats.bytes_head) / stats.bytes_head_decomp; + } + + std::cout << std::fixed << std::setprecision(2) << R"( +finished in )" + << util::format_duration(duration) << ", " << rps << " req/s, " + << util::utos_funit(bps) << R"(B/s +requests: )" << stats.req_todo + << " total, " << stats.req_started << " started, " << stats.req_done + << " done, " << stats.req_status_success << " succeeded, " + << stats.req_failed << " failed, " << stats.req_error + << " errored, " << stats.req_timedout << R"( timeout +status codes: )" + << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, " + << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx +traffic: )" << util::utos_funit(stats.bytes_total) + << "B (" << stats.bytes_total << ") total, " + << util::utos_funit(stats.bytes_head) << "B (" << stats.bytes_head + << ") headers (space savings " << header_space_savings * 100 + << "%), " << util::utos_funit(stats.bytes_body) << "B (" + << stats.bytes_body << R"() data)" << std::endl; +#ifdef ENABLE_HTTP3 + if (config.is_quic()) { + std::cout << "UDP datagram: " << stats.udp_dgram_sent << " sent, " + << stats.udp_dgram_recv << " received" << std::endl; + } +#endif // ENABLE_HTTP3 + std::cout + << R"( min max mean sd +/- sd +time for request: )" + << std::setw(10) << util::format_duration(ts.request.min) << " " + << std::setw(10) << util::format_duration(ts.request.max) << " " + << std::setw(10) << util::format_duration(ts.request.mean) << " " + << std::setw(10) << util::format_duration(ts.request.sd) << std::setw(9) + << util::dtos(ts.request.within_sd) << "%" + << "\ntime for connect: " << std::setw(10) + << util::format_duration(ts.connect.min) << " " << std::setw(10) + << util::format_duration(ts.connect.max) << " " << std::setw(10) + << util::format_duration(ts.connect.mean) << " " << std::setw(10) + << util::format_duration(ts.connect.sd) << std::setw(9) + << util::dtos(ts.connect.within_sd) << "%" + << "\ntime to 1st byte: " << std::setw(10) + << util::format_duration(ts.ttfb.min) << " " << std::setw(10) + << util::format_duration(ts.ttfb.max) << " " << std::setw(10) + << util::format_duration(ts.ttfb.mean) << " " << std::setw(10) + << util::format_duration(ts.ttfb.sd) << std::setw(9) + << util::dtos(ts.ttfb.within_sd) << "%" + << "\nreq/s : " << std::setw(10) << ts.rps.min << " " + << std::setw(10) << ts.rps.max << " " << std::setw(10) << ts.rps.mean + << " " << std::setw(10) << ts.rps.sd << std::setw(9) + << util::dtos(ts.rps.within_sd) << "%" << std::endl; + + SSL_CTX_free(ssl_ctx); + + if (config.log_fd != -1) { + close(config.log_fd); + } + + return 0; +} + +} // namespace h2load + +int main(int argc, char **argv) { return h2load::main(argc, argv); } diff --git a/lib/nghttp2/src/h2load.h b/lib/nghttp2/src/h2load.h new file mode 100644 index 00000000000..d848fdf2ee2 --- /dev/null +++ b/lib/nghttp2/src/h2load.h @@ -0,0 +1,510 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 H2LOAD_H +#define H2LOAD_H + +#include "nghttp2_config.h" + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#include + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef ENABLE_HTTP3 +# include +# include +#endif // ENABLE_HTTP3 + +#include + +#include + +#include "http2.h" +#ifdef ENABLE_HTTP3 +# include "quic.h" +#endif // ENABLE_HTTP3 +#include "memchunk.h" +#include "template.h" + +using namespace nghttp2; + +namespace h2load { + +constexpr auto BACKOFF_WRITE_BUFFER_THRES = 16_k; + +class Session; +struct Worker; + +struct Config { + std::vector> nva; + std::vector h1reqs; + std::vector timings; + nghttp2::Headers custom_headers; + std::string scheme; + std::string host; + std::string connect_to_host; + std::string ifile; + std::string ciphers; + std::string tls13_ciphers; + // supported groups (or curves). + std::string groups; + // length of upload data + int64_t data_length; + // memory mapped upload data + uint8_t *data; + addrinfo *addrs; + size_t nreqs; + size_t nclients; + size_t nthreads; + // The maximum number of concurrent streams per session. + ssize_t max_concurrent_streams; + size_t window_bits; + size_t connection_window_bits; + size_t max_frame_size; + // rate at which connections should be made + size_t rate; + ev_tstamp rate_period; + // amount of time for main measurements in timing-based test + ev_tstamp duration; + // amount of time to wait before starting measurements in timing-based test + ev_tstamp warm_up_time; + // amount of time to wait for activity on a given connection + ev_tstamp conn_active_timeout; + // amount of time to wait after the last request is made on a connection + ev_tstamp conn_inactivity_timeout; + enum { PROTO_HTTP2, PROTO_HTTP1_1 } no_tls_proto; + uint32_t header_table_size; + uint32_t encoder_header_table_size; + // file descriptor for upload data + int data_fd; + // file descriptor to write per-request stats to. + int log_fd; + // base file name of qlog output files + std::string qlog_file_base; + uint16_t port; + uint16_t default_port; + uint16_t connect_to_port; + bool verbose; + bool timing_script; + std::string base_uri; + // true if UNIX domain socket is used. In this case, base_uri is + // not used in usual way. + bool base_uri_unix; + // used when UNIX domain socket is used (base_uri_unix is true). + sockaddr_un unix_addr; + // list of supported NPN/ALPN protocol strings in the order of + // preference. + std::vector npn_list; + // The number of request per second for each client. + double rps; + // Disables GSO for UDP connections. + bool no_udp_gso; + // The maximum UDP datagram payload size to send. + size_t max_udp_payload_size; + // Enable ktls. + bool ktls; + + Config(); + ~Config(); + + bool is_rate_mode() const; + bool is_timing_based_mode() const; + bool has_base_uri() const; + bool rps_enabled() const; + bool is_quic() const; +}; + +struct RequestStat { + // time point when request was sent + std::chrono::steady_clock::time_point request_time; + // same, but in wall clock reference frame + std::chrono::system_clock::time_point request_wall_time; + // time point when stream was closed + std::chrono::steady_clock::time_point stream_close_time; + // upload data length sent so far + int64_t data_offset; + // HTTP status code + int status; + // true if stream was successfully closed. This means stream was + // not reset, but it does not mean HTTP level error (e.g., 404). + bool completed; +}; + +struct ClientStat { + // time client started (i.e., first connect starts) + std::chrono::steady_clock::time_point client_start_time; + // time client end (i.e., client somehow processed all requests it + // is responsible for, and disconnected) + std::chrono::steady_clock::time_point client_end_time; + // The number of requests completed successful, but not necessarily + // means successful HTTP status code. + size_t req_success; + + // The following 3 numbers are overwritten each time when connection + // is made. + + // time connect starts + std::chrono::steady_clock::time_point connect_start_time; + // time to connect + std::chrono::steady_clock::time_point connect_time; + // time to first byte (TTFB) + std::chrono::steady_clock::time_point ttfb; +}; + +struct SDStat { + // min, max, mean and sd (standard deviation) + double min, max, mean, sd; + // percentage of samples inside mean -/+ sd + double within_sd; +}; + +struct SDStats { + // time for request + SDStat request; + // time for connect + SDStat connect; + // time to first byte (TTFB) + SDStat ttfb; + // request per second for each client + SDStat rps; +}; + +struct Stats { + Stats(size_t req_todo, size_t nclients); + // The total number of requests + size_t req_todo; + // The number of requests issued so far + size_t req_started; + // The number of requests finished + size_t req_done; + // The number of requests completed successful, but not necessarily + // means successful HTTP status code. + size_t req_success; + // The number of requests marked as success. HTTP status code is + // also considered as success. This is subset of req_done. + size_t req_status_success; + // The number of requests failed. This is subset of req_done. + size_t req_failed; + // The number of requests failed due to network errors. This is + // subset of req_failed. + size_t req_error; + // The number of requests that failed due to timeout. + size_t req_timedout; + // The number of bytes received on the "wire". If SSL/TLS is used, + // this is the number of decrypted bytes the application received. + int64_t bytes_total; + // The number of bytes received for header fields. This is + // compressed version. + int64_t bytes_head; + // The number of bytes received for header fields after they are + // decompressed. + int64_t bytes_head_decomp; + // The number of bytes received in DATA frame. + int64_t bytes_body; + // The number of each HTTP status category, status[i] is status code + // in the range [i*100, (i+1)*100). + std::array status; + // The statistics per request + std::vector req_stats; + // The statistics per client + std::vector client_stats; + // The number of UDP datagrams received. + size_t udp_dgram_recv; + // The number of UDP datagrams sent. + size_t udp_dgram_sent; +}; + +enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED }; + +// This type tells whether the client is in warmup phase or not or is over +enum class Phase { + INITIAL_IDLE, // Initial idle state before warm-up phase + WARM_UP, // Warm up phase when no measurements are done + MAIN_DURATION, // Main measurement phase; if timing-based + // test is not run, this is the default phase + DURATION_OVER // This phase occurs after the measurements are over +}; + +struct Client; + +// We use reservoir sampling method +struct Sampling { + // maximum number of samples + size_t max_samples; + // number of samples seen, including discarded samples. + size_t n; +}; + +struct Worker { + MemchunkPool mcpool; + std::mt19937 randgen; + Stats stats; + Sampling request_times_smp; + Sampling client_smp; + struct ev_loop *loop; + SSL_CTX *ssl_ctx; + Config *config; + size_t progress_interval; + uint32_t id; + bool tls_info_report_done; + bool app_info_report_done; + size_t nconns_made; + // number of clients this worker handles + size_t nclients; + // number of requests each client issues + size_t nreqs_per_client; + // at most nreqs_rem clients get an extra request + size_t nreqs_rem; + size_t rate; + // maximum number of samples in this worker thread + size_t max_samples; + ev_timer timeout_watcher; + // The next client ID this worker assigns + uint32_t next_client_id; + // Keeps track of the current phase (for timing-based experiment) for the + // worker + Phase current_phase; + // We need to keep track of the clients in order to stop them when needed + std::vector clients; + // This is only active when there is not a bounded number of requests + // specified + ev_timer duration_watcher; + ev_timer warmup_watcher; + + Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients, + size_t rate, size_t max_samples, Config *config); + ~Worker(); + Worker(Worker &&o) = default; + void run(); + void sample_req_stat(RequestStat *req_stat); + void sample_client_stat(ClientStat *cstat); + void report_progress(); + void report_rate_progress(); + // This function calls the destructors of all the clients. + void stop_all_clients(); + // This function frees a client from the list of clients for this Worker. + void free_client(Client *); +}; + +struct Stream { + RequestStat req_stat; + int status_success; + Stream(); +}; + +struct Client { + DefaultMemchunks wb; + std::unordered_map streams; + ClientStat cstat; + std::unique_ptr session; + ev_io wev; + ev_io rev; + std::function readfn, writefn; + Worker *worker; + SSL *ssl; +#ifdef ENABLE_HTTP3 + struct { + ngtcp2_crypto_conn_ref conn_ref; + ev_timer pkt_timer; + ngtcp2_conn *conn; + ngtcp2_ccerr last_error; + bool close_requested; + FILE *qlog_file; + + struct { + bool send_blocked; + size_t num_blocked; + size_t num_blocked_sent; + struct { + Address remote_addr; + const uint8_t *data; + size_t datalen; + size_t gso_size; + } blocked[2]; + std::unique_ptr data; + } tx; + } quic; +#endif // ENABLE_HTTP3 + ev_timer request_timeout_watcher; + addrinfo *next_addr; + // Address for the current address. When try_new_connection() is + // used and current_addr is not nullptr, it is used instead of + // trying next address though next_addr. To try new address, set + // nullptr to current_addr before calling connect(). + addrinfo *current_addr; + size_t reqidx; + ClientState state; + // The number of requests this client has to issue. + size_t req_todo; + // The number of requests left to issue + size_t req_left; + // The number of requests currently have started, but not abandoned + // or finished. + size_t req_inflight; + // The number of requests this client has issued so far. + size_t req_started; + // The number of requests this client has done so far. + size_t req_done; + // The client id per worker + uint32_t id; + int fd; + Address local_addr; + ev_timer conn_active_watcher; + ev_timer conn_inactivity_watcher; + std::string selected_proto; + bool new_connection_requested; + // true if the current connection will be closed, and no more new + // request cannot be processed. + bool final; + // rps_watcher is a timer to invoke callback periodically to + // generate a new request. + ev_timer rps_watcher; + // The timestamp that starts the period which contributes to the + // next request generation. + std::chrono::steady_clock::time_point rps_duration_started; + // The number of requests allowed by rps, but limited by stream + // concurrency. + size_t rps_req_pending; + // The number of in-flight streams. req_inflight has similar value + // but it only measures requests made during Phase::MAIN_DURATION. + // rps_req_inflight measures the number of requests in all phases, + // and it is only used if --rps is given. + size_t rps_req_inflight; + + enum { ERR_CONNECT_FAIL = -100 }; + + Client(uint32_t id, Worker *worker, size_t req_todo); + ~Client(); + int make_socket(addrinfo *addr); + int connect(); + void disconnect(); + void fail(); + // Call this function when do_read() returns -1. This function + // tries to connect to the remote host again if it is requested. If + // so, this function returns 0, and this object should be retained. + // Otherwise, this function returns -1, and this object should be + // deleted. + int try_again_or_fail(); + void timeout(); + void restart_timeout(); + int submit_request(); + void process_request_failure(); + void process_timedout_streams(); + void process_abandoned_streams(); + void report_tls_info(); + void report_app_info(); + void terminate_session(); + // Asks client to create new connection, instead of just fail. + void try_new_connection(); + + int do_read(); + int do_write(); + + // low-level I/O callback functions called by do_read/do_write + int connected(); + int read_clear(); + int write_clear(); + int tls_handshake(); + int read_tls(); + int write_tls(); + + int on_read(const uint8_t *data, size_t len); + int on_write(); + + int connection_made(); + + void on_request(int32_t stream_id); + void on_header(int32_t stream_id, const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen); + void on_status_code(int32_t stream_id, uint16_t status); + // |success| == true means that the request/response was exchanged + // |successfully, but it does not mean response carried successful + // |HTTP status code. + void on_stream_close(int32_t stream_id, bool success, bool final = false); + // Returns RequestStat for |stream_id|. This function must be + // called after on_request(stream_id), and before + // on_stream_close(stream_id, ...). Otherwise, this will return + // nullptr. + RequestStat *get_req_stat(int32_t stream_id); + + void record_request_time(RequestStat *req_stat); + void record_connect_start_time(); + void record_connect_time(); + void record_ttfb(); + void clear_connect_times(); + void record_client_start_time(); + void record_client_end_time(); + + void signal_write(); + +#ifdef ENABLE_HTTP3 + // QUIC + int quic_init(const sockaddr *local_addr, socklen_t local_addrlen, + const sockaddr *remote_addr, socklen_t remote_addrlen); + void quic_free(); + int read_quic(); + int write_quic(); + int write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data, + size_t datalen, size_t gso_size); + void on_send_blocked(const ngtcp2_addr &remote_addr, const uint8_t *data, + size_t datalen, size_t gso_size); + int send_blocked_packet(); + void quic_close_connection(); + + int quic_handshake_completed(); + int quic_recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen); + int quic_acked_stream_data_offset(int64_t stream_id, size_t datalen); + int quic_stream_close(int64_t stream_id, uint64_t app_error_code); + int quic_stream_reset(int64_t stream_id, uint64_t app_error_code); + int quic_stream_stop_sending(int64_t stream_id, uint64_t app_error_code); + int quic_extend_max_local_streams(); + int quic_extend_max_stream_data(int64_t stream_id); + + int quic_write_client_handshake(ngtcp2_encryption_level level, + const uint8_t *data, size_t datalen); + int quic_pkt_timeout(); + void quic_restart_pkt_timer(); + void quic_write_qlog(const void *data, size_t datalen); + int quic_make_http3_session(); +#endif // ENABLE_HTTP3 +}; + +} // namespace h2load + +#endif // H2LOAD_H diff --git a/lib/nghttp2/src/h2load_http1_session.cc b/lib/nghttp2/src/h2load_http1_session.cc new file mode 100644 index 00000000000..6bdcd0471e6 --- /dev/null +++ b/lib/nghttp2/src/h2load_http1_session.cc @@ -0,0 +1,306 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 British Broadcasting Corporation + * + * 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 "h2load_http1_session.h" + +#include +#include + +#include "h2load.h" +#include "util.h" +#include "template.h" + +#include +#include + +using namespace nghttp2; + +namespace h2load { + +namespace { +// HTTP response message begin +int htp_msg_begincb(llhttp_t *htp) { + auto session = static_cast(htp->data); + + if (session->stream_resp_counter_ > session->stream_req_counter_) { + return -1; + } + + return 0; +} +} // namespace + +namespace { +// HTTP response status code +int htp_statuscb(llhttp_t *htp, const char *at, size_t length) { + auto session = static_cast(htp->data); + auto client = session->get_client(); + + if (htp->status_code / 100 == 1) { + return 0; + } + + client->on_status_code(session->stream_resp_counter_, htp->status_code); + + return 0; +} +} // namespace + +namespace { +// HTTP response message complete +int htp_msg_completecb(llhttp_t *htp) { + auto session = static_cast(htp->data); + auto client = session->get_client(); + + if (htp->status_code / 100 == 1) { + return 0; + } + + client->final = llhttp_should_keep_alive(htp) == 0; + auto req_stat = client->get_req_stat(session->stream_resp_counter_); + + assert(req_stat); + + auto config = client->worker->config; + if (req_stat->data_offset >= config->data_length) { + client->on_stream_close(session->stream_resp_counter_, true, client->final); + } + + session->stream_resp_counter_ += 2; + + if (client->final) { + session->stream_req_counter_ = session->stream_resp_counter_; + + // Connection is going down. If we have still request to do, + // create new connection and keep on doing the job. + if (client->req_left) { + client->try_new_connection(); + } + + return HPE_PAUSED; + } + + return 0; +} +} // namespace + +namespace { +int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) { + auto session = static_cast(htp->data); + auto client = session->get_client(); + + client->worker->stats.bytes_head += len; + client->worker->stats.bytes_head_decomp += len; + return 0; +} +} // namespace + +namespace { +int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) { + auto session = static_cast(htp->data); + auto client = session->get_client(); + + client->worker->stats.bytes_head += len; + client->worker->stats.bytes_head_decomp += len; + return 0; +} +} // namespace + +namespace { +int htp_hdrs_completecb(llhttp_t *htp) { + return !http2::expect_response_body(htp->status_code); +} +} // namespace + +namespace { +int htp_body_cb(llhttp_t *htp, const char *data, size_t len) { + auto session = static_cast(htp->data); + auto client = session->get_client(); + + client->record_ttfb(); + client->worker->stats.bytes_body += len; + + return 0; +} +} // namespace + +namespace { +constexpr llhttp_settings_t htp_hooks = { + htp_msg_begincb, // llhttp_cb on_message_begin; + nullptr, // llhttp_data_cb on_url; + htp_statuscb, // llhttp_data_cb on_status; + nullptr, // llhttp_data_cb on_method; + nullptr, // llhttp_data_cb on_version; + htp_hdr_keycb, // llhttp_data_cb on_header_field; + htp_hdr_valcb, // llhttp_data_cb on_header_value; + nullptr, // llhttp_data_cb on_chunk_extension_name; + nullptr, // llhttp_data_cb on_chunk_extension_value; + htp_hdrs_completecb, // llhttp_cb on_headers_complete; + htp_body_cb, // llhttp_data_cb on_body; + htp_msg_completecb, // llhttp_cb on_message_complete; + nullptr, // llhttp_cb on_url_complete; + nullptr, // llhttp_cb on_status_complete; + nullptr, // llhttp_cb on_method_complete; + nullptr, // llhttp_cb on_version_complete; + nullptr, // llhttp_cb on_header_field_complete; + nullptr, // llhttp_cb on_header_value_complete; + nullptr, // llhttp_cb on_chunk_extension_name_complete; + nullptr, // llhttp_cb on_chunk_extension_value_complete; + nullptr, // llhttp_cb on_chunk_header; + nullptr, // llhttp_cb on_chunk_complete; + nullptr, // llhttp_cb on_reset; +}; +} // namespace + +Http1Session::Http1Session(Client *client) + : stream_req_counter_(1), + stream_resp_counter_(1), + client_(client), + htp_(), + complete_(false) { + llhttp_init(&htp_, HTTP_RESPONSE, &htp_hooks); + htp_.data = this; +} + +Http1Session::~Http1Session() {} + +void Http1Session::on_connect() { client_->signal_write(); } + +int Http1Session::submit_request() { + auto config = client_->worker->config; + const auto &req = config->h1reqs[client_->reqidx]; + client_->reqidx++; + + if (client_->reqidx == config->h1reqs.size()) { + client_->reqidx = 0; + } + + client_->on_request(stream_req_counter_); + + auto req_stat = client_->get_req_stat(stream_req_counter_); + + client_->record_request_time(req_stat); + client_->wb.append(req); + + if (config->data_fd == -1 || config->data_length == 0) { + // increment for next request + stream_req_counter_ += 2; + + return 0; + } + + return on_write(); +} + +int Http1Session::on_read(const uint8_t *data, size_t len) { + auto htperr = + llhttp_execute(&htp_, reinterpret_cast(data), len); + auto nread = htperr == HPE_OK + ? len + : static_cast(reinterpret_cast( + llhttp_get_error_pos(&htp_)) - + data); + + if (client_->worker->config->verbose) { + std::cout.write(reinterpret_cast(data), nread); + } + + if (htperr == HPE_PAUSED) { + // pause is done only when connection: close is requested + return -1; + } + + if (htperr != HPE_OK) { + std::cerr << "[ERROR] HTTP parse error: " + << "(" << llhttp_errno_name(htperr) << ") " + << llhttp_get_error_reason(&htp_) << std::endl; + return -1; + } + + return 0; +} + +int Http1Session::on_write() { + if (complete_) { + return -1; + } + + auto config = client_->worker->config; + auto req_stat = client_->get_req_stat(stream_req_counter_); + if (!req_stat) { + return 0; + } + + if (req_stat->data_offset < config->data_length) { + auto req_stat = client_->get_req_stat(stream_req_counter_); + auto &wb = client_->wb; + + // TODO unfortunately, wb has no interface to use with read(2) + // family functions. + std::array buf; + + ssize_t nread; + while ((nread = pread(config->data_fd, buf.data(), buf.size(), + req_stat->data_offset)) == -1 && + errno == EINTR) + ; + + if (nread == -1) { + return -1; + } + + req_stat->data_offset += nread; + + wb.append(buf.data(), nread); + + if (client_->worker->config->verbose) { + std::cout << "[send " << nread << " byte(s)]" << std::endl; + } + + if (req_stat->data_offset == config->data_length) { + // increment for next request + stream_req_counter_ += 2; + + if (stream_resp_counter_ == stream_req_counter_) { + // Response has already been received + client_->on_stream_close(stream_resp_counter_ - 2, true, + client_->final); + } + } + } + + return 0; +} + +void Http1Session::terminate() { complete_ = true; } + +Client *Http1Session::get_client() { return client_; } + +size_t Http1Session::max_concurrent_streams() { + auto config = client_->worker->config; + + return config->data_fd == -1 ? config->max_concurrent_streams : 1; +} + +} // namespace h2load diff --git a/lib/nghttp2/src/h2load_http1_session.h b/lib/nghttp2/src/h2load_http1_session.h new file mode 100644 index 00000000000..cc10f50fb1f --- /dev/null +++ b/lib/nghttp2/src/h2load_http1_session.h @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 British Broadcasting Corporation + * + * 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 H2LOAD_HTTP1_SESSION_H +#define H2LOAD_HTTP1_SESSION_H + +#include "h2load_session.h" + +#include + +#include "llhttp.h" + +namespace h2load { + +struct Client; + +class Http1Session : public Session { +public: + Http1Session(Client *client); + virtual ~Http1Session(); + virtual void on_connect(); + virtual int submit_request(); + virtual int on_read(const uint8_t *data, size_t len); + virtual int on_write(); + virtual void terminate(); + virtual size_t max_concurrent_streams(); + Client *get_client(); + int32_t stream_req_counter_; + int32_t stream_resp_counter_; + +private: + Client *client_; + llhttp_t htp_; + bool complete_; +}; + +} // namespace h2load + +#endif // H2LOAD_HTTP1_SESSION_H diff --git a/lib/nghttp2/src/h2load_http2_session.cc b/lib/nghttp2/src/h2load_http2_session.cc new file mode 100644 index 00000000000..9cafa0e384a --- /dev/null +++ b/lib/nghttp2/src/h2load_http2_session.cc @@ -0,0 +1,313 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "h2load_http2_session.h" + +#include +#include +#include + +#include "h2load.h" +#include "util.h" +#include "template.h" + +using namespace nghttp2; + +namespace h2load { + +Http2Session::Http2Session(Client *client) + : client_(client), session_(nullptr) {} + +Http2Session::~Http2Session() { nghttp2_session_del(session_); } + +namespace { +int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + auto client = static_cast(user_data); + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { + return 0; + } + client->on_header(frame->hd.stream_id, name, namelen, value, valuelen); + client->worker->stats.bytes_head_decomp += namelen + valuelen; + + if (client->worker->config->verbose) { + std::cout << "[stream_id=" << frame->hd.stream_id << "] "; + std::cout.write(reinterpret_cast(name), namelen); + std::cout << ": "; + std::cout.write(reinterpret_cast(value), valuelen); + std::cout << "\n"; + } + + return 0; +} +} // namespace + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + auto client = static_cast(user_data); + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { + return 0; + } + client->worker->stats.bytes_head += + frame->hd.length - frame->headers.padlen - + ((frame->hd.flags & NGHTTP2_FLAG_PRIORITY) ? 5 : 0); + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + client->record_ttfb(); + } + return 0; +} +} // namespace + +namespace { +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + auto client = static_cast(user_data); + client->record_ttfb(); + client->worker->stats.bytes_body += len; + return 0; +} +} // namespace + +namespace { +int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + auto client = static_cast(user_data); + client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR); + + return 0; +} +} // namespace + +namespace { +int before_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + auto client = static_cast(user_data); + auto req_stat = client->get_req_stat(frame->hd.stream_id); + assert(req_stat); + client->record_request_time(req_stat); + + return 0; +} +} // namespace + +namespace { +ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, + uint8_t *buf, size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) { + auto client = static_cast(user_data); + auto config = client->worker->config; + auto req_stat = client->get_req_stat(stream_id); + assert(req_stat); + ssize_t nread; + while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) == + -1 && + errno == EINTR) + ; + + if (nread == -1) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + req_stat->data_offset += nread; + + if (req_stat->data_offset == config->data_length) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return nread; + } + + if (req_stat->data_offset > config->data_length || nread == 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + return nread; +} + +} // namespace + +namespace { +ssize_t send_callback(nghttp2_session *session, const uint8_t *data, + size_t length, int flags, void *user_data) { + auto client = static_cast(user_data); + auto &wb = client->wb; + + if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) { + return NGHTTP2_ERR_WOULDBLOCK; + } + + return wb.append(data, length); +} +} // namespace + +void Http2Session::on_connect() { + int rv; + + // This is required with --disable-assert. + (void)rv; + + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + + auto callbacks_deleter = defer(nghttp2_session_callbacks_del, callbacks); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + + nghttp2_session_callbacks_set_before_frame_send_callback( + callbacks, before_frame_send_callback); + + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + + nghttp2_option *opt; + + rv = nghttp2_option_new(&opt); + assert(rv == 0); + + auto config = client_->worker->config; + + if (config->encoder_header_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { + nghttp2_option_set_max_deflate_dynamic_table_size( + opt, config->encoder_header_table_size); + } + + nghttp2_session_client_new2(&session_, callbacks, client_, opt); + + nghttp2_option_del(opt); + + std::array iv; + size_t niv = 2; + iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[0].value = 0; + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = (1 << config->window_bits) - 1; + + if (config->header_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { + iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[niv].value = config->header_table_size; + ++niv; + } + if (config->max_frame_size != 16_k) { + iv[niv].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[niv].value = config->max_frame_size; + ++niv; + } + + rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(), niv); + + assert(rv == 0); + + auto connection_window = (1 << config->connection_window_bits) - 1; + nghttp2_session_set_local_window_size(session_, NGHTTP2_FLAG_NONE, 0, + connection_window); + + client_->signal_write(); +} + +int Http2Session::submit_request() { + if (nghttp2_session_check_request_allowed(session_) == 0) { + return -1; + } + + auto config = client_->worker->config; + auto &nva = config->nva[client_->reqidx++]; + + if (client_->reqidx == config->nva.size()) { + client_->reqidx = 0; + } + + nghttp2_data_provider prd{{0}, file_read_callback}; + + auto stream_id = + nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(), + config->data_fd == -1 ? nullptr : &prd, nullptr); + if (stream_id < 0) { + return -1; + } + + client_->on_request(stream_id); + + return 0; +} + +int Http2Session::on_read(const uint8_t *data, size_t len) { + auto rv = nghttp2_session_mem_recv(session_, data, len); + if (rv < 0) { + return -1; + } + + assert(static_cast(rv) == len); + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) { + return -1; + } + + client_->signal_write(); + + return 0; +} + +int Http2Session::on_write() { + auto rv = nghttp2_session_send(session_); + if (rv != 0) { + return -1; + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) { + return -1; + } + + return 0; +} + +void Http2Session::terminate() { + nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR); +} + +size_t Http2Session::max_concurrent_streams() { + return (size_t)client_->worker->config->max_concurrent_streams; +} + +} // namespace h2load diff --git a/lib/nghttp2/src/h2load_http2_session.h b/lib/nghttp2/src/h2load_http2_session.h new file mode 100644 index 00000000000..1b9b9f773a9 --- /dev/null +++ b/lib/nghttp2/src/h2load_http2_session.h @@ -0,0 +1,54 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 H2LOAD_HTTP2_SESSION_H +#define H2LOAD_HTTP2_SESSION_H + +#include "h2load_session.h" + +#include + +namespace h2load { + +struct Client; + +class Http2Session : public Session { +public: + Http2Session(Client *client); + virtual ~Http2Session(); + virtual void on_connect(); + virtual int submit_request(); + virtual int on_read(const uint8_t *data, size_t len); + virtual int on_write(); + virtual void terminate(); + virtual size_t max_concurrent_streams(); + +private: + Client *client_; + nghttp2_session *session_; +}; + +} // namespace h2load + +#endif // H2LOAD_HTTP2_SESSION_H diff --git a/lib/nghttp2/src/h2load_http3_session.cc b/lib/nghttp2/src/h2load_http3_session.cc new file mode 100644 index 00000000000..f4779beb5f3 --- /dev/null +++ b/lib/nghttp2/src/h2load_http3_session.cc @@ -0,0 +1,457 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 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 "h2load_http3_session.h" + +#include + +#include + +#include "h2load.h" + +namespace h2load { + +Http3Session::Http3Session(Client *client) + : client_(client), conn_(nullptr), npending_request_(0), reqidx_(0) {} + +Http3Session::~Http3Session() { nghttp3_conn_del(conn_); } + +void Http3Session::on_connect() {} + +int Http3Session::submit_request() { + if (npending_request_) { + ++npending_request_; + return 0; + } + + auto config = client_->worker->config; + reqidx_ = client_->reqidx; + + if (++client_->reqidx == config->nva.size()) { + client_->reqidx = 0; + } + + auto stream_id = submit_request_internal(); + if (stream_id < 0) { + if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) { + ++npending_request_; + return 0; + } + return -1; + } + + 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) { + auto s = static_cast(user_data); + + s->read_data(vec, veccnt, pflags); + + return 1; +} +} // namespace + +void Http3Session::read_data(nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags) { + assert(veccnt > 0); + + auto config = client_->worker->config; + + vec[0].base = config->data; + vec[0].len = config->data_length; + *pflags |= NGHTTP3_DATA_FLAG_EOF; +} + +int64_t Http3Session::submit_request_internal() { + int rv; + int64_t stream_id; + + auto config = client_->worker->config; + auto &nva = config->nva[reqidx_]; + + rv = ngtcp2_conn_open_bidi_stream(client_->quic.conn, &stream_id, nullptr); + if (rv != 0) { + return rv; + } + + nghttp3_data_reader dr{}; + dr.read_data = h2load::read_data; + + rv = nghttp3_conn_submit_request( + conn_, stream_id, reinterpret_cast(nva.data()), nva.size(), + config->data_fd == -1 ? nullptr : &dr, nullptr); + if (rv != 0) { + return rv; + } + + client_->on_request(stream_id); + auto req_stat = client_->get_req_stat(stream_id); + assert(req_stat); + client_->record_request_time(req_stat); + + return stream_id; +} + +int Http3Session::on_read(const uint8_t *data, size_t len) { return -1; } + +int Http3Session::on_write() { return -1; } + +void Http3Session::terminate() {} + +size_t Http3Session::max_concurrent_streams() { + return (size_t)client_->worker->config->max_concurrent_streams; +} + +namespace { +int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, + void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + if (s->stream_close(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) { + if (!ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id)); + ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1); + } + client_->on_stream_close(stream_id, app_error_code == NGHTTP3_H3_NO_ERROR); + return 0; +} + +namespace { +int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, + size_t datalen, void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + s->recv_data(stream_id, data, datalen); + return 0; +} +} // namespace + +void Http3Session::recv_data(int64_t stream_id, const uint8_t *data, + size_t datalen) { + client_->record_ttfb(); + client_->worker->stats.bytes_body += datalen; + consume(stream_id, datalen); +} + +namespace { +int deferred_consume(nghttp3_conn *conn, int64_t stream_id, size_t nconsumed, + void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + s->consume(stream_id, nconsumed); + return 0; +} +} // namespace + +void Http3Session::consume(int64_t stream_id, size_t nconsumed) { + ngtcp2_conn_extend_max_stream_offset(client_->quic.conn, stream_id, + nconsumed); + ngtcp2_conn_extend_max_offset(client_->quic.conn, nconsumed); +} + +namespace { +int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + auto s = static_cast(user_data); + s->begin_headers(stream_id); + return 0; +} +} // namespace + +void Http3Session::begin_headers(int64_t stream_id) { + auto payloadlen = nghttp3_conn_get_frame_payload_left(conn_, stream_id); + assert(payloadlen > 0); + + client_->worker->stats.bytes_head += payloadlen; +} + +namespace { +int 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) { + auto s = static_cast(user_data); + auto k = nghttp3_rcbuf_get_buf(name); + auto v = nghttp3_rcbuf_get_buf(value); + s->recv_header(stream_id, &k, &v); + return 0; +} +} // namespace + +void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name, + const nghttp3_vec *value) { + client_->on_header(stream_id, name->base, name->len, value->base, value->len); + client_->worker->stats.bytes_head_decomp += name->len + value->len; +} + +namespace { +int stop_sending(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, + void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + if (s->stop_sending(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Http3Session::stop_sending(int64_t stream_id, uint64_t app_error_code) { + auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, 0, stream_id, + app_error_code); + if (rv != 0) { + std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +namespace { +int reset_stream(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code, + void *user_data, void *stream_user_data) { + auto s = static_cast(user_data); + if (s->reset_stream(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Http3Session::reset_stream(int64_t stream_id, uint64_t app_error_code) { + auto rv = ngtcp2_conn_shutdown_stream_write(client_->quic.conn, 0, stream_id, + app_error_code); + if (rv != 0) { + std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) { + auto rv = nghttp3_conn_close_stream(conn_, stream_id, app_error_code); + switch (rv) { + case 0: + return 0; + case NGHTTP3_ERR_STREAM_NOT_FOUND: + if (!ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id)); + ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1); + } + return 0; + default: + return -1; + } +} + +int Http3Session::shutdown_stream_read(int64_t stream_id) { + auto rv = nghttp3_conn_shutdown_stream_read(conn_, stream_id); + if (rv != 0) { + return -1; + } + return 0; +} + +int Http3Session::extend_max_local_streams() { + auto config = client_->worker->config; + + for (; npending_request_; --npending_request_) { + auto stream_id = submit_request_internal(); + if (stream_id < 0) { + if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) { + return 0; + } + return -1; + } + + if (++reqidx_ == config->nva.size()) { + reqidx_ = 0; + } + } + + return 0; +} + +int Http3Session::init_conn() { + int rv; + + assert(conn_ == nullptr); + + if (ngtcp2_conn_get_streams_uni_left(client_->quic.conn) < 3) { + return -1; + } + + nghttp3_callbacks callbacks{ + nullptr, // acked_stream_data + h2load::stream_close, + h2load::recv_data, + h2load::deferred_consume, + h2load::begin_headers, + h2load::recv_header, + nullptr, // end_headers + nullptr, // begin_trailers + h2load::recv_header, + nullptr, // end_trailers + h2load::stop_sending, + nullptr, // end_stream + h2load::reset_stream, + nullptr, // shutdown + }; + + auto config = client_->worker->config; + + nghttp3_settings settings; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = config->header_table_size; + settings.qpack_blocked_streams = 100; + + auto mem = nghttp3_mem_default(); + + rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this); + if (rv != 0) { + std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + int64_t ctrl_stream_id; + + rv = + ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, nullptr); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id); + if (rv != 0) { + std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + int64_t qpack_enc_stream_id, qpack_dec_stream_id; + + rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id, + nullptr); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id, + nullptr); + if (rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id, + qpack_dec_stream_id); + if (rv != 0) { + std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +ssize_t Http3Session::read_stream(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + auto nconsumed = nghttp3_conn_read_stream( + conn_, 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_ccerr_set_application_error( + &client_->quic.last_error, + nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr, 0); + return -1; + } + return nconsumed; +} + +ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin, + nghttp3_vec *vec, size_t veccnt) { + auto sveccnt = + nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt); + if (sveccnt < 0) { + ngtcp2_ccerr_set_application_error( + &client_->quic.last_error, + nghttp3_err_infer_quic_app_error_code(sveccnt), nullptr, 0); + return -1; + } + return sveccnt; +} + +void Http3Session::block_stream(int64_t stream_id) { + nghttp3_conn_block_stream(conn_, stream_id); +} + +int Http3Session::unblock_stream(int64_t stream_id) { + if (nghttp3_conn_unblock_stream(conn_, stream_id) != 0) { + return -1; + } + + return 0; +} + +void Http3Session::shutdown_stream_write(int64_t stream_id) { + nghttp3_conn_shutdown_stream_write(conn_, stream_id); +} + +int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) { + auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen); + if (rv != 0) { + ngtcp2_ccerr_set_application_error( + &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv), + nullptr, 0); + return -1; + } + return 0; +} + +int Http3Session::add_ack_offset(int64_t stream_id, size_t datalen) { + auto rv = nghttp3_conn_add_ack_offset(conn_, stream_id, datalen); + if (rv != 0) { + ngtcp2_ccerr_set_application_error( + &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv), + nullptr, 0); + return -1; + } + return 0; +} + +} // namespace h2load diff --git a/lib/nghttp2/src/h2load_http3_session.h b/lib/nghttp2/src/h2load_http3_session.h new file mode 100644 index 00000000000..89c7ca00f5e --- /dev/null +++ b/lib/nghttp2/src/h2load_http3_session.h @@ -0,0 +1,83 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 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 H2LOAD_HTTP3_SESSION_H +#define H2LOAD_HTTP3_SESSION_H + +#include "h2load_session.h" + +#include + +namespace h2load { + +struct Client; + +class Http3Session : public Session { +public: + Http3Session(Client *client); + virtual ~Http3Session(); + virtual void on_connect(); + virtual int submit_request(); + virtual int on_read(const uint8_t *data, size_t len); + virtual int on_write(); + virtual void terminate(); + virtual size_t max_concurrent_streams(); + + int init_conn(); + int stream_close(int64_t stream_id, uint64_t app_error_code); + void recv_data(int64_t stream_id, const uint8_t *data, size_t datalen); + void consume(int64_t stream_id, size_t nconsumed); + void begin_headers(int64_t stream_id); + void recv_header(int64_t stream_id, const nghttp3_vec *name, + const nghttp3_vec *value); + 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 close_stream(int64_t stream_id, uint64_t app_error_code); + int shutdown_stream_read(int64_t stream_id); + int extend_max_local_streams(); + int64_t submit_request_internal(); + + ssize_t read_stream(uint32_t flags, int64_t stream_id, const uint8_t *data, + size_t datalen); + ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec, + size_t veccnt); + void block_stream(int64_t stream_id); + int unblock_stream(int64_t stream_id); + void shutdown_stream_write(int64_t stream_id); + int add_write_offset(int64_t stream_id, size_t ndatalen); + int add_ack_offset(int64_t stream_id, size_t datalen); + + void read_data(nghttp3_vec *vec, size_t veccnt, uint32_t *pflags); + +private: + Client *client_; + nghttp3_conn *conn_; + size_t npending_request_; + size_t reqidx_; +}; + +} // namespace h2load + +#endif // H2LOAD_HTTP3_SESSION_H diff --git a/lib/nghttp2/src/h2load_quic.cc b/lib/nghttp2/src/h2load_quic.cc new file mode 100644 index 00000000000..65fbc109cb1 --- /dev/null +++ b/lib/nghttp2/src/h2load_quic.cc @@ -0,0 +1,844 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 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 "h2load_quic.h" + +#include + +#include + +#ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# include +#endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +#ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +# include +#endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL + +#include +#include + +#include "h2load_http3_session.h" + +namespace h2load { + +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast(user_data); + + if (c->quic_handshake_completed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::quic_handshake_completed() { return connection_made(); } + +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 c = static_cast(user_data); + if (c->quic_recv_stream_data(flags, stream_id, data, datalen) != 0) { + // TODO Better to do this gracefully rather than + // NGTCP2_ERR_CALLBACK_FAILURE. Perhaps, call + // ngtcp2_conn_write_application_close() ? + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::quic_recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + if (worker->current_phase == Phase::MAIN_DURATION) { + worker->stats.bytes_total += datalen; + } + + auto s = static_cast(session.get()); + auto nconsumed = s->read_stream(flags, stream_id, data, datalen); + if (nconsumed == -1) { + return -1; + } + + ngtcp2_conn_extend_max_stream_offset(quic.conn, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(quic.conn, nconsumed); + + return 0; +} + +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(user_data); + if (c->quic_acked_stream_data_offset(stream_id, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::quic_acked_stream_data_offset(int64_t stream_id, size_t datalen) { + auto s = static_cast(session.get()); + if (s->add_ack_offset(stream_id, datalen) != 0) { + return -1; + } + return 0; +} + +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(user_data); + + if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + if (c->quic_stream_close(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::quic_stream_close(int64_t stream_id, uint64_t app_error_code) { + auto s = static_cast(session.get()); + if (s->close_stream(stream_id, app_error_code) != 0) { + return -1; + } + return 0; +} + +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(user_data); + if (c->quic_stream_reset(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::quic_stream_reset(int64_t stream_id, uint64_t app_error_code) { + auto s = static_cast(session.get()); + if (s->shutdown_stream_read(stream_id) != 0) { + 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 c = static_cast(user_data); + if (c->quic_stream_stop_sending(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::quic_stream_stop_sending(int64_t stream_id, + uint64_t app_error_code) { + auto s = static_cast(session.get()); + if (s->shutdown_stream_read(stream_id) != 0) { + return -1; + } + return 0; +} + +namespace { +int extend_max_local_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, + void *user_data) { + auto c = static_cast(user_data); + + if (c->quic_extend_max_local_streams() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::quic_extend_max_local_streams() { + auto s = static_cast(session.get()); + if (s->extend_max_local_streams() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + 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 c = static_cast(user_data); + + if (c->quic_extend_max_stream_data(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::quic_extend_max_stream_data(int64_t stream_id) { + auto s = static_cast(session.get()); + if (s->unblock_stream(stream_id) != 0) { + return -1; + } + return 0; +} + +namespace { +int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, + size_t cidlen, void *user_data) { + if (RAND_bytes(cid->data, 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; +} +} // namespace + +namespace { +void debug_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"); +} +} // namespace + +namespace { +int generate_cid(ngtcp2_cid &dest) { + dest.datalen = 8; + + if (RAND_bytes(dest.data, dest.datalen) != 1) { + return -1; + } + + return 0; +} +} // namespace + +namespace { +ngtcp2_tstamp quic_timestamp() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} +} // namespace + +// qlog write callback -- excerpted from ngtcp2/examples/client_base.cc +namespace { +void qlog_write_cb(void *user_data, uint32_t flags, const void *data, + size_t datalen) { + auto c = static_cast(user_data); + c->quic_write_qlog(data, datalen); +} +} // namespace + +void Client::quic_write_qlog(const void *data, size_t datalen) { + assert(quic.qlog_file != nullptr); + fwrite(data, 1, datalen, quic.qlog_file); +} + +namespace { +void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { + util::random_bytes(dest, dest + destlen, + *static_cast(rand_ctx->native_handle)); +} +} // namespace + +namespace { +int recv_rx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level, + void *user_data) { + if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) { + return 0; + } + + auto c = static_cast(user_data); + + if (c->quic_make_http3_session() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::quic_make_http3_session() { + auto s = std::make_unique(this); + if (s->init_conn() == -1) { + return -1; + } + session = std::move(s); + + return 0; +} + +namespace { +ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { + auto c = static_cast(conn_ref->user_data); + return c->quic.conn; +} +} // namespace + +int Client::quic_init(const sockaddr *local_addr, socklen_t local_addrlen, + const sockaddr *remote_addr, socklen_t remote_addrlen) { + int rv; + + if (!ssl) { + ssl = SSL_new(worker->ssl_ctx); + + quic.conn_ref.get_conn = get_conn; + quic.conn_ref.user_data = this; + + SSL_set_app_data(ssl, &quic.conn_ref); + SSL_set_connect_state(ssl); + SSL_set_quic_use_legacy_codepoint(ssl, 0); + } + + auto callbacks = ngtcp2_callbacks{ + ngtcp2_crypto_client_initial_cb, + nullptr, // recv_client_initial + ngtcp2_crypto_recv_crypto_data_cb, + h2load::handshake_completed, + nullptr, // recv_version_negotiation + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + h2load::recv_stream_data, + h2load::acked_stream_data_offset, + nullptr, // stream_open + h2load::stream_close, + nullptr, // recv_stateless_reset + ngtcp2_crypto_recv_retry_cb, + h2load::extend_max_local_streams_bidi, + nullptr, // extend_max_local_streams_uni + h2load::rand, + get_new_connection_id, + nullptr, // remove_connection_id + ngtcp2_crypto_update_key_cb, + nullptr, // path_validation + nullptr, // select_preferred_addr + h2load::stream_reset, + nullptr, // extend_max_remote_streams_bidi + nullptr, // extend_max_remote_streams_uni + h2load::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, + h2load::stream_stop_sending, + nullptr, // version_negotiation + h2load::recv_rx_key, + nullptr, // recv_tx_key + }; + + ngtcp2_cid scid, dcid; + if (generate_cid(scid) != 0) { + return -1; + } + if (generate_cid(dcid) != 0) { + return -1; + } + + auto config = worker->config; + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + if (config->verbose) { + settings.log_printf = debug_log_printf; + } + settings.initial_ts = quic_timestamp(); + settings.rand_ctx.native_handle = &worker->randgen; + if (!config->qlog_file_base.empty()) { + assert(quic.qlog_file == nullptr); + auto path = config->qlog_file_base; + path += '.'; + path += util::utos(worker->id); + path += '.'; + path += util::utos(id); + path += ".sqlog"; + quic.qlog_file = fopen(path.c_str(), "w"); + if (quic.qlog_file == nullptr) { + std::cerr << "Failed to open a qlog file: " << path << std::endl; + return -1; + } + settings.qlog_write = qlog_write_cb; + } + 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; + } + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + auto max_stream_data = + std::min((1 << 26) - 1, (1 << config->window_bits) - 1); + params.initial_max_stream_data_bidi_local = max_stream_data; + params.initial_max_stream_data_uni = max_stream_data; + params.initial_max_data = (1 << config->connection_window_bits) - 1; + params.initial_max_streams_bidi = 0; + params.initial_max_streams_uni = 100; + params.max_idle_timeout = 30 * NGTCP2_SECONDS; + + auto path = ngtcp2_path{ + { + const_cast(local_addr), + local_addrlen, + }, + { + const_cast(remote_addr), + remote_addrlen, + }, + }; + + assert(config->npn_list.size()); + + uint32_t quic_version; + + if (config->npn_list[0] == NGHTTP3_ALPN_H3) { + quic_version = NGTCP2_PROTO_VER_V1; + } else { + quic_version = NGTCP2_PROTO_VER_MIN; + } + + rv = ngtcp2_conn_client_new(&quic.conn, &dcid, &scid, &path, quic_version, + &callbacks, &settings, ¶ms, nullptr, this); + if (rv != 0) { + return -1; + } + + ngtcp2_conn_set_tls_native_handle(quic.conn, ssl); + + return 0; +} + +void Client::quic_free() { + ngtcp2_conn_del(quic.conn); + if (quic.qlog_file != nullptr) { + fclose(quic.qlog_file); + quic.qlog_file = nullptr; + } +} + +void Client::quic_close_connection() { + if (!quic.conn) { + return; + } + + std::array buf; + ngtcp2_path_storage ps; + ngtcp2_path_storage_zero(&ps); + + auto nwrite = ngtcp2_conn_write_connection_close( + quic.conn, &ps.path, nullptr, buf.data(), buf.size(), &quic.last_error, + quic_timestamp()); + + if (nwrite <= 0) { + return; + } + + write_udp(reinterpret_cast(ps.path.remote.addr), + ps.path.remote.addrlen, buf.data(), nwrite, 0); +} + +int Client::quic_write_client_handshake(ngtcp2_encryption_level level, + const uint8_t *data, size_t datalen) { + int rv; + + assert(level < 2); + + rv = ngtcp2_conn_submit_crypto_data(quic.conn, level, data, datalen); + if (rv != 0) { + std::cerr << "ngtcp2_conn_submit_crypto_data: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto c = static_cast(w->data); + + if (c->quic_pkt_timeout() != 0) { + c->fail(); + c->worker->free_client(c); + delete c; + return; + } +} + +int Client::quic_pkt_timeout() { + int rv; + auto now = quic_timestamp(); + + rv = ngtcp2_conn_handle_expiry(quic.conn, now); + if (rv != 0) { + ngtcp2_ccerr_set_liberr(&quic.last_error, rv, nullptr, 0); + return -1; + } + + return write_quic(); +} + +void Client::quic_restart_pkt_timer() { + auto expiry = ngtcp2_conn_get_expiry(quic.conn); + auto now = quic_timestamp(); + auto t = expiry > now ? static_cast(expiry - now) / NGTCP2_SECONDS + : 1e-9; + quic.pkt_timer.repeat = t; + ev_timer_again(worker->loop, &quic.pkt_timer); +} + +int Client::read_quic() { + std::array buf; + sockaddr_union su; + int rv; + 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(uint16_t))]; + msg.msg_control = msg_ctrl; + + auto ts = quic_timestamp(); + + for (;;) { + msg.msg_namelen = sizeof(su); + msg.msg_controllen = sizeof(msg_ctrl); + + auto nread = recvmsg(fd, &msg, 0); + if (nread == -1) { + return 0; + } + + auto gso_size = util::msghdr_get_udp_gro(&msg); + if (gso_size == 0) { + gso_size = static_cast(nread); + } + + assert(quic.conn); + + ++worker->stats.udp_dgram_recv; + + auto path = ngtcp2_path{ + { + &local_addr.su.sa, + static_cast(local_addr.len), + }, + { + &su.sa, + msg.msg_namelen, + }, + }; + + auto data = buf.data(); + + for (;;) { + auto datalen = std::min(static_cast(nread), gso_size); + + ++pktcnt; + + rv = ngtcp2_conn_read_pkt(quic.conn, &path, &pi, data, datalen, ts); + if (rv != 0) { + if (!quic.last_error.error_code) { + if (rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_ccerr_set_tls_alert(&quic.last_error, + ngtcp2_conn_get_tls_alert(quic.conn), + nullptr, 0); + } else { + ngtcp2_ccerr_set_liberr(&quic.last_error, rv, nullptr, 0); + } + } + + return -1; + } + + nread -= datalen; + if (nread == 0) { + break; + } + + data += datalen; + } + + if (pktcnt >= 100) { + break; + } + } + + return 0; +} + +int Client::write_quic() { + int rv; + + ev_io_stop(worker->loop, &wev); + + if (quic.close_requested) { + return -1; + } + + if (quic.tx.send_blocked) { + rv = send_blocked_packet(); + if (rv != 0) { + return -1; + } + + if (quic.tx.send_blocked) { + return 0; + } + } + + std::array vec; + size_t pktcnt = 0; + auto max_udp_payload_size = + ngtcp2_conn_get_max_tx_udp_payload_size(quic.conn); +#ifdef UDP_SEGMENT + auto path_max_udp_payload_size = + ngtcp2_conn_get_path_max_tx_udp_payload_size(quic.conn); +#endif // UDP_SEGMENT + auto max_pktcnt = + ngtcp2_conn_get_send_quantum(quic.conn) / max_udp_payload_size; + uint8_t *bufpos = quic.tx.data.get(); + ngtcp2_path_storage ps; + size_t gso_size = 0; + + ngtcp2_path_storage_zero(&ps); + + auto s = static_cast(session.get()); + auto ts = quic_timestamp(); + + for (;;) { + int64_t stream_id = -1; + int fin = 0; + ssize_t sveccnt = 0; + + if (session && ngtcp2_conn_get_max_data_left(quic.conn)) { + sveccnt = s->write_stream(stream_id, fin, vec.data(), vec.size()); + if (sveccnt == -1) { + return -1; + } + } + + ngtcp2_ssize ndatalen; + auto v = vec.data(); + auto vcnt = static_cast(sveccnt); + + uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + if (fin) { + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + + auto nwrite = ngtcp2_conn_writev_stream( + quic.conn, &ps.path, nullptr, bufpos, max_udp_payload_size, &ndatalen, + flags, stream_id, reinterpret_cast(v), vcnt, ts); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + assert(ndatalen == -1); + s->block_stream(stream_id); + continue; + case NGTCP2_ERR_STREAM_SHUT_WR: + assert(ndatalen == -1); + s->shutdown_stream_write(stream_id); + continue; + case NGTCP2_ERR_WRITE_MORE: + assert(ndatalen >= 0); + if (s->add_write_offset(stream_id, ndatalen) != 0) { + return -1; + } + continue; + } + + ngtcp2_ccerr_set_liberr(&quic.last_error, nwrite, nullptr, 0); + return -1; + } else if (ndatalen >= 0 && s->add_write_offset(stream_id, ndatalen) != 0) { + return -1; + } + + quic_restart_pkt_timer(); + + if (nwrite == 0) { + if (bufpos - quic.tx.data.get()) { + auto data = quic.tx.data.get(); + auto datalen = bufpos - quic.tx.data.get(); + rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, + datalen, gso_size); + if (rv == 1) { + on_send_blocked(ps.path.remote, data, datalen, gso_size); + signal_write(); + return 0; + } + } + return 0; + } + + bufpos += nwrite; + +#ifdef UDP_SEGMENT + if (worker->config->no_udp_gso) { +#endif // UDP_SEGMENT + auto data = quic.tx.data.get(); + auto datalen = bufpos - quic.tx.data.get(); + rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen, + 0); + if (rv == 1) { + on_send_blocked(ps.path.remote, data, datalen, 0); + signal_write(); + return 0; + } + + if (++pktcnt == max_pktcnt) { + signal_write(); + return 0; + } + + bufpos = quic.tx.data.get(); + +#ifdef UDP_SEGMENT + continue; + } +#endif // UDP_SEGMENT + +#ifdef UDP_SEGMENT + if (pktcnt == 0) { + gso_size = nwrite; + } else if (static_cast(nwrite) > gso_size || + (gso_size > path_max_udp_payload_size && + static_cast(nwrite) != gso_size)) { + auto data = quic.tx.data.get(); + auto datalen = bufpos - quic.tx.data.get() - nwrite; + rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen, + gso_size); + if (rv == 1) { + on_send_blocked(ps.path.remote, data, datalen, gso_size); + on_send_blocked(ps.path.remote, bufpos - nwrite, nwrite, 0); + } else { + auto data = bufpos - nwrite; + rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, + nwrite, 0); + if (rv == 1) { + on_send_blocked(ps.path.remote, data, nwrite, 0); + } + } + + signal_write(); + return 0; + } + + // Assume that the path does not change. + if (++pktcnt == max_pktcnt || static_cast(nwrite) < gso_size) { + auto data = quic.tx.data.get(); + auto datalen = bufpos - quic.tx.data.get(); + rv = write_udp(ps.path.remote.addr, ps.path.remote.addrlen, data, datalen, + gso_size); + if (rv == 1) { + on_send_blocked(ps.path.remote, data, datalen, gso_size); + } + signal_write(); + return 0; + } +#endif // UDP_SEGMENT + } +} + +void Client::on_send_blocked(const ngtcp2_addr &remote_addr, + const uint8_t *data, size_t datalen, + size_t gso_size) { + assert(quic.tx.num_blocked || !quic.tx.send_blocked); + assert(quic.tx.num_blocked < 2); + + quic.tx.send_blocked = true; + + auto &p = quic.tx.blocked[quic.tx.num_blocked++]; + + memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen); + + p.remote_addr.len = remote_addr.addrlen; + p.data = data; + p.datalen = datalen; + p.gso_size = gso_size; +} + +int Client::send_blocked_packet() { + int rv; + + assert(quic.tx.send_blocked); + + for (; quic.tx.num_blocked_sent < quic.tx.num_blocked; + ++quic.tx.num_blocked_sent) { + auto &p = quic.tx.blocked[quic.tx.num_blocked_sent]; + + rv = write_udp(&p.remote_addr.su.sa, p.remote_addr.len, p.data, p.datalen, + p.gso_size); + if (rv == 1) { + signal_write(); + + return 0; + } + } + + quic.tx.send_blocked = false; + quic.tx.num_blocked = 0; + quic.tx.num_blocked_sent = 0; + + return 0; +} + +} // namespace h2load diff --git a/lib/nghttp2/src/h2load_quic.h b/lib/nghttp2/src/h2load_quic.h new file mode 100644 index 00000000000..225f00fb297 --- /dev/null +++ b/lib/nghttp2/src/h2load_quic.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 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 H2LOAD_QUIC_H +#define H2LOAD_QUIC_H + +#include "nghttp2_config.h" + +#include + +#include "h2load.h" + +namespace h2load { +void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents); +} // namespace h2load + +#endif // H2LOAD_QUIC_H diff --git a/lib/nghttp2/src/h2load_session.h b/lib/nghttp2/src/h2load_session.h new file mode 100644 index 00000000000..ab3b8ec80a7 --- /dev/null +++ b/lib/nghttp2/src/h2load_session.h @@ -0,0 +1,59 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 H2LOAD_SESSION_H +#define H2LOAD_SESSION_H + +#include "nghttp2_config.h" + +#include + +#include + +#include "h2load.h" + +namespace h2load { + +class Session { +public: + virtual ~Session() {} + // Called when the connection was made. + virtual void on_connect() = 0; + // Called when one request must be issued. + virtual int submit_request() = 0; + // Called when incoming bytes are available. The subclass has to + // return the number of bytes read. + virtual int on_read(const uint8_t *data, size_t len) = 0; + // Called when write is available. Returns 0 on success, otherwise + // return -1. + virtual int on_write() = 0; + // Called when the underlying session must be terminated. + virtual void terminate() = 0; + // Return the maximum concurrency per connection + virtual size_t max_concurrent_streams() = 0; +}; + +} // namespace h2load + +#endif // H2LOAD_SESSION_H diff --git a/lib/nghttp2/src/http-parser.patch b/lib/nghttp2/src/http-parser.patch new file mode 100644 index 00000000000..ef809409da8 --- /dev/null +++ b/lib/nghttp2/src/http-parser.patch @@ -0,0 +1,28 @@ +commit a143133d43420ef89e4ba0d84c73998863cf9f81 +Author: Tatsuhiro Tsujikawa +Date: Wed Jul 11 18:46:00 2012 +0900 + + Use http_parser for tunneling connection transparently + +diff --git a/examples/http-parser/http_parser.c b/examples/http-parser/http_parser.c +index 0c11eb8..610da57 100644 +--- a/examples/http-parser/http_parser.c ++++ b/examples/http-parser/http_parser.c +@@ -1627,9 +1627,14 @@ size_t http_parser_execute (http_parser *parser, + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { +- parser->state = NEW_MESSAGE(); +- CALLBACK_NOTIFY(message_complete); +- return (p - data) + 1; ++ /* We want to use http_parser for tunneling connection ++ transparently */ ++ /* Read body until EOF */ ++ parser->state = s_body_identity_eof; ++ break; ++ /* parser->state = NEW_MESSAGE(); */ ++ /* CALLBACK_NOTIFY(message_complete); */ ++ /* return (p - data) + 1; */ + } + + if (parser->flags & F_SKIPBODY) { diff --git a/lib/nghttp2/src/http2.cc b/lib/nghttp2/src/http2.cc new file mode 100644 index 00000000000..acbb4e4a525 --- /dev/null +++ b/lib/nghttp2/src/http2.cc @@ -0,0 +1,2096 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "http2.h" + +#include "llhttp.h" + +#include "util.h" + +namespace nghttp2 { + +namespace http2 { + +StringRef get_reason_phrase(unsigned int status_code) { + switch (status_code) { + case 100: + return StringRef::from_lit("Continue"); + case 101: + return StringRef::from_lit("Switching Protocols"); + case 103: + return StringRef::from_lit("Early Hints"); + case 200: + return StringRef::from_lit("OK"); + case 201: + return StringRef::from_lit("Created"); + case 202: + return StringRef::from_lit("Accepted"); + case 203: + return StringRef::from_lit("Non-Authoritative Information"); + case 204: + return StringRef::from_lit("No Content"); + case 205: + return StringRef::from_lit("Reset Content"); + case 206: + return StringRef::from_lit("Partial Content"); + case 300: + return StringRef::from_lit("Multiple Choices"); + case 301: + return StringRef::from_lit("Moved Permanently"); + case 302: + return StringRef::from_lit("Found"); + case 303: + return StringRef::from_lit("See Other"); + case 304: + return StringRef::from_lit("Not Modified"); + case 305: + return StringRef::from_lit("Use Proxy"); + // case 306: return StringRef::from_lit("(Unused)"); + case 307: + return StringRef::from_lit("Temporary Redirect"); + case 308: + return StringRef::from_lit("Permanent Redirect"); + case 400: + return StringRef::from_lit("Bad Request"); + case 401: + return StringRef::from_lit("Unauthorized"); + case 402: + return StringRef::from_lit("Payment Required"); + case 403: + return StringRef::from_lit("Forbidden"); + case 404: + return StringRef::from_lit("Not Found"); + case 405: + return StringRef::from_lit("Method Not Allowed"); + case 406: + return StringRef::from_lit("Not Acceptable"); + case 407: + return StringRef::from_lit("Proxy Authentication Required"); + case 408: + return StringRef::from_lit("Request Timeout"); + case 409: + return StringRef::from_lit("Conflict"); + case 410: + return StringRef::from_lit("Gone"); + case 411: + return StringRef::from_lit("Length Required"); + case 412: + return StringRef::from_lit("Precondition Failed"); + case 413: + return StringRef::from_lit("Payload Too Large"); + case 414: + return StringRef::from_lit("URI Too Long"); + case 415: + return StringRef::from_lit("Unsupported Media Type"); + case 416: + return StringRef::from_lit("Requested Range Not Satisfiable"); + case 417: + return StringRef::from_lit("Expectation Failed"); + case 421: + return StringRef::from_lit("Misdirected Request"); + case 425: + // https://tools.ietf.org/html/rfc8470 + return StringRef::from_lit("Too Early"); + case 426: + return StringRef::from_lit("Upgrade Required"); + case 428: + return StringRef::from_lit("Precondition Required"); + case 429: + return StringRef::from_lit("Too Many Requests"); + case 431: + return StringRef::from_lit("Request Header Fields Too Large"); + case 451: + return StringRef::from_lit("Unavailable For Legal Reasons"); + case 500: + return StringRef::from_lit("Internal Server Error"); + case 501: + return StringRef::from_lit("Not Implemented"); + case 502: + return StringRef::from_lit("Bad Gateway"); + case 503: + return StringRef::from_lit("Service Unavailable"); + case 504: + return StringRef::from_lit("Gateway Timeout"); + case 505: + return StringRef::from_lit("HTTP Version Not Supported"); + case 511: + return StringRef::from_lit("Network Authentication Required"); + default: + return StringRef{}; + } +} + +StringRef stringify_status(BlockAllocator &balloc, unsigned int status_code) { + switch (status_code) { + case 100: + return StringRef::from_lit("100"); + case 101: + return StringRef::from_lit("101"); + case 103: + return StringRef::from_lit("103"); + case 200: + return StringRef::from_lit("200"); + case 201: + return StringRef::from_lit("201"); + case 202: + return StringRef::from_lit("202"); + case 203: + return StringRef::from_lit("203"); + case 204: + return StringRef::from_lit("204"); + case 205: + return StringRef::from_lit("205"); + case 206: + return StringRef::from_lit("206"); + case 300: + return StringRef::from_lit("300"); + case 301: + return StringRef::from_lit("301"); + case 302: + return StringRef::from_lit("302"); + case 303: + return StringRef::from_lit("303"); + case 304: + return StringRef::from_lit("304"); + case 305: + return StringRef::from_lit("305"); + // case 306: return StringRef::from_lit("306"); + case 307: + return StringRef::from_lit("307"); + case 308: + return StringRef::from_lit("308"); + case 400: + return StringRef::from_lit("400"); + case 401: + return StringRef::from_lit("401"); + case 402: + return StringRef::from_lit("402"); + case 403: + return StringRef::from_lit("403"); + case 404: + return StringRef::from_lit("404"); + case 405: + return StringRef::from_lit("405"); + case 406: + return StringRef::from_lit("406"); + case 407: + return StringRef::from_lit("407"); + case 408: + return StringRef::from_lit("408"); + case 409: + return StringRef::from_lit("409"); + case 410: + return StringRef::from_lit("410"); + case 411: + return StringRef::from_lit("411"); + case 412: + return StringRef::from_lit("412"); + case 413: + return StringRef::from_lit("413"); + case 414: + return StringRef::from_lit("414"); + case 415: + return StringRef::from_lit("415"); + case 416: + return StringRef::from_lit("416"); + case 417: + return StringRef::from_lit("417"); + case 421: + return StringRef::from_lit("421"); + case 426: + return StringRef::from_lit("426"); + case 428: + return StringRef::from_lit("428"); + case 429: + return StringRef::from_lit("429"); + case 431: + return StringRef::from_lit("431"); + case 451: + return StringRef::from_lit("451"); + case 500: + return StringRef::from_lit("500"); + case 501: + return StringRef::from_lit("501"); + case 502: + return StringRef::from_lit("502"); + case 503: + return StringRef::from_lit("503"); + case 504: + return StringRef::from_lit("504"); + case 505: + return StringRef::from_lit("505"); + case 511: + return StringRef::from_lit("511"); + default: + return util::make_string_ref_uint(balloc, status_code); + } +} + +void capitalize(DefaultMemchunks *buf, const StringRef &s) { + buf->append(util::upcase(s[0])); + for (size_t i = 1; i < s.size(); ++i) { + if (s[i - 1] == '-') { + buf->append(util::upcase(s[i])); + } else { + buf->append(s[i]); + } + } +} + +bool lws(const char *value) { + for (; *value; ++value) { + switch (*value) { + case '\t': + case ' ': + continue; + default: + return false; + } + } + return true; +} + +void copy_url_component(std::string &dest, const http_parser_url *u, int field, + const char *url) { + if (u->field_set & (1 << field)) { + dest.assign(url + u->field_data[field].off, u->field_data[field].len); + } +} + +Headers::value_type to_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index, int32_t token) { + return Header(std::string(reinterpret_cast(name), namelen), + std::string(reinterpret_cast(value), valuelen), + no_index, token); +} + +void add_header(Headers &nva, const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, bool no_index, + int32_t token) { + if (valuelen > 0) { + size_t i, j; + for (i = 0; i < valuelen && (value[i] == ' ' || value[i] == '\t'); ++i) + ; + for (j = valuelen - 1; j > i && (value[j] == ' ' || value[j] == '\t'); --j) + ; + value += i; + valuelen -= i + (valuelen - j - 1); + } + nva.push_back(to_header(name, namelen, value, valuelen, no_index, token)); +} + +const Headers::value_type *get_header(const Headers &nva, const char *name) { + const Headers::value_type *res = nullptr; + for (auto &nv : nva) { + if (nv.name == name) { + res = &nv; + } + } + return res; +} + +bool non_empty_value(const HeaderRefs::value_type *nv) { + return nv && !nv->value.empty(); +} + +namespace { +nghttp2_nv make_nv_internal(const std::string &name, const std::string &value, + bool no_index, uint8_t nv_flags) { + uint8_t flags; + + flags = + nv_flags | (no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE); + + return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), + value.size(), flags}; +} +} // namespace + +namespace { +nghttp2_nv make_nv_internal(const StringRef &name, const StringRef &value, + bool no_index, uint8_t nv_flags) { + uint8_t flags; + + flags = + nv_flags | (no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE); + + return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), + value.size(), flags}; +} +} // namespace + +nghttp2_nv make_nv(const std::string &name, const std::string &value, + bool no_index) { + return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE); +} + +nghttp2_nv make_nv(const StringRef &name, const StringRef &value, + bool no_index) { + return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE); +} + +nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool no_index) { + return make_nv_internal(name, value, no_index, + NGHTTP2_NV_FLAG_NO_COPY_NAME | + NGHTTP2_NV_FLAG_NO_COPY_VALUE); +} + +nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value, + bool no_index) { + return make_nv_internal(name, value, no_index, + NGHTTP2_NV_FLAG_NO_COPY_NAME | + NGHTTP2_NV_FLAG_NO_COPY_VALUE); +} + +namespace { +void copy_headers_to_nva_internal(std::vector &nva, + const HeaderRefs &headers, uint8_t nv_flags, + uint32_t flags) { + auto it_forwarded = std::end(headers); + auto it_xff = std::end(headers); + auto it_xfp = std::end(headers); + auto it_via = std::end(headers); + + for (auto it = std::begin(headers); it != std::end(headers); ++it) { + auto kv = &(*it); + if (kv->name.empty() || kv->name[0] == ':') { + continue; + } + switch (kv->token) { + case HD_COOKIE: + case HD_CONNECTION: + case HD_HOST: + case HD_HTTP2_SETTINGS: + case HD_KEEP_ALIVE: + case HD_PROXY_CONNECTION: + case HD_SERVER: + case HD_TE: + case HD_TRANSFER_ENCODING: + case HD_UPGRADE: + continue; + case HD_EARLY_DATA: + if (flags & HDOP_STRIP_EARLY_DATA) { + continue; + } + break; + case HD_SEC_WEBSOCKET_ACCEPT: + if (flags & HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) { + continue; + } + break; + case HD_SEC_WEBSOCKET_KEY: + if (flags & HDOP_STRIP_SEC_WEBSOCKET_KEY) { + continue; + } + break; + case HD_FORWARDED: + if (flags & HDOP_STRIP_FORWARDED) { + continue; + } + + if (it_forwarded == std::end(headers)) { + it_forwarded = it; + continue; + } + + kv = &(*it_forwarded); + it_forwarded = it; + break; + case HD_X_FORWARDED_FOR: + if (flags & HDOP_STRIP_X_FORWARDED_FOR) { + continue; + } + + if (it_xff == std::end(headers)) { + it_xff = it; + continue; + } + + kv = &(*it_xff); + it_xff = it; + break; + case HD_X_FORWARDED_PROTO: + if (flags & HDOP_STRIP_X_FORWARDED_PROTO) { + continue; + } + + if (it_xfp == std::end(headers)) { + it_xfp = it; + continue; + } + + kv = &(*it_xfp); + it_xfp = it; + break; + case HD_VIA: + if (flags & HDOP_STRIP_VIA) { + continue; + } + + if (it_via == std::end(headers)) { + it_via = it; + continue; + } + + kv = &(*it_via); + it_via = it; + break; + } + nva.push_back( + make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags)); + } +} +} // namespace + +void copy_headers_to_nva(std::vector &nva, + const HeaderRefs &headers, uint32_t flags) { + copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE, flags); +} + +void copy_headers_to_nva_nocopy(std::vector &nva, + const HeaderRefs &headers, uint32_t flags) { + copy_headers_to_nva_internal( + nva, headers, + NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE, flags); +} + +void build_http1_headers_from_headers(DefaultMemchunks *buf, + const HeaderRefs &headers, + uint32_t flags) { + auto it_forwarded = std::end(headers); + auto it_xff = std::end(headers); + auto it_xfp = std::end(headers); + auto it_via = std::end(headers); + + for (auto it = std::begin(headers); it != std::end(headers); ++it) { + auto kv = &(*it); + if (kv->name.empty() || kv->name[0] == ':') { + continue; + } + switch (kv->token) { + case HD_CONNECTION: + case HD_COOKIE: + case HD_HOST: + case HD_HTTP2_SETTINGS: + case HD_KEEP_ALIVE: + case HD_PROXY_CONNECTION: + case HD_SERVER: + case HD_UPGRADE: + continue; + case HD_EARLY_DATA: + if (flags & HDOP_STRIP_EARLY_DATA) { + continue; + } + break; + case HD_TRANSFER_ENCODING: + if (flags & HDOP_STRIP_TRANSFER_ENCODING) { + continue; + } + break; + case HD_FORWARDED: + if (flags & HDOP_STRIP_FORWARDED) { + continue; + } + + if (it_forwarded == std::end(headers)) { + it_forwarded = it; + continue; + } + + kv = &(*it_forwarded); + it_forwarded = it; + break; + case HD_X_FORWARDED_FOR: + if (flags & HDOP_STRIP_X_FORWARDED_FOR) { + continue; + } + + if (it_xff == std::end(headers)) { + it_xff = it; + continue; + } + + kv = &(*it_xff); + it_xff = it; + break; + case HD_X_FORWARDED_PROTO: + if (flags & HDOP_STRIP_X_FORWARDED_PROTO) { + continue; + } + + if (it_xfp == std::end(headers)) { + it_xfp = it; + continue; + } + + kv = &(*it_xfp); + it_xfp = it; + break; + case HD_VIA: + if (flags & HDOP_STRIP_VIA) { + continue; + } + + if (it_via == std::end(headers)) { + it_via = it; + continue; + } + + kv = &(*it_via); + it_via = it; + break; + } + capitalize(buf, kv->name); + buf->append(": "); + buf->append(kv->value); + buf->append("\r\n"); + } +} + +int32_t determine_window_update_transmission(nghttp2_session *session, + int32_t stream_id) { + int32_t recv_length, window_size; + if (stream_id == 0) { + recv_length = nghttp2_session_get_effective_recv_data_length(session); + window_size = nghttp2_session_get_effective_local_window_size(session); + } else { + recv_length = nghttp2_session_get_stream_effective_recv_data_length( + session, stream_id); + window_size = nghttp2_session_get_stream_effective_local_window_size( + session, stream_id); + } + if (recv_length != -1 && window_size != -1) { + if (recv_length >= window_size / 2) { + return recv_length; + } + } + return -1; +} + +void dump_nv(FILE *out, const char **nv) { + for (size_t i = 0; nv[i]; i += 2) { + fprintf(out, "%s: %s\n", nv[i], nv[i + 1]); + } + fputc('\n', out); + fflush(out); +} + +void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen) { + auto end = nva + nvlen; + for (; nva != end; ++nva) { + fprintf(out, "%s: %s\n", nva->name, nva->value); + } + fputc('\n', out); + fflush(out); +} + +void dump_nv(FILE *out, const Headers &nva) { + for (auto &nv : nva) { + fprintf(out, "%s: %s\n", nv.name.c_str(), nv.value.c_str()); + } + fputc('\n', out); + fflush(out); +} + +void dump_nv(FILE *out, const HeaderRefs &nva) { + for (auto &nv : nva) { + fprintf(out, "%s: %s\n", nv.name.c_str(), nv.value.c_str()); + } + fputc('\n', out); + fflush(out); +} + +void erase_header(HeaderRef *hd) { + hd->name = StringRef{}; + hd->token = -1; +} + +StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri, + const http_parser_url &u, + const StringRef &match_host, + const StringRef &request_authority, + const StringRef &upstream_scheme) { + // We just rewrite scheme and authority. + if ((u.field_set & (1 << UF_HOST)) == 0) { + return StringRef{}; + } + auto field = &u.field_data[UF_HOST]; + if (!util::starts_with(std::begin(match_host), std::end(match_host), + &uri[field->off], &uri[field->off] + field->len) || + (match_host.size() != field->len && match_host[field->len] != ':')) { + return StringRef{}; + } + + auto len = 0; + if (!request_authority.empty()) { + len += upstream_scheme.size() + str_size("://") + request_authority.size(); + } + + if (u.field_set & (1 << UF_PATH)) { + field = &u.field_data[UF_PATH]; + len += field->len; + } + + if (u.field_set & (1 << UF_QUERY)) { + field = &u.field_data[UF_QUERY]; + len += 1 + field->len; + } + + if (u.field_set & (1 << UF_FRAGMENT)) { + field = &u.field_data[UF_FRAGMENT]; + len += 1 + field->len; + } + + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + + if (!request_authority.empty()) { + p = std::copy(std::begin(upstream_scheme), std::end(upstream_scheme), p); + p = util::copy_lit(p, "://"); + p = std::copy(std::begin(request_authority), std::end(request_authority), + p); + } + if (u.field_set & (1 << UF_PATH)) { + field = &u.field_data[UF_PATH]; + p = std::copy_n(&uri[field->off], field->len, p); + } + if (u.field_set & (1 << UF_QUERY)) { + field = &u.field_data[UF_QUERY]; + *p++ = '?'; + p = std::copy_n(&uri[field->off], field->len, p); + } + if (u.field_set & (1 << UF_FRAGMENT)) { + field = &u.field_data[UF_FRAGMENT]; + *p++ = '#'; + p = std::copy_n(&uri[field->off], field->len, p); + } + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +int parse_http_status_code(const StringRef &src) { + if (src.size() != 3) { + return -1; + } + + int status = 0; + for (auto c : src) { + if (!isdigit(c)) { + return -1; + } + status *= 10; + status += c - '0'; + } + + if (status < 100) { + return -1; + } + + return status; +} + +int lookup_token(const StringRef &name) { + return lookup_token(name.byte(), name.size()); +} + +// This function was generated by genheaderfunc.py. Inspired by h2o +// header lookup. https://github.com/h2o/h2o +int lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) { + case 2: + switch (name[1]) { + case 'e': + if (util::streq_l("t", name, 1)) { + return HD_TE; + } + break; + } + break; + case 3: + switch (name[2]) { + case 'a': + if (util::streq_l("vi", name, 2)) { + return HD_VIA; + } + break; + } + break; + case 4: + switch (name[3]) { + case 'e': + if (util::streq_l("dat", name, 3)) { + return HD_DATE; + } + break; + case 'k': + if (util::streq_l("lin", name, 3)) { + return HD_LINK; + } + break; + case 't': + if (util::streq_l("hos", name, 3)) { + return HD_HOST; + } + break; + } + break; + case 5: + switch (name[4]) { + case 'h': + if (util::streq_l(":pat", name, 4)) { + return HD__PATH; + } + break; + case 't': + if (util::streq_l(":hos", name, 4)) { + return HD__HOST; + } + break; + } + break; + case 6: + switch (name[5]) { + case 'e': + if (util::streq_l("cooki", name, 5)) { + return HD_COOKIE; + } + break; + case 'r': + if (util::streq_l("serve", name, 5)) { + return HD_SERVER; + } + break; + case 't': + if (util::streq_l("expec", name, 5)) { + return HD_EXPECT; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'c': + if (util::streq_l("alt-sv", name, 6)) { + return HD_ALT_SVC; + } + break; + case 'd': + if (util::streq_l(":metho", name, 6)) { + return HD__METHOD; + } + break; + case 'e': + if (util::streq_l(":schem", name, 6)) { + return HD__SCHEME; + } + if (util::streq_l("upgrad", name, 6)) { + return HD_UPGRADE; + } + break; + case 'r': + if (util::streq_l("traile", name, 6)) { + return HD_TRAILER; + } + break; + case 's': + if (util::streq_l(":statu", name, 6)) { + return HD__STATUS; + } + break; + } + break; + case 8: + switch (name[7]) { + case 'n': + if (util::streq_l("locatio", name, 7)) { + return HD_LOCATION; + } + break; + case 'y': + if (util::streq_l("priorit", name, 7)) { + return HD_PRIORITY; + } + break; + } + break; + case 9: + switch (name[8]) { + case 'd': + if (util::streq_l("forwarde", name, 8)) { + return HD_FORWARDED; + } + break; + case 'l': + if (util::streq_l(":protoco", name, 8)) { + return HD__PROTOCOL; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'a': + if (util::streq_l("early-dat", name, 9)) { + return HD_EARLY_DATA; + } + break; + case 'e': + if (util::streq_l("keep-aliv", name, 9)) { + return HD_KEEP_ALIVE; + } + break; + case 'n': + if (util::streq_l("connectio", name, 9)) { + return HD_CONNECTION; + } + break; + case 't': + if (util::streq_l("user-agen", name, 9)) { + return HD_USER_AGENT; + } + break; + case 'y': + if (util::streq_l(":authorit", name, 9)) { + return HD__AUTHORITY; + } + break; + } + break; + case 12: + switch (name[11]) { + case 'e': + if (util::streq_l("content-typ", name, 11)) { + return HD_CONTENT_TYPE; + } + break; + } + break; + case 13: + switch (name[12]) { + case 'l': + if (util::streq_l("cache-contro", name, 12)) { + return HD_CACHE_CONTROL; + } + break; + } + break; + case 14: + switch (name[13]) { + case 'h': + if (util::streq_l("content-lengt", name, 13)) { + return HD_CONTENT_LENGTH; + } + break; + case 's': + if (util::streq_l("http2-setting", name, 13)) { + return HD_HTTP2_SETTINGS; + } + break; + } + break; + case 15: + switch (name[14]) { + case 'e': + if (util::streq_l("accept-languag", name, 14)) { + return HD_ACCEPT_LANGUAGE; + } + break; + case 'g': + if (util::streq_l("accept-encodin", name, 14)) { + return HD_ACCEPT_ENCODING; + } + break; + case 'r': + if (util::streq_l("x-forwarded-fo", name, 14)) { + return HD_X_FORWARDED_FOR; + } + break; + } + break; + case 16: + switch (name[15]) { + case 'n': + if (util::streq_l("proxy-connectio", name, 15)) { + return HD_PROXY_CONNECTION; + } + break; + } + break; + case 17: + switch (name[16]) { + case 'e': + if (util::streq_l("if-modified-sinc", name, 16)) { + return HD_IF_MODIFIED_SINCE; + } + break; + case 'g': + if (util::streq_l("transfer-encodin", name, 16)) { + return HD_TRANSFER_ENCODING; + } + break; + case 'o': + if (util::streq_l("x-forwarded-prot", name, 16)) { + return HD_X_FORWARDED_PROTO; + } + break; + case 'y': + if (util::streq_l("sec-websocket-ke", name, 16)) { + return HD_SEC_WEBSOCKET_KEY; + } + break; + } + break; + case 20: + switch (name[19]) { + case 't': + if (util::streq_l("sec-websocket-accep", name, 19)) { + return HD_SEC_WEBSOCKET_ACCEPT; + } + break; + } + break; + } + return -1; +} + +void init_hdidx(HeaderIndex &hdidx) { + std::fill(std::begin(hdidx), std::end(hdidx), -1); +} + +void index_header(HeaderIndex &hdidx, int32_t token, size_t idx) { + if (token == -1) { + return; + } + assert(token < HD_MAXIDX); + hdidx[token] = idx; +} + +const Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token, + const Headers &nva) { + auto i = hdidx[token]; + if (i == -1) { + return nullptr; + } + return &nva[i]; +} + +Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token, + Headers &nva) { + auto i = hdidx[token]; + if (i == -1) { + return nullptr; + } + return &nva[i]; +} + +namespace { +template InputIt skip_lws(InputIt first, InputIt last) { + for (; first != last; ++first) { + switch (*first) { + case ' ': + case '\t': + continue; + default: + return first; + } + } + return first; +} +} // namespace + +namespace { +template +InputIt skip_to_next_field(InputIt first, InputIt last) { + for (; first != last; ++first) { + switch (*first) { + case ' ': + case '\t': + case ',': + continue; + default: + return first; + } + } + return first; +} +} // namespace + +namespace { +// Skip to the right dquote ('"'), handling backslash escapes. +// Returns |last| if input is not terminated with '"'. +template +InputIt skip_to_right_dquote(InputIt first, InputIt last) { + for (; first != last;) { + switch (*first) { + case '"': + return first; + // quoted-pair + case '\\': + ++first; + if (first == last) { + return first; + } + + switch (*first) { + case '\t': + case ' ': + break; + default: + if ((0x21 <= *first && *first <= 0x7e) /* VCHAR */ || + (0x80 <= *first && *first <= 0xff) /* obs-text */) { + break; + } + + return last; + } + + break; + // qdtext + case '\t': + case ' ': + case '!': + break; + default: + if ((0x23 <= *first && *first <= 0x5b) || + (0x5d <= *first && *first <= 0x7e)) { + break; + } + + return last; + } + ++first; + } + return first; +} +} // namespace + +namespace { +// Returns true if link-param does not match pattern |pat| of length +// |patlen| or it has empty value (""). |pat| should be parmname +// followed by "=". +bool check_link_param_empty(const char *first, const char *last, + const char *pat, size_t patlen) { + if (first + patlen <= last) { + if (std::equal(pat, pat + patlen, first, util::CaseCmp())) { + // we only accept URI if pat is followd by "" (e.g., + // loadpolicy="") here. + if (first + patlen + 2 <= last) { + if (*(first + patlen) != '"' || *(first + patlen + 1) != '"') { + return false; + } + } else { + // here we got invalid production (anchor=") or anchor=? + return false; + } + } + } + return true; +} +} // namespace + +namespace { +// Returns true if link-param consists of only parmname, and it +// matches string [pat, pat + patlen). +bool check_link_param_without_value(const char *first, const char *last, + const char *pat, size_t patlen) { + if (first + patlen > last) { + return false; + } + + if (first + patlen == last) { + return std::equal(pat, pat + patlen, first, util::CaseCmp()); + } + + switch (*(first + patlen)) { + case ';': + case ',': + return std::equal(pat, pat + patlen, first, util::CaseCmp()); + } + + return false; +} +} // namespace + +namespace { +std::pair +parse_next_link_header_once(const char *first, const char *last) { + first = skip_to_next_field(first, last); + if (first == last || *first != '<') { + return {{StringRef{}}, last}; + } + auto url_first = ++first; + first = std::find(first, last, '>'); + if (first == last) { + return {{StringRef{}}, first}; + } + auto url_last = first++; + if (first == last) { + return {{StringRef{}}, first}; + } + // we expect ';' or ',' here + switch (*first) { + case ',': + return {{StringRef{}}, ++first}; + case ';': + ++first; + break; + default: + return {{StringRef{}}, last}; + } + + auto ok = false; + auto ign = false; + for (;;) { + first = skip_lws(first, last); + if (first == last) { + return {{StringRef{}}, first}; + } + // we expect link-param + + if (!ign) { + if (!ok) { + // rel can take several relations using quoted form. + static constexpr char PLP[] = "rel=\""; + static constexpr size_t PLPLEN = str_size(PLP); + + static constexpr char PLT[] = "preload"; + static constexpr size_t PLTLEN = str_size(PLT); + if (first + PLPLEN < last && *(first + PLPLEN - 1) == '"' && + std::equal(PLP, PLP + PLPLEN, first, util::CaseCmp())) { + // we have to search preload in whitespace separated list: + // rel="preload something http://example.org/foo" + first += PLPLEN; + auto start = first; + for (; first != last;) { + if (*first != ' ' && *first != '"') { + ++first; + continue; + } + + if (start == first) { + return {{StringRef{}}, last}; + } + + if (!ok && start + PLTLEN == first && + std::equal(PLT, PLT + PLTLEN, start, util::CaseCmp())) { + ok = true; + } + + if (*first == '"') { + break; + } + first = skip_lws(first, last); + start = first; + } + if (first == last) { + return {{StringRef{}}, last}; + } + assert(*first == '"'); + ++first; + if (first == last || *first == ',') { + goto almost_done; + } + if (*first == ';') { + ++first; + // parse next link-param + continue; + } + return {{StringRef{}}, last}; + } + } + // we are only interested in rel=preload parameter. Others are + // simply skipped. + static constexpr char PL[] = "rel=preload"; + static constexpr size_t PLLEN = str_size(PL); + if (first + PLLEN == last) { + if (std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { + // ok = true; + // this is the end of sequence + return {{{url_first, url_last}}, last}; + } + } else if (first + PLLEN + 1 <= last) { + switch (*(first + PLLEN)) { + case ',': + if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { + break; + } + // ok = true; + // skip including ',' + first += PLLEN + 1; + return {{{url_first, url_last}}, first}; + case ';': + if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { + break; + } + ok = true; + // skip including ';' + first += PLLEN + 1; + // continue parse next link-param + continue; + } + } + // we have to reject URI if we have nonempty anchor parameter. + static constexpr char ANCHOR[] = "anchor="; + static constexpr size_t ANCHORLEN = str_size(ANCHOR); + if (!ign && !check_link_param_empty(first, last, ANCHOR, ANCHORLEN)) { + ign = true; + } + + // reject URI if we have non-empty loadpolicy. This could be + // tightened up to just pick up "next" or "insert". + static constexpr char LOADPOLICY[] = "loadpolicy="; + static constexpr size_t LOADPOLICYLEN = str_size(LOADPOLICY); + if (!ign && + !check_link_param_empty(first, last, LOADPOLICY, LOADPOLICYLEN)) { + ign = true; + } + + // reject URI if we have nopush attribute. + static constexpr char NOPUSH[] = "nopush"; + static constexpr size_t NOPUSHLEN = str_size(NOPUSH); + if (!ign && + check_link_param_without_value(first, last, NOPUSH, NOPUSHLEN)) { + ign = true; + } + } + + auto param_first = first; + for (; first != last;) { + if (util::in_attr_char(*first)) { + ++first; + continue; + } + // '*' is only allowed at the end of parameter name and must be + // followed by '=' + if (last - first >= 2 && first != param_first) { + if (*first == '*' && *(first + 1) == '=') { + ++first; + break; + } + } + if (*first == '=' || *first == ';' || *first == ',') { + break; + } + return {{StringRef{}}, last}; + } + if (param_first == first) { + // empty parmname + return {{StringRef{}}, last}; + } + // link-param without value is acceptable (see link-extension) if + // it is not followed by '=' + if (first == last || *first == ',') { + goto almost_done; + } + if (*first == ';') { + ++first; + // parse next link-param + continue; + } + // now parsing link-param value + assert(*first == '='); + ++first; + if (first == last) { + // empty value is not acceptable + return {{StringRef{}}, first}; + } + if (*first == '"') { + // quoted-string + first = skip_to_right_dquote(first + 1, last); + if (first == last) { + return {{StringRef{}}, first}; + } + ++first; + if (first == last || *first == ',') { + goto almost_done; + } + if (*first == ';') { + ++first; + // parse next link-param + continue; + } + return {{StringRef{}}, last}; + } + // not quoted-string, skip to next ',' or ';' + if (*first == ',' || *first == ';') { + // empty value + return {{StringRef{}}, last}; + } + for (; first != last; ++first) { + if (*first == ',' || *first == ';') { + break; + } + } + if (first == last || *first == ',') { + goto almost_done; + } + assert(*first == ';'); + ++first; + // parse next link-param + } + +almost_done: + assert(first == last || *first == ','); + + if (first != last) { + ++first; + } + if (ok && !ign) { + return {{{url_first, url_last}}, first}; + } + return {{StringRef{}}, first}; +} +} // namespace + +std::vector parse_link_header(const StringRef &src) { + std::vector res; + for (auto first = std::begin(src); first != std::end(src);) { + auto rv = parse_next_link_header_once(first, std::end(src)); + first = rv.second; + auto &link = rv.first; + if (!link.uri.empty()) { + res.push_back(link); + } + } + return res; +} + +std::string path_join(const StringRef &base_path, const StringRef &base_query, + const StringRef &rel_path, const StringRef &rel_query) { + BlockAllocator balloc(1024, 1024); + + return path_join(balloc, base_path, base_query, rel_path, rel_query).str(); +} + +bool expect_response_body(int status_code) { + return status_code == 101 || + (status_code / 100 != 1 && status_code != 304 && status_code != 204); +} + +bool expect_response_body(const std::string &method, int status_code) { + return method != "HEAD" && expect_response_body(status_code); +} + +bool expect_response_body(int method_token, int status_code) { + return method_token != HTTP_HEAD && expect_response_body(status_code); +} + +int lookup_method_token(const StringRef &name) { + return lookup_method_token(name.byte(), name.size()); +} + +// This function was generated by genmethodfunc.py. +int lookup_method_token(const uint8_t *name, size_t namelen) { + switch (namelen) { + case 3: + switch (name[2]) { + case 'L': + if (util::streq_l("AC", name, 2)) { + return HTTP_ACL; + } + break; + case 'T': + if (util::streq_l("GE", name, 2)) { + return HTTP_GET; + } + if (util::streq_l("PU", name, 2)) { + return HTTP_PUT; + } + break; + } + break; + case 4: + switch (name[3]) { + case 'D': + if (util::streq_l("BIN", name, 3)) { + return HTTP_BIND; + } + if (util::streq_l("HEA", name, 3)) { + return HTTP_HEAD; + } + break; + case 'E': + if (util::streq_l("MOV", name, 3)) { + return HTTP_MOVE; + } + break; + case 'K': + if (util::streq_l("LIN", name, 3)) { + return HTTP_LINK; + } + if (util::streq_l("LOC", name, 3)) { + return HTTP_LOCK; + } + break; + case 'T': + if (util::streq_l("POS", name, 3)) { + return HTTP_POST; + } + break; + case 'Y': + if (util::streq_l("COP", name, 3)) { + return HTTP_COPY; + } + break; + } + break; + case 5: + switch (name[4]) { + case 'E': + if (util::streq_l("MERG", name, 4)) { + return HTTP_MERGE; + } + if (util::streq_l("PURG", name, 4)) { + return HTTP_PURGE; + } + if (util::streq_l("TRAC", name, 4)) { + return HTTP_TRACE; + } + break; + case 'H': + if (util::streq_l("PATC", name, 4)) { + return HTTP_PATCH; + } + break; + case 'L': + if (util::streq_l("MKCO", name, 4)) { + return HTTP_MKCOL; + } + break; + } + break; + case 6: + switch (name[5]) { + case 'D': + if (util::streq_l("REBIN", name, 5)) { + return HTTP_REBIND; + } + if (util::streq_l("UNBIN", name, 5)) { + return HTTP_UNBIND; + } + break; + case 'E': + if (util::streq_l("DELET", name, 5)) { + return HTTP_DELETE; + } + if (util::streq_l("SOURC", name, 5)) { + return HTTP_SOURCE; + } + break; + case 'H': + if (util::streq_l("SEARC", name, 5)) { + return HTTP_SEARCH; + } + break; + case 'K': + if (util::streq_l("UNLIN", name, 5)) { + return HTTP_UNLINK; + } + if (util::streq_l("UNLOC", name, 5)) { + return HTTP_UNLOCK; + } + break; + case 'T': + if (util::streq_l("REPOR", name, 5)) { + return HTTP_REPORT; + } + break; + case 'Y': + if (util::streq_l("NOTIF", name, 5)) { + return HTTP_NOTIFY; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'H': + if (util::streq_l("MSEARC", name, 6)) { + return HTTP_MSEARCH; + } + break; + case 'S': + if (util::streq_l("OPTION", name, 6)) { + return HTTP_OPTIONS; + } + break; + case 'T': + if (util::streq_l("CONNEC", name, 6)) { + return HTTP_CONNECT; + } + break; + } + break; + case 8: + switch (name[7]) { + case 'D': + if (util::streq_l("PROPFIN", name, 7)) { + return HTTP_PROPFIND; + } + break; + case 'T': + if (util::streq_l("CHECKOU", name, 7)) { + return HTTP_CHECKOUT; + } + break; + } + break; + case 9: + switch (name[8]) { + case 'E': + if (util::streq_l("SUBSCRIB", name, 8)) { + return HTTP_SUBSCRIBE; + } + break; + case 'H': + if (util::streq_l("PROPPATC", name, 8)) { + return HTTP_PROPPATCH; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'R': + if (util::streq_l("MKCALENDA", name, 9)) { + return HTTP_MKCALENDAR; + } + break; + case 'Y': + if (util::streq_l("MKACTIVIT", name, 9)) { + return HTTP_MKACTIVITY; + } + break; + } + break; + case 11: + switch (name[10]) { + case 'E': + if (util::streq_l("UNSUBSCRIB", name, 10)) { + return HTTP_UNSUBSCRIBE; + } + break; + } + break; + } + return -1; +} + +StringRef to_method_string(int method_token) { + // we happened to use same value for method with llhttp. + return StringRef{ + llhttp_method_name(static_cast(method_token))}; +} + +StringRef get_pure_path_component(const StringRef &uri) { + int rv; + + http_parser_url u{}; + rv = http_parser_parse_url(uri.c_str(), uri.size(), 0, &u); + if (rv != 0) { + return StringRef{}; + } + + if (u.field_set & (1 << UF_PATH)) { + auto &f = u.field_data[UF_PATH]; + return StringRef{uri.c_str() + f.off, f.len}; + } + + return StringRef::from_lit("/"); +} + +int construct_push_component(BlockAllocator &balloc, StringRef &scheme, + StringRef &authority, StringRef &path, + const StringRef &base, const StringRef &uri) { + int rv; + StringRef rel, relq; + + if (uri.size() == 0) { + return -1; + } + + http_parser_url u{}; + + rv = http_parser_parse_url(uri.c_str(), uri.size(), 0, &u); + + if (rv != 0) { + if (uri[0] == '/') { + return -1; + } + + // treat link_url as relative URI. + auto end = std::find(std::begin(uri), std::end(uri), '#'); + auto q = std::find(std::begin(uri), end, '?'); + + rel = StringRef{std::begin(uri), q}; + if (q != end) { + relq = StringRef{q + 1, std::end(uri)}; + } + } else { + if (u.field_set & (1 << UF_SCHEMA)) { + scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA); + } + + if (u.field_set & (1 << UF_HOST)) { + auto auth = util::get_uri_field(uri.c_str(), u, UF_HOST); + auto len = auth.size(); + auto port_exists = u.field_set & (1 << UF_PORT); + if (port_exists) { + len += 1 + str_size("65535"); + } + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + p = std::copy(std::begin(auth), std::end(auth), p); + if (port_exists) { + *p++ = ':'; + p = util::utos(p, u.port); + } + *p = '\0'; + + authority = StringRef{iov.base, p}; + } + + if (u.field_set & (1 << UF_PATH)) { + auto &f = u.field_data[UF_PATH]; + rel = StringRef{uri.c_str() + f.off, f.len}; + } else { + rel = StringRef::from_lit("/"); + } + + if (u.field_set & (1 << UF_QUERY)) { + auto &f = u.field_data[UF_QUERY]; + relq = StringRef{uri.c_str() + f.off, f.len}; + } + } + + path = http2::path_join(balloc, base, StringRef{}, rel, relq); + + return 0; +} + +namespace { +template 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 InputIt eat_dir(InputIt first, InputIt last) { + auto p = eat_file(first, last); + + --p; + + assert(*p == '/'); + + return eat_file(first, p); +} +} // namespace + +StringRef path_join(BlockAllocator &balloc, const StringRef &base_path, + const StringRef &base_query, const StringRef &rel_path, + const StringRef &rel_query) { + auto res = make_byte_ref( + balloc, std::max(static_cast(1), base_path.size()) + + rel_path.size() + 1 + + std::max(base_query.size(), rel_query.size()) + 1); + auto p = res.base; + + if (rel_path.empty()) { + if (base_path.empty()) { + *p++ = '/'; + } else { + p = std::copy(std::begin(base_path), std::end(base_path), p); + } + if (rel_query.empty()) { + if (!base_query.empty()) { + *p++ = '?'; + p = std::copy(std::begin(base_query), std::end(base_query), p); + } + *p = '\0'; + return StringRef{res.base, p}; + } + *p++ = '?'; + p = std::copy(std::begin(rel_query), std::end(rel_query), p); + *p = '\0'; + return StringRef{res.base, p}; + } + + auto first = std::begin(rel_path); + auto last = std::end(rel_path); + + if (rel_path[0] == '/') { + *p++ = '/'; + ++first; + for (; first != last && *first == '/'; ++first) + ; + } else if (base_path.empty()) { + *p++ = '/'; + } else { + p = std::copy(std::begin(base_path), std::end(base_path), p); + } + + for (; first != last;) { + if (*first == '.') { + if (first + 1 == last) { + if (*(p - 1) != '/') { + p = eat_file(res.base, p); + } + break; + } + if (*(first + 1) == '/') { + if (*(p - 1) != '/') { + p = eat_file(res.base, p); + } + first += 2; + continue; + } + if (*(first + 1) == '.') { + if (first + 2 == last) { + p = eat_dir(res.base, p); + break; + } + if (*(first + 2) == '/') { + p = eat_dir(res.base, p); + first += 3; + continue; + } + } + } + if (*(p - 1) != '/') { + p = eat_file(res.base, 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) + ; + } + if (!rel_query.empty()) { + *p++ = '?'; + p = std::copy(std::begin(rel_query), std::end(rel_query), p); + } + *p = '\0'; + return StringRef{res.base, p}; +} + +StringRef normalize_path(BlockAllocator &balloc, const StringRef &path, + const StringRef &query) { + // First, decode %XX for unreserved characters, then do + // http2::path_join + + // We won't find %XX if length is less than 3. + if (path.size() < 3 || + std::find(std::begin(path), std::end(path), '%') == std::end(path)) { + return path_join(balloc, StringRef{}, StringRef{}, path, query); + } + + // includes last terminal NULL. + auto result = make_byte_ref(balloc, path.size() + 1); + auto p = result.base; + + auto it = std::begin(path); + for (; it + 2 < std::end(path);) { + if (*it == '%') { + if (util::is_hex_digit(*(it + 1)) && util::is_hex_digit(*(it + 2))) { + auto c = + (util::hex_to_uint(*(it + 1)) << 4) + util::hex_to_uint(*(it + 2)); + if (util::in_rfc3986_unreserved_chars(c)) { + *p++ = c; + + it += 3; + + continue; + } + *p++ = '%'; + *p++ = util::upcase(*(it + 1)); + *p++ = util::upcase(*(it + 2)); + + it += 3; + + continue; + } + } + *p++ = *it++; + } + + p = std::copy(it, std::end(path), p); + *p = '\0'; + + return path_join(balloc, StringRef{}, StringRef{}, StringRef{result.base, p}, + query); +} + +StringRef normalize_path_colon(BlockAllocator &balloc, const StringRef &path, + const StringRef &query) { + // First, decode %XX for unreserved characters and ':', then do + // http2::path_join + + // We won't find %XX if length is less than 3. + if (path.size() < 3 || + std::find(std::begin(path), std::end(path), '%') == std::end(path)) { + return path_join(balloc, StringRef{}, StringRef{}, path, query); + } + + // includes last terminal NULL. + auto result = make_byte_ref(balloc, path.size() + 1); + auto p = result.base; + + auto it = std::begin(path); + for (; it + 2 < std::end(path);) { + if (*it == '%') { + if (util::is_hex_digit(*(it + 1)) && util::is_hex_digit(*(it + 2))) { + auto c = + (util::hex_to_uint(*(it + 1)) << 4) + util::hex_to_uint(*(it + 2)); + if (util::in_rfc3986_unreserved_chars(c) || c == ':') { + *p++ = c; + + it += 3; + + continue; + } + *p++ = '%'; + *p++ = util::upcase(*(it + 1)); + *p++ = util::upcase(*(it + 2)); + + it += 3; + + continue; + } + } + *p++ = *it++; + } + + p = std::copy(it, std::end(path), p); + *p = '\0'; + + return path_join(balloc, StringRef{}, StringRef{}, StringRef{result.base, p}, + query); +} + +std::string normalize_path(const StringRef &path, const StringRef &query) { + BlockAllocator balloc(1024, 1024); + + return normalize_path(balloc, path, query).str(); +} + +StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src) { + if (src.empty() || src[0] != '/') { + return src; + } + // probably, not necessary most of the case, but just in case. + auto fragment = std::find(std::begin(src), std::end(src), '#'); + auto raw_query = std::find(std::begin(src), fragment, '?'); + auto query = raw_query; + if (query != fragment) { + ++query; + } + return normalize_path(balloc, StringRef{std::begin(src), raw_query}, + StringRef{query, fragment}); +} + +StringRef copy_lower(BlockAllocator &balloc, const StringRef &src) { + auto iov = make_byte_ref(balloc, src.size() + 1); + auto p = iov.base; + p = std::copy(std::begin(src), std::end(src), p); + *p = '\0'; + util::inp_strlower(iov.base, p); + return StringRef{iov.base, p}; +} + +bool contains_trailers(const StringRef &s) { + constexpr auto trailers = StringRef::from_lit("trailers"); + + for (auto p = std::begin(s), end = std::end(s);; ++p) { + p = std::find_if(p, end, [](char c) { return c != ' ' && c != '\t'; }); + if (p == end || static_cast(end - p) < trailers.size()) { + return false; + } + if (util::strieq(trailers, StringRef{p, p + trailers.size()})) { + // Make sure that there is no character other than white spaces + // before next "," or end of string. + p = std::find_if(p + trailers.size(), end, + [](char c) { return c != ' ' && c != '\t'; }); + if (p == end || *p == ',') { + return true; + } + } + // Skip to next ",". + p = std::find_if(p, end, [](char c) { return c == ','; }); + if (p == end) { + return false; + } + } +} + +StringRef make_websocket_accept_token(uint8_t *dest, const StringRef &key) { + static constexpr uint8_t magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + std::array s; + auto p = std::copy(std::begin(key), std::end(key), std::begin(s)); + std::copy_n(magic, str_size(magic), p); + + std::array h; + if (util::sha1(h.data(), StringRef{std::begin(s), std::end(s)}) != 0) { + return StringRef{}; + } + + auto end = base64::encode(std::begin(h), std::end(h), dest); + return StringRef{dest, end}; +} + +bool legacy_http1(int major, int minor) { + return major <= 0 || (major == 1 && minor == 0); +} + +bool check_transfer_encoding(const StringRef &s) { + if (s.empty()) { + return false; + } + + auto it = std::begin(s); + + for (;;) { + // token + if (!util::in_token(*it)) { + return false; + } + + ++it; + + for (; it != std::end(s) && util::in_token(*it); ++it) + ; + + if (it == std::end(s)) { + return true; + } + + for (;;) { + // OWS + it = skip_lws(it, std::end(s)); + if (it == std::end(s)) { + return false; + } + + if (*it == ',') { + ++it; + + it = skip_lws(it, std::end(s)); + if (it == std::end(s)) { + return false; + } + + break; + } + + if (*it != ';') { + return false; + } + + ++it; + + // transfer-parameter follows + + // OWS + it = skip_lws(it, std::end(s)); + if (it == std::end(s)) { + return false; + } + + // token + if (!util::in_token(*it)) { + return false; + } + + ++it; + + for (; it != std::end(s) && util::in_token(*it); ++it) + ; + + if (it == std::end(s)) { + return false; + } + + // No BWS allowed + if (*it != '=') { + return false; + } + + ++it; + + if (util::in_token(*it)) { + // token + ++it; + + for (; it != std::end(s) && util::in_token(*it); ++it) + ; + } else if (*it == '"') { + // quoted-string + ++it; + + it = skip_to_right_dquote(it, std::end(s)); + if (it == std::end(s)) { + return false; + } + + ++it; + } else { + return false; + } + + if (it == std::end(s)) { + return true; + } + } + } +} + +} // namespace http2 + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/http2.h b/lib/nghttp2/src/http2.h new file mode 100644 index 00000000000..7cfe46193f7 --- /dev/null +++ b/lib/nghttp2/src/http2.h @@ -0,0 +1,457 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 HTTP2_H +#define HTTP2_H + +#include "nghttp2_config.h" + +#include +#include +#include +#include +#include + +#include + +#include "url-parser/url_parser.h" + +#include "util.h" +#include "memchunk.h" +#include "template.h" +#include "allocator.h" +#include "base64.h" + +namespace nghttp2 { + +struct Header { + Header(std::string name, std::string value, bool no_index = false, + int32_t token = -1) + : name(std::move(name)), + value(std::move(value)), + token(token), + no_index(no_index) {} + + Header() : token(-1), no_index(false) {} + + bool operator==(const Header &other) const { + return name == other.name && value == other.value; + } + + bool operator<(const Header &rhs) const { + return name < rhs.name || (name == rhs.name && value < rhs.value); + } + + std::string name; + std::string value; + int32_t token; + bool no_index; +}; + +struct HeaderRef { + HeaderRef(const StringRef &name, const StringRef &value, + bool no_index = false, int32_t token = -1) + : name(name), value(value), token(token), no_index(no_index) {} + + HeaderRef() : token(-1), no_index(false) {} + + bool operator==(const HeaderRef &other) const { + return name == other.name && value == other.value; + } + + bool operator<(const HeaderRef &rhs) const { + return name < rhs.name || (name == rhs.name && value < rhs.value); + } + + StringRef name; + StringRef value; + int32_t token; + bool no_index; +}; + +using Headers = std::vector
; +using HeaderRefs = std::vector; + +namespace http2 { + +// Returns reason-phrase for given |status code|. If there is no +// known reason-phrase for the given code, returns empty string. +StringRef get_reason_phrase(unsigned int status_code); + +// Returns string version of |status_code|. (e.g., "404") +StringRef stringify_status(BlockAllocator &balloc, unsigned int status_code); + +void capitalize(DefaultMemchunks *buf, const StringRef &s); + +// Returns true if |value| is LWS +bool lws(const char *value); + +// Copies the |field| component value from |u| and |url| to the +// |dest|. If |u| does not have |field|, then this function does +// nothing. +void copy_url_component(std::string &dest, const http_parser_url *u, int field, + const char *url); + +Headers::value_type to_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index, int32_t token); + +// Add name/value pairs to |nva|. If |no_index| is true, this +// name/value pair won't be indexed when it is forwarded to the next +// hop. This function strips white spaces around |value|. +void add_header(Headers &nva, const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, bool no_index, + int32_t token); + +// Returns pointer to the entry in |nva| which has name |name|. If +// more than one entries which have the name |name|, last occurrence +// in |nva| is returned. If no such entry exist, returns nullptr. +const Headers::value_type *get_header(const Headers &nva, const char *name); + +// Returns true if the value of |nv| is not empty. +bool non_empty_value(const HeaderRefs::value_type *nv); + +// Creates nghttp2_nv using |name| and |value| and returns it. The +// returned value only references the data pointer to name.c_str() and +// value.c_str(). If |no_index| is true, nghttp2_nv flags member has +// NGHTTP2_NV_FLAG_NO_INDEX flag set. +nghttp2_nv make_nv(const std::string &name, const std::string &value, + bool no_index = false); + +nghttp2_nv make_nv(const StringRef &name, const StringRef &value, + bool no_index = false); + +nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool no_index = false); + +nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value, + bool no_index = false); + +// Create nghttp2_nv from string literal |name| and |value|. +template +constexpr nghttp2_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1, + NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE}; +} + +// Create nghttp2_nv from string literal |name| and c-string |value|. +template +nghttp2_nv make_nv_lc(const char (&name)[N], const char *value) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), + NGHTTP2_NV_FLAG_NO_COPY_NAME}; +} + +template +nghttp2_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), + NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE}; +} + +// Create nghttp2_nv from string literal |name| and std::string +// |value|. +template +nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP2_NV_FLAG_NO_COPY_NAME}; +} + +template +nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE}; +} + +template +nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE}; +} + +enum HeaderBuildOp { + HDOP_NONE, + // Forwarded header fields must be stripped. If this flag is not + // set, all Forwarded header fields other than last one are added. + HDOP_STRIP_FORWARDED = 1, + // X-Forwarded-For header fields must be stripped. If this flag is + // not set, all X-Forwarded-For header fields other than last one + // are added. + HDOP_STRIP_X_FORWARDED_FOR = 1 << 1, + // X-Forwarded-Proto header fields must be stripped. If this flag + // is not set, all X-Forwarded-Proto header fields other than last + // one are added. + HDOP_STRIP_X_FORWARDED_PROTO = 1 << 2, + // Via header fields must be stripped. If this flag is not set, all + // Via header fields other than last one are added. + HDOP_STRIP_VIA = 1 << 3, + // Early-Data header fields must be stripped. If this flag is not + // set, all Early-Data header fields are added. + HDOP_STRIP_EARLY_DATA = 1 << 4, + // Strip above all header fields. + HDOP_STRIP_ALL = HDOP_STRIP_FORWARDED | HDOP_STRIP_X_FORWARDED_FOR | + HDOP_STRIP_X_FORWARDED_PROTO | HDOP_STRIP_VIA | + HDOP_STRIP_EARLY_DATA, + // Sec-WebSocket-Accept header field must be stripped. If this flag + // is not set, all Sec-WebSocket-Accept header fields are added. + HDOP_STRIP_SEC_WEBSOCKET_ACCEPT = 1 << 5, + // Sec-WebSocket-Key header field must be stripped. If this flag is + // not set, all Sec-WebSocket-Key header fields are added. + HDOP_STRIP_SEC_WEBSOCKET_KEY = 1 << 6, + // Transfer-Encoding header field must be stripped. If this flag is + // not set, all Transfer-Encoding header fields are added. + HDOP_STRIP_TRANSFER_ENCODING = 1 << 7, +}; + +// Appends headers in |headers| to |nv|. |headers| must be indexed +// before this call (its element's token field is assigned). Certain +// headers, including disallowed headers in HTTP/2 spec and headers +// which require special handling (i.e. via), are not copied. |flags| +// is one or more of HeaderBuildOp flags. They tell function that +// certain header fields should not be added. +void copy_headers_to_nva(std::vector &nva, + const HeaderRefs &headers, uint32_t flags); + +// Just like copy_headers_to_nva(), but this adds +// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE. +void copy_headers_to_nva_nocopy(std::vector &nva, + const HeaderRefs &headers, uint32_t flags); + +// Appends HTTP/1.1 style header lines to |buf| from headers in +// |headers|. |headers| must be indexed before this call (its +// element's token field is assigned). Certain headers, which +// requires special handling (i.e. via and cookie), are not appended. +// |flags| is one or more of HeaderBuildOp flags. They tell function +// that certain header fields should not be added. +void build_http1_headers_from_headers(DefaultMemchunks *buf, + const HeaderRefs &headers, + uint32_t flags); + +// Return positive window_size_increment if WINDOW_UPDATE should be +// sent for the stream |stream_id|. If |stream_id| == 0, this function +// determines the necessity of the WINDOW_UPDATE for a connection. +// +// If the function determines WINDOW_UPDATE is not necessary at the +// moment, it returns -1. +int32_t determine_window_update_transmission(nghttp2_session *session, + int32_t stream_id); + +// Dumps name/value pairs in |nv| to |out|. The |nv| must be +// terminated by nullptr. +void dump_nv(FILE *out, const char **nv); + +// Dumps name/value pairs in |nva| to |out|. +void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen); + +// Dumps name/value pairs in |nva| to |out|. +void dump_nv(FILE *out, const Headers &nva); + +void dump_nv(FILE *out, const HeaderRefs &nva); + +// Ereases header in |hd|. +void erase_header(HeaderRef *hd); + +// Rewrites redirection URI which usually appears in location header +// field. The |uri| is the URI in the location header field. The |u| +// stores the result of parsed |uri|. The |request_authority| is the +// host or :authority header field value in the request. The +// |upstream_scheme| is either "https" or "http" in the upstream +// interface. Rewrite is done only if location header field value +// contains |match_host| as host excluding port. The |match_host| and +// |request_authority| could be different. If |request_authority| is +// empty, strip authority. +// +// This function returns the new rewritten URI on success. If the +// location URI is not subject to the rewrite, this function returns +// empty string. +StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri, + const http_parser_url &u, + const StringRef &match_host, + const StringRef &request_authority, + const StringRef &upstream_scheme); + +// Returns parsed HTTP status code. Returns -1 on failure. +int parse_http_status_code(const StringRef &src); + +// Header fields to be indexed, except HD_MAXIDX which is convenient +// member to get maximum value. +// +// generated by genheaderfunc.py +enum { + HD__AUTHORITY, + HD__HOST, + HD__METHOD, + HD__PATH, + HD__PROTOCOL, + HD__SCHEME, + HD__STATUS, + HD_ACCEPT_ENCODING, + HD_ACCEPT_LANGUAGE, + HD_ALT_SVC, + HD_CACHE_CONTROL, + HD_CONNECTION, + HD_CONTENT_LENGTH, + HD_CONTENT_TYPE, + HD_COOKIE, + HD_DATE, + HD_EARLY_DATA, + HD_EXPECT, + HD_FORWARDED, + HD_HOST, + HD_HTTP2_SETTINGS, + HD_IF_MODIFIED_SINCE, + HD_KEEP_ALIVE, + HD_LINK, + HD_LOCATION, + HD_PRIORITY, + HD_PROXY_CONNECTION, + HD_SEC_WEBSOCKET_ACCEPT, + HD_SEC_WEBSOCKET_KEY, + HD_SERVER, + HD_TE, + HD_TRAILER, + HD_TRANSFER_ENCODING, + HD_UPGRADE, + HD_USER_AGENT, + HD_VIA, + HD_X_FORWARDED_FOR, + HD_X_FORWARDED_PROTO, + HD_MAXIDX, +}; + +using HeaderIndex = std::array; + +// Looks up header token for header name |name| of length |namelen|. +// Only headers we are interested in are tokenized. If header name +// cannot be tokenized, returns -1. +int lookup_token(const uint8_t *name, size_t namelen); +int lookup_token(const StringRef &name); + +// Initializes |hdidx|, header index. The |hdidx| must point to the +// array containing at least HD_MAXIDX elements. +void init_hdidx(HeaderIndex &hdidx); +// Indexes header |token| using index |idx|. +void index_header(HeaderIndex &hdidx, int32_t token, size_t idx); + +// Returns header denoted by |token| using index |hdidx|. +const Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token, + const Headers &nva); + +Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token, + Headers &nva); + +struct LinkHeader { + // The region of URI. This might not be NULL-terminated. + StringRef uri; +}; + +// Returns next URI-reference in Link header field value |src|. If no +// URI-reference found after searching all input, returned uri field +// is empty. This imply that empty URI-reference is ignored during +// parsing. +std::vector parse_link_header(const StringRef &src); + +// Constructs path by combining base path |base_path| with another +// path |rel_path|. The base path and another path can have optional +// query component. This function assumes |base_path| is normalized. +// In other words, it does not contain ".." or "." path components +// and starts with "/" if it is not empty. +std::string path_join(const StringRef &base, const StringRef &base_query, + const StringRef &rel_path, const StringRef &rel_query); + +StringRef path_join(BlockAllocator &balloc, const StringRef &base_path, + const StringRef &base_query, const StringRef &rel_path, + const StringRef &rel_query); + +// true if response has body, taking into account the request method +// and status code. +bool expect_response_body(const std::string &method, int status_code); +bool expect_response_body(int method_token, int status_code); + +// true if response has body, taking into account status code only. +bool expect_response_body(int status_code); + +// Looks up method token for method name |name| of length |namelen|. +// Only methods defined in llhttp.h (llhttp_method) are tokenized. If +// method name cannot be tokenized, returns -1. +int lookup_method_token(const uint8_t *name, size_t namelen); +int lookup_method_token(const StringRef &name); + +// Returns string representation of |method_token|. This is wrapper +// around llhttp_method_name from llhttp. If |method_token| is +// unknown, program aborts. The returned StringRef is guaranteed to +// be NULL-terminated. +StringRef to_method_string(int method_token); + +StringRef normalize_path(BlockAllocator &balloc, const StringRef &path, + const StringRef &query); + +// normalize_path_colon is like normalize_path, but it additionally +// does percent-decoding %3A in order to workaround the issue that ':' +// cannot be included in backend pattern. +StringRef normalize_path_colon(BlockAllocator &balloc, const StringRef &path, + const StringRef &query); + +std::string normalize_path(const StringRef &path, const StringRef &query); + +StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src); + +// Returns path component of |uri|. The returned path does not +// include query component. This function returns empty string if it +// fails. +StringRef get_pure_path_component(const StringRef &uri); + +// Deduces scheme, authority and path from given |uri|, and stores +// them in |scheme|, |authority|, and |path| respectively. If |uri| +// is relative path, path resolution takes place using path given in +// |base| of length |baselen|. This function returns 0 if it +// succeeds, or -1. +int construct_push_component(BlockAllocator &balloc, StringRef &scheme, + StringRef &authority, StringRef &path, + const StringRef &base, const StringRef &uri); + +// Copies |src| and return its lower-cased version. +StringRef copy_lower(BlockAllocator &balloc, const StringRef &src); + +// Returns true if te header field value |s| contains "trailers". +bool contains_trailers(const StringRef &s); + +// Creates Sec-WebSocket-Accept value for |key|. The capacity of +// buffer pointed by |dest| must have at least 24 bytes (base64 +// encoded length of 16 bytes data). It returns empty string in case +// of error. +StringRef make_websocket_accept_token(uint8_t *dest, const StringRef &key); + +// Returns true if HTTP version represents pre-HTTP/1.1 (e.g., +// HTTP/0.9 or HTTP/1.0). +bool legacy_http1(int major, int minor); + +// Returns true if transfer-encoding field value |s| conforms RFC +// strictly. This function does not allow empty value, BWS, and empty +// list elements. +bool check_transfer_encoding(const StringRef &s); + +} // namespace http2 + +} // namespace nghttp2 + +#endif // HTTP2_H diff --git a/lib/nghttp2/src/http2_test.cc b/lib/nghttp2/src/http2_test.cc new file mode 100644 index 00000000000..f8be9f45446 --- /dev/null +++ b/lib/nghttp2/src/http2_test.cc @@ -0,0 +1,1249 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "http2_test.h" + +#include +#include +#include + +#include + +#include "url-parser/url_parser.h" + +#include "http2.h" +#include "util.h" + +using namespace nghttp2; + +#define MAKE_NV(K, V) \ + { \ + (uint8_t *)K, (uint8_t *)V, sizeof(K) - 1, sizeof(V) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +namespace shrpx { + +namespace { +void check_nv(const HeaderRef &a, const nghttp2_nv *b) { + CU_ASSERT(a.name.size() == b->namelen); + CU_ASSERT(a.value.size() == b->valuelen); + CU_ASSERT(memcmp(a.name.c_str(), b->name, b->namelen) == 0); + CU_ASSERT(memcmp(a.value.c_str(), b->value, b->valuelen) == 0); +} +} // namespace + +void test_http2_add_header(void) { + auto nva = Headers(); + + http2::add_header(nva, (const uint8_t *)"alpha", 5, (const uint8_t *)"123", 3, + false, -1); + CU_ASSERT(Headers::value_type("alpha", "123") == nva[0]); + CU_ASSERT(!nva[0].no_index); + + nva.clear(); + + http2::add_header(nva, (const uint8_t *)"alpha", 5, (const uint8_t *)"", 0, + true, -1); + CU_ASSERT(Headers::value_type("alpha", "") == nva[0]); + CU_ASSERT(nva[0].no_index); + + nva.clear(); + + http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" b", 2, + false, -1); + CU_ASSERT(Headers::value_type("a", "b") == nva[0]); + + nva.clear(); + + http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)"b ", 2, + false, -1); + CU_ASSERT(Headers::value_type("a", "b") == nva[0]); + + nva.clear(); + + http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" b ", 5, + false, -1); + CU_ASSERT(Headers::value_type("a", "b") == nva[0]); + + nva.clear(); + + http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" bravo ", + 9, false, -1); + CU_ASSERT(Headers::value_type("a", "bravo") == nva[0]); + + nva.clear(); + + http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" ", 4, + false, -1); + CU_ASSERT(Headers::value_type("a", "") == nva[0]); + + nva.clear(); + + http2::add_header(nva, (const uint8_t *)"te", 2, (const uint8_t *)"trailers", + 8, false, http2::HD_TE); + CU_ASSERT(http2::HD_TE == nva[0].token); +} + +void test_http2_get_header(void) { + auto nva = Headers{{"alpha", "1"}, {"bravo", "2"}, {"bravo", "3"}, + {"charlie", "4"}, {"delta", "5"}, {"echo", "6"}, + {"content-length", "7"}}; + const Headers::value_type *rv; + rv = http2::get_header(nva, "delta"); + CU_ASSERT(rv != nullptr); + CU_ASSERT("delta" == rv->name); + + rv = http2::get_header(nva, "bravo"); + CU_ASSERT(rv != nullptr); + CU_ASSERT("bravo" == rv->name); + + rv = http2::get_header(nva, "foxtrot"); + CU_ASSERT(rv == nullptr); + + http2::HeaderIndex hdidx; + http2::init_hdidx(hdidx); + hdidx[http2::HD_CONTENT_LENGTH] = 6; + rv = http2::get_header(hdidx, http2::HD_CONTENT_LENGTH, nva); + CU_ASSERT("content-length" == rv->name); +} + +namespace { +auto headers = HeaderRefs{ + {StringRef::from_lit("alpha"), StringRef::from_lit("0"), true}, + {StringRef::from_lit("bravo"), StringRef::from_lit("1")}, + {StringRef::from_lit("connection"), StringRef::from_lit("2"), false, + http2::HD_CONNECTION}, + {StringRef::from_lit("connection"), StringRef::from_lit("3"), false, + http2::HD_CONNECTION}, + {StringRef::from_lit("delta"), StringRef::from_lit("4")}, + {StringRef::from_lit("expect"), StringRef::from_lit("5")}, + {StringRef::from_lit("foxtrot"), StringRef::from_lit("6")}, + {StringRef::from_lit("tango"), StringRef::from_lit("7")}, + {StringRef::from_lit("te"), StringRef::from_lit("8"), false, http2::HD_TE}, + {StringRef::from_lit("te"), StringRef::from_lit("9"), false, http2::HD_TE}, + {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("10"), false, + http2::HD_X_FORWARDED_FOR}, + {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("11"), false, + http2::HD_X_FORWARDED_FOR}, + {StringRef::from_lit("zulu"), StringRef::from_lit("12")}}; +} // namespace + +namespace { +auto headers2 = HeaderRefs{ + {StringRef::from_lit("x-forwarded-for"), StringRef::from_lit("xff1"), false, + http2::HD_X_FORWARDED_FOR}, + {StringRef::from_lit("x-forwarded-for"), StringRef::from_lit("xff2"), false, + http2::HD_X_FORWARDED_FOR}, + {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("xfp1"), + false, http2::HD_X_FORWARDED_PROTO}, + {StringRef::from_lit("x-forwarded-proto"), StringRef::from_lit("xfp2"), + false, http2::HD_X_FORWARDED_PROTO}, + {StringRef::from_lit("forwarded"), StringRef::from_lit("fwd1"), false, + http2::HD_FORWARDED}, + {StringRef::from_lit("forwarded"), StringRef::from_lit("fwd2"), false, + http2::HD_FORWARDED}, + {StringRef::from_lit("via"), StringRef::from_lit("via1"), false, + http2::HD_VIA}, + {StringRef::from_lit("via"), StringRef::from_lit("via2"), false, + http2::HD_VIA}, +}; +} // namespace + +void test_http2_copy_headers_to_nva(void) { + auto ans = std::vector{0, 1, 4, 5, 6, 7, 12}; + std::vector nva; + + http2::copy_headers_to_nva_nocopy(nva, headers, + http2::HDOP_STRIP_X_FORWARDED_FOR); + CU_ASSERT(7 == nva.size()); + for (size_t i = 0; i < ans.size(); ++i) { + check_nv(headers[ans[i]], &nva[i]); + + if (ans[i] == 0) { + CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE | + NGHTTP2_NV_FLAG_NO_INDEX) == nva[i].flags); + } else { + CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME | + NGHTTP2_NV_FLAG_NO_COPY_VALUE) == nva[i].flags); + } + } + + nva.clear(); + http2::copy_headers_to_nva(nva, headers, http2::HDOP_STRIP_X_FORWARDED_FOR); + CU_ASSERT(7 == nva.size()); + for (size_t i = 0; i < ans.size(); ++i) { + check_nv(headers[ans[i]], &nva[i]); + + if (ans[i] == 0) { + CU_ASSERT(nva[i].flags & NGHTTP2_NV_FLAG_NO_INDEX); + } else { + CU_ASSERT(NGHTTP2_NV_FLAG_NONE == nva[i].flags); + } + } + + nva.clear(); + + auto ans2 = std::vector{0, 2, 4, 6}; + http2::copy_headers_to_nva(nva, headers2, http2::HDOP_NONE); + CU_ASSERT(ans2.size() == nva.size()); + for (size_t i = 0; i < ans2.size(); ++i) { + check_nv(headers2[ans2[i]], &nva[i]); + } + + nva.clear(); + + http2::copy_headers_to_nva(nva, headers2, http2::HDOP_STRIP_ALL); + CU_ASSERT(nva.empty()); +} + +void test_http2_build_http1_headers_from_headers(void) { + MemchunkPool pool; + DefaultMemchunks buf(&pool); + http2::build_http1_headers_from_headers(&buf, headers, + http2::HDOP_STRIP_X_FORWARDED_FOR); + auto hdrs = std::string(buf.head->pos, buf.head->last); + CU_ASSERT("Alpha: 0\r\n" + "Bravo: 1\r\n" + "Delta: 4\r\n" + "Expect: 5\r\n" + "Foxtrot: 6\r\n" + "Tango: 7\r\n" + "Te: 8\r\n" + "Te: 9\r\n" + "Zulu: 12\r\n" == hdrs); + + buf.reset(); + + http2::build_http1_headers_from_headers(&buf, headers2, http2::HDOP_NONE); + hdrs = std::string(buf.head->pos, buf.head->last); + CU_ASSERT("X-Forwarded-For: xff1\r\n" + "X-Forwarded-Proto: xfp1\r\n" + "Forwarded: fwd1\r\n" + "Via: via1\r\n" == hdrs); + + buf.reset(); + + http2::build_http1_headers_from_headers(&buf, headers2, + http2::HDOP_STRIP_ALL); + CU_ASSERT(0 == buf.rleft()); +} + +void test_http2_lws(void) { + CU_ASSERT(!http2::lws("alpha")); + CU_ASSERT(http2::lws(" ")); + CU_ASSERT(http2::lws("")); +} + +namespace { +void check_rewrite_location_uri(const std::string &want, const std::string &uri, + const std::string &match_host, + const std::string &req_authority, + const std::string &upstream_scheme) { + BlockAllocator balloc(4096, 4096); + http_parser_url u{}; + CU_ASSERT(0 == http_parser_parse_url(uri.c_str(), uri.size(), 0, &u)); + auto got = http2::rewrite_location_uri( + balloc, StringRef{uri}, u, StringRef{match_host}, + StringRef{req_authority}, StringRef{upstream_scheme}); + CU_ASSERT(want == got); +} +} // namespace + +void test_http2_rewrite_location_uri(void) { + check_rewrite_location_uri("https://localhost:3000/alpha?bravo#charlie", + "http://localhost:3001/alpha?bravo#charlie", + "localhost:3001", "localhost:3000", "https"); + check_rewrite_location_uri("https://localhost/", "http://localhost:3001/", + "localhost", "localhost", "https"); + check_rewrite_location_uri("http://localhost/", "http://localhost:3001/", + "localhost", "localhost", "http"); + check_rewrite_location_uri("http://localhost:443/", "http://localhost:3001/", + "localhost", "localhost:443", "http"); + check_rewrite_location_uri("https://localhost:80/", "http://localhost:3001/", + "localhost", "localhost:80", "https"); + check_rewrite_location_uri("", "http://localhost:3001/", "127.0.0.1", + "127.0.0.1", "https"); + check_rewrite_location_uri("https://localhost:3000/", + "http://localhost:3001/", "localhost", + "localhost:3000", "https"); + check_rewrite_location_uri("https://localhost:3000/", "http://localhost/", + "localhost", "localhost:3000", "https"); + + // match_host != req_authority + check_rewrite_location_uri("https://example.org", "http://127.0.0.1:8080", + "127.0.0.1", "example.org", "https"); + check_rewrite_location_uri("", "http://example.org", "127.0.0.1", + "example.org", "https"); +} + +void test_http2_parse_http_status_code(void) { + CU_ASSERT(200 == http2::parse_http_status_code(StringRef::from_lit("200"))); + CU_ASSERT(102 == http2::parse_http_status_code(StringRef::from_lit("102"))); + CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("099"))); + CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("99"))); + CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("-1"))); + CU_ASSERT(-1 == http2::parse_http_status_code(StringRef::from_lit("20a"))); + CU_ASSERT(-1 == http2::parse_http_status_code(StringRef{})); +} + +void test_http2_index_header(void) { + http2::HeaderIndex hdidx; + http2::init_hdidx(hdidx); + + http2::index_header(hdidx, http2::HD__AUTHORITY, 0); + http2::index_header(hdidx, -1, 1); + + CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]); +} + +void test_http2_lookup_token(void) { + CU_ASSERT(http2::HD__AUTHORITY == + http2::lookup_token(StringRef::from_lit(":authority"))); + CU_ASSERT(-1 == http2::lookup_token(StringRef::from_lit(":authorit"))); + CU_ASSERT(-1 == http2::lookup_token(StringRef::from_lit(":Authority"))); + CU_ASSERT(http2::HD_EXPECT == + http2::lookup_token(StringRef::from_lit("expect"))); +} + +void test_http2_parse_link_header(void) { + { + // only URI appears; we don't extract URI unless it bears rel=preload + auto res = http2::parse_link_header(StringRef::from_lit("")); + CU_ASSERT(0 == res.size()); + } + { + // URI url should be extracted + auto res = + http2::parse_link_header(StringRef::from_lit("; rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // With extra link-param. URI url should be extracted + auto res = http2::parse_link_header( + StringRef::from_lit("; rel=preload; as=file")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // With extra link-param. URI url should be extracted + auto res = http2::parse_link_header( + StringRef::from_lit("; as=file; rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // With extra link-param and quote-string. URI url should be + // extracted + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel=preload; title="foo,bar")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // With extra link-param and quote-string. URI url should be + // extracted + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; title="foo,bar"; rel=preload)")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // ',' after quote-string + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; title="foo,bar", ; rel=preload)")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url2" == res[0].uri); + } + { + // Only first URI should be extracted. + auto res = http2::parse_link_header( + StringRef::from_lit("; rel=preload, ")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // Both have rel=preload, so both urls should be extracted + auto res = http2::parse_link_header( + StringRef::from_lit("; rel=preload, ; rel=preload")); + CU_ASSERT(2 == res.size()); + CU_ASSERT("url" == res[0].uri); + CU_ASSERT("url2" == res[1].uri); + } + { + // Second URI uri should be extracted. + auto res = http2::parse_link_header( + StringRef::from_lit(", ;rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url2" == res[0].uri); + } + { + // Error if input ends with ';' + auto res = + http2::parse_link_header(StringRef::from_lit(";rel=preload;")); + CU_ASSERT(0 == res.size()); + } + { + // Error if link header ends with ';' + auto res = http2::parse_link_header( + StringRef::from_lit(";rel=preload;, ")); + CU_ASSERT(0 == res.size()); + } + { + // OK if input ends with ',' + auto res = + http2::parse_link_header(StringRef::from_lit(";rel=preload,")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // Multiple repeated ','s between fields is OK + auto res = http2::parse_link_header( + StringRef::from_lit(",,,;rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url2" == res[0].uri); + } + { + // Error if url is not enclosed by <> + auto res = + http2::parse_link_header(StringRef::from_lit("url>;rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Error if url is not enclosed by <> + auto res = + http2::parse_link_header(StringRef::from_lit(";rel=preload; as=")); + CU_ASSERT(0 == res.size()); + } + { + // Empty parameter value is not allowed + auto res = + http2::parse_link_header(StringRef::from_lit(";as=;rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Empty parameter value is not allowed + auto res = http2::parse_link_header( + StringRef::from_lit(";as=, ;rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Empty parameter name is not allowed + auto res = http2::parse_link_header( + StringRef::from_lit("; =file; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Without whitespaces + auto res = http2::parse_link_header( + StringRef::from_lit(";as=file;rel=preload,;rel=preload")); + CU_ASSERT(2 == res.size()); + CU_ASSERT("url" == res[0].uri); + CU_ASSERT("url2" == res[1].uri); + } + { + // link-extension may have no value + auto res = + http2::parse_link_header(StringRef::from_lit("; as; rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // ext-name-star + auto res = http2::parse_link_header( + StringRef::from_lit("; foo*=bar; rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // '*' is not allowed expect for trailing one + auto res = http2::parse_link_header( + StringRef::from_lit("; *=bar; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // '*' is not allowed expect for trailing one + auto res = http2::parse_link_header( + StringRef::from_lit("; foo*bar=buzz; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // ext-name-star must be followed by '=' + auto res = http2::parse_link_header( + StringRef::from_lit("; foo*; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // '>' is not followed by ';' + auto res = + http2::parse_link_header(StringRef::from_lit(" rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // Starting with whitespace is no problem. + auto res = + http2::parse_link_header(StringRef::from_lit(" ; rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // preload is a prefix of bogus rel parameter value + auto res = + http2::parse_link_header(StringRef::from_lit("; rel=preloadx")); + CU_ASSERT(0 == res.size()); + } + { + // preload in relation-types list + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="preload")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // preload in relation-types list followed by another parameter + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="preload foo")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // preload in relation-types list following another parameter + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="foo preload")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // preload in relation-types list between other parameters + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="foo preload bar")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // preload in relation-types list between other parameters + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="foo preload bar")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // no preload in relation-types list + auto res = + http2::parse_link_header(StringRef::from_lit(R"(; rel="foo")")); + CU_ASSERT(0 == res.size()); + } + { + // no preload in relation-types list, multiple unrelated elements. + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="foo bar")")); + CU_ASSERT(0 == res.size()); + } + { + // preload in relation-types list, followed by another link-value. + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="preload", )")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // preload in relation-types list, following another link-value. + auto res = http2::parse_link_header( + StringRef::from_lit(R"(, ; rel="preload")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url2" == res[0].uri); + } + { + // preload in relation-types list, followed by another link-param. + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="preload"; as="font")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // preload in relation-types list, followed by character other + // than ';' or ',' + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="preload".)")); + CU_ASSERT(0 == res.size()); + } + { + // preload in relation-types list, followed by ';' but it + // terminates input + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="preload";)")); + CU_ASSERT(0 == res.size()); + } + { + // preload in relation-types list, followed by ',' but it + // terminates input + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="preload",)")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // preload in relation-types list but there is preceding white + // space. + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel=" preload")")); + CU_ASSERT(0 == res.size()); + } + { + // preload in relation-types list but there is trailing white + // space. + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel="preload ")")); + CU_ASSERT(0 == res.size()); + } + { + // backslash escaped characters in quoted-string + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel=preload; title="foo\"baz\"bar")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // anchor="" is acceptable + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel=preload; anchor="")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // With anchor="#foo", url should be ignored + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel=preload; anchor="#foo")")); + CU_ASSERT(0 == res.size()); + } + { + // With anchor=f, url should be ignored + auto res = http2::parse_link_header( + StringRef::from_lit("; rel=preload; anchor=f")); + CU_ASSERT(0 == res.size()); + } + { + // First url is ignored With anchor="#foo", but url should be + // accepted. + auto res = http2::parse_link_header(StringRef::from_lit( + R"(; rel=preload; anchor="#foo", ; rel=preload)")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url2" == res[0].uri); + } + { + // With loadpolicy="next", url should be ignored + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel=preload; loadpolicy="next")")); + CU_ASSERT(0 == res.size()); + } + { + // url should be picked up if empty loadpolicy is specified + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel=preload; loadpolicy="")")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // case-insensitive match + auto res = http2::parse_link_header( + StringRef::from_lit(R"(; rel=preload; ANCHOR="#foo", ; )" + R"(REL=PRELOAD, ; REL="foo PRELOAD bar")")); + CU_ASSERT(2 == res.size()); + CU_ASSERT("url2" == res[0].uri); + CU_ASSERT("url3" == res[1].uri); + } + { + // nopush at the end of input + auto res = http2::parse_link_header( + StringRef::from_lit("; rel=preload; nopush")); + CU_ASSERT(0 == res.size()); + } + { + // nopush followed by ';' + auto res = http2::parse_link_header( + StringRef::from_lit("; rel=preload; nopush; foo")); + CU_ASSERT(0 == res.size()); + } + { + // nopush followed by ',' + auto res = http2::parse_link_header( + StringRef::from_lit("; nopush; rel=preload")); + CU_ASSERT(0 == res.size()); + } + { + // string whose prefix is nopush + auto res = http2::parse_link_header( + StringRef::from_lit("; nopushyes; rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } + { + // rel=preload twice + auto res = http2::parse_link_header( + StringRef::from_lit("; rel=preload; rel=preload")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("url" == res[0].uri); + } +} + +void test_http2_path_join(void) { + { + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("/"); + CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("/alpha"); + CU_ASSERT("/alpha" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // rel ends with trailing '/' + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("/alpha/"); + CU_ASSERT("/alpha/" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // rel contains multiple components + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("/alpha/bravo"); + CU_ASSERT("/alpha/bravo" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // rel is relative + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("alpha/bravo"); + CU_ASSERT("/alpha/bravo" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // rel is relative and base ends without /, which means it refers + // to file. + auto base = StringRef::from_lit("/alpha"); + auto rel = StringRef::from_lit("bravo/charlie"); + CU_ASSERT("/bravo/charlie" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // rel contains repeated '/'s + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("/alpha/////bravo/////"); + CU_ASSERT("/alpha/bravo/" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // base ends with '/', so '..' eats 'bravo' + auto base = StringRef::from_lit("/alpha/bravo/"); + auto rel = StringRef::from_lit("../charlie/delta"); + CU_ASSERT("/alpha/charlie/delta" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // base does not end with '/', so '..' eats 'alpha/bravo' + auto base = StringRef::from_lit("/alpha/bravo"); + auto rel = StringRef::from_lit("../charlie"); + CU_ASSERT("/charlie" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // 'charlie' is eaten by following '..' + auto base = StringRef::from_lit("/alpha/bravo/"); + auto rel = StringRef::from_lit("../charlie/../delta"); + CU_ASSERT("/alpha/delta" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // excessive '..' results in '/' + auto base = StringRef::from_lit("/alpha/bravo/"); + auto rel = StringRef::from_lit("../../../"); + CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // excessive '..' and path component + auto base = StringRef::from_lit("/alpha/bravo/"); + auto rel = StringRef::from_lit("../../../charlie"); + CU_ASSERT("/charlie" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // rel ends with '..' + auto base = StringRef::from_lit("/alpha/bravo/"); + auto rel = StringRef::from_lit("charlie/.."); + CU_ASSERT("/alpha/bravo/" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // base empty and rel contains '..' + auto base = StringRef{}; + auto rel = StringRef::from_lit("charlie/.."); + CU_ASSERT("/" == http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // '.' is ignored + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("charlie/././././delta"); + CU_ASSERT("/charlie/delta" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // trailing '.' is ignored + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("charlie/."); + CU_ASSERT("/charlie/" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // query + auto base = StringRef::from_lit("/"); + auto rel = StringRef::from_lit("/"); + auto relq = StringRef::from_lit("q"); + CU_ASSERT("/?q" == http2::path_join(base, StringRef{}, rel, relq)); + } + { + // empty rel and query + auto base = StringRef::from_lit("/alpha"); + auto rel = StringRef{}; + auto relq = StringRef::from_lit("q"); + CU_ASSERT("/alpha?q" == http2::path_join(base, StringRef{}, rel, relq)); + } + { + // both rel and query are empty + auto base = StringRef::from_lit("/alpha"); + auto baseq = StringRef::from_lit("r"); + auto rel = StringRef{}; + auto relq = StringRef{}; + CU_ASSERT("/alpha?r" == http2::path_join(base, baseq, rel, relq)); + } + { + // empty base + auto base = StringRef{}; + auto rel = StringRef::from_lit("/alpha"); + CU_ASSERT("/alpha" == + http2::path_join(base, StringRef{}, rel, StringRef{})); + } + { + // everything is empty + CU_ASSERT("/" == http2::path_join(StringRef{}, StringRef{}, StringRef{}, + StringRef{})); + } + { + // only baseq is not empty + auto base = StringRef{}; + auto baseq = StringRef::from_lit("r"); + auto rel = StringRef{}; + CU_ASSERT("/?r" == http2::path_join(base, baseq, rel, StringRef{})); + } + { + // path starts with multiple '/'s. + auto base = StringRef{}; + auto baseq = StringRef{}; + auto rel = StringRef::from_lit("//alpha//bravo"); + auto relq = StringRef::from_lit("charlie"); + CU_ASSERT("/alpha/bravo?charlie" == + http2::path_join(base, baseq, rel, relq)); + } + // Test cases from RFC 3986, section 5.4. + constexpr auto base = StringRef::from_lit("/b/c/d;p"); + constexpr auto baseq = StringRef::from_lit("q"); + { + auto rel = StringRef::from_lit("g"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("./g"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g/"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g/" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("/g"); + auto relq = StringRef{}; + CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef{}; + auto relq = StringRef::from_lit("y"); + CU_ASSERT("/b/c/d;p?y" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g"); + auto relq = StringRef::from_lit("y"); + CU_ASSERT("/b/c/g?y" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit(";x"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/;x" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g;x"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g;x" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g;x"); + auto relq = StringRef::from_lit("y"); + CU_ASSERT("/b/c/g;x?y" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef{}; + auto relq = StringRef{}; + CU_ASSERT("/b/c/d;p?q" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("."); + auto relq = StringRef{}; + CU_ASSERT("/b/c/" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("./"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit(".."); + auto relq = StringRef{}; + CU_ASSERT("/b/" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("../"); + auto relq = StringRef{}; + CU_ASSERT("/b/" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("../g"); + auto relq = StringRef{}; + CU_ASSERT("/b/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("../.."); + auto relq = StringRef{}; + CU_ASSERT("/" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("../../"); + auto relq = StringRef{}; + CU_ASSERT("/" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("../../g"); + auto relq = StringRef{}; + CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("../../../g"); + auto relq = StringRef{}; + CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("../../../../g"); + auto relq = StringRef{}; + CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("/./g"); + auto relq = StringRef{}; + CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("/../g"); + auto relq = StringRef{}; + CU_ASSERT("/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g."); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g." == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit(".g"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/.g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g.."); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g.." == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("..g"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/..g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("./../g"); + auto relq = StringRef{}; + CU_ASSERT("/b/g" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("./g/."); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g/" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g/./h"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g/h" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g/../h"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/h" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g;x=1/./y"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/g;x=1/y" == http2::path_join(base, baseq, rel, relq)); + } + { + auto rel = StringRef::from_lit("g;x=1/../y"); + auto relq = StringRef{}; + CU_ASSERT("/b/c/y" == http2::path_join(base, baseq, rel, relq)); + } +} + +void test_http2_normalize_path(void) { + CU_ASSERT("/alpha/charlie" == + http2::normalize_path( + StringRef::from_lit("/alpha/bravo/../charlie"), StringRef{})); + + CU_ASSERT("/alpha" == + http2::normalize_path(StringRef::from_lit("/a%6c%70%68%61"), + StringRef{})); + + CU_ASSERT( + "/alpha%2F%3A" == + http2::normalize_path(StringRef::from_lit("/alpha%2f%3a"), StringRef{})); + + CU_ASSERT("/%2F" == + http2::normalize_path(StringRef::from_lit("%2f"), StringRef{})); + + CU_ASSERT("/%f" == + http2::normalize_path(StringRef::from_lit("%f"), StringRef{})); + + CU_ASSERT("/%" == + http2::normalize_path(StringRef::from_lit("%"), StringRef{})); + + CU_ASSERT("/" == http2::normalize_path(StringRef{}, StringRef{})); + + CU_ASSERT("/alpha?bravo" == + http2::normalize_path(StringRef::from_lit("/alpha"), + StringRef::from_lit("bravo"))); +} + +void test_http2_rewrite_clean_path(void) { + BlockAllocator balloc(4096, 4096); + + // unreserved characters + CU_ASSERT("/alpha/bravo/" == + http2::rewrite_clean_path(balloc, + StringRef::from_lit("/alpha/%62ravo/"))); + + // percent-encoding is converted to upper case. + CU_ASSERT("/delta%3A" == http2::rewrite_clean_path( + balloc, StringRef::from_lit("/delta%3a"))); + + // path component is normalized before matching + CU_ASSERT( + "/alpha/bravo/" == + http2::rewrite_clean_path( + balloc, StringRef::from_lit("/alpha/charlie/%2e././bravo/delta/.."))); + + CU_ASSERT("alpha%3a" == + http2::rewrite_clean_path(balloc, StringRef::from_lit("alpha%3a"))); + + CU_ASSERT("" == http2::rewrite_clean_path(balloc, StringRef{})); + + CU_ASSERT( + "/alpha?bravo" == + http2::rewrite_clean_path(balloc, StringRef::from_lit("//alpha?bravo"))); +} + +void test_http2_get_pure_path_component(void) { + CU_ASSERT("/" == http2::get_pure_path_component(StringRef::from_lit("/"))); + + CU_ASSERT("/foo" == + http2::get_pure_path_component(StringRef::from_lit("/foo"))); + + CU_ASSERT("/bar" == http2::get_pure_path_component( + StringRef::from_lit("https://example.org/bar"))); + + CU_ASSERT("/alpha" == http2::get_pure_path_component(StringRef::from_lit( + "https://example.org/alpha?q=a"))); + + CU_ASSERT("/bravo" == http2::get_pure_path_component(StringRef::from_lit( + "https://example.org/bravo?q=a#fragment"))); + + CU_ASSERT("" == + http2::get_pure_path_component(StringRef::from_lit("\x01\x02"))); +} + +void test_http2_construct_push_component(void) { + BlockAllocator balloc(4096, 4096); + StringRef base, uri; + StringRef scheme, authority, path; + + base = StringRef::from_lit("/b/"); + uri = StringRef::from_lit("https://example.org/foo"); + + CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority, + path, base, uri)); + CU_ASSERT("https" == scheme); + CU_ASSERT("example.org" == authority); + CU_ASSERT("/foo" == path); + + scheme = StringRef{}; + authority = StringRef{}; + path = StringRef{}; + + uri = StringRef::from_lit("/foo/bar?q=a"); + + CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority, + path, base, uri)); + CU_ASSERT("" == scheme); + CU_ASSERT("" == authority); + CU_ASSERT("/foo/bar?q=a" == path); + + scheme = StringRef{}; + authority = StringRef{}; + path = StringRef{}; + + uri = StringRef::from_lit("foo/../bar?q=a"); + + CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority, + path, base, uri)); + CU_ASSERT("" == scheme); + CU_ASSERT("" == authority); + CU_ASSERT("/b/bar?q=a" == path); + + scheme = StringRef{}; + authority = StringRef{}; + path = StringRef{}; + + uri = StringRef{}; + + CU_ASSERT(-1 == http2::construct_push_component(balloc, scheme, authority, + path, base, uri)); + scheme = StringRef{}; + authority = StringRef{}; + path = StringRef{}; + + uri = StringRef::from_lit("?q=a"); + + CU_ASSERT(0 == http2::construct_push_component(balloc, scheme, authority, + path, base, uri)); + CU_ASSERT("" == scheme); + CU_ASSERT("" == authority); + CU_ASSERT("/b/?q=a" == path); +} + +void test_http2_contains_trailers(void) { + CU_ASSERT(!http2::contains_trailers(StringRef::from_lit(""))); + CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers"))); + // Match must be case-insensitive. + CU_ASSERT(http2::contains_trailers(StringRef::from_lit("TRAILERS"))); + CU_ASSERT(!http2::contains_trailers(StringRef::from_lit("trailer"))); + CU_ASSERT(!http2::contains_trailers(StringRef::from_lit("trailers 3"))); + CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers,"))); + CU_ASSERT(http2::contains_trailers(StringRef::from_lit("trailers,foo"))); + CU_ASSERT(http2::contains_trailers(StringRef::from_lit("foo,trailers"))); + CU_ASSERT(http2::contains_trailers(StringRef::from_lit("foo,trailers,bar"))); + CU_ASSERT( + http2::contains_trailers(StringRef::from_lit("foo, trailers ,bar"))); + CU_ASSERT(http2::contains_trailers(StringRef::from_lit(",trailers"))); +} + +void test_http2_check_transfer_encoding(void) { + CU_ASSERT(http2::check_transfer_encoding(StringRef::from_lit("chunked"))); + CU_ASSERT(http2::check_transfer_encoding(StringRef::from_lit("foo,chunked"))); + CU_ASSERT( + http2::check_transfer_encoding(StringRef::from_lit("foo, chunked"))); + CU_ASSERT( + http2::check_transfer_encoding(StringRef::from_lit("foo , chunked"))); + CU_ASSERT( + http2::check_transfer_encoding(StringRef::from_lit("chunked;foo=bar"))); + CU_ASSERT( + http2::check_transfer_encoding(StringRef::from_lit("chunked ; foo=bar"))); + CU_ASSERT(http2::check_transfer_encoding( + StringRef::from_lit(R"(chunked;foo="bar")"))); + CU_ASSERT(http2::check_transfer_encoding( + StringRef::from_lit(R"(chunked;foo="\bar\"";FOO=BAR)"))); + CU_ASSERT( + http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo="")"))); + CU_ASSERT(http2::check_transfer_encoding( + StringRef::from_lit(R"(chunked;foo="bar" , gzip)"))); + + CU_ASSERT(!http2::check_transfer_encoding(StringRef{})); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(",chunked"))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked,"))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked, "))); + CU_ASSERT( + !http2::check_transfer_encoding(StringRef::from_lit("foo,,chunked"))); + CU_ASSERT( + !http2::check_transfer_encoding(StringRef::from_lit("chunked;foo"))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked;"))); + CU_ASSERT( + !http2::check_transfer_encoding(StringRef::from_lit("chunked;foo=bar;"))); + CU_ASSERT( + !http2::check_transfer_encoding(StringRef::from_lit("chunked;?=bar"))); + CU_ASSERT( + !http2::check_transfer_encoding(StringRef::from_lit("chunked;=bar"))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked;;"))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("chunked?"))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(","))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(" "))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit(";"))); + CU_ASSERT(!http2::check_transfer_encoding(StringRef::from_lit("\""))); + CU_ASSERT(!http2::check_transfer_encoding( + StringRef::from_lit(R"(chunked;foo="bar)"))); + CU_ASSERT(!http2::check_transfer_encoding( + StringRef::from_lit(R"(chunked;foo="bar\)"))); + CU_ASSERT( + !http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo="bar\)" + "\x0a" + R"(")"))); + CU_ASSERT( + !http2::check_transfer_encoding(StringRef::from_lit(R"(chunked;foo=")" + "\x0a" + R"(")"))); + CU_ASSERT(!http2::check_transfer_encoding( + StringRef::from_lit(R"(chunked;foo="bar",,gzip)"))); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/http2_test.h b/lib/nghttp2/src/http2_test.h new file mode 100644 index 00000000000..382470d471f --- /dev/null +++ b/lib/nghttp2/src/http2_test.h @@ -0,0 +1,54 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTP2_TEST_H +#define SHRPX_HTTP2_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_http2_add_header(void); +void test_http2_get_header(void); +void test_http2_copy_headers_to_nva(void); +void test_http2_build_http1_headers_from_headers(void); +void test_http2_lws(void); +void test_http2_rewrite_location_uri(void); +void test_http2_parse_http_status_code(void); +void test_http2_index_header(void); +void test_http2_lookup_token(void); +void test_http2_parse_link_header(void); +void test_http2_path_join(void); +void test_http2_normalize_path(void); +void test_http2_rewrite_clean_path(void); +void test_http2_get_pure_path_component(void); +void test_http2_construct_push_component(void); +void test_http2_contains_trailers(void); +void test_http2_check_transfer_encoding(void); + +} // namespace shrpx + +#endif // SHRPX_HTTP2_TEST_H diff --git a/lib/nghttp2/src/http3.cc b/lib/nghttp2/src/http3.cc new file mode 100644 index 00000000000..61134ad74dc --- /dev/null +++ b/lib/nghttp2/src/http3.cc @@ -0,0 +1,206 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 "http3.h" + +namespace nghttp2 { + +namespace http3 { + +namespace { +nghttp3_nv make_nv_internal(const std::string &name, const std::string &value, + bool never_index, uint8_t nv_flags) { + uint8_t flags; + + flags = nv_flags | + (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE); + + return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), + value.size(), flags}; +} +} // namespace + +namespace { +nghttp3_nv make_nv_internal(const StringRef &name, const StringRef &value, + bool never_index, uint8_t nv_flags) { + uint8_t flags; + + flags = nv_flags | + (never_index ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE); + + return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(), + value.size(), flags}; +} +} // namespace + +nghttp3_nv make_nv(const std::string &name, const std::string &value, + bool never_index) { + return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE); +} + +nghttp3_nv make_nv(const StringRef &name, const StringRef &value, + bool never_index) { + return make_nv_internal(name, value, never_index, NGHTTP3_NV_FLAG_NONE); +} + +nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool never_index) { + return make_nv_internal(name, value, never_index, + NGHTTP3_NV_FLAG_NO_COPY_NAME | + NGHTTP3_NV_FLAG_NO_COPY_VALUE); +} + +nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value, + bool never_index) { + return make_nv_internal(name, value, never_index, + NGHTTP3_NV_FLAG_NO_COPY_NAME | + NGHTTP3_NV_FLAG_NO_COPY_VALUE); +} + +namespace { +void copy_headers_to_nva_internal(std::vector &nva, + const HeaderRefs &headers, uint8_t nv_flags, + uint32_t flags) { + auto it_forwarded = std::end(headers); + auto it_xff = std::end(headers); + auto it_xfp = std::end(headers); + auto it_via = std::end(headers); + + for (auto it = std::begin(headers); it != std::end(headers); ++it) { + auto kv = &(*it); + if (kv->name.empty() || kv->name[0] == ':') { + continue; + } + switch (kv->token) { + case http2::HD_COOKIE: + case http2::HD_CONNECTION: + case http2::HD_HOST: + case http2::HD_HTTP2_SETTINGS: + case http2::HD_KEEP_ALIVE: + case http2::HD_PROXY_CONNECTION: + case http2::HD_SERVER: + case http2::HD_TE: + case http2::HD_TRANSFER_ENCODING: + case http2::HD_UPGRADE: + continue; + case http2::HD_EARLY_DATA: + if (flags & http2::HDOP_STRIP_EARLY_DATA) { + continue; + } + break; + case http2::HD_SEC_WEBSOCKET_ACCEPT: + if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) { + continue; + } + break; + case http2::HD_SEC_WEBSOCKET_KEY: + if (flags & http2::HDOP_STRIP_SEC_WEBSOCKET_KEY) { + continue; + } + break; + case http2::HD_FORWARDED: + if (flags & http2::HDOP_STRIP_FORWARDED) { + continue; + } + + if (it_forwarded == std::end(headers)) { + it_forwarded = it; + continue; + } + + kv = &(*it_forwarded); + it_forwarded = it; + break; + case http2::HD_X_FORWARDED_FOR: + if (flags & http2::HDOP_STRIP_X_FORWARDED_FOR) { + continue; + } + + if (it_xff == std::end(headers)) { + it_xff = it; + continue; + } + + kv = &(*it_xff); + it_xff = it; + break; + case http2::HD_X_FORWARDED_PROTO: + if (flags & http2::HDOP_STRIP_X_FORWARDED_PROTO) { + continue; + } + + if (it_xfp == std::end(headers)) { + it_xfp = it; + continue; + } + + kv = &(*it_xfp); + it_xfp = it; + break; + case http2::HD_VIA: + if (flags & http2::HDOP_STRIP_VIA) { + continue; + } + + if (it_via == std::end(headers)) { + it_via = it; + continue; + } + + kv = &(*it_via); + it_via = it; + break; + } + nva.push_back( + make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags)); + } +} +} // namespace + +void copy_headers_to_nva(std::vector &nva, + const HeaderRefs &headers, uint32_t flags) { + copy_headers_to_nva_internal(nva, headers, NGHTTP3_NV_FLAG_NONE, flags); +} + +void copy_headers_to_nva_nocopy(std::vector &nva, + const HeaderRefs &headers, uint32_t flags) { + copy_headers_to_nva_internal( + nva, headers, + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE, flags); +} + +int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value, + size_t valuelen) { + if (!nghttp3_check_header_name(name, namelen)) { + return 0; + } + if (!nghttp3_check_header_value(value, valuelen)) { + return 0; + } + return 1; +} + +} // namespace http3 + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/http3.h b/lib/nghttp2/src/http3.h new file mode 100644 index 00000000000..81ee0d732d9 --- /dev/null +++ b/lib/nghttp2/src/http3.h @@ -0,0 +1,123 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 HTTP3_H +#define HTTP3_H + +#include "nghttp2_config.h" + +#include +#include +#include + +#include + +#include "http2.h" +#include "template.h" + +namespace nghttp2 { + +namespace http3 { + +// Creates nghttp3_nv using |name| and |value| and returns it. The +// returned value only references the data pointer to name.c_str() and +// value.c_str(). If |no_index| is true, nghttp3_nv flags member has +// NGHTTP3_NV_FLAG_NEVER_INDEX flag set. +nghttp3_nv make_nv(const std::string &name, const std::string &value, + bool never_index = false); + +nghttp3_nv make_nv(const StringRef &name, const StringRef &value, + bool never_index = false); + +nghttp3_nv make_nv_nocopy(const std::string &name, const std::string &value, + bool never_index = false); + +nghttp3_nv make_nv_nocopy(const StringRef &name, const StringRef &value, + bool never_index = false); + +// Create nghttp3_nv from string literal |name| and |value|. +template +constexpr nghttp3_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1, + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE}; +} + +// Create nghttp3_nv from string literal |name| and c-string |value|. +template +nghttp3_nv make_nv_lc(const char (&name)[N], const char *value) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), + NGHTTP3_NV_FLAG_NO_COPY_NAME}; +} + +template +nghttp3_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) { + return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value), + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE}; +} + +// Create nghttp3_nv from string literal |name| and std::string +// |value|. +template +nghttp3_nv make_nv_ls(const char (&name)[N], const std::string &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP3_NV_FLAG_NO_COPY_NAME}; +} + +template +nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE}; +} + +template +nghttp3_nv make_nv_ls_nocopy(const char (&name)[N], const StringRef &value) { + return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(), + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE}; +} + +// Appends headers in |headers| to |nv|. |headers| must be indexed +// before this call (its element's token field is assigned). Certain +// headers, including disallowed headers in HTTP/3 spec and headers +// which require special handling (i.e. via), are not copied. |flags| +// is one or more of HeaderBuildOp flags. They tell function that +// certain header fields should not be added. +void copy_headers_to_nva(std::vector &nva, + const HeaderRefs &headers, uint32_t flags); + +// Just like copy_headers_to_nva(), but this adds +// NGHTTP3_NV_FLAG_NO_COPY_NAME and NGHTTP3_NV_FLAG_NO_COPY_VALUE. +void copy_headers_to_nva_nocopy(std::vector &nva, + const HeaderRefs &headers, uint32_t flags); + +// Checks the header name/value pair using nghttp3_check_header_name() +// and nghttp3_check_header_value(). If both function returns nonzero, +// this function returns nonzero. +int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value, + size_t valuelen); + +} // namespace http3 + +} // namespace nghttp2 + +#endif // HTTP3_H diff --git a/lib/nghttp2/src/inflatehd.cc b/lib/nghttp2/src/inflatehd.cc new file mode 100644 index 00000000000..f484042a3aa --- /dev/null +++ b/lib/nghttp2/src/inflatehd.cc @@ -0,0 +1,289 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 +#endif // HAVE_CONFIG_H + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "template.h" +#include "comp_helper.h" + +namespace nghttp2 { + +typedef struct { + int dump_header_table; +} inflate_config; + +static inflate_config config; + +static uint8_t to_ud(char c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A' + 10; + } else if (c >= 'a' && c <= 'z') { + return c - 'a' + 10; + } else { + return c - '0'; + } +} + +static void decode_hex(uint8_t *dest, const char *src, size_t len) { + size_t i; + for (i = 0; i < len; i += 2) { + *dest++ = to_ud(src[i]) << 4 | to_ud(src[i + 1]); + } +} + +static void to_json(nghttp2_hd_inflater *inflater, json_t *headers, + json_t *wire, int seq, size_t old_settings_table_size) { + auto obj = json_object(); + json_object_set_new(obj, "seq", json_integer(seq)); + json_object_set(obj, "wire", wire); + json_object_set(obj, "headers", headers); + auto max_dyn_table_size = + nghttp2_hd_inflate_get_max_dynamic_table_size(inflater); + if (old_settings_table_size != max_dyn_table_size) { + json_object_set_new(obj, "header_table_size", + json_integer(max_dyn_table_size)); + } + if (config.dump_header_table) { + json_object_set_new(obj, "header_table", + dump_inflate_header_table(inflater)); + } + json_dumpf(obj, stdout, JSON_INDENT(2) | JSON_PRESERVE_ORDER); + json_decref(obj); + printf("\n"); +} + +static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) { + ssize_t rv; + nghttp2_nv nv; + int inflate_flags; + size_t old_settings_table_size = + nghttp2_hd_inflate_get_max_dynamic_table_size(inflater); + + auto wire = json_object_get(obj, "wire"); + + if (wire == nullptr) { + fprintf(stderr, "'wire' key is missing at %d\n", seq); + return -1; + } + + if (!json_is_string(wire)) { + fprintf(stderr, "'wire' value is not string at %d\n", seq); + return -1; + } + + auto table_size = json_object_get(obj, "header_table_size"); + + if (table_size) { + if (!json_is_integer(table_size)) { + fprintf(stderr, + "The value of 'header_table_size key' is not integer at %d\n", + seq); + return -1; + } + rv = nghttp2_hd_inflate_change_table_size(inflater, + json_integer_value(table_size)); + if (rv != 0) { + fprintf(stderr, + "nghttp2_hd_change_table_size() failed with error %s at %d\n", + nghttp2_strerror(rv), seq); + return -1; + } + } + + auto inputlen = strlen(json_string_value(wire)); + + if (inputlen & 1) { + fprintf(stderr, "Badly formatted output value at %d\n", seq); + exit(EXIT_FAILURE); + } + + auto buflen = inputlen / 2; + auto buf = std::vector(buflen); + + decode_hex(buf.data(), json_string_value(wire), inputlen); + + auto headers = json_array(); + + auto p = buf.data(); + for (;;) { + inflate_flags = 0; + rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, p, buflen, 1); + if (rv < 0) { + fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq); + exit(EXIT_FAILURE); + } + p += rv; + buflen -= rv; + if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + json_array_append_new( + headers, dump_header(nv.name, nv.namelen, nv.value, nv.valuelen)); + } + if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + break; + } + } + assert(buflen == 0); + nghttp2_hd_inflate_end_headers(inflater); + to_json(inflater, headers, wire, seq, old_settings_table_size); + json_decref(headers); + + return 0; +} + +static int perform(void) { + nghttp2_hd_inflater *inflater = nullptr; + json_error_t error; + + auto json = json_loadf(stdin, 0, &error); + + if (json == nullptr) { + fprintf(stderr, "JSON loading failed\n"); + exit(EXIT_FAILURE); + } + + auto cases = json_object_get(json, "cases"); + + if (cases == nullptr) { + fprintf(stderr, "Missing 'cases' key in root object\n"); + exit(EXIT_FAILURE); + } + + if (!json_is_array(cases)) { + fprintf(stderr, "'cases' must be JSON array\n"); + exit(EXIT_FAILURE); + } + + nghttp2_hd_inflate_new(&inflater); + output_json_header(); + auto len = json_array_size(cases); + + for (size_t i = 0; i < len; ++i) { + auto obj = json_array_get(cases, i); + if (!json_is_object(obj)) { + fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i); + continue; + } + if (inflate_hd(obj, inflater, i) != 0) { + continue; + } + if (i + 1 < len) { + printf(",\n"); + } + } + output_json_footer(); + nghttp2_hd_inflate_del(inflater); + json_decref(json); + + return 0; +} + +static void print_help(void) { + std::cout << R"(HPACK HTTP/2 header decoder +Usage: inflatehd [OPTIONS] < INPUT + +Reads JSON data from stdin and outputs inflated name/value pairs in +JSON. + +The root JSON object must contain "context" key, which indicates which +compression context is used. If it is "request", request compression +context is used. Otherwise, response compression context is used. +The value of "cases" key contains the sequence of compressed header +block. They share the same compression context and are processed in +the order they appear. Each item in the sequence is a JSON object and +it must have at least "wire" key. Its value is a string containing +compressed header block in hex string. + +Example: + +{ + "context": "request", + "cases": + [ + { "wire": "0284f77778ff" }, + { "wire": "0185fafd3c3c7f81" } + ] +} + +The output of this program can be used as input for deflatehd. + +OPTIONS: + -d, --dump-header-table + Output dynamic header table.)" + << std::endl; + ; +} + +constexpr static struct option long_options[] = { + {"dump-header-table", no_argument, nullptr, 'd'}, {nullptr, 0, nullptr, 0}}; + +int main(int argc, char **argv) { + config.dump_header_table = 0; + while (1) { + int option_index = 0; + int c = getopt_long(argc, argv, "dh", long_options, &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'h': + print_help(); + exit(EXIT_SUCCESS); + case 'd': + // --dump-header-table + config.dump_header_table = 1; + break; + case '?': + exit(EXIT_FAILURE); + default: + break; + } + } + perform(); + return 0; +} + +} // namespace nghttp2 + +int main(int argc, char **argv) { + return nghttp2::run_app(nghttp2::main, argc, argv); +} diff --git a/lib/nghttp2/src/libevent_util.cc b/lib/nghttp2/src/libevent_util.cc new file mode 100644 index 00000000000..3b60b6d9dfb --- /dev/null +++ b/lib/nghttp2/src/libevent_util.cc @@ -0,0 +1,162 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "libevent_util.h" + +#include +#include + +namespace nghttp2 { + +namespace util { + +EvbufferBuffer::EvbufferBuffer() + : evbuffer_(nullptr), + bucket_(nullptr), + buf_(nullptr), + bufmax_(0), + buflen_(0), + limit_(0), + writelen_(0) {} + +EvbufferBuffer::EvbufferBuffer(evbuffer *evbuffer, uint8_t *buf, size_t bufmax, + ssize_t limit) + : evbuffer_(evbuffer), + bucket_(limit == -1 ? nullptr : evbuffer_new()), + buf_(buf), + bufmax_(bufmax), + buflen_(0), + limit_(limit), + writelen_(0) {} + +void EvbufferBuffer::reset(evbuffer *evbuffer, uint8_t *buf, size_t bufmax, + ssize_t limit) { + evbuffer_ = evbuffer; + buf_ = buf; + if (limit != -1 && !bucket_) { + bucket_ = evbuffer_new(); + } + bufmax_ = bufmax; + buflen_ = 0; + limit_ = limit; + writelen_ = 0; +} + +EvbufferBuffer::~EvbufferBuffer() { + if (bucket_) { + evbuffer_free(bucket_); + } +} + +int EvbufferBuffer::write_buffer() { + for (auto pos = buf_, end = buf_ + buflen_; pos < end;) { + // To avoid merging chunks in evbuffer, we first add to temporal + // buffer bucket_ and then move its chain to evbuffer_. + auto nwrite = std::min(end - pos, limit_); + auto rv = evbuffer_add(bucket_, pos, nwrite); + if (rv == -1) { + return -1; + } + rv = evbuffer_add_buffer(evbuffer_, bucket_); + if (rv == -1) { + return -1; + } + pos += nwrite; + } + return 0; +} + +int EvbufferBuffer::flush() { + int rv; + if (buflen_ > 0) { + if (limit_ == -1) { + rv = evbuffer_add(evbuffer_, buf_, buflen_); + } else { + rv = write_buffer(); + } + if (rv == -1) { + return -1; + } + writelen_ += buflen_; + buflen_ = 0; + } + return 0; +} + +int EvbufferBuffer::add(const uint8_t *data, size_t datalen) { + int rv; + if (buflen_ + datalen > bufmax_) { + if (buflen_ > 0) { + if (limit_ == -1) { + rv = evbuffer_add(evbuffer_, buf_, buflen_); + } else { + rv = write_buffer(); + } + if (rv == -1) { + return -1; + } + writelen_ += buflen_; + buflen_ = 0; + } + if (datalen > bufmax_) { + if (limit_ == -1) { + rv = evbuffer_add(evbuffer_, data, datalen); + } else { + rv = write_buffer(); + } + if (rv == -1) { + return -1; + } + writelen_ += buflen_; + return 0; + } + } + memcpy(buf_ + buflen_, data, datalen); + buflen_ += datalen; + return 0; +} + +size_t EvbufferBuffer::get_buflen() const { return buflen_; } + +size_t EvbufferBuffer::get_writelen() const { return writelen_; } + +void bev_enable_unless(bufferevent *bev, int events) { + if ((bufferevent_get_enabled(bev) & events) == events) { + return; + } + + bufferevent_enable(bev, events); +} + +void bev_disable_unless(bufferevent *bev, int events) { + if ((bufferevent_get_enabled(bev) & events) == 0) { + return; + } + + bufferevent_disable(bev, events); +} + +} // namespace util + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/libevent_util.h b/lib/nghttp2/src/libevent_util.h new file mode 100644 index 00000000000..1d1ee91c422 --- /dev/null +++ b/lib/nghttp2/src/libevent_util.h @@ -0,0 +1,75 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 LIBEVENT_UTIL_H +#define LIBEVENT_UTIL_H + +#include "nghttp2_config.h" + +#include +#include + +namespace nghttp2 { + +namespace util { + +class EvbufferBuffer { +public: + EvbufferBuffer(); + // If |limit| is not -1, at most min(limit, bufmax) size bytes are + // added to evbuffer_. + EvbufferBuffer(evbuffer *evbuffer, uint8_t *buf, size_t bufmax, + ssize_t limit = -1); + ~EvbufferBuffer(); + void reset(evbuffer *evbuffer, uint8_t *buf, size_t bufmax, + ssize_t limit = -1); + int flush(); + int add(const uint8_t *data, size_t datalen); + size_t get_buflen() const; + int write_buffer(); + // Returns the number of written bytes to evbuffer_ so far. reset() + // resets this value to 0. + size_t get_writelen() const; + +private: + evbuffer *evbuffer_; + evbuffer *bucket_; + uint8_t *buf_; + size_t bufmax_; + size_t buflen_; + ssize_t limit_; + size_t writelen_; +}; + +// These functions are provided to reduce epoll_ctl syscall. Avoid +// calling bufferevent_enable/disable() unless it is required by +// sniffing current enabled events. +void bev_enable_unless(bufferevent *bev, int events); +void bev_disable_unless(bufferevent *bev, int events); + +} // namespace util + +} // namespace nghttp2 + +#endif // LIBEVENT_UTIL_H diff --git a/lib/nghttp2/src/memchunk.h b/lib/nghttp2/src/memchunk.h new file mode 100644 index 00000000000..7a7f2e9b056 --- /dev/null +++ b/lib/nghttp2/src/memchunk.h @@ -0,0 +1,664 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 MEMCHUNK_H +#define MEMCHUNK_H + +#include "nghttp2_config.h" + +#include +#ifdef _WIN32 +/* Structure for scatter/gather I/O. */ +struct iovec { + void *iov_base; /* Pointer to data. */ + size_t iov_len; /* Length of data. */ +}; +#else // !_WIN32 +# include +#endif // !_WIN32 + +#include +#include +#include +#include +#include +#include +#include + +#include "template.h" + +namespace nghttp2 { + +#define DEFAULT_WR_IOVCNT 16 + +#if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT +# define MAX_WR_IOVCNT IOV_MAX +#else // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT +# define MAX_WR_IOVCNT DEFAULT_WR_IOVCNT +#endif // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT + +template struct Memchunk { + Memchunk(Memchunk *next_chunk) + : pos(std::begin(buf)), last(pos), knext(next_chunk), next(nullptr) {} + size_t len() const { return last - pos; } + size_t left() const { return std::end(buf) - last; } + void reset() { pos = last = std::begin(buf); } + std::array buf; + uint8_t *pos, *last; + Memchunk *knext; + Memchunk *next; + static const size_t size = N; +}; + +template struct Pool { + Pool() : pool(nullptr), freelist(nullptr), poolsize(0), freelistsize(0) {} + ~Pool() { clear(); } + T *get() { + if (freelist) { + auto m = freelist; + freelist = freelist->next; + m->next = nullptr; + m->reset(); + freelistsize -= T::size; + return m; + } + + pool = new T{pool}; + poolsize += T::size; + return pool; + } + void recycle(T *m) { + m->next = freelist; + freelist = m; + freelistsize += T::size; + } + void clear() { + freelist = nullptr; + freelistsize = 0; + for (auto p = pool; p;) { + auto knext = p->knext; + delete p; + p = knext; + } + pool = nullptr; + poolsize = 0; + } + using value_type = T; + T *pool; + T *freelist; + size_t poolsize; + size_t freelistsize; +}; + +template struct Memchunks { + Memchunks(Pool *pool) + : pool(pool), + head(nullptr), + tail(nullptr), + len(0), + mark(nullptr), + mark_pos(nullptr), + mark_offset(0) {} + Memchunks(const Memchunks &) = delete; + Memchunks(Memchunks &&other) noexcept + : pool{other.pool}, // keep other.pool + head{std::exchange(other.head, nullptr)}, + tail{std::exchange(other.tail, nullptr)}, + len{std::exchange(other.len, 0)}, + mark{std::exchange(other.mark, nullptr)}, + mark_pos{std::exchange(other.mark_pos, nullptr)}, + mark_offset{std::exchange(other.mark_offset, 0)} {} + Memchunks &operator=(const Memchunks &) = delete; + Memchunks &operator=(Memchunks &&other) noexcept { + if (this == &other) { + return *this; + } + + reset(); + + pool = other.pool; + head = std::exchange(other.head, nullptr); + tail = std::exchange(other.tail, nullptr); + len = std::exchange(other.len, 0); + mark = std::exchange(other.mark, nullptr); + mark_pos = std::exchange(other.mark_pos, nullptr); + mark_offset = std::exchange(other.mark_offset, 0); + + return *this; + } + ~Memchunks() { + if (!pool) { + return; + } + for (auto m = head; m;) { + auto next = m->next; + pool->recycle(m); + m = next; + } + } + size_t append(char c) { + if (!tail) { + head = tail = pool->get(); + } else if (tail->left() == 0) { + tail->next = pool->get(); + tail = tail->next; + } + *tail->last++ = c; + ++len; + return 1; + } + size_t append(const void *src, size_t count) { + if (count == 0) { + return 0; + } + + auto first = static_cast(src); + auto last = first + count; + + if (!tail) { + head = tail = pool->get(); + } + + for (;;) { + auto n = std::min(static_cast(last - first), tail->left()); + tail->last = std::copy_n(first, n, tail->last); + first += n; + len += n; + if (first == last) { + break; + } + + tail->next = pool->get(); + tail = tail->next; + } + + return count; + } + template size_t append(const char (&s)[N]) { + return append(s, N - 1); + } + size_t append(const std::string &s) { return append(s.c_str(), s.size()); } + size_t append(const StringRef &s) { return append(s.c_str(), s.size()); } + size_t append(const ImmutableString &s) { + return append(s.c_str(), s.size()); + } + size_t copy(Memchunks &dest) { + auto m = head; + while (m) { + dest.append(m->pos, m->len()); + m = m->next; + } + return len; + } + size_t remove(void *dest, size_t count) { + assert(mark == nullptr); + + if (!tail || count == 0) { + return 0; + } + + auto first = static_cast(dest); + auto last = first + count; + + auto m = head; + + while (m) { + auto next = m->next; + auto n = std::min(static_cast(last - first), m->len()); + + assert(m->len()); + first = std::copy_n(m->pos, n, first); + m->pos += n; + len -= n; + if (m->len() > 0) { + break; + } + pool->recycle(m); + m = next; + } + head = m; + if (head == nullptr) { + tail = nullptr; + } + + return first - static_cast(dest); + } + size_t remove(Memchunks &dest, size_t count) { + assert(mark == nullptr); + + if (!tail || count == 0) { + return 0; + } + + auto left = count; + auto m = head; + + while (m) { + auto next = m->next; + auto n = std::min(left, m->len()); + + assert(m->len()); + dest.append(m->pos, n); + m->pos += n; + len -= n; + left -= n; + if (m->len() > 0) { + break; + } + pool->recycle(m); + m = next; + } + head = m; + if (head == nullptr) { + tail = nullptr; + } + + return count - left; + } + size_t remove(Memchunks &dest) { + assert(pool == dest.pool); + assert(mark == nullptr); + + if (head == nullptr) { + return 0; + } + + auto n = len; + + if (dest.tail == nullptr) { + dest.head = head; + } else { + dest.tail->next = head; + } + + dest.tail = tail; + dest.len += len; + + head = tail = nullptr; + len = 0; + + return n; + } + size_t drain(size_t count) { + assert(mark == nullptr); + + auto ndata = count; + auto m = head; + while (m) { + auto next = m->next; + auto n = std::min(count, m->len()); + m->pos += n; + count -= n; + len -= n; + if (m->len() > 0) { + break; + } + + pool->recycle(m); + m = next; + } + head = m; + if (head == nullptr) { + tail = nullptr; + } + return ndata - count; + } + size_t drain_mark(size_t count) { + auto ndata = count; + auto m = head; + while (m) { + auto next = m->next; + auto n = std::min(count, m->len()); + m->pos += n; + count -= n; + len -= n; + mark_offset -= n; + + if (m->len() > 0) { + assert(mark != m || m->pos <= mark_pos); + break; + } + if (mark == m) { + assert(m->pos <= mark_pos); + + mark = nullptr; + mark_pos = nullptr; + mark_offset = 0; + } + + pool->recycle(m); + m = next; + } + head = m; + if (head == nullptr) { + tail = nullptr; + } + return ndata - count; + } + int riovec(struct iovec *iov, int iovcnt) const { + if (!head) { + return 0; + } + auto m = head; + int i; + for (i = 0; i < iovcnt && m; ++i, m = m->next) { + iov[i].iov_base = m->pos; + iov[i].iov_len = m->len(); + } + return i; + } + int riovec_mark(struct iovec *iov, int iovcnt) { + if (!head || iovcnt == 0) { + return 0; + } + + int i = 0; + Memchunk *m; + if (mark) { + if (mark_pos != mark->last) { + iov[0].iov_base = mark_pos; + iov[0].iov_len = mark->len() - (mark_pos - mark->pos); + + mark_pos = mark->last; + mark_offset += iov[0].iov_len; + i = 1; + } + m = mark->next; + } else { + i = 0; + m = head; + } + + for (; i < iovcnt && m; ++i, m = m->next) { + iov[i].iov_base = m->pos; + iov[i].iov_len = m->len(); + + mark = m; + mark_pos = m->last; + mark_offset += m->len(); + } + + return i; + } + size_t rleft() const { return len; } + size_t rleft_mark() const { return len - mark_offset; } + void reset() { + for (auto m = head; m;) { + auto next = m->next; + pool->recycle(m); + m = next; + } + len = 0; + head = tail = mark = nullptr; + mark_pos = nullptr; + mark_offset = 0; + } + + Pool *pool; + Memchunk *head, *tail; + size_t len; + Memchunk *mark; + uint8_t *mark_pos; + size_t mark_offset; +}; + +// Wrapper around Memchunks to offer "peeking" functionality. +template struct PeekMemchunks { + PeekMemchunks(Pool *pool) + : memchunks(pool), + cur(nullptr), + cur_pos(nullptr), + cur_last(nullptr), + len(0), + peeking(true) {} + PeekMemchunks(const PeekMemchunks &) = delete; + PeekMemchunks(PeekMemchunks &&other) noexcept + : memchunks{std::move(other.memchunks)}, + cur{std::exchange(other.cur, nullptr)}, + cur_pos{std::exchange(other.cur_pos, nullptr)}, + cur_last{std::exchange(other.cur_last, nullptr)}, + len{std::exchange(other.len, 0)}, + peeking{std::exchange(other.peeking, true)} {} + PeekMemchunks &operator=(const PeekMemchunks &) = delete; + PeekMemchunks &operator=(PeekMemchunks &&other) noexcept { + if (this == &other) { + return *this; + } + + memchunks = std::move(other.memchunks); + cur = std::exchange(other.cur, nullptr); + cur_pos = std::exchange(other.cur_pos, nullptr); + cur_last = std::exchange(other.cur_last, nullptr); + len = std::exchange(other.len, 0); + peeking = std::exchange(other.peeking, true); + + return *this; + } + size_t append(const void *src, size_t count) { + count = memchunks.append(src, count); + len += count; + return count; + } + size_t remove(void *dest, size_t count) { + if (!peeking) { + count = memchunks.remove(dest, count); + len -= count; + return count; + } + + if (count == 0 || len == 0) { + return 0; + } + + if (!cur) { + cur = memchunks.head; + cur_pos = cur->pos; + } + + // cur_last could be updated in append + cur_last = cur->last; + + if (cur_pos == cur_last) { + assert(cur->next); + cur = cur->next; + } + + auto first = static_cast(dest); + auto last = first + count; + + for (;;) { + auto n = std::min(last - first, cur_last - cur_pos); + + first = std::copy_n(cur_pos, n, first); + cur_pos += n; + len -= n; + + if (first == last) { + break; + } + assert(cur_pos == cur_last); + if (!cur->next) { + break; + } + cur = cur->next; + cur_pos = cur->pos; + cur_last = cur->last; + } + return first - static_cast(dest); + } + size_t rleft() const { return len; } + size_t rleft_buffered() const { return memchunks.rleft(); } + void disable_peek(bool drain) { + if (!peeking) { + return; + } + if (drain) { + auto n = rleft_buffered() - rleft(); + memchunks.drain(n); + assert(len == memchunks.rleft()); + } else { + len = memchunks.rleft(); + } + cur = nullptr; + cur_pos = cur_last = nullptr; + peeking = false; + } + void reset() { + memchunks.reset(); + cur = nullptr; + cur_pos = cur_last = nullptr; + len = 0; + peeking = true; + } + Memchunks memchunks; + // Pointer to the Memchunk currently we are reading/writing. + Memchunk *cur; + // Region inside cur, we have processed to cur_pos. + uint8_t *cur_pos, *cur_last; + // This is the length we have left unprocessed. len <= + // memchunk.rleft() must hold. + size_t len; + // true if peeking is enabled. Initially it is true. + bool peeking; +}; + +using Memchunk16K = Memchunk<16_k>; +using MemchunkPool = Pool; +using DefaultMemchunks = Memchunks; +using DefaultPeekMemchunks = PeekMemchunks; + +inline int limit_iovec(struct iovec *iov, int iovcnt, size_t max) { + if (max == 0) { + return 0; + } + for (int i = 0; i < iovcnt; ++i) { + auto d = std::min(max, iov[i].iov_len); + iov[i].iov_len = d; + max -= d; + if (max == 0) { + return i + 1; + } + } + return iovcnt; +} + +// MemchunkBuffer is similar to Buffer, but it uses pooled Memchunk +// for its underlying buffer. +template struct MemchunkBuffer { + MemchunkBuffer(Pool *pool) : pool(pool), chunk(nullptr) {} + MemchunkBuffer(const MemchunkBuffer &) = delete; + MemchunkBuffer(MemchunkBuffer &&other) noexcept + : pool(other.pool), chunk(other.chunk) { + other.chunk = nullptr; + } + MemchunkBuffer &operator=(const MemchunkBuffer &) = delete; + MemchunkBuffer &operator=(MemchunkBuffer &&other) noexcept { + if (this == &other) { + return *this; + } + + pool = other.pool; + chunk = other.chunk; + + other.chunk = nullptr; + + return *this; + } + + ~MemchunkBuffer() { + if (!pool || !chunk) { + return; + } + pool->recycle(chunk); + } + + // Ensures that the underlying buffer is allocated. + void ensure_chunk() { + if (chunk) { + return; + } + chunk = pool->get(); + } + + // Releases the underlying buffer. + void release_chunk() { + if (!chunk) { + return; + } + pool->recycle(chunk); + chunk = nullptr; + } + + // Returns true if the underlying buffer is allocated. + bool chunk_avail() const { return chunk != nullptr; } + + // The functions below must be called after the underlying buffer is + // allocated (use ensure_chunk). + + // MemchunkBuffer provides the same interface functions with Buffer. + // Since we has chunk as a member variable, pos and last are + // implemented as wrapper functions. + + uint8_t *pos() const { return chunk->pos; } + uint8_t *last() const { return chunk->last; } + + size_t rleft() const { return chunk->len(); } + size_t wleft() const { return chunk->left(); } + size_t write(const void *src, size_t count) { + count = std::min(count, wleft()); + auto p = static_cast(src); + chunk->last = std::copy_n(p, count, chunk->last); + return count; + } + size_t write(size_t count) { + count = std::min(count, wleft()); + chunk->last += count; + return count; + } + size_t drain(size_t count) { + count = std::min(count, rleft()); + chunk->pos += count; + return count; + } + size_t drain_reset(size_t count) { + count = std::min(count, rleft()); + std::copy(chunk->pos + count, chunk->last, std::begin(chunk->buf)); + chunk->last = std::begin(chunk->buf) + (chunk->last - (chunk->pos + count)); + chunk->pos = std::begin(chunk->buf); + return count; + } + void reset() { chunk->reset(); } + uint8_t *begin() { return std::begin(chunk->buf); } + uint8_t &operator[](size_t n) { return chunk->buf[n]; } + const uint8_t &operator[](size_t n) const { return chunk->buf[n]; } + + Pool *pool; + Memchunk *chunk; +}; + +using DefaultMemchunkBuffer = MemchunkBuffer; + +} // namespace nghttp2 + +#endif // MEMCHUNK_H diff --git a/lib/nghttp2/src/memchunk_test.cc b/lib/nghttp2/src/memchunk_test.cc new file mode 100644 index 00000000000..236d9ea4135 --- /dev/null +++ b/lib/nghttp2/src/memchunk_test.cc @@ -0,0 +1,340 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "memchunk_test.h" + +#include + +#include + +#include "memchunk.h" +#include "util.h" + +namespace nghttp2 { + +void test_pool_recycle(void) { + MemchunkPool pool; + + CU_ASSERT(!pool.pool); + CU_ASSERT(0 == pool.poolsize); + CU_ASSERT(nullptr == pool.freelist); + + auto m1 = pool.get(); + + CU_ASSERT(m1 == pool.pool); + CU_ASSERT(MemchunkPool::value_type::size == pool.poolsize); + CU_ASSERT(nullptr == pool.freelist); + + auto m2 = pool.get(); + + CU_ASSERT(m2 == pool.pool); + CU_ASSERT(2 * MemchunkPool::value_type::size == pool.poolsize); + CU_ASSERT(nullptr == pool.freelist); + CU_ASSERT(m1 == m2->knext); + CU_ASSERT(nullptr == m1->knext); + + auto m3 = pool.get(); + + CU_ASSERT(m3 == pool.pool); + CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize); + CU_ASSERT(nullptr == pool.freelist); + + pool.recycle(m3); + + CU_ASSERT(m3 == pool.pool); + CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize); + CU_ASSERT(m3 == pool.freelist); + + auto m4 = pool.get(); + + CU_ASSERT(m3 == m4); + CU_ASSERT(m4 == pool.pool); + CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize); + CU_ASSERT(nullptr == pool.freelist); + + pool.recycle(m2); + pool.recycle(m1); + + CU_ASSERT(m1 == pool.freelist); + CU_ASSERT(m2 == m1->next); + CU_ASSERT(nullptr == m2->next); +} + +using Memchunk16 = Memchunk<16>; +using MemchunkPool16 = Pool; +using Memchunks16 = Memchunks; +using PeekMemchunks16 = PeekMemchunks; + +void test_memchunks_append(void) { + MemchunkPool16 pool; + Memchunks16 chunks(&pool); + + chunks.append("012"); + + auto m = chunks.tail; + + CU_ASSERT(3 == m->len()); + CU_ASSERT(13 == m->left()); + + chunks.append("3456789abcdef@"); + + CU_ASSERT(16 == m->len()); + CU_ASSERT(0 == m->left()); + + m = chunks.tail; + + CU_ASSERT(1 == m->len()); + CU_ASSERT(15 == m->left()); + CU_ASSERT(17 == chunks.rleft()); + + char buf[16]; + size_t nread; + + nread = chunks.remove(buf, 8); + + CU_ASSERT(8 == nread); + CU_ASSERT(0 == memcmp("01234567", buf, nread)); + CU_ASSERT(9 == chunks.rleft()); + + nread = chunks.remove(buf, sizeof(buf)); + + CU_ASSERT(9 == nread); + CU_ASSERT(0 == memcmp("89abcdef@", buf, nread)); + CU_ASSERT(0 == chunks.rleft()); + CU_ASSERT(nullptr == chunks.head); + CU_ASSERT(nullptr == chunks.tail); + CU_ASSERT(32 == pool.poolsize); +} + +void test_memchunks_drain(void) { + MemchunkPool16 pool; + Memchunks16 chunks(&pool); + + chunks.append("0123456789"); + + size_t nread; + + nread = chunks.drain(3); + + CU_ASSERT(3 == nread); + + char buf[16]; + + nread = chunks.remove(buf, sizeof(buf)); + + CU_ASSERT(7 == nread); + CU_ASSERT(0 == memcmp("3456789", buf, nread)); +} + +void test_memchunks_riovec(void) { + MemchunkPool16 pool; + Memchunks16 chunks(&pool); + + std::array buf{}; + + chunks.append(buf.data(), buf.size()); + + std::array iov; + auto iovcnt = chunks.riovec(iov.data(), iov.size()); + + auto m = chunks.head; + + CU_ASSERT(2 == iovcnt); + CU_ASSERT(m->buf.data() == iov[0].iov_base); + CU_ASSERT(m->len() == iov[0].iov_len); + + m = m->next; + + CU_ASSERT(m->buf.data() == iov[1].iov_base); + CU_ASSERT(m->len() == iov[1].iov_len); + + chunks.drain(2 * 16); + + iovcnt = chunks.riovec(iov.data(), iov.size()); + + CU_ASSERT(1 == iovcnt); + + m = chunks.head; + CU_ASSERT(m->buf.data() == iov[0].iov_base); + CU_ASSERT(m->len() == iov[0].iov_len); +} + +void test_memchunks_recycle(void) { + MemchunkPool16 pool; + { + Memchunks16 chunks(&pool); + std::array buf{}; + chunks.append(buf.data(), buf.size()); + } + CU_ASSERT(32 == pool.poolsize); + CU_ASSERT(nullptr != pool.freelist); + + auto m = pool.freelist; + m = m->next; + + CU_ASSERT(nullptr != m); + CU_ASSERT(nullptr == m->next); +} + +void test_memchunks_reset(void) { + MemchunkPool16 pool; + Memchunks16 chunks(&pool); + + std::array b{}; + + chunks.append(b.data(), b.size()); + + CU_ASSERT(32 == chunks.rleft()); + + chunks.reset(); + + CU_ASSERT(0 == chunks.rleft()); + CU_ASSERT(nullptr == chunks.head); + CU_ASSERT(nullptr == chunks.tail); + + auto m = pool.freelist; + + CU_ASSERT(nullptr != m); + CU_ASSERT(nullptr != m->next); + CU_ASSERT(nullptr == m->next->next); +} + +void test_peek_memchunks_append(void) { + MemchunkPool16 pool; + PeekMemchunks16 pchunks(&pool); + + std::array b{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', + }, + d; + + pchunks.append(b.data(), b.size()); + + CU_ASSERT(32 == pchunks.rleft()); + CU_ASSERT(32 == pchunks.rleft_buffered()); + + CU_ASSERT(0 == pchunks.remove(nullptr, 0)); + + CU_ASSERT(32 == pchunks.rleft()); + CU_ASSERT(32 == pchunks.rleft_buffered()); + + CU_ASSERT(12 == pchunks.remove(d.data(), 12)); + + CU_ASSERT(std::equal(std::begin(b), std::begin(b) + 12, std::begin(d))); + + CU_ASSERT(20 == pchunks.rleft()); + CU_ASSERT(32 == pchunks.rleft_buffered()); + + CU_ASSERT(20 == pchunks.remove(d.data(), d.size())); + + CU_ASSERT(std::equal(std::begin(b) + 12, std::end(b), std::begin(d))); + + CU_ASSERT(0 == pchunks.rleft()); + CU_ASSERT(32 == pchunks.rleft_buffered()); +} + +void test_peek_memchunks_disable_peek_drain(void) { + MemchunkPool16 pool; + PeekMemchunks16 pchunks(&pool); + + std::array b{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', + }, + d; + + pchunks.append(b.data(), b.size()); + + CU_ASSERT(12 == pchunks.remove(d.data(), 12)); + + pchunks.disable_peek(true); + + CU_ASSERT(!pchunks.peeking); + CU_ASSERT(20 == pchunks.rleft()); + CU_ASSERT(20 == pchunks.rleft_buffered()); + + CU_ASSERT(20 == pchunks.remove(d.data(), d.size())); + + CU_ASSERT(std::equal(std::begin(b) + 12, std::end(b), std::begin(d))); + + CU_ASSERT(0 == pchunks.rleft()); + CU_ASSERT(0 == pchunks.rleft_buffered()); +} + +void test_peek_memchunks_disable_peek_no_drain(void) { + MemchunkPool16 pool; + PeekMemchunks16 pchunks(&pool); + + std::array b{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', + }, + d; + + pchunks.append(b.data(), b.size()); + + CU_ASSERT(12 == pchunks.remove(d.data(), 12)); + + pchunks.disable_peek(false); + + CU_ASSERT(!pchunks.peeking); + CU_ASSERT(32 == pchunks.rleft()); + CU_ASSERT(32 == pchunks.rleft_buffered()); + + CU_ASSERT(32 == pchunks.remove(d.data(), d.size())); + + CU_ASSERT(std::equal(std::begin(b), std::end(b), std::begin(d))); + + CU_ASSERT(0 == pchunks.rleft()); + CU_ASSERT(0 == pchunks.rleft_buffered()); +} + +void test_peek_memchunks_reset(void) { + MemchunkPool16 pool; + PeekMemchunks16 pchunks(&pool); + + std::array b{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', + }, + d; + + pchunks.append(b.data(), b.size()); + + CU_ASSERT(12 == pchunks.remove(d.data(), 12)); + + pchunks.disable_peek(true); + pchunks.reset(); + + CU_ASSERT(0 == pchunks.rleft()); + CU_ASSERT(0 == pchunks.rleft_buffered()); + + CU_ASSERT(nullptr == pchunks.cur); + CU_ASSERT(nullptr == pchunks.cur_pos); + CU_ASSERT(nullptr == pchunks.cur_last); + CU_ASSERT(pchunks.peeking); +} + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/memchunk_test.h b/lib/nghttp2/src/memchunk_test.h new file mode 100644 index 00000000000..7d677e787ee --- /dev/null +++ b/lib/nghttp2/src/memchunk_test.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 MEMCHUNK_TEST_H +#define MEMCHUNK_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_pool_recycle(void); +void test_memchunks_append(void); +void test_memchunks_drain(void); +void test_memchunks_riovec(void); +void test_memchunks_recycle(void); +void test_memchunks_reset(void); +void test_peek_memchunks_append(void); +void test_peek_memchunks_disable_peek_drain(void); +void test_peek_memchunks_disable_peek_no_drain(void); +void test_peek_memchunks_reset(void); + +} // namespace nghttp2 + +#endif // MEMCHUNK_TEST_H diff --git a/lib/nghttp2/src/network.h b/lib/nghttp2/src/network.h new file mode 100644 index 00000000000..45311d84fb4 --- /dev/null +++ b/lib/nghttp2/src/network.h @@ -0,0 +1,67 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 +#endif // HAVE_CONFIG_H + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef _WIN32 +# include +#else // !_WIN32 +# include +#endif // !_WIN32 +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H + +namespace nghttp2 { + +union sockaddr_union { + sockaddr_storage storage; + sockaddr sa; + sockaddr_in6 in6; + sockaddr_in in; +#ifndef _WIN32 + sockaddr_un un; +#endif // !_WIN32 +}; + +struct Address { + size_t len; + union sockaddr_union su; +}; + +} // namespace nghttp2 + +#endif // NETWORK_H diff --git a/lib/nghttp2/src/nghttp.cc b/lib/nghttp2/src/nghttp.cc new file mode 100644 index 00000000000..cb6a3b15358 --- /dev/null +++ b/lib/nghttp2/src/nghttp.cc @@ -0,0 +1,3164 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp.h" + +#include +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_JANSSON +# include +#endif // HAVE_JANSSON + +#include "app_helper.h" +#include "HtmlParser.h" +#include "util.h" +#include "base64.h" +#include "tls.h" +#include "template.h" +#include "ssl_compat.h" + +#ifndef O_BINARY +# define O_BINARY (0) +#endif // O_BINARY + +namespace nghttp2 { + +// The anchor stream nodes when --no-dep is not used. The stream ID = +// 1 is excluded since it is used as first stream in upgrade case. We +// follows the same dependency anchor nodes as Firefox does. +struct Anchor { + int32_t stream_id; + // stream ID this anchor depends on + int32_t dep_stream_id; + // .. with this weight. + int32_t weight; +}; + +// This is index into anchors. Firefox uses ANCHOR_FOLLOWERS for html +// file. +enum { + ANCHOR_LEADERS, + ANCHOR_UNBLOCKED, + ANCHOR_BACKGROUND, + ANCHOR_SPECULATIVE, + ANCHOR_FOLLOWERS, +}; + +namespace { +constexpr auto anchors = std::array{{ + {3, 0, 201}, + {5, 0, 101}, + {7, 0, 1}, + {9, 7, 1}, + {11, 3, 1}, +}}; +} // namespace + +Config::Config() + : header_table_size(-1), + min_header_table_size(std::numeric_limits::max()), + encoder_header_table_size(-1), + padding(0), + max_concurrent_streams(100), + peer_max_concurrent_streams(100), + multiply(1), + timeout(0.), + window_bits(-1), + connection_window_bits(-1), + verbose(0), + port_override(0), + null_out(false), + remote_name(false), + get_assets(false), + stat(false), + upgrade(false), + continuation(false), + no_content_length(false), + no_dep(false), + hexdump(false), + no_push(false), + expect_continue(false), + verify_peer(true), + ktls(false), + no_rfc7540_pri(false) { + nghttp2_option_new(&http2_option); + nghttp2_option_set_peer_max_concurrent_streams(http2_option, + peer_max_concurrent_streams); + nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ALTSVC); + nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ORIGIN); +} + +Config::~Config() { nghttp2_option_del(http2_option); } + +namespace { +Config config; +} // namespace + +namespace { +void print_protocol_nego_error() { + std::cerr << "[ERROR] HTTP/2 protocol was not selected." + << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")" + << std::endl; +} +} // namespace + +namespace { +std::string strip_fragment(const char *raw_uri) { + const char *end; + for (end = raw_uri; *end && *end != '#'; ++end) + ; + size_t len = end - raw_uri; + return std::string(raw_uri, len); +} +} // namespace + +Request::Request(const std::string &uri, const http_parser_url &u, + const nghttp2_data_provider *data_prd, int64_t data_length, + const nghttp2_priority_spec &pri_spec, int level) + : uri(uri), + u(u), + pri_spec(pri_spec), + data_length(data_length), + data_offset(0), + response_len(0), + inflater(nullptr), + data_prd(data_prd), + header_buffer_size(0), + stream_id(-1), + status(0), + level(level), + expect_final_response(false) { + http2::init_hdidx(res_hdidx); + http2::init_hdidx(req_hdidx); +} + +Request::~Request() { nghttp2_gzip_inflate_del(inflater); } + +void Request::init_inflater() { + int rv; + // This is required with --disable-assert. + (void)rv; + rv = nghttp2_gzip_inflate_new(&inflater); + assert(rv == 0); +} + +StringRef Request::get_real_scheme() const { + return config.scheme_override.empty() + ? util::get_uri_field(uri.c_str(), u, UF_SCHEMA) + : StringRef{config.scheme_override}; +} + +StringRef Request::get_real_host() const { + return config.host_override.empty() + ? util::get_uri_field(uri.c_str(), u, UF_HOST) + : StringRef{config.host_override}; +} + +uint16_t Request::get_real_port() const { + auto scheme = get_real_scheme(); + return config.host_override.empty() ? util::has_uri_field(u, UF_PORT) ? u.port + : scheme == "https" ? 443 + : 80 + : config.port_override == 0 ? scheme == "https" ? 443 : 80 + : config.port_override; +} + +void Request::init_html_parser() { + // We crawl HTML using overridden scheme, host, and port. + auto scheme = get_real_scheme(); + auto host = get_real_host(); + auto port = get_real_port(); + auto ipv6_lit = + std::find(std::begin(host), std::end(host), ':') != std::end(host); + + auto base_uri = scheme.str(); + base_uri += "://"; + if (ipv6_lit) { + base_uri += '['; + } + base_uri += host; + if (ipv6_lit) { + base_uri += ']'; + } + if (!((scheme == "https" && port == 443) || + (scheme == "http" && port == 80))) { + base_uri += ':'; + base_uri += util::utos(port); + } + base_uri += util::get_uri_field(uri.c_str(), u, UF_PATH); + if (util::has_uri_field(u, UF_QUERY)) { + base_uri += '?'; + base_uri += util::get_uri_field(uri.c_str(), u, UF_QUERY); + } + + html_parser = std::make_unique(base_uri); +} + +int Request::update_html_parser(const uint8_t *data, size_t len, int fin) { + if (!html_parser) { + return 0; + } + return html_parser->parse_chunk(reinterpret_cast(data), len, + fin); +} + +std::string Request::make_reqpath() const { + std::string path = util::has_uri_field(u, UF_PATH) + ? util::get_uri_field(uri.c_str(), u, UF_PATH).str() + : "/"; + if (util::has_uri_field(u, UF_QUERY)) { + path += '?'; + path.append(uri.c_str() + u.field_data[UF_QUERY].off, + u.field_data[UF_QUERY].len); + } + return path; +} + +namespace { +// Perform special handling |host| if it is IPv6 literal and includes +// zone ID per RFC 6874. +std::string decode_host(const StringRef &host) { + auto zone_start = std::find(std::begin(host), std::end(host), '%'); + if (zone_start == std::end(host) || + !util::ipv6_numeric_addr( + std::string(std::begin(host), zone_start).c_str())) { + return host.str(); + } + // case: ::1% + if (zone_start + 1 == std::end(host)) { + return StringRef{host.c_str(), host.size() - 1}.str(); + } + // case: ::1%12 or ::1%1 + if (zone_start + 3 >= std::end(host)) { + return host.str(); + } + // If we see "%25", followed by more characters, then decode %25 as + // '%'. + auto zone_id_src = (*(zone_start + 1) == '2' && *(zone_start + 2) == '5') + ? zone_start + 3 + : zone_start + 1; + auto zone_id = util::percent_decode(zone_id_src, std::end(host)); + auto res = std::string(std::begin(host), zone_start + 1); + res += zone_id; + return res; +} +} // namespace + +namespace { +nghttp2_priority_spec resolve_dep(int res_type) { + nghttp2_priority_spec pri_spec; + + if (config.no_dep) { + nghttp2_priority_spec_default_init(&pri_spec); + + return pri_spec; + } + + int32_t anchor_id; + int32_t weight; + switch (res_type) { + case REQ_CSS: + case REQ_JS: + anchor_id = anchors[ANCHOR_LEADERS].stream_id; + weight = 32; + break; + case REQ_UNBLOCK_JS: + anchor_id = anchors[ANCHOR_UNBLOCKED].stream_id; + weight = 32; + break; + case REQ_IMG: + anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id; + weight = 12; + break; + default: + anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id; + weight = 32; + } + + nghttp2_priority_spec_init(&pri_spec, anchor_id, weight, 0); + return pri_spec; +} +} // namespace + +bool Request::is_ipv6_literal_addr() const { + if (util::has_uri_field(u, UF_HOST)) { + return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':', + u.field_data[UF_HOST].len); + } else { + return false; + } +} + +Headers::value_type *Request::get_res_header(int32_t token) { + auto idx = res_hdidx[token]; + if (idx == -1) { + return nullptr; + } + return &res_nva[idx]; +} + +Headers::value_type *Request::get_req_header(int32_t token) { + auto idx = req_hdidx[token]; + if (idx == -1) { + return nullptr; + } + return &req_nva[idx]; +} + +void Request::record_request_start_time() { + timing.state = RequestState::ON_REQUEST; + timing.request_start_time = get_time(); +} + +void Request::record_response_start_time() { + timing.state = RequestState::ON_RESPONSE; + timing.response_start_time = get_time(); +} + +void Request::record_response_end_time() { + timing.state = RequestState::ON_COMPLETE; + timing.response_end_time = get_time(); +} + +namespace { +void continue_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto client = static_cast(ev_userdata(loop)); + auto req = static_cast(w->data); + int error; + + error = nghttp2_submit_data(client->session, NGHTTP2_FLAG_END_STREAM, + req->stream_id, req->data_prd); + + if (error) { + std::cerr << "[ERROR] nghttp2_submit_data() returned error: " + << nghttp2_strerror(error) << std::endl; + nghttp2_submit_rst_stream(client->session, NGHTTP2_FLAG_NONE, + req->stream_id, NGHTTP2_INTERNAL_ERROR); + } + + client->signal_write(); +} +} // namespace + +ContinueTimer::ContinueTimer(struct ev_loop *loop, Request *req) : loop(loop) { + ev_timer_init(&timer, continue_timeout_cb, 1., 0.); + timer.data = req; +} + +ContinueTimer::~ContinueTimer() { stop(); } + +void ContinueTimer::start() { ev_timer_start(loop, &timer); } + +void ContinueTimer::stop() { ev_timer_stop(loop, &timer); } + +void ContinueTimer::dispatch_continue() { + // Only dispatch the timeout callback if it hasn't already been called. + if (ev_is_active(&timer)) { + ev_feed_event(loop, &timer, 0); + } +} + +namespace { +int htp_msg_begincb(llhttp_t *htp) { + if (config.verbose) { + print_timer(); + std::cout << " HTTP Upgrade response" << std::endl; + } + return 0; +} +} // namespace + +namespace { +int htp_msg_completecb(llhttp_t *htp) { + auto client = static_cast(htp->data); + client->upgrade_response_status_code = htp->status_code; + client->upgrade_response_complete = true; + return 0; +} +} // namespace + +namespace { +constexpr llhttp_settings_t htp_hooks = { + htp_msg_begincb, // llhttp_cb on_message_begin; + nullptr, // llhttp_data_cb on_url; + nullptr, // llhttp_data_cb on_status; + nullptr, // llhttp_data_cb on_method; + nullptr, // llhttp_data_cb on_version; + nullptr, // llhttp_data_cb on_header_field; + nullptr, // llhttp_data_cb on_header_value; + nullptr, // llhttp_data_cb on_chunk_extension_name; + nullptr, // llhttp_data_cb on_chunk_extension_value; + nullptr, // llhttp_cb on_headers_complete; + nullptr, // llhttp_data_cb on_body; + htp_msg_completecb, // llhttp_cb on_message_complete; + nullptr, // llhttp_cb on_url_complete; + nullptr, // llhttp_cb on_status_complete; + nullptr, // llhttp_cb on_method_complete; + nullptr, // llhttp_cb on_version_complete; + nullptr, // llhttp_cb on_header_field_complete; + nullptr, // llhttp_cb on_header_value_complete; + nullptr, // llhttp_cb on_chunk_extension_name_complete; + nullptr, // llhttp_cb on_chunk_extension_value_complete; + nullptr, // llhttp_cb on_chunk_header; + nullptr, // llhttp_cb on_chunk_complete; + nullptr, // llhttp_cb on_reset; +}; +} // namespace + +namespace { +int submit_request(HttpClient *client, const Headers &headers, Request *req) { + auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA); + auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"}, + {":path", req->make_reqpath()}, + {":scheme", scheme.str()}, + {":authority", client->hostport}, + {"accept", "*/*"}, + {"accept-encoding", "gzip, deflate"}, + {"user-agent", "nghttp2/" NGHTTP2_VERSION}}; + bool expect_continue = false; + + if (config.continuation) { + for (size_t i = 0; i < 6; ++i) { + build_headers.emplace_back("continuation-test-" + util::utos(i + 1), + std::string(4_k, '-')); + } + } + + auto num_initial_headers = build_headers.size(); + + if (req->data_prd) { + if (!config.no_content_length) { + build_headers.emplace_back("content-length", + util::utos(req->data_length)); + } + if (config.expect_continue) { + expect_continue = true; + build_headers.emplace_back("expect", "100-continue"); + } + } + + for (auto &kv : headers) { + size_t i; + for (i = 0; i < num_initial_headers; ++i) { + if (kv.name == build_headers[i].name) { + build_headers[i].value = kv.value; + break; + } + } + if (i < num_initial_headers) { + continue; + } + + build_headers.emplace_back(kv.name, kv.value, kv.no_index); + } + + auto nva = std::vector(); + nva.reserve(build_headers.size()); + + for (auto &kv : build_headers) { + nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); + } + + auto method = http2::get_header(build_headers, ":method"); + assert(method); + + req->method = method->value; + + std::string trailer_names; + if (!config.trailer.empty()) { + trailer_names = config.trailer[0].name; + for (size_t i = 1; i < config.trailer.size(); ++i) { + trailer_names += ", "; + trailer_names += config.trailer[i].name; + } + nva.push_back(http2::make_nv_ls("trailer", trailer_names)); + } + + int32_t stream_id; + + if (expect_continue) { + stream_id = nghttp2_submit_headers(client->session, 0, -1, &req->pri_spec, + nva.data(), nva.size(), req); + } else { + stream_id = + nghttp2_submit_request(client->session, &req->pri_spec, nva.data(), + nva.size(), req->data_prd, req); + } + + if (stream_id < 0) { + std::cerr << "[ERROR] nghttp2_submit_" + << (expect_continue ? "headers" : "request") + << "() returned error: " << nghttp2_strerror(stream_id) + << std::endl; + return -1; + } + + req->stream_id = stream_id; + client->request_done(req); + + req->req_nva = std::move(build_headers); + + if (expect_continue) { + auto timer = std::make_unique(client->loop, req); + req->continue_timer = std::move(timer); + } + + return 0; +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto client = static_cast(w->data); + if (client->do_read() != 0) { + client->disconnect(); + } +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto client = static_cast(w->data); + auto rv = client->do_write(); + if (rv == HttpClient::ERR_CONNECT_FAIL) { + client->connect_fail(); + return; + } + if (rv != 0) { + client->disconnect(); + } +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto client = static_cast(w->data); + std::cerr << "[ERROR] Timeout" << std::endl; + client->disconnect(); +} +} // namespace + +namespace { +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto client = static_cast(w->data); + ev_timer_stop(loop, w); + + nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT); + + client->signal_write(); +} +} // namespace + +HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks, + struct ev_loop *loop, SSL_CTX *ssl_ctx) + : wb(&mcpool), + session(nullptr), + callbacks(callbacks), + loop(loop), + ssl_ctx(ssl_ctx), + ssl(nullptr), + addrs(nullptr), + next_addr(nullptr), + cur_addr(nullptr), + complete(0), + success(0), + settings_payloadlen(0), + state(ClientState::IDLE), + upgrade_response_status_code(0), + fd(-1), + upgrade_response_complete(false) { + ev_io_init(&wev, writecb, 0, EV_WRITE); + ev_io_init(&rev, readcb, 0, EV_READ); + + wev.data = this; + rev.data = this; + + ev_timer_init(&wt, timeoutcb, 0., config.timeout); + ev_timer_init(&rt, timeoutcb, 0., config.timeout); + + wt.data = this; + rt.data = this; + + ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.); + + settings_timer.data = this; +} + +HttpClient::~HttpClient() { + disconnect(); + + if (addrs) { + freeaddrinfo(addrs); + addrs = nullptr; + next_addr = nullptr; + } +} + +bool HttpClient::need_upgrade() const { + return config.upgrade && scheme == "http"; +} + +int HttpClient::resolve_host(const std::string &host, uint16_t port) { + int rv; + this->host = host; + addrinfo hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_ADDRCONFIG; + rv = getaddrinfo(host.c_str(), util::utos(port).c_str(), &hints, &addrs); + if (rv != 0) { + std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv) + << std::endl; + return -1; + } + if (addrs == nullptr) { + std::cerr << "[ERROR] No address returned" << std::endl; + return -1; + } + next_addr = addrs; + return 0; +} + +namespace { +// Just returns 1 to continue handshake. +int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; } +} // namespace + +int HttpClient::initiate_connection() { + int rv; + + cur_addr = nullptr; + while (next_addr) { + cur_addr = next_addr; + next_addr = next_addr->ai_next; + fd = util::create_nonblock_socket(cur_addr->ai_family); + if (fd == -1) { + continue; + } + + if (ssl_ctx) { + // We are establishing TLS connection. + ssl = SSL_new(ssl_ctx); + if (!ssl) { + std::cerr << "[ERROR] SSL_new() failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + SSL_set_connect_state(ssl); + + // If the user overrode the :authority or host header, use that + // value for the SNI extension + const auto &host_string = + config.host_override.empty() ? host : config.host_override; + +#if LIBRESSL_2_7_API || \ + (!LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L) || \ + defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto param = SSL_get0_param(ssl); + X509_VERIFY_PARAM_set_hostflags(param, 0); + X509_VERIFY_PARAM_set1_host(param, host_string.c_str(), + host_string.size()); +#endif // LIBRESSL_2_7_API || (!LIBRESSL_IN_USE && + // OPENSSL_VERSION_NUMBER >= 0x10002000L) || + // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_cb); + + if (!util::numeric_host(host_string.c_str())) { + SSL_set_tlsext_host_name(ssl, host_string.c_str()); + } + } + + rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen); + + if (rv != 0 && errno != EINPROGRESS) { + if (ssl) { + SSL_free(ssl); + ssl = nullptr; + } + close(fd); + fd = -1; + continue; + } + break; + } + + if (fd == -1) { + return -1; + } + + writefn = &HttpClient::connected; + + if (need_upgrade()) { + on_readfn = &HttpClient::on_upgrade_read; + on_writefn = &HttpClient::on_upgrade_connect; + } else { + on_readfn = &HttpClient::on_read; + on_writefn = &HttpClient::on_write; + } + + ev_io_set(&rev, fd, EV_READ); + ev_io_set(&wev, fd, EV_WRITE); + + ev_io_start(loop, &wev); + + ev_timer_again(loop, &wt); + + return 0; +} + +void HttpClient::disconnect() { + state = ClientState::IDLE; + + for (auto req = std::begin(reqvec); req != std::end(reqvec); ++req) { + if ((*req)->continue_timer) { + (*req)->continue_timer->stop(); + } + } + + ev_timer_stop(loop, &settings_timer); + + ev_timer_stop(loop, &rt); + ev_timer_stop(loop, &wt); + + ev_io_stop(loop, &rev); + ev_io_stop(loop, &wev); + + nghttp2_session_del(session); + session = nullptr; + + if (ssl) { + SSL_set_shutdown(ssl, SSL_get_shutdown(ssl) | SSL_RECEIVED_SHUTDOWN); + ERR_clear_error(); + SSL_shutdown(ssl); + SSL_free(ssl); + ssl = nullptr; + } + + if (fd != -1) { + shutdown(fd, SHUT_WR); + close(fd); + fd = -1; + } +} + +int HttpClient::read_clear() { + ev_timer_again(loop, &rt); + + std::array buf; + + for (;;) { + ssize_t nread; + while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return -1; + } + + if (nread == 0) { + return -1; + } + + if (on_readfn(*this, buf.data(), nread) != 0) { + return -1; + } + } + + return 0; +} + +int HttpClient::write_clear() { + ev_timer_again(loop, &rt); + + std::array iov; + + for (;;) { + if (on_writefn(*this) != 0) { + return -1; + } + + auto iovcnt = wb.riovec(iov.data(), iov.size()); + + if (iovcnt == 0) { + break; + } + + ssize_t nwrite; + while ((nwrite = writev(fd, iov.data(), iovcnt)) == -1 && errno == EINTR) + ; + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + ev_io_start(loop, &wev); + ev_timer_again(loop, &wt); + return 0; + } + return -1; + } + + wb.drain(nwrite); + } + + ev_io_stop(loop, &wev); + ev_timer_stop(loop, &wt); + + return 0; +} + +int HttpClient::noop() { return 0; } + +void HttpClient::connect_fail() { + if (state == ClientState::IDLE) { + std::cerr << "[ERROR] Could not connect to the address " + << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen) + << std::endl; + } + auto cur_state = state; + disconnect(); + if (cur_state == ClientState::IDLE) { + if (initiate_connection() == 0) { + std::cerr << "Trying next address " + << util::numeric_name(cur_addr->ai_addr, cur_addr->ai_addrlen) + << std::endl; + } + } +} + +int HttpClient::connected() { + if (!util::check_socket_connected(fd)) { + return ERR_CONNECT_FAIL; + } + + if (config.verbose) { + print_timer(); + std::cout << " Connected" << std::endl; + } + + state = ClientState::CONNECTED; + + ev_io_start(loop, &rev); + ev_io_stop(loop, &wev); + + ev_timer_again(loop, &rt); + ev_timer_stop(loop, &wt); + + if (ssl) { + SSL_set_fd(ssl, fd); + + readfn = &HttpClient::tls_handshake; + writefn = &HttpClient::tls_handshake; + + return do_write(); + } + + readfn = &HttpClient::read_clear; + writefn = &HttpClient::write_clear; + + if (need_upgrade()) { + htp = std::make_unique(); + llhttp_init(htp.get(), HTTP_RESPONSE, &htp_hooks); + htp->data = this; + + return do_write(); + } + + if (connection_made() != 0) { + return -1; + } + + return 0; +} + +namespace { +size_t populate_settings(nghttp2_settings_entry *iv) { + size_t niv = 2; + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = config.max_concurrent_streams; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + if (config.window_bits != -1) { + iv[1].value = (1 << config.window_bits) - 1; + } else { + iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE; + } + + if (config.header_table_size >= 0) { + if (config.min_header_table_size < config.header_table_size) { + iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[niv].value = config.min_header_table_size; + ++niv; + } + + iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[niv].value = config.header_table_size; + ++niv; + } + + if (config.no_push) { + iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[niv].value = 0; + ++niv; + } + + if (config.no_rfc7540_pri) { + iv[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[niv].value = 1; + ++niv; + } + + return niv; +} +} // namespace + +int HttpClient::on_upgrade_connect() { + ssize_t rv; + record_connect_end_time(); + assert(!reqvec.empty()); + std::array iv; + size_t niv = populate_settings(iv.data()); + assert(settings_payload.size() >= 8 * niv); + rv = nghttp2_pack_settings_payload(settings_payload.data(), + settings_payload.size(), iv.data(), niv); + if (rv < 0) { + return -1; + } + settings_payloadlen = rv; + auto token68 = + base64::encode(std::begin(settings_payload), + std::begin(settings_payload) + settings_payloadlen); + util::to_token68(token68); + + std::string req; + if (reqvec[0]->data_prd) { + // If the request contains upload data, use OPTIONS * to upgrade + req = "OPTIONS *"; + } else { + auto meth = std::find_if( + std::begin(config.headers), std::end(config.headers), + [](const Header &kv) { return util::streq_l(":method", kv.name); }); + + if (meth == std::end(config.headers)) { + req = "GET "; + reqvec[0]->method = "GET"; + } else { + req = (*meth).value; + req += ' '; + reqvec[0]->method = (*meth).value; + } + req += reqvec[0]->make_reqpath(); + } + + auto headers = Headers{{"host", hostport}, + {"connection", "Upgrade, HTTP2-Settings"}, + {"upgrade", NGHTTP2_CLEARTEXT_PROTO_VERSION_ID}, + {"http2-settings", token68}, + {"accept", "*/*"}, + {"user-agent", "nghttp2/" NGHTTP2_VERSION}}; + auto initial_headerslen = headers.size(); + + for (auto &kv : config.headers) { + size_t i; + if (kv.name.empty() || kv.name[0] == ':') { + continue; + } + for (i = 0; i < initial_headerslen; ++i) { + if (kv.name == headers[i].name) { + headers[i].value = kv.value; + break; + } + } + if (i < initial_headerslen) { + continue; + } + headers.emplace_back(kv.name, kv.value, kv.no_index); + } + + req += " HTTP/1.1\r\n"; + + for (auto &kv : headers) { + req += kv.name; + req += ": "; + req += kv.value; + req += "\r\n"; + } + req += "\r\n"; + + wb.append(req); + + if (config.verbose) { + print_timer(); + std::cout << " HTTP Upgrade request\n" << req << std::endl; + } + + if (!reqvec[0]->data_prd) { + // record request time if this is a part of real request. + reqvec[0]->record_request_start_time(); + reqvec[0]->req_nva = std::move(headers); + } + + on_writefn = &HttpClient::noop; + + signal_write(); + + return 0; +} + +int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) { + int rv; + + auto htperr = + llhttp_execute(htp.get(), reinterpret_cast(data), len); + auto nread = htperr == HPE_OK + ? len + : static_cast(reinterpret_cast( + llhttp_get_error_pos(htp.get())) - + data); + + if (config.verbose) { + std::cout.write(reinterpret_cast(data), nread); + } + + if (htperr != HPE_OK && htperr != HPE_PAUSED_UPGRADE) { + std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: " + << "(" << llhttp_errno_name(htperr) << ") " + << llhttp_get_error_reason(htp.get()) << std::endl; + return -1; + } + + if (!upgrade_response_complete) { + return 0; + } + + if (config.verbose) { + std::cout << std::endl; + } + + if (upgrade_response_status_code != 101) { + std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl; + + return -1; + } + + if (config.verbose) { + print_timer(); + std::cout << " HTTP Upgrade success" << std::endl; + } + + on_readfn = &HttpClient::on_read; + on_writefn = &HttpClient::on_write; + + rv = connection_made(); + if (rv != 0) { + return rv; + } + + // Read remaining data in the buffer because it is not notified + // callback anymore. + rv = on_readfn(*this, data + nread, len - nread); + if (rv != 0) { + return rv; + } + + return 0; +} + +int HttpClient::do_read() { return readfn(*this); } +int HttpClient::do_write() { return writefn(*this); } + +int HttpClient::connection_made() { + int rv; + + if (!need_upgrade()) { + record_connect_end_time(); + } + + if (ssl) { + // Check NPN or ALPN result + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG + for (int i = 0; i < 2; ++i) { + if (next_proto) { + auto proto = StringRef{next_proto, next_proto_len}; + if (config.verbose) { + std::cout << "The negotiated protocol: " << proto << std::endl; + } + if (!util::check_h2_is_selected(proto)) { + next_proto = nullptr; + } + break; + } +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len); +#else // OPENSSL_VERSION_NUMBER < 0x10002000L + break; +#endif // OPENSSL_VERSION_NUMBER < 0x10002000L + } + if (!next_proto) { + print_protocol_nego_error(); + return -1; + } + } + + rv = nghttp2_session_client_new2(&session, callbacks, this, + config.http2_option); + + if (rv != 0) { + return -1; + } + if (need_upgrade()) { + // Adjust stream user-data depending on the existence of upload + // data + Request *stream_user_data = nullptr; + if (!reqvec[0]->data_prd) { + stream_user_data = reqvec[0].get(); + } + // If HEAD is used, that is only when user specified it with -H + // option. + auto head_request = stream_user_data && stream_user_data->method == "HEAD"; + rv = nghttp2_session_upgrade2(session, settings_payload.data(), + settings_payloadlen, head_request, + stream_user_data); + if (rv != 0) { + std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: " + << nghttp2_strerror(rv) << std::endl; + return -1; + } + if (stream_user_data) { + stream_user_data->stream_id = 1; + request_done(stream_user_data); + } + } + // If upgrade succeeds, the SETTINGS value sent with + // HTTP2-Settings header field has already been submitted to + // session object. + if (!need_upgrade()) { + std::array iv; + auto niv = populate_settings(iv.data()); + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), niv); + if (rv != 0) { + return -1; + } + } + if (!config.no_dep) { + // Create anchor stream nodes + nghttp2_priority_spec pri_spec; + + for (auto &anchor : anchors) { + nghttp2_priority_spec_init(&pri_spec, anchor.dep_stream_id, anchor.weight, + 0); + rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, anchor.stream_id, + &pri_spec); + if (rv != 0) { + return -1; + } + } + + rv = nghttp2_session_set_next_stream_id( + session, anchors[ANCHOR_FOLLOWERS].stream_id + 2); + if (rv != 0) { + return -1; + } + + if (need_upgrade() && !reqvec[0]->data_prd) { + // Amend the priority because we cannot send priority in + // HTTP/1.1 Upgrade. + auto &anchor = anchors[ANCHOR_FOLLOWERS]; + nghttp2_priority_spec_init(&pri_spec, anchor.stream_id, + reqvec[0]->pri_spec.weight, 0); + + rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec); + if (rv != 0) { + return -1; + } + } + } else if (need_upgrade() && !reqvec[0]->data_prd && + reqvec[0]->pri_spec.weight != NGHTTP2_DEFAULT_WEIGHT) { + // Amend the priority because we cannot send priority in HTTP/1.1 + // Upgrade. + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, reqvec[0]->pri_spec.weight, 0); + + rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec); + if (rv != 0) { + return -1; + } + } + + ev_timer_again(loop, &settings_timer); + + if (config.connection_window_bits != -1) { + int32_t window_size = (1 << config.connection_window_bits) - 1; + rv = nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0, + window_size); + if (rv != 0) { + return -1; + } + } + // Adjust first request depending on the existence of the upload + // data + for (auto i = std::begin(reqvec) + (need_upgrade() && !reqvec[0]->data_prd); + i != std::end(reqvec); ++i) { + if (submit_request(this, config.headers, (*i).get()) != 0) { + return -1; + } + } + + signal_write(); + + return 0; +} + +int HttpClient::on_read(const uint8_t *data, size_t len) { + if (config.hexdump) { + util::hexdump(stdout, data, len); + } + + auto rv = nghttp2_session_mem_recv(session, data, len); + if (rv < 0) { + std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv) << std::endl; + return -1; + } + + assert(static_cast(rv) == len); + + if (nghttp2_session_want_read(session) == 0 && + nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) { + return -1; + } + + signal_write(); + + return 0; +} + +int HttpClient::on_write() { + for (;;) { + if (wb.rleft() >= 16384) { + return 0; + } + + const uint8_t *data; + auto len = nghttp2_session_mem_send(session, &data); + if (len < 0) { + std::cerr << "[ERROR] nghttp2_session_send() returned error: " + << nghttp2_strerror(len) << std::endl; + return -1; + } + + if (len == 0) { + break; + } + + wb.append(data, len); + } + + if (nghttp2_session_want_read(session) == 0 && + nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) { + return -1; + } + + return 0; +} + +int HttpClient::tls_handshake() { + ev_timer_again(loop, &rt); + + ERR_clear_error(); + + auto rv = SSL_do_handshake(ssl); + + if (rv <= 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + ev_io_stop(loop, &wev); + ev_timer_stop(loop, &wt); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(loop, &wev); + ev_timer_again(loop, &wt); + return 0; + default: + return -1; + } + } + + ev_io_stop(loop, &wev); + ev_timer_stop(loop, &wt); + + readfn = &HttpClient::read_tls; + writefn = &HttpClient::write_tls; + + if (config.verify_peer) { + auto verify_res = SSL_get_verify_result(ssl); + if (verify_res != X509_V_OK) { + std::cerr << "[WARNING] Certificate verification failed: " + << X509_verify_cert_error_string(verify_res) << std::endl; + } + } + + if (connection_made() != 0) { + return -1; + } + + return 0; +} + +int HttpClient::read_tls() { + ev_timer_again(loop, &rt); + + ERR_clear_error(); + + std::array buf; + for (;;) { + auto rv = SSL_read(ssl, buf.data(), buf.size()); + + if (rv <= 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + return 0; + case SSL_ERROR_WANT_WRITE: + // renegotiation started + return -1; + default: + return -1; + } + } + + if (on_readfn(*this, buf.data(), rv) != 0) { + return -1; + } + } +} + +int HttpClient::write_tls() { + ev_timer_again(loop, &rt); + + ERR_clear_error(); + + struct iovec iov; + + for (;;) { + if (on_writefn(*this) != 0) { + return -1; + } + + auto iovcnt = wb.riovec(&iov, 1); + + if (iovcnt == 0) { + break; + } + + auto rv = SSL_write(ssl, iov.iov_base, iov.iov_len); + + if (rv <= 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + // renegotiation started + return -1; + case SSL_ERROR_WANT_WRITE: + ev_io_start(loop, &wev); + ev_timer_again(loop, &wt); + return 0; + default: + return -1; + } + } + + wb.drain(rv); + } + + ev_io_stop(loop, &wev); + ev_timer_stop(loop, &wt); + + return 0; +} + +void HttpClient::signal_write() { ev_io_start(loop, &wev); } + +bool HttpClient::all_requests_processed() const { + return complete == reqvec.size(); +} + +void HttpClient::update_hostport() { + if (reqvec.empty()) { + return; + } + scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA) + .str(); + std::stringstream ss; + if (reqvec[0]->is_ipv6_literal_addr()) { + // we may have zone ID, which must start with "%25", or "%". RFC + // 6874 defines "%25" only, and just "%" is allowed for just + // convenience to end-user input. + auto host = + util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST); + auto end = std::find(std::begin(host), std::end(host), '%'); + ss << "["; + ss.write(host.c_str(), end - std::begin(host)); + ss << "]"; + } else { + util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST); + } + if (util::has_uri_field(reqvec[0]->u, UF_PORT) && + reqvec[0]->u.port != + util::get_default_port(reqvec[0]->uri.c_str(), reqvec[0]->u)) { + ss << ":" << reqvec[0]->u.port; + } + hostport = ss.str(); +} + +bool HttpClient::add_request(const std::string &uri, + const nghttp2_data_provider *data_prd, + int64_t data_length, + const nghttp2_priority_spec &pri_spec, int level) { + http_parser_url u{}; + if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { + return false; + } + if (path_cache.count(uri)) { + return false; + } + + if (config.multiply == 1) { + path_cache.insert(uri); + } + + reqvec.push_back(std::make_unique(uri, u, data_prd, data_length, + pri_spec, level)); + return true; +} + +void HttpClient::record_start_time() { + timing.system_start_time = std::chrono::system_clock::now(); + timing.start_time = get_time(); +} + +void HttpClient::record_domain_lookup_end_time() { + timing.domain_lookup_end_time = get_time(); +} + +void HttpClient::record_connect_end_time() { + timing.connect_end_time = get_time(); +} + +void HttpClient::request_done(Request *req) { + if (req->stream_id % 2 == 0) { + return; + } +} + +#ifdef HAVE_JANSSON +void HttpClient::output_har(FILE *outfile) { + static auto PAGE_ID = "page_0"; + + auto root = json_object(); + auto log = json_object(); + json_object_set_new(root, "log", log); + json_object_set_new(log, "version", json_string("1.2")); + + auto creator = json_object(); + json_object_set_new(log, "creator", creator); + + json_object_set_new(creator, "name", json_string("nghttp")); + json_object_set_new(creator, "version", json_string(NGHTTP2_VERSION)); + + auto pages = json_array(); + json_object_set_new(log, "pages", pages); + + auto page = json_object(); + json_array_append_new(pages, page); + + json_object_set_new( + page, "startedDateTime", + json_string(util::format_iso8601(timing.system_start_time).c_str())); + json_object_set_new(page, "id", json_string(PAGE_ID)); + json_object_set_new(page, "title", json_string("")); + + json_object_set_new(page, "pageTimings", json_object()); + + auto entries = json_array(); + json_object_set_new(log, "entries", entries); + + auto dns_delta = std::chrono::duration_cast( + timing.domain_lookup_end_time - timing.start_time) + .count() / + 1000.0; + auto connect_delta = + std::chrono::duration_cast( + timing.connect_end_time - timing.domain_lookup_end_time) + .count() / + 1000.0; + + for (size_t i = 0; i < reqvec.size(); ++i) { + auto &req = reqvec[i]; + + if (req->timing.state != RequestState::ON_COMPLETE) { + continue; + } + + auto entry = json_object(); + json_array_append_new(entries, entry); + + auto &req_timing = req->timing; + auto request_time = + (i == 0) ? timing.system_start_time + : timing.system_start_time + + std::chrono::duration_cast< + std::chrono::system_clock::duration>( + req_timing.request_start_time - timing.start_time); + + auto wait_delta = + std::chrono::duration_cast( + req_timing.response_start_time - req_timing.request_start_time) + .count() / + 1000.0; + auto receive_delta = + std::chrono::duration_cast( + req_timing.response_end_time - req_timing.response_start_time) + .count() / + 1000.0; + + auto time_sum = + std::chrono::duration_cast( + (i == 0) ? (req_timing.response_end_time - timing.start_time) + : (req_timing.response_end_time - + req_timing.request_start_time)) + .count() / + 1000.0; + + json_object_set_new( + entry, "startedDateTime", + json_string(util::format_iso8601(request_time).c_str())); + json_object_set_new(entry, "time", json_real(time_sum)); + + auto pushed = req->stream_id % 2 == 0; + + json_object_set_new(entry, "comment", + json_string(pushed ? "Pushed Object" : "")); + + auto request = json_object(); + json_object_set_new(entry, "request", request); + + auto req_headers = json_array(); + json_object_set_new(request, "headers", req_headers); + + for (auto &nv : req->req_nva) { + auto hd = json_object(); + json_array_append_new(req_headers, hd); + + json_object_set_new(hd, "name", json_string(nv.name.c_str())); + json_object_set_new(hd, "value", json_string(nv.value.c_str())); + } + + json_object_set_new(request, "method", json_string(req->method.c_str())); + json_object_set_new(request, "url", json_string(req->uri.c_str())); + json_object_set_new(request, "httpVersion", json_string("HTTP/2.0")); + json_object_set_new(request, "cookies", json_array()); + json_object_set_new(request, "queryString", json_array()); + json_object_set_new(request, "headersSize", json_integer(-1)); + json_object_set_new(request, "bodySize", json_integer(-1)); + + auto response = json_object(); + json_object_set_new(entry, "response", response); + + auto res_headers = json_array(); + json_object_set_new(response, "headers", res_headers); + + for (auto &nv : req->res_nva) { + auto hd = json_object(); + json_array_append_new(res_headers, hd); + + json_object_set_new(hd, "name", json_string(nv.name.c_str())); + json_object_set_new(hd, "value", json_string(nv.value.c_str())); + } + + json_object_set_new(response, "status", json_integer(req->status)); + json_object_set_new(response, "statusText", json_string("")); + json_object_set_new(response, "httpVersion", json_string("HTTP/2.0")); + json_object_set_new(response, "cookies", json_array()); + + auto content = json_object(); + json_object_set_new(response, "content", content); + + json_object_set_new(content, "size", json_integer(req->response_len)); + + auto content_type_ptr = http2::get_header(req->res_nva, "content-type"); + + const char *content_type = ""; + if (content_type_ptr) { + content_type = content_type_ptr->value.c_str(); + } + + json_object_set_new(content, "mimeType", json_string(content_type)); + + json_object_set_new(response, "redirectURL", json_string("")); + json_object_set_new(response, "headersSize", json_integer(-1)); + json_object_set_new(response, "bodySize", json_integer(-1)); + json_object_set_new(entry, "cache", json_object()); + + auto timings = json_object(); + json_object_set_new(entry, "timings", timings); + + auto dns_timing = (i == 0) ? dns_delta : 0; + auto connect_timing = (i == 0) ? connect_delta : 0; + + json_object_set_new(timings, "dns", json_real(dns_timing)); + json_object_set_new(timings, "connect", json_real(connect_timing)); + + json_object_set_new(timings, "blocked", json_real(0.0)); + json_object_set_new(timings, "send", json_real(0.0)); + json_object_set_new(timings, "wait", json_real(wait_delta)); + json_object_set_new(timings, "receive", json_real(receive_delta)); + + json_object_set_new(entry, "pageref", json_string(PAGE_ID)); + json_object_set_new(entry, "connection", + json_string(util::utos(req->stream_id).c_str())); + } + + json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2)); + json_decref(root); +} +#endif // HAVE_JANSSON + +namespace { +void update_html_parser(HttpClient *client, Request *req, const uint8_t *data, + size_t len, int fin) { + if (!req->html_parser) { + return; + } + req->update_html_parser(data, len, fin); + + auto scheme = req->get_real_scheme(); + auto host = req->get_real_host(); + auto port = req->get_real_port(); + + for (auto &p : req->html_parser->get_links()) { + auto uri = strip_fragment(p.first.c_str()); + auto res_type = p.second; + + http_parser_url u{}; + if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { + continue; + } + + if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, scheme) || + !util::fieldeq(uri.c_str(), u, UF_HOST, host)) { + continue; + } + + auto link_port = util::has_uri_field(u, UF_PORT) ? u.port + : scheme == "https" ? 443 + : 80; + + if (port != link_port) { + continue; + } + + // No POST data for assets + auto pri_spec = resolve_dep(res_type); + + if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) { + submit_request(client, config.headers, client->reqvec.back().get()); + } + } + req->html_parser->clear_links(); +} +} // namespace + +namespace { +HttpClient *get_client(void *user_data) { + return static_cast(user_data); +} +} // namespace + +namespace { +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + auto client = get_client(user_data); + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + + if (!req) { + return 0; + } + + if (config.verbose >= 2) { + verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len, + user_data); + } + + req->response_len += len; + + if (req->inflater) { + while (len > 0) { + const size_t MAX_OUTLEN = 4_k; + std::array out; + size_t outlen = MAX_OUTLEN; + size_t tlen = len; + int rv = + nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen); + if (rv != 0) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, + NGHTTP2_INTERNAL_ERROR); + break; + } + + if (!config.null_out) { + std::cout.write(reinterpret_cast(out.data()), outlen); + } + + update_html_parser(client, req, out.data(), outlen, 0); + data += tlen; + len -= tlen; + } + + return 0; + } + + if (!config.null_out) { + std::cout.write(reinterpret_cast(data), len); + } + + update_html_parser(client, req, data, len, 0); + + return 0; +} +} // namespace + +namespace { +ssize_t select_padding_callback(nghttp2_session *session, + const nghttp2_frame *frame, size_t max_payload, + void *user_data) { + return std::min(max_payload, frame->hd.length + config.padding); +} +} // namespace + +namespace { +void check_response_header(nghttp2_session *session, Request *req) { + bool gzip = false; + + req->expect_final_response = false; + + auto status_hd = req->get_res_header(http2::HD__STATUS); + + if (!status_hd) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, + NGHTTP2_PROTOCOL_ERROR); + return; + } + + auto status = http2::parse_http_status_code(StringRef{status_hd->value}); + if (status == -1) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, + NGHTTP2_PROTOCOL_ERROR); + return; + } + + req->status = status; + + for (auto &nv : req->res_nva) { + if ("content-encoding" == nv.name) { + gzip = util::strieq_l("gzip", nv.value) || + util::strieq_l("deflate", nv.value); + continue; + } + } + + if (req->status / 100 == 1) { + if (req->continue_timer && (req->status == 100)) { + // If the request is waiting for a 100 Continue, complete the handshake. + req->continue_timer->dispatch_continue(); + } + + req->expect_final_response = true; + req->status = 0; + req->res_nva.clear(); + http2::init_hdidx(req->res_hdidx); + return; + } else if (req->continue_timer) { + // A final response stops any pending Expect/Continue handshake. + req->continue_timer->stop(); + } + + if (gzip) { + if (!req->inflater) { + req->init_inflater(); + } + } + if (config.get_assets && req->level == 0) { + if (!req->html_parser) { + req->init_html_parser(); + } + } +} +} // namespace + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + auto client = get_client(user_data); + switch (frame->hd.type) { + case NGHTTP2_HEADERS: { + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!req) { + break; + } + + switch (frame->headers.cat) { + case NGHTTP2_HCAT_RESPONSE: + case NGHTTP2_HCAT_PUSH_RESPONSE: + req->record_response_start_time(); + break; + default: + break; + } + + break; + } + case NGHTTP2_PUSH_PROMISE: { + auto stream_id = frame->push_promise.promised_stream_id; + http_parser_url u{}; + // TODO Set pri and level + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_default_init(&pri_spec); + + auto req = std::make_unique("", u, nullptr, 0, pri_spec); + req->stream_id = stream_id; + + nghttp2_session_set_stream_user_data(session, stream_id, req.get()); + + client->request_done(req.get()); + req->record_request_start_time(); + client->reqvec.push_back(std::move(req)); + + break; + } + } + return 0; +} +} // namespace + +namespace { +int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + if (config.verbose) { + verbose_on_header_callback(session, frame, name, namelen, value, valuelen, + flags, user_data); + } + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: { + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + + if (!req) { + break; + } + + /* ignore trailer header */ + if (frame->headers.cat == NGHTTP2_HCAT_HEADERS && + !req->expect_final_response) { + break; + } + + if (req->header_buffer_size + namelen + valuelen > 64_k) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + return 0; + } + + req->header_buffer_size += namelen + valuelen; + + auto token = http2::lookup_token(name, namelen); + + http2::index_header(req->res_hdidx, token, req->res_nva.size()); + http2::add_header(req->res_nva, name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX, token); + break; + } + case NGHTTP2_PUSH_PROMISE: { + auto req = static_cast(nghttp2_session_get_stream_user_data( + session, frame->push_promise.promised_stream_id)); + + if (!req) { + break; + } + + if (req->header_buffer_size + namelen + valuelen > 64_k) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + frame->push_promise.promised_stream_id, + NGHTTP2_INTERNAL_ERROR); + return 0; + } + + req->header_buffer_size += namelen + valuelen; + + auto token = http2::lookup_token(name, namelen); + + http2::index_header(req->req_hdidx, token, req->req_nva.size()); + http2::add_header(req->req_nva, name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX, token); + break; + } + } + return 0; +} +} // namespace + +namespace { +int on_frame_recv_callback2(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + int rv = 0; + + if (config.verbose) { + verbose_on_frame_recv_callback(session, frame, user_data); + } + + auto client = get_client(user_data); + switch (frame->hd.type) { + case NGHTTP2_DATA: { + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!req) { + return 0; + ; + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + req->record_response_end_time(); + ++client->success; + } + + break; + } + case NGHTTP2_HEADERS: { + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + // If this is the HTTP Upgrade with OPTIONS method to avoid POST, + // req is nullptr. + if (!req) { + return 0; + ; + } + + switch (frame->headers.cat) { + case NGHTTP2_HCAT_RESPONSE: + case NGHTTP2_HCAT_PUSH_RESPONSE: + check_response_header(session, req); + break; + case NGHTTP2_HCAT_HEADERS: + if (req->expect_final_response) { + check_response_header(session, req); + break; + } + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); + return 0; + } + break; + default: + assert(0); + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + req->record_response_end_time(); + ++client->success; + } + + break; + } + case NGHTTP2_SETTINGS: + if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { + break; + } + ev_timer_stop(client->loop, &client->settings_timer); + break; + case NGHTTP2_PUSH_PROMISE: { + auto req = static_cast(nghttp2_session_get_stream_user_data( + session, frame->push_promise.promised_stream_id)); + if (!req) { + break; + } + + // Reset for response header field reception + req->header_buffer_size = 0; + + auto scheme = req->get_req_header(http2::HD__SCHEME); + auto authority = req->get_req_header(http2::HD__AUTHORITY); + auto path = req->get_req_header(http2::HD__PATH); + + if (!authority) { + authority = req->get_req_header(http2::HD_HOST); + } + + // libnghttp2 guarantees :scheme, :method, :path and (:authority | + // host) exist and non-empty. + if (path->value[0] != '/') { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + frame->push_promise.promised_stream_id, + NGHTTP2_PROTOCOL_ERROR); + break; + } + std::string uri = scheme->value; + uri += "://"; + uri += authority->value; + uri += path->value; + http_parser_url u{}; + if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + frame->push_promise.promised_stream_id, + NGHTTP2_PROTOCOL_ERROR); + break; + } + req->uri = uri; + req->u = u; + + if (client->path_cache.count(uri)) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + frame->push_promise.promised_stream_id, + NGHTTP2_CANCEL); + break; + } + + if (config.multiply == 1) { + client->path_cache.insert(uri); + } + + break; + } + } + return rv; +} +} // namespace + +namespace { +int before_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + assert(req); + req->record_request_start_time(); + return 0; +} + +} // namespace + +namespace { +int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + if (config.verbose) { + verbose_on_frame_send_callback(session, frame, user_data); + } + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!req) { + return 0; + } + + // If this request is using Expect/Continue, start its ContinueTimer. + if (req->continue_timer) { + req->continue_timer->start(); + } + + return 0; +} +} // namespace + +namespace { +int on_frame_not_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, int lib_error_code, + void *user_data) { + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!req) { + return 0; + } + + std::cerr << "[ERROR] request " << req->uri + << " failed: " << nghttp2_strerror(lib_error_code) << std::endl; + + return 0; +} +} // namespace + +namespace { +int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + auto client = get_client(user_data); + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + + if (!req) { + return 0; + } + + // If this request is using Expect/Continue, stop its ContinueTimer. + if (req->continue_timer) { + req->continue_timer->stop(); + } + + update_html_parser(client, req, nullptr, 0, 1); + ++client->complete; + + if (client->all_requests_processed()) { + nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR); + } + + return 0; +} +} // namespace + +struct RequestResult { + std::chrono::microseconds time; +}; + +namespace { +void print_stats(const HttpClient &client) { + std::cout << "***** Statistics *****" << std::endl; + + std::vector reqs; + reqs.reserve(client.reqvec.size()); + for (const auto &req : client.reqvec) { + if (req->timing.state == RequestState::ON_COMPLETE) { + reqs.push_back(req.get()); + } + } + + std::sort(std::begin(reqs), std::end(reqs), + [](const Request *lhs, const Request *rhs) { + const auto <iming = lhs->timing; + const auto &rtiming = rhs->timing; + return ltiming.response_end_time < rtiming.response_end_time || + (ltiming.response_end_time == rtiming.response_end_time && + ltiming.request_start_time < rtiming.request_start_time); + }); + + std::cout << R"( +Request timing: + responseEnd: the time when last byte of response was received + relative to connectEnd + requestStart: the time just before first byte of request was sent + relative to connectEnd. If '*' is shown, this was + pushed by server. + process: responseEnd - requestStart + code: HTTP status code + size: number of bytes received as response body without + inflation. + URI: request URI + +see http://www.w3.org/TR/resource-timing/#processing-model + +sorted by 'complete' + +id responseEnd requestStart process code size request path)" + << std::endl; + + const auto &base = client.timing.connect_end_time; + for (const auto &req : reqs) { + auto response_end = std::chrono::duration_cast( + req->timing.response_end_time - base); + auto request_start = std::chrono::duration_cast( + req->timing.request_start_time - base); + auto total = std::chrono::duration_cast( + req->timing.response_end_time - req->timing.request_start_time); + auto pushed = req->stream_id % 2 == 0; + + std::cout << std::setw(3) << req->stream_id << " " << std::setw(11) + << ("+" + util::format_duration(response_end)) << " " + << (pushed ? "*" : " ") << std::setw(11) + << ("+" + util::format_duration(request_start)) << " " + << std::setw(8) << util::format_duration(total) << " " + << std::setw(4) << req->status << " " << std::setw(4) + << util::utos_unit(req->response_len) << " " + << req->make_reqpath() << std::endl; + } +} +} // namespace + +#ifndef OPENSSL_NO_NEXTPROTONEG +namespace { +int client_select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + if (config.verbose) { + print_timer(); + std::cout << "[NPN] server offers:" << std::endl; + } + for (unsigned int i = 0; i < inlen; i += in[i] + 1) { + if (config.verbose) { + std::cout << " * "; + std::cout.write(reinterpret_cast(&in[i + 1]), in[i]); + std::cout << std::endl; + } + } + if (!util::select_h2(const_cast(out), outlen, in, + inlen)) { + print_protocol_nego_error(); + return SSL_TLSEXT_ERR_NOACK; + } + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // !OPENSSL_NO_NEXTPROTONEG + +namespace { +int communicate( + const std::string &scheme, const std::string &host, uint16_t port, + std::vector< + std::tuple> + requests, + const nghttp2_session_callbacks *callbacks) { + int result = 0; + auto loop = EV_DEFAULT; + SSL_CTX *ssl_ctx = nullptr; + if (scheme == "https") { + ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + std::cerr << "[ERROR] Failed to create SSL_CTX: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + result = -1; + goto fin; + } + + auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; + +#ifdef SSL_OP_ENABLE_KTLS + if (config.ktls) { + ssl_opts |= SSL_OP_ENABLE_KTLS; + } +#endif // SSL_OP_ENABLE_KTLS + + SSL_CTX_set_options(ssl_ctx, ssl_opts); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + std::cerr << "[WARNING] Could not load system trusted CA certificates: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + } + + if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION, + nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) { + std::cerr << "[ERROR] Could not set TLS versions" << std::endl; + result = -1; + goto fin; + } + + if (SSL_CTX_set_cipher_list(ssl_ctx, tls::DEFAULT_CIPHER_LIST) == 0) { + std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + result = -1; + goto fin; + } + if (!config.keyfile.empty()) { + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(), + SSL_FILETYPE_PEM) != 1) { + std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + result = -1; + goto fin; + } + } + if (!config.certfile.empty()) { + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, + config.certfile.c_str()) != 1) { + std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + result = -1; + goto fin; + } + } +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb, + nullptr); +#endif // !OPENSSL_NO_NEXTPROTONEG + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + auto proto_list = util::get_default_alpn(); + + SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size()); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + } + { + HttpClient client{callbacks, loop, ssl_ctx}; + + int32_t dep_stream_id = 0; + + if (!config.no_dep) { + dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id; + } + + for (auto &req : requests) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, dep_stream_id, std::get<3>(req), 0); + + for (int i = 0; i < config.multiply; ++i) { + client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req), + pri_spec); + } + } + client.update_hostport(); + + client.record_start_time(); + + if (client.resolve_host(host, port) != 0) { + goto fin; + } + + client.record_domain_lookup_end_time(); + + if (client.initiate_connection() != 0) { + std::cerr << "[ERROR] Could not connect to " << host << ", port " << port + << std::endl; + goto fin; + } + + ev_set_userdata(loop, &client); + ev_run(loop, 0); + ev_set_userdata(loop, nullptr); + +#ifdef HAVE_JANSSON + if (!config.harfile.empty()) { + FILE *outfile; + if (config.harfile == "-") { + outfile = stdout; + } else { + outfile = fopen(config.harfile.c_str(), "wb"); + } + + if (outfile) { + client.output_har(outfile); + + if (outfile != stdout) { + fclose(outfile); + } + } else { + std::cerr << "Cannot open file " << config.harfile << ". " + << "har file could not be created." << std::endl; + } + } +#endif // HAVE_JANSSON + + if (client.success != client.reqvec.size()) { + std::cerr << "Some requests were not processed. total=" + << client.reqvec.size() << ", processed=" << client.success + << std::endl; + } + if (config.stat) { + print_stats(client); + } + } +fin: + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + } + return result; +} +} // namespace + +namespace { +ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, + uint8_t *buf, size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) { + int rv; + auto req = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + assert(req); + int fd = source->fd; + ssize_t nread; + + while ((nread = pread(fd, buf, length, req->data_offset)) == -1 && + errno == EINTR) + ; + + if (nread == -1) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + req->data_offset += nread; + + if (req->data_offset == req->data_length) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + if (!config.trailer.empty()) { + std::vector nva; + nva.reserve(config.trailer.size()); + for (auto &kv : config.trailer) { + nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); + } + rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size()); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } else { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + } + + return nread; + } + + if (req->data_offset > req->data_length || nread == 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + return nread; +} +} // namespace + +namespace { +int run(char **uris, int n) { + nghttp2_session_callbacks *callbacks; + + nghttp2_session_callbacks_new(&callbacks); + auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback2); + + if (config.verbose) { + nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( + callbacks, verbose_on_invalid_frame_recv_callback); + + nghttp2_session_callbacks_set_error_callback2(callbacks, + verbose_error_callback); + } + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + + nghttp2_session_callbacks_set_before_frame_send_callback( + callbacks, before_frame_send_callback); + + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + + nghttp2_session_callbacks_set_on_frame_not_send_callback( + callbacks, on_frame_not_send_callback); + + if (config.padding) { + nghttp2_session_callbacks_set_select_padding_callback( + callbacks, select_padding_callback); + } + + std::string prev_scheme; + std::string prev_host; + uint16_t prev_port = 0; + int failures = 0; + int data_fd = -1; + nghttp2_data_provider data_prd; + struct stat data_stat; + + if (!config.datafile.empty()) { + if (config.datafile == "-") { + if (fstat(0, &data_stat) == 0 && + (data_stat.st_mode & S_IFMT) == S_IFREG) { + // use STDIN if it is a regular file + data_fd = 0; + } else { + // copy the contents of STDIN to a temporary file + char tempfn[] = "/tmp/nghttp.temp.XXXXXX"; + data_fd = mkstemp(tempfn); + if (data_fd == -1) { + std::cerr << "[ERROR] Could not create a temporary file in /tmp" + << std::endl; + return 1; + } + if (unlink(tempfn) != 0) { + std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn + << std::endl; + } + while (1) { + std::array buf; + ssize_t rret, wret; + while ((rret = read(0, buf.data(), buf.size())) == -1 && + errno == EINTR) + ; + if (rret == 0) + break; + if (rret == -1) { + std::cerr << "[ERROR] I/O error while reading from STDIN" + << std::endl; + return 1; + } + while ((wret = write(data_fd, buf.data(), rret)) == -1 && + errno == EINTR) + ; + if (wret != rret) { + std::cerr << "[ERROR] I/O error while writing to temporary file" + << std::endl; + return 1; + } + } + if (fstat(data_fd, &data_stat) == -1) { + close(data_fd); + std::cerr << "[ERROR] Could not stat temporary file" << std::endl; + return 1; + } + } + } else { + data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY); + if (data_fd == -1) { + std::cerr << "[ERROR] Could not open file " << config.datafile + << std::endl; + return 1; + } + if (fstat(data_fd, &data_stat) == -1) { + close(data_fd); + std::cerr << "[ERROR] Could not stat file " << config.datafile + << std::endl; + return 1; + } + } + data_prd.source.fd = data_fd; + data_prd.read_callback = file_read_callback; + } + std::vector< + std::tuple> + requests; + + size_t next_weight_idx = 0; + + for (int i = 0; i < n; ++i) { + http_parser_url u{}; + auto uri = strip_fragment(uris[i]); + if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { + ++next_weight_idx; + std::cerr << "[ERROR] Could not parse URI " << uri << std::endl; + continue; + } + if (!util::has_uri_field(u, UF_SCHEMA)) { + ++next_weight_idx; + std::cerr << "[ERROR] URI " << uri << " does not have scheme part" + << std::endl; + continue; + } + auto port = util::has_uri_field(u, UF_PORT) + ? u.port + : util::get_default_port(uri.c_str(), u); + auto host = decode_host(util::get_uri_field(uri.c_str(), u, UF_HOST)); + if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) || + host != prev_host || port != prev_port) { + if (!requests.empty()) { + if (communicate(prev_scheme, prev_host, prev_port, std::move(requests), + callbacks) != 0) { + ++failures; + } + requests.clear(); + } + prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA).str(); + prev_host = std::move(host); + prev_port = port; + } + requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd, + data_stat.st_size, config.weight[next_weight_idx++]); + } + if (!requests.empty()) { + if (communicate(prev_scheme, prev_host, prev_port, std::move(requests), + callbacks) != 0) { + ++failures; + } + } + return failures; +} +} // namespace + +namespace { +void print_version(std::ostream &out) { + out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl; +} +} // namespace + +namespace { +void print_usage(std::ostream &out) { + out << R"(Usage: nghttp [OPTIONS]... ... +HTTP/2 client)" + << std::endl; +} +} // namespace + +namespace { +void print_help(std::ostream &out) { + print_usage(out); + out << R"( + Specify URI to access. +Options: + -v, --verbose + Print debug information such as reception and + transmission of frames and name/value pairs. Specifying + this option multiple times increases verbosity. + -n, --null-out + Discard downloaded data. + -O, --remote-name + Save download data in the current directory. The + filename is derived from URI. If URI ends with '/', + 'index.html' is used as a filename. Not implemented + yet. + -t, --timeout= + Timeout each request after . Set 0 to disable + timeout. + -w, --window-bits= + Sets the stream level initial window size to 2**-1. + -W, --connection-window-bits= + Sets the connection level initial window size to + 2**-1. + -a, --get-assets + Download assets such as stylesheets, images and script + files linked from the downloaded resource. Only links + whose origins are the same with the linking resource + will be downloaded. nghttp prioritizes resources using + HTTP/2 dependency based priority. The priority order, + from highest to lowest, is html itself, css, javascript + and images. + -s, --stat Print statistics. + -H, --header=
+ Add a header to the requests. Example: -H':method: PUT' + --trailer=
+ Add a trailer header to the requests.
must not + include pseudo header field (header field name starting + with ':'). To send trailer, one must use -d option to + send request body. Example: --trailer 'foo: bar'. + --cert= + Use the specified client certificate file. The file + must be in PEM format. + --key= Use the client private key file. The file must be in + PEM format. + -d, --data= + Post FILE to server. If '-' is given, data will be read + from stdin. + -m, --multiply= + Request each URI times. By default, same URI is not + requested twice. This option disables it too. + -u, --upgrade + Perform HTTP Upgrade for HTTP/2. This option is ignored + if the request URI has https scheme. If -d is used, the + HTTP upgrade request is performed with OPTIONS method. + -p, --weight= + Sets weight of given URI. This option can be used + multiple times, and N-th -p option sets weight of N-th + URI in the command line. If the number of -p option is + less than the number of URI, the last -p option value is + repeated. If there is no -p option, default weight, 16, + is assumed. The valid value range is + [)" + << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT << R"(], inclusive. + -M, --peer-max-concurrent-streams= + Use as SETTINGS_MAX_CONCURRENT_STREAMS value of + remote endpoint as if it is received in SETTINGS frame. + Default: 100 + -c, --header-table-size= + Specify decoder header table size. If this option is + used multiple times, and the minimum value among the + given values except for last one is strictly less than + the last value, that minimum value is set in SETTINGS + frame payload before the last value, to simulate + multiple header table size change. + --encoder-header-table-size= + Specify encoder header table size. The decoder (server) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which server specified. + -b, --padding= + Add at most bytes to a frame payload as padding. + Specify 0 to disable padding. + -r, --har= + Output HTTP transactions in HAR format. If '-' + is given, data is written to stdout. + --color Force colored log output. + --continuation + Send large header to test CONTINUATION. + --no-content-length + Don't send content-length header field. + --no-dep Don't send dependency based priority hint to server. + --hexdump Display the incoming traffic in hexadecimal (Canonical + hex+ASCII display). If SSL/TLS is used, decrypted data + are used. + --no-push Disable server push. + --max-concurrent-streams= + The number of concurrent pushed streams this client + accepts. + --expect-continue + Perform an Expect/Continue handshake: wait to send DATA + (up to a short timeout) until the server sends a 100 + Continue interim response. This option is ignored unless + combined with the -d option. + -y, --no-verify-peer + Suppress warning on server certificate verification + failure. + --ktls Enable ktls. + --no-rfc7540-pri + Disable RFC7540 priorities. + --version Display version information and exit. + -h, --help Display this help and exit. + +-- + + The argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The argument is an integer and an optional unit (e.g., 1s + is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms + (hours, minutes, seconds and milliseconds, respectively). If a unit + is omitted, a second is used as unit.)" + << std::endl; +} +} // namespace + +int main(int argc, char **argv) { + tls::libssl_init(); + + bool color = false; + while (1) { + static int flag = 0; + constexpr static option long_options[] = { + {"verbose", no_argument, nullptr, 'v'}, + {"null-out", no_argument, nullptr, 'n'}, + {"remote-name", no_argument, nullptr, 'O'}, + {"timeout", required_argument, nullptr, 't'}, + {"window-bits", required_argument, nullptr, 'w'}, + {"connection-window-bits", required_argument, nullptr, 'W'}, + {"get-assets", no_argument, nullptr, 'a'}, + {"stat", no_argument, nullptr, 's'}, + {"help", no_argument, nullptr, 'h'}, + {"header", required_argument, nullptr, 'H'}, + {"data", required_argument, nullptr, 'd'}, + {"multiply", required_argument, nullptr, 'm'}, + {"upgrade", no_argument, nullptr, 'u'}, + {"weight", required_argument, nullptr, 'p'}, + {"peer-max-concurrent-streams", required_argument, nullptr, 'M'}, + {"header-table-size", required_argument, nullptr, 'c'}, + {"padding", required_argument, nullptr, 'b'}, + {"har", required_argument, nullptr, 'r'}, + {"no-verify-peer", no_argument, nullptr, 'y'}, + {"cert", required_argument, &flag, 1}, + {"key", required_argument, &flag, 2}, + {"color", no_argument, &flag, 3}, + {"continuation", no_argument, &flag, 4}, + {"version", no_argument, &flag, 5}, + {"no-content-length", no_argument, &flag, 6}, + {"no-dep", no_argument, &flag, 7}, + {"trailer", required_argument, &flag, 9}, + {"hexdump", no_argument, &flag, 10}, + {"no-push", no_argument, &flag, 11}, + {"max-concurrent-streams", required_argument, &flag, 12}, + {"expect-continue", no_argument, &flag, 13}, + {"encoder-header-table-size", required_argument, &flag, 14}, + {"ktls", no_argument, &flag, 15}, + {"no-rfc7540-pri", no_argument, &flag, 16}, + {nullptr, 0, nullptr, 0}}; + int option_index = 0; + int c = + getopt_long(argc, argv, "M:Oab:c:d:m:np:r:hH:vst:uw:yW:", long_options, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'M': { + // peer-max-concurrent-streams option + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-M: Bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.peer_max_concurrent_streams = n; + break; + } + case 'O': + config.remote_name = true; + break; + case 'h': + print_help(std::cout); + exit(EXIT_SUCCESS); + case 'b': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-b: Bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.padding = n; + break; + } + case 'n': + config.null_out = true; + break; + case 'p': { + auto n = util::parse_uint(optarg); + if (n == -1 || NGHTTP2_MIN_WEIGHT > n || n > NGHTTP2_MAX_WEIGHT) { + std::cerr << "-p: specify the integer in the range [" + << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT + << "], inclusive" << std::endl; + exit(EXIT_FAILURE); + } + config.weight.push_back(n); + break; + } + case 'r': +#ifdef HAVE_JANSSON + config.harfile = optarg; +#else // !HAVE_JANSSON + std::cerr << "[WARNING]: -r, --har option is ignored because\n" + << "the binary was not compiled with libjansson." << std::endl; +#endif // !HAVE_JANSSON + break; + case 'v': + ++config.verbose; + break; + case 't': + config.timeout = util::parse_duration_with_unit(optarg); + if (config.timeout == std::numeric_limits::infinity()) { + std::cerr << "-t: bad timeout value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + break; + case 'u': + config.upgrade = true; + break; + case 'w': + case 'W': { + auto n = util::parse_uint(optarg); + if (n == -1 || n > 30) { + std::cerr << "-" << static_cast(c) + << ": specify the integer in the range [0, 30], inclusive" + << std::endl; + exit(EXIT_FAILURE); + } + if (c == 'w') { + config.window_bits = n; + } else { + config.connection_window_bits = n; + } + break; + } + case 'H': { + char *header = optarg; + // Skip first possible ':' in the header name + char *value = strchr(optarg + 1, ':'); + if (!value || (header[0] == ':' && header + 1 == value)) { + std::cerr << "-H: invalid header: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + *value = 0; + value++; + while (isspace(*value)) { + value++; + } + if (*value == 0) { + // This could also be a valid case for suppressing a header + // similar to curl + std::cerr << "-H: invalid header - value missing: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + config.headers.emplace_back(header, value, false); + util::inp_strlower(config.headers.back().name); + break; + } + case 'a': +#ifdef HAVE_LIBXML2 + config.get_assets = true; +#else // !HAVE_LIBXML2 + std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n" + << "the binary was not compiled with libxml2." << std::endl; +#endif // !HAVE_LIBXML2 + break; + case 's': + config.stat = true; + break; + case 'd': + config.datafile = optarg; + break; + case 'm': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-m: Bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.multiply = n; + break; + } + case 'c': { + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "-c: Bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + if (n > std::numeric_limits::max()) { + std::cerr << "-c: Value too large. It should be less than or equal to " + << std::numeric_limits::max() << std::endl; + exit(EXIT_FAILURE); + } + config.header_table_size = n; + config.min_header_table_size = std::min(config.min_header_table_size, n); + break; + } + case 'y': + config.verify_peer = false; + break; + case '?': + util::show_candidates(argv[optind - 1], long_options); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // cert option + config.certfile = optarg; + break; + case 2: + // key option + config.keyfile = optarg; + break; + case 3: + // color option + color = true; + break; + case 4: + // continuation option + config.continuation = true; + break; + case 5: + // version option + print_version(std::cout); + exit(EXIT_SUCCESS); + case 6: + // no-content-length option + config.no_content_length = true; + break; + case 7: + // no-dep option + config.no_dep = true; + break; + case 9: { + // trailer option + auto header = optarg; + auto value = strchr(optarg, ':'); + if (!value) { + std::cerr << "--trailer: invalid header: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + *value = 0; + value++; + while (isspace(*value)) { + value++; + } + if (*value == 0) { + // This could also be a valid case for suppressing a header + // similar to curl + std::cerr << "--trailer: invalid header - value missing: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + config.trailer.emplace_back(header, value, false); + util::inp_strlower(config.trailer.back().name); + break; + } + case 10: + // hexdump option + config.hexdump = true; + break; + case 11: + // no-push option + config.no_push = true; + break; + case 12: { + // max-concurrent-streams option + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "--max-concurrent-streams: Bad option value: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + config.max_concurrent_streams = n; + break; + } + case 13: + // expect-continue option + config.expect_continue = true; + break; + case 14: { + // encoder-header-table-size option + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "--encoder-header-table-size: Bad option value: " + << optarg << std::endl; + exit(EXIT_FAILURE); + } + if (n > std::numeric_limits::max()) { + std::cerr << "--encoder-header-table-size: Value too large. It " + "should be less than or equal to " + << std::numeric_limits::max() << std::endl; + exit(EXIT_FAILURE); + } + config.encoder_header_table_size = n; + break; + } + case 15: + // ktls option + config.ktls = true; + break; + case 16: + // no-rfc7540-pri option + config.no_rfc7540_pri = true; + break; + } + break; + default: + break; + } + } + + int32_t weight_to_fill; + if (config.weight.empty()) { + weight_to_fill = NGHTTP2_DEFAULT_WEIGHT; + } else { + weight_to_fill = config.weight.back(); + } + config.weight.insert(std::end(config.weight), argc - optind, weight_to_fill); + + // Find scheme overridden by extra header fields. + auto scheme_it = + std::find_if(std::begin(config.headers), std::end(config.headers), + [](const Header &nv) { return nv.name == ":scheme"; }); + if (scheme_it != std::end(config.headers)) { + config.scheme_override = (*scheme_it).value; + } + + // Find host and port overridden by extra header fields. + auto authority_it = + std::find_if(std::begin(config.headers), std::end(config.headers), + [](const Header &nv) { return nv.name == ":authority"; }); + if (authority_it == std::end(config.headers)) { + authority_it = + std::find_if(std::begin(config.headers), std::end(config.headers), + [](const Header &nv) { return nv.name == "host"; }); + } + + if (authority_it != std::end(config.headers)) { + // authority_it may looks like "host:port". + auto uri = "https://" + (*authority_it).value; + http_parser_url u{}; + if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { + std::cerr << "[ERROR] Could not parse authority in " + << (*authority_it).name << ": " << (*authority_it).value + << std::endl; + exit(EXIT_FAILURE); + } + + config.host_override = util::get_uri_field(uri.c_str(), u, UF_HOST).str(); + if (util::has_uri_field(u, UF_PORT)) { + config.port_override = u.port; + } + } + + set_color_output(color || isatty(fileno(stdout))); + + nghttp2_option_set_peer_max_concurrent_streams( + config.http2_option, config.peer_max_concurrent_streams); + + if (config.encoder_header_table_size != -1) { + nghttp2_option_set_max_deflate_dynamic_table_size( + config.http2_option, config.encoder_header_table_size); + } + + struct sigaction act {}; + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, nullptr); + reset_timer(); + return run(argv + optind, argc - optind); +} + +} // namespace nghttp2 + +int main(int argc, char **argv) { + return nghttp2::run_app(nghttp2::main, argc, argv); +} diff --git a/lib/nghttp2/src/nghttp.h b/lib/nghttp2/src/nghttp.h new file mode 100644 index 00000000000..a8804142275 --- /dev/null +++ b/lib/nghttp2/src/nghttp.h @@ -0,0 +1,310 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 NGHTTP_H +#define NGHTTP_H + +#include "nghttp2_config.h" + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "llhttp.h" + +#include "memchunk.h" +#include "http2.h" +#include "nghttp2_gzip.h" +#include "template.h" + +namespace nghttp2 { + +class HtmlParser; + +struct Config { + Config(); + ~Config(); + + Headers headers; + Headers trailer; + std::vector weight; + std::string certfile; + std::string keyfile; + std::string datafile; + std::string harfile; + std::string scheme_override; + std::string host_override; + nghttp2_option *http2_option; + int64_t header_table_size; + int64_t min_header_table_size; + int64_t encoder_header_table_size; + size_t padding; + size_t max_concurrent_streams; + ssize_t peer_max_concurrent_streams; + int multiply; + // milliseconds + ev_tstamp timeout; + int window_bits; + int connection_window_bits; + int verbose; + uint16_t port_override; + bool null_out; + bool remote_name; + bool get_assets; + bool stat; + bool upgrade; + bool continuation; + bool no_content_length; + bool no_dep; + bool hexdump; + bool no_push; + bool expect_continue; + bool verify_peer; + bool ktls; + bool no_rfc7540_pri; +}; + +enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE }; + +struct RequestTiming { + // The point in time when request is started to be sent. + // Corresponds to requestStart in Resource Timing TR. + std::chrono::steady_clock::time_point request_start_time; + // The point in time when first byte of response is received. + // Corresponds to responseStart in Resource Timing TR. + std::chrono::steady_clock::time_point response_start_time; + // The point in time when last byte of response is received. + // Corresponds to responseEnd in Resource Timing TR. + std::chrono::steady_clock::time_point response_end_time; + RequestState state; + RequestTiming() : state(RequestState::INITIAL) {} +}; + +struct Request; // forward declaration for ContinueTimer + +struct ContinueTimer { + ContinueTimer(struct ev_loop *loop, Request *req); + ~ContinueTimer(); + + void start(); + void stop(); + + // Schedules an immediate run of the continue callback on the loop, if the + // callback has not already been run + void dispatch_continue(); + + struct ev_loop *loop; + ev_timer timer; +}; + +struct Request { + // For pushed request, |uri| is empty and |u| is zero-cleared. + Request(const std::string &uri, const http_parser_url &u, + const nghttp2_data_provider *data_prd, int64_t data_length, + const nghttp2_priority_spec &pri_spec, int level = 0); + ~Request(); + + void init_inflater(); + + void init_html_parser(); + int update_html_parser(const uint8_t *data, size_t len, int fin); + + std::string make_reqpath() const; + + bool is_ipv6_literal_addr() const; + + Headers::value_type *get_res_header(int32_t token); + Headers::value_type *get_req_header(int32_t token); + + void record_request_start_time(); + void record_response_start_time(); + void record_response_end_time(); + + // Returns scheme taking into account overridden scheme. + StringRef get_real_scheme() const; + // Returns request host, without port, taking into account + // overridden host. + StringRef get_real_host() const; + // Returns request port, taking into account overridden host, port, + // and scheme. + uint16_t get_real_port() const; + + Headers res_nva; + Headers req_nva; + std::string method; + // URI without fragment + std::string uri; + http_parser_url u; + nghttp2_priority_spec pri_spec; + RequestTiming timing; + int64_t data_length; + int64_t data_offset; + // Number of bytes received from server + int64_t response_len; + nghttp2_gzip *inflater; + std::unique_ptr html_parser; + const nghttp2_data_provider *data_prd; + size_t header_buffer_size; + int32_t stream_id; + int status; + // Recursion level: 0: first entity, 1: entity linked from first entity + int level; + http2::HeaderIndex res_hdidx; + // used for incoming PUSH_PROMISE + http2::HeaderIndex req_hdidx; + bool expect_final_response; + // only assigned if this request is using Expect/Continue + std::unique_ptr continue_timer; +}; + +struct SessionTiming { + // The point in time when operation was started. Corresponds to + // startTime in Resource Timing TR, but recorded in system clock time. + std::chrono::system_clock::time_point system_start_time; + // Same as above, but recorded in steady clock time. + std::chrono::steady_clock::time_point start_time; + // The point in time when DNS resolution was completed. Corresponds + // to domainLookupEnd in Resource Timing TR. + std::chrono::steady_clock::time_point domain_lookup_end_time; + // The point in time when connection was established or SSL/TLS + // handshake was completed. Corresponds to connectEnd in Resource + // Timing TR. + std::chrono::steady_clock::time_point connect_end_time; +}; + +enum class ClientState { IDLE, CONNECTED }; + +struct HttpClient { + HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop, + SSL_CTX *ssl_ctx); + ~HttpClient(); + + bool need_upgrade() const; + int resolve_host(const std::string &host, uint16_t port); + int initiate_connection(); + void disconnect(); + + int noop(); + int read_clear(); + int write_clear(); + int connected(); + int tls_handshake(); + int read_tls(); + int write_tls(); + + int do_read(); + int do_write(); + + int on_upgrade_connect(); + int on_upgrade_read(const uint8_t *data, size_t len); + int on_read(const uint8_t *data, size_t len); + int on_write(); + + int connection_made(); + void connect_fail(); + void request_done(Request *req); + + void signal_write(); + + bool all_requests_processed() const; + void update_hostport(); + bool add_request(const std::string &uri, + const nghttp2_data_provider *data_prd, int64_t data_length, + const nghttp2_priority_spec &pri_spec, int level = 0); + + void record_start_time(); + void record_domain_lookup_end_time(); + void record_connect_end_time(); + +#ifdef HAVE_JANSSON + void output_har(FILE *outfile); +#endif // HAVE_JANSSON + + MemchunkPool mcpool; + DefaultMemchunks wb; + std::vector> reqvec; + // Insert path already added in reqvec to prevent multiple request + // for 1 resource. + std::set path_cache; + std::string scheme; + std::string host; + std::string hostport; + // Used for parse the HTTP upgrade response from server + std::unique_ptr htp; + SessionTiming timing; + ev_io wev; + ev_io rev; + ev_timer wt; + ev_timer rt; + ev_timer settings_timer; + std::function readfn, writefn; + std::function on_readfn; + std::function on_writefn; + nghttp2_session *session; + const nghttp2_session_callbacks *callbacks; + struct ev_loop *loop; + SSL_CTX *ssl_ctx; + SSL *ssl; + addrinfo *addrs; + addrinfo *next_addr; + addrinfo *cur_addr; + // The number of completed requests, including failed ones. + size_t complete; + // The number of requests that local endpoint received END_STREAM + // from peer. + size_t success; + // The length of settings_payload + size_t settings_payloadlen; + ClientState state; + // The HTTP status code of the response message of HTTP Upgrade. + unsigned int upgrade_response_status_code; + int fd; + // true if the response message of HTTP Upgrade request is fully + // received. It is not relevant the upgrade succeeds, or not. + bool upgrade_response_complete; + // SETTINGS payload sent as token68 in HTTP Upgrade + std::array settings_payload; + + enum { ERR_CONNECT_FAIL = -100 }; +}; + +} // namespace nghttp2 + +#endif // NGHTTP_H diff --git a/lib/nghttp2/src/nghttp2_config.h b/lib/nghttp2/src/nghttp2_config.h new file mode 100644 index 00000000000..8e6cd05f1da --- /dev/null +++ b/lib/nghttp2/src/nghttp2_config.h @@ -0,0 +1,32 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_CONFIG_H +#define NGHTTP2_CONFIG_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +#endif // NGHTTP2_CONFIG_H diff --git a/lib/nghttp2/src/nghttp2_gzip.c b/lib/nghttp2/src/nghttp2_gzip.c new file mode 100644 index 00000000000..5f17ab2a0c3 --- /dev/null +++ b/lib/nghttp2/src/nghttp2_gzip.c @@ -0,0 +1,87 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_gzip.h" + +#include + +int nghttp2_gzip_inflate_new(nghttp2_gzip **inflater_ptr) { + int rv; + *inflater_ptr = calloc(1, sizeof(nghttp2_gzip)); + if (*inflater_ptr == NULL) { + return -1; + } + rv = inflateInit2(&(*inflater_ptr)->zst, 47); + if (rv != Z_OK) { + free(*inflater_ptr); + return -1; + } + return 0; +} + +void nghttp2_gzip_inflate_del(nghttp2_gzip *inflater) { + if (inflater != NULL) { + inflateEnd(&inflater->zst); + free(inflater); + } +} + +int nghttp2_gzip_inflate(nghttp2_gzip *inflater, uint8_t *out, + size_t *outlen_ptr, const uint8_t *in, + size_t *inlen_ptr) { + int rv; + if (inflater->finished) { + return -1; + } + inflater->zst.avail_in = (unsigned int)*inlen_ptr; + inflater->zst.next_in = (unsigned char *)in; + inflater->zst.avail_out = (unsigned int)*outlen_ptr; + inflater->zst.next_out = out; + + rv = inflate(&inflater->zst, Z_NO_FLUSH); + + *inlen_ptr -= inflater->zst.avail_in; + *outlen_ptr -= inflater->zst.avail_out; + switch (rv) { + case Z_STREAM_END: + inflater->finished = 1; + /* FALL THROUGH */ + case Z_OK: + case Z_BUF_ERROR: + return 0; + case Z_DATA_ERROR: + case Z_STREAM_ERROR: + case Z_NEED_DICT: + case Z_MEM_ERROR: + return -1; + default: + assert(0); + /* We need this for some compilers */ + return 0; + } +} + +int nghttp2_gzip_inflate_finished(nghttp2_gzip *inflater) { + return inflater->finished; +} diff --git a/lib/nghttp2/src/nghttp2_gzip.h b/lib/nghttp2/src/nghttp2_gzip.h new file mode 100644 index 00000000000..a40352c3c2f --- /dev/null +++ b/lib/nghttp2/src/nghttp2_gzip.h @@ -0,0 +1,122 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_GZIP_H + +# ifdef HAVE_CONFIG_H +# include +# endif /* HAVE_CONFIG_H */ +# include + +# include + +# ifdef __cplusplus +extern "C" { +# endif + +/** + * @struct + * + * The gzip stream to inflate data. + */ +typedef struct { + z_stream zst; + int8_t finished; +} nghttp2_gzip; + +/** + * @function + * + * A helper function to set up a per request gzip stream to inflate + * data. + * + * This function returns 0 if it succeeds, or -1. + */ +int nghttp2_gzip_inflate_new(nghttp2_gzip **inflater_ptr); + +/** + * @function + * + * Frees the inflate stream. The |inflater| may be ``NULL``. + */ +void nghttp2_gzip_inflate_del(nghttp2_gzip *inflater); + +/** + * @function + * + * Inflates data in |in| with the length |*inlen_ptr| and stores the + * inflated data to |out| which has allocated size at least + * |*outlen_ptr|. On return, |*outlen_ptr| is updated to represent + * the number of data written in |out|. Similarly, |*inlen_ptr| is + * updated to represent the number of input bytes processed. + * + * This function returns 0 if it succeeds, or -1. + * + * The example follows:: + * + * void on_data_chunk_recv_callback(nghttp2_session *session, + * uint8_t flags, + * int32_t stream_id, + * const uint8_t *data, size_t len, + * void *user_data) + * { + * ... + * req = nghttp2_session_get_stream_user_data(session, stream_id); + * nghttp2_gzip *inflater = req->inflater; + * while(len > 0) { + * uint8_t out[MAX_OUTLEN]; + * size_t outlen = MAX_OUTLEN; + * size_t tlen = len; + * int rv; + * rv = nghttp2_gzip_inflate(inflater, out, &outlen, data, &tlen); + * if(rv != 0) { + * nghttp2_submit_rst_stream(session, stream_id, + * NGHTTP2_INTERNAL_ERROR); + * break; + * } + * ... Do stuff ... + * data += tlen; + * len -= tlen; + * } + * .... + * } + */ +int nghttp2_gzip_inflate(nghttp2_gzip *inflater, uint8_t *out, + size_t *outlen_ptr, const uint8_t *in, + size_t *inlen_ptr); + +/** + * @function + * + * Returns nonzero if |inflater| sees the end of deflate stream. + * After this function returns nonzero, `nghttp2_gzip_inflate()` with + * |inflater| gets to return error. + */ +int nghttp2_gzip_inflate_finished(nghttp2_gzip *inflater); + +# ifdef __cplusplus +} +# endif + +#endif /* NGHTTP2_GZIP_H */ diff --git a/lib/nghttp2/src/nghttp2_gzip_test.c b/lib/nghttp2/src/nghttp2_gzip_test.c new file mode 100644 index 00000000000..de19d5da338 --- /dev/null +++ b/lib/nghttp2/src/nghttp2_gzip_test.c @@ -0,0 +1,111 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_gzip_test.h" + +#include +#include + +#include + +#include + +#include "nghttp2_gzip.h" + +static size_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in, + size_t inlen) { + int rv; + z_stream zst = {0}; + + rv = deflateInit(&zst, Z_DEFAULT_COMPRESSION); + CU_ASSERT(rv == Z_OK); + + zst.avail_in = (unsigned int)inlen; + zst.next_in = (uint8_t *)in; + zst.avail_out = (unsigned int)outlen; + zst.next_out = out; + rv = deflate(&zst, Z_SYNC_FLUSH); + CU_ASSERT(rv == Z_OK); + + deflateEnd(&zst); + + return outlen - zst.avail_out; +} + +static const char input[] = + "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."; + +void test_nghttp2_gzip_inflate(void) { + nghttp2_gzip *inflater; + uint8_t in[4096], out[4096], *inptr; + size_t inlen = sizeof(in); + size_t inproclen, outproclen; + const char *inputptr = input; + + inlen = deflate_data(in, inlen, (const uint8_t *)input, sizeof(input) - 1); + + CU_ASSERT(0 == nghttp2_gzip_inflate_new(&inflater)); + /* First 16 bytes */ + inptr = in; + inproclen = inlen; + outproclen = 16; + CU_ASSERT( + 0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen)); + CU_ASSERT(16 == outproclen); + CU_ASSERT(inproclen > 0); + CU_ASSERT(0 == memcmp(inputptr, out, outproclen)); + /* Next 32 bytes */ + inptr += inproclen; + inlen -= inproclen; + inproclen = inlen; + inputptr += outproclen; + outproclen = 32; + CU_ASSERT( + 0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen)); + CU_ASSERT(32 == outproclen); + CU_ASSERT(inproclen > 0); + CU_ASSERT(0 == memcmp(inputptr, out, outproclen)); + /* Rest */ + inptr += inproclen; + inlen -= inproclen; + inproclen = inlen; + inputptr += outproclen; + outproclen = sizeof(out); + CU_ASSERT( + 0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen)); + CU_ASSERT(sizeof(input) - 49 == outproclen); + CU_ASSERT(inproclen > 0); + CU_ASSERT(0 == memcmp(inputptr, out, outproclen)); + + inlen -= inproclen; + CU_ASSERT(0 == inlen); + + nghttp2_gzip_inflate_del(inflater); +} diff --git a/lib/nghttp2/src/nghttp2_gzip_test.h b/lib/nghttp2/src/nghttp2_gzip_test.h new file mode 100644 index 00000000000..8d554f7209a --- /dev/null +++ b/lib/nghttp2/src/nghttp2_gzip_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_GZIP_TEST_H +#define NGHTTP2_GZIP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#ifdef __cplusplus +extern "C" { +#endif + +void test_nghttp2_gzip_inflate(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NGHTTP2_GZIP_TEST_H */ diff --git a/lib/nghttp2/src/nghttpd.cc b/lib/nghttp2/src/nghttpd.cc new file mode 100644 index 00000000000..3c5f5b4bec1 --- /dev/null +++ b/lib/nghttp2/src/nghttpd.cc @@ -0,0 +1,508 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_config.h" + +#ifdef __sgi +# define daemon _daemonize +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "app_helper.h" +#include "HttpServer.h" +#include "util.h" +#include "tls.h" + +namespace nghttp2 { + +namespace { +int parse_push_config(Config &config, const char *optarg) { + const char *eq = strchr(optarg, '='); + if (eq == nullptr) { + return -1; + } + auto &paths = config.push[std::string(optarg, eq)]; + auto optarg_end = optarg + strlen(optarg); + auto i = eq + 1; + for (;;) { + const char *j = strchr(i, ','); + if (j == nullptr) { + j = optarg_end; + } + paths.emplace_back(i, j); + if (j == optarg_end) { + break; + } + i = j; + ++i; + } + + return 0; +} +} // namespace + +namespace { +void print_version(std::ostream &out) { + out << "nghttpd nghttp2/" NGHTTP2_VERSION << std::endl; +} +} // namespace + +namespace { +void print_usage(std::ostream &out) { + out << "Usage: nghttpd [OPTION]... [ ]\n" + << "HTTP/2 server" << std::endl; +} +} // namespace + +namespace { +void print_help(std::ostream &out) { + Config config; + print_usage(out); + out << R"( + Specify listening port number. + + Set path to server's private key. Required unless + --no-tls is specified. + Set path to server's certificate. Required unless + --no-tls is specified. +Options: + -a, --address= + The address to bind to. If not specified the default IP + address determined by getaddrinfo is used. + -D, --daemon + Run in a background. If -D is used, the current working + directory is changed to '/'. Therefore if this option + is used, -d option must be specified. + -V, --verify-client + The server sends a client certificate request. If the + client did not return a certificate, the handshake is + terminated. Currently, this option just requests a + client certificate and does not verify it. + -d, --htdocs= + Specify document root. If this option is not specified, + the document root is the current working directory. + -v, --verbose + Print debug information such as reception/ transmission + of frames and name/value pairs. + --no-tls Disable SSL/TLS. + -c, --header-table-size= + Specify decoder header table size. + --encoder-header-table-size= + Specify encoder header table size. The decoder (client) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which client specified. + --color Force colored log output. + -p, --push== + Push resources s when is requested. + This option can be used repeatedly to specify multiple + push configurations. and s are + relative to document root. See --htdocs option. + Example: -p/=/foo.png -p/doc=/bar.css + -b, --padding= + Add at most bytes to a frame payload as padding. + Specify 0 to disable padding. + -m, --max-concurrent-streams= + Set the maximum number of the concurrent streams in one + HTTP/2 session. + Default: )" + << config.max_concurrent_streams << R"( + -n, --workers= + Set the number of worker threads. + Default: 1 + -e, --error-gzip + Make error response gzipped. + -w, --window-bits= + Sets the stream level initial window size to 2**-1. + -W, --connection-window-bits= + Sets the connection level initial window size to + 2**-1. + --dh-param-file= + Path to file that contains DH parameters in PEM format. + Without this option, DHE cipher suites are not + available. + --early-response + Start sending response when request HEADERS is received, + rather than complete request is received. + --trailer=
+ Add a trailer header to a response.
must not + include pseudo header field (header field name starting + with ':'). The trailer is sent only if a response has + body part. Example: --trailer 'foo: bar'. + --hexdump Display the incoming traffic in hexadecimal (Canonical + hex+ASCII display). If SSL/TLS is used, decrypted data + are used. + --echo-upload + Send back uploaded content if method is POST or PUT. + --mime-types-file= + Path to file that contains MIME media types and the + extensions that represent them. + Default: )" + << config.mime_types_file << R"( + --no-content-length + Don't send content-length header field. + --ktls Enable ktls. + --no-rfc7540-pri + Disable RFC7540 priorities. + --version Display version information and exit. + -h, --help Display this help and exit. + +-- + + The argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024).)" + << std::endl; +} +} // namespace + +int main(int argc, char **argv) { + tls::libssl_init(); + +#ifndef NOTHREADS + tls::LibsslGlobalLock lock; +#endif // NOTHREADS + + Config config; + bool color = false; + auto mime_types_file_set_manually = false; + + while (1) { + static int flag = 0; + constexpr static option long_options[] = { + {"address", required_argument, nullptr, 'a'}, + {"daemon", no_argument, nullptr, 'D'}, + {"htdocs", required_argument, nullptr, 'd'}, + {"help", no_argument, nullptr, 'h'}, + {"verbose", no_argument, nullptr, 'v'}, + {"verify-client", no_argument, nullptr, 'V'}, + {"header-table-size", required_argument, nullptr, 'c'}, + {"push", required_argument, nullptr, 'p'}, + {"padding", required_argument, nullptr, 'b'}, + {"max-concurrent-streams", required_argument, nullptr, 'm'}, + {"workers", required_argument, nullptr, 'n'}, + {"error-gzip", no_argument, nullptr, 'e'}, + {"window-bits", required_argument, nullptr, 'w'}, + {"connection-window-bits", required_argument, nullptr, 'W'}, + {"no-tls", no_argument, &flag, 1}, + {"color", no_argument, &flag, 2}, + {"version", no_argument, &flag, 3}, + {"dh-param-file", required_argument, &flag, 4}, + {"early-response", no_argument, &flag, 5}, + {"trailer", required_argument, &flag, 6}, + {"hexdump", no_argument, &flag, 7}, + {"echo-upload", no_argument, &flag, 8}, + {"mime-types-file", required_argument, &flag, 9}, + {"no-content-length", no_argument, &flag, 10}, + {"encoder-header-table-size", required_argument, &flag, 11}, + {"ktls", no_argument, &flag, 12}, + {"no-rfc7540-pri", no_argument, &flag, 13}, + {nullptr, 0, nullptr, 0}}; + int option_index = 0; + int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'a': + config.address = optarg; + break; + case 'D': + config.daemon = true; + break; + case 'V': + config.verify_client = true; + break; + case 'b': { + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-b: Bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.padding = n; + break; + } + case 'd': + config.htdocs = optarg; + break; + case 'e': + config.error_gzip = true; + break; + case 'm': { + // max-concurrent-streams option + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-m: invalid argument: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.max_concurrent_streams = n; + break; + } + case 'n': { +#ifdef NOTHREADS + std::cerr << "-n: WARNING: Threading disabled at build time, " + << "no threads created." << std::endl; +#else + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-n: Bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.num_worker = n; +#endif // NOTHREADS + break; + } + case 'h': + print_help(std::cout); + exit(EXIT_SUCCESS); + case 'v': + config.verbose = true; + break; + case 'c': { + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "-c: Bad option value: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + if (n > std::numeric_limits::max()) { + std::cerr << "-c: Value too large. It should be less than or equal to " + << std::numeric_limits::max() << std::endl; + exit(EXIT_FAILURE); + } + config.header_table_size = n; + break; + } + case 'p': + if (parse_push_config(config, optarg) != 0) { + std::cerr << "-p: Bad option value: " << optarg << std::endl; + } + break; + case 'w': + case 'W': { + auto n = util::parse_uint(optarg); + if (n == -1 || n > 30) { + std::cerr << "-" << static_cast(c) + << ": specify the integer in the range [0, 30], inclusive" + << std::endl; + exit(EXIT_FAILURE); + } + + if (c == 'w') { + config.window_bits = n; + } else { + config.connection_window_bits = n; + } + + break; + } + case '?': + util::show_candidates(argv[optind - 1], long_options); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // no-tls option + config.no_tls = true; + break; + case 2: + // color option + color = true; + break; + case 3: + // version + print_version(std::cout); + exit(EXIT_SUCCESS); + case 4: + // dh-param-file + config.dh_param_file = optarg; + break; + case 5: + // early-response + config.early_response = true; + break; + case 6: { + // trailer option + auto header = optarg; + auto value = strchr(optarg, ':'); + if (!value) { + std::cerr << "--trailer: invalid header: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + *value = 0; + value++; + while (isspace(*value)) { + value++; + } + if (*value == 0) { + // This could also be a valid case for suppressing a header + // similar to curl + std::cerr << "--trailer: invalid header - value missing: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + config.trailer.emplace_back(header, value, false); + util::inp_strlower(config.trailer.back().name); + break; + } + case 7: + // hexdump option + config.hexdump = true; + break; + case 8: + // echo-upload option + config.echo_upload = true; + break; + case 9: + // mime-types-file option + mime_types_file_set_manually = true; + config.mime_types_file = optarg; + break; + case 10: + // no-content-length option + config.no_content_length = true; + break; + case 11: { + // encoder-header-table-size option + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + std::cerr << "--encoder-header-table-size: Bad option value: " + << optarg << std::endl; + exit(EXIT_FAILURE); + } + if (n > std::numeric_limits::max()) { + std::cerr << "--encoder-header-table-size: Value too large. It " + "should be less than or equal to " + << std::numeric_limits::max() << std::endl; + exit(EXIT_FAILURE); + } + config.encoder_header_table_size = n; + break; + } + case 12: + // tls option + config.ktls = true; + break; + case 13: + // no-rfc7540-pri option + config.no_rfc7540_pri = true; + break; + } + break; + default: + break; + } + } + if (argc - optind < (config.no_tls ? 1 : 3)) { + print_usage(std::cerr); + std::cerr << "Too few arguments" << std::endl; + exit(EXIT_FAILURE); + } + + { + auto portStr = argv[optind++]; + auto n = util::parse_uint(portStr); + if (n == -1 || n > std::numeric_limits::max()) { + std::cerr << ": Bad value: " << portStr << std::endl; + exit(EXIT_FAILURE); + } + config.port = n; + } + + if (!config.no_tls) { + config.private_key_file = argv[optind++]; + config.cert_file = argv[optind++]; + } + + if (config.daemon) { + if (config.htdocs.empty()) { + print_usage(std::cerr); + std::cerr << "-d option must be specified when -D is used." << std::endl; + exit(EXIT_FAILURE); + } +#ifdef __sgi + if (daemon(0, 0, 0, 0) == -1) { +#else + if (util::daemonize(0, 0) == -1) { +#endif + perror("daemon"); + exit(EXIT_FAILURE); + } + } + if (config.htdocs.empty()) { + config.htdocs = "./"; + } + + if (util::read_mime_types(config.mime_types, + config.mime_types_file.c_str()) != 0) { + if (mime_types_file_set_manually) { + std::cerr << "--mime-types-file: Could not open mime types file: " + << config.mime_types_file << std::endl; + } + } + + auto &trailer_names = config.trailer_names; + for (auto &h : config.trailer) { + trailer_names += h.name; + trailer_names += ", "; + } + if (trailer_names.size() >= 2) { + trailer_names.resize(trailer_names.size() - 2); + } + + set_color_output(color || isatty(fileno(stdout))); + + struct sigaction act {}; + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, nullptr); + + reset_timer(); + + HttpServer server(&config); + if (server.run() != 0) { + exit(EXIT_FAILURE); + } + return 0; +} + +} // namespace nghttp2 + +int main(int argc, char **argv) { + return nghttp2::run_app(nghttp2::main, argc, argv); +} diff --git a/lib/nghttp2/src/quic.cc b/lib/nghttp2/src/quic.cc new file mode 100644 index 00000000000..1c68a5bed46 --- /dev/null +++ b/lib/nghttp2/src/quic.cc @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 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 "quic.h" + +#include + +#include +#include + +#include "template.h" + +using namespace nghttp2; + +namespace quic { + +Error err_transport(int liberr) { + if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) { + return {ErrorType::TransportVersionNegotiation, 0}; + } + return {ErrorType::Transport, + ngtcp2_err_infer_quic_transport_error_code(liberr)}; +} + +Error err_transport_idle_timeout() { + return {ErrorType::TransportIdleTimeout, 0}; +} + +Error err_transport_tls(int alert) { + return {ErrorType::Transport, ngtcp2_err_infer_quic_transport_error_code( + NGTCP2_CRYPTO_ERROR | alert)}; +} + +Error err_application(int liberr) { + return {ErrorType::Application, + nghttp3_err_infer_quic_app_error_code(liberr)}; +} + +} // namespace quic diff --git a/lib/nghttp2/src/quic.h b/lib/nghttp2/src/quic.h new file mode 100644 index 00000000000..d72ae1feb5e --- /dev/null +++ b/lib/nghttp2/src/quic.h @@ -0,0 +1,56 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2019 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 QUIC_H +#define QUIC_H + +#include "nghttp2_config.h" + +#include "stdint.h" + +namespace quic { + +enum class ErrorType { + Transport, + TransportVersionNegotiation, + TransportIdleTimeout, + Application, +}; + +struct Error { + Error(ErrorType type, uint64_t code) : type(type), code(code) {} + Error() : type(ErrorType::Transport), code(0) {} + + ErrorType type; + uint64_t code; +}; + +Error err_transport(int liberr); +Error err_transport_idle_timeout(); +Error err_transport_tls(int alert); +Error err_application(int liberr); + +} // namespace quic + +#endif // QUIC_H diff --git a/lib/nghttp2/src/shrpx-unittest.cc b/lib/nghttp2/src/shrpx-unittest.cc new file mode 100644 index 00000000000..07373d57087 --- /dev/null +++ b/lib/nghttp2/src/shrpx-unittest.cc @@ -0,0 +1,248 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 +#endif // HAVE_CONFIG_H + +#include +#include +#include +// include test cases' include files here +#include "shrpx_tls_test.h" +#include "shrpx_downstream_test.h" +#include "shrpx_config_test.h" +#include "shrpx_worker_test.h" +#include "http2_test.h" +#include "util_test.h" +#include "nghttp2_gzip_test.h" +#include "buffer_test.h" +#include "memchunk_test.h" +#include "template_test.h" +#include "shrpx_http_test.h" +#include "base64_test.h" +#include "shrpx_config.h" +#include "tls.h" +#include "shrpx_router_test.h" +#include "shrpx_log.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; + + nghttp2::tls::libssl_init(); + + shrpx::create_config(); + + // 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("shrpx_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, "tls_create_lookup_tree", + shrpx::test_shrpx_tls_create_lookup_tree) || + !CU_add_test(pSuite, "tls_cert_lookup_tree_add_ssl_ctx", + shrpx::test_shrpx_tls_cert_lookup_tree_add_ssl_ctx) || + !CU_add_test(pSuite, "tls_tls_hostname_match", + shrpx::test_shrpx_tls_tls_hostname_match) || + !CU_add_test(pSuite, "tls_tls_verify_numeric_hostname", + shrpx::test_shrpx_tls_verify_numeric_hostname) || + !CU_add_test(pSuite, "tls_tls_verify_dns_hostname", + shrpx::test_shrpx_tls_verify_dns_hostname) || + !CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) || + !CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) || + !CU_add_test(pSuite, "http2_copy_headers_to_nva", + shrpx::test_http2_copy_headers_to_nva) || + !CU_add_test(pSuite, "http2_build_http1_headers_from_headers", + shrpx::test_http2_build_http1_headers_from_headers) || + !CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) || + !CU_add_test(pSuite, "http2_rewrite_location_uri", + shrpx::test_http2_rewrite_location_uri) || + !CU_add_test(pSuite, "http2_parse_http_status_code", + shrpx::test_http2_parse_http_status_code) || + !CU_add_test(pSuite, "http2_index_header", + shrpx::test_http2_index_header) || + !CU_add_test(pSuite, "http2_lookup_token", + shrpx::test_http2_lookup_token) || + !CU_add_test(pSuite, "http2_parse_link_header", + shrpx::test_http2_parse_link_header) || + !CU_add_test(pSuite, "http2_path_join", shrpx::test_http2_path_join) || + !CU_add_test(pSuite, "http2_normalize_path", + shrpx::test_http2_normalize_path) || + !CU_add_test(pSuite, "http2_rewrite_clean_path", + shrpx::test_http2_rewrite_clean_path) || + !CU_add_test(pSuite, "http2_get_pure_path_component", + shrpx::test_http2_get_pure_path_component) || + !CU_add_test(pSuite, "http2_construct_push_component", + shrpx::test_http2_construct_push_component) || + !CU_add_test(pSuite, "http2_contains_trailers", + shrpx::test_http2_contains_trailers) || + !CU_add_test(pSuite, "http2_check_transfer_encoding", + shrpx::test_http2_check_transfer_encoding) || + !CU_add_test(pSuite, "downstream_field_store_append_last_header", + shrpx::test_downstream_field_store_append_last_header) || + !CU_add_test(pSuite, "downstream_field_store_header", + shrpx::test_downstream_field_store_header) || + !CU_add_test(pSuite, "downstream_crumble_request_cookie", + shrpx::test_downstream_crumble_request_cookie) || + !CU_add_test(pSuite, "downstream_assemble_request_cookie", + shrpx::test_downstream_assemble_request_cookie) || + !CU_add_test(pSuite, "downstream_rewrite_location_response_header", + shrpx::test_downstream_rewrite_location_response_header) || + !CU_add_test(pSuite, "downstream_supports_non_final_response", + shrpx::test_downstream_supports_non_final_response) || + !CU_add_test(pSuite, "downstream_find_affinity_cookie", + shrpx::test_downstream_find_affinity_cookie) || + !CU_add_test(pSuite, "config_parse_header", + shrpx::test_shrpx_config_parse_header) || + !CU_add_test(pSuite, "config_parse_log_format", + shrpx::test_shrpx_config_parse_log_format) || + !CU_add_test(pSuite, "config_read_tls_ticket_key_file", + shrpx::test_shrpx_config_read_tls_ticket_key_file) || + !CU_add_test(pSuite, "config_read_tls_ticket_key_file_aes_256", + shrpx::test_shrpx_config_read_tls_ticket_key_file_aes_256) || + !CU_add_test(pSuite, "worker_match_downstream_addr_group", + shrpx::test_shrpx_worker_match_downstream_addr_group) || + !CU_add_test(pSuite, "http_create_forwarded", + shrpx::test_shrpx_http_create_forwarded) || + !CU_add_test(pSuite, "http_create_via_header_value", + shrpx::test_shrpx_http_create_via_header_value) || + !CU_add_test(pSuite, "http_create_affinity_cookie", + shrpx::test_shrpx_http_create_affinity_cookie) || + !CU_add_test(pSuite, "http_create_atlsvc_header_field_value", + shrpx::test_shrpx_http_create_altsvc_header_value) || + !CU_add_test(pSuite, "http_check_http_scheme", + shrpx::test_shrpx_http_check_http_scheme) || + !CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) || + !CU_add_test(pSuite, "router_match_wildcard", + shrpx::test_shrpx_router_match_wildcard) || + !CU_add_test(pSuite, "router_match_prefix", + shrpx::test_shrpx_router_match_prefix) || + !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) || + !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) || + !CU_add_test(pSuite, "util_inp_strlower", + shrpx::test_util_inp_strlower) || + !CU_add_test(pSuite, "util_to_base64", shrpx::test_util_to_base64) || + !CU_add_test(pSuite, "util_to_token68", shrpx::test_util_to_token68) || + !CU_add_test(pSuite, "util_percent_encode_token", + shrpx::test_util_percent_encode_token) || + !CU_add_test(pSuite, "util_percent_decode", + shrpx::test_util_percent_decode) || + !CU_add_test(pSuite, "util_quote_string", + shrpx::test_util_quote_string) || + !CU_add_test(pSuite, "util_utox", shrpx::test_util_utox) || + !CU_add_test(pSuite, "util_http_date", shrpx::test_util_http_date) || + !CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) || + !CU_add_test(pSuite, "util_ipv6_numeric_addr", + shrpx::test_util_ipv6_numeric_addr) || + !CU_add_test(pSuite, "util_utos", shrpx::test_util_utos) || + !CU_add_test(pSuite, "util_make_string_ref_uint", + shrpx::test_util_make_string_ref_uint) || + !CU_add_test(pSuite, "util_utos_unit", shrpx::test_util_utos_unit) || + !CU_add_test(pSuite, "util_utos_funit", shrpx::test_util_utos_funit) || + !CU_add_test(pSuite, "util_parse_uint_with_unit", + shrpx::test_util_parse_uint_with_unit) || + !CU_add_test(pSuite, "util_parse_uint", shrpx::test_util_parse_uint) || + !CU_add_test(pSuite, "util_parse_duration_with_unit", + shrpx::test_util_parse_duration_with_unit) || + !CU_add_test(pSuite, "util_duration_str", + shrpx::test_util_duration_str) || + !CU_add_test(pSuite, "util_format_duration", + shrpx::test_util_format_duration) || + !CU_add_test(pSuite, "util_starts_with", shrpx::test_util_starts_with) || + !CU_add_test(pSuite, "util_ends_with", shrpx::test_util_ends_with) || + !CU_add_test(pSuite, "util_parse_http_date", + shrpx::test_util_parse_http_date) || + !CU_add_test(pSuite, "util_localtime_date", + shrpx::test_util_localtime_date) || + !CU_add_test(pSuite, "util_get_uint64", shrpx::test_util_get_uint64) || + !CU_add_test(pSuite, "util_parse_config_str_list", + shrpx::test_util_parse_config_str_list) || + !CU_add_test(pSuite, "util_make_http_hostport", + shrpx::test_util_make_http_hostport) || + !CU_add_test(pSuite, "util_make_hostport", + shrpx::test_util_make_hostport) || + !CU_add_test(pSuite, "util_strifind", shrpx::test_util_strifind) || + !CU_add_test(pSuite, "util_random_alpha_digit", + shrpx::test_util_random_alpha_digit) || + !CU_add_test(pSuite, "util_format_hex", shrpx::test_util_format_hex) || + !CU_add_test(pSuite, "util_is_hex_string", + shrpx::test_util_is_hex_string) || + !CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) || + !CU_add_test(pSuite, "util_extract_host", + shrpx::test_util_extract_host) || + !CU_add_test(pSuite, "util_split_hostport", + shrpx::test_util_split_hostport) || + !CU_add_test(pSuite, "util_split_str", shrpx::test_util_split_str) || + !CU_add_test(pSuite, "util_rstrip", shrpx::test_util_rstrip) || + !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || + !CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) || + !CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) || + !CU_add_test(pSuite, "memchunk_append", nghttp2::test_memchunks_append) || + !CU_add_test(pSuite, "memchunk_drain", nghttp2::test_memchunks_drain) || + !CU_add_test(pSuite, "memchunk_riovec", nghttp2::test_memchunks_riovec) || + !CU_add_test(pSuite, "memchunk_recycle", + nghttp2::test_memchunks_recycle) || + !CU_add_test(pSuite, "memchunk_reset", nghttp2::test_memchunks_reset) || + !CU_add_test(pSuite, "peek_memchunk_append", + nghttp2::test_peek_memchunks_append) || + !CU_add_test(pSuite, "peek_memchunk_disable_peek_drain", + nghttp2::test_peek_memchunks_disable_peek_drain) || + !CU_add_test(pSuite, "peek_memchunk_disable_peek_no_drain", + nghttp2::test_peek_memchunks_disable_peek_no_drain) || + !CU_add_test(pSuite, "peek_memchunk_reset", + nghttp2::test_peek_memchunks_reset) || + !CU_add_test(pSuite, "template_immutable_string", + nghttp2::test_template_immutable_string) || + !CU_add_test(pSuite, "template_string_ref", + nghttp2::test_template_string_ref) || + !CU_add_test(pSuite, "base64_encode", nghttp2::test_base64_encode) || + !CU_add_test(pSuite, "base64_decode", nghttp2::test_base64_decode)) { + 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/lib/nghttp2/src/shrpx.cc b/lib/nghttp2/src/shrpx.cc new file mode 100644 index 00000000000..ccd9e0642cb --- /dev/null +++ b/lib/nghttp2/src/shrpx.cc @@ -0,0 +1,5335 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx.h" + +#include +#include +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#include +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#include +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include +#ifdef HAVE_SYSLOG_H +# include +#endif // HAVE_SYSLOG_H +#ifdef HAVE_LIMITS_H +# include +#endif // HAVE_LIMITS_H +#ifdef HAVE_SYS_TIME_H +# include +#endif // HAVE_SYS_TIME_H +#include +#ifdef HAVE_LIBSYSTEMD +# include +#endif // HAVE_LIBSYSTEMD +#ifdef HAVE_LIBBPF +# include +#endif // HAVE_LIBBPF + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifdef ENABLE_HTTP3 +# include +# include +#endif // ENABLE_HTTP3 + +#include "shrpx_config.h" +#include "shrpx_tls.h" +#include "shrpx_log_config.h" +#include "shrpx_worker.h" +#include "shrpx_http2_upstream.h" +#include "shrpx_http2_session.h" +#include "shrpx_worker_process.h" +#include "shrpx_process.h" +#include "shrpx_signal.h" +#include "shrpx_connection.h" +#include "shrpx_log.h" +#include "shrpx_http.h" +#include "util.h" +#include "app_helper.h" +#include "tls.h" +#include "template.h" +#include "allocator.h" +#include "ssl_compat.h" +#include "xsi_strerror.h" + +extern char **environ; + +using namespace nghttp2; + +namespace shrpx { + +// Deprecated: Environment variables to tell new binary the listening +// socket's file descriptors. They are not close-on-exec. +constexpr auto ENV_LISTENER4_FD = StringRef::from_lit("NGHTTPX_LISTENER4_FD"); +constexpr auto ENV_LISTENER6_FD = StringRef::from_lit("NGHTTPX_LISTENER6_FD"); + +// Deprecated: Environment variable to tell new binary the port number +// the current binary is listening to. +constexpr auto ENV_PORT = StringRef::from_lit("NGHTTPX_PORT"); + +// Deprecated: Environment variable to tell new binary the listening +// socket's file descriptor if frontend listens UNIX domain socket. +constexpr auto ENV_UNIX_FD = StringRef::from_lit("NGHTTP2_UNIX_FD"); +// Deprecated: Environment variable to tell new binary the UNIX domain +// socket path. +constexpr auto ENV_UNIX_PATH = StringRef::from_lit("NGHTTP2_UNIX_PATH"); + +// Prefix of environment variables to tell new binary the listening +// socket's file descriptor. They are not close-on-exec. For TCP +// socket, the value must be comma separated 2 parameters: tcp,. +// is file descriptor. For UNIX domain socket, the value must be +// comma separated 3 parameters: unix,,. is file +// descriptor. is a path to UNIX domain socket. +constexpr auto ENV_ACCEPT_PREFIX = StringRef::from_lit("NGHTTPX_ACCEPT_"); + +// This environment variable contains PID of the original main +// process, assuming that it created this main process as a result of +// SIGUSR2. The new main process is expected to send QUIT signal to +// the original main process to shut it down gracefully. +constexpr auto ENV_ORIG_PID = StringRef::from_lit("NGHTTPX_ORIG_PID"); + +// Prefix of environment variables to tell new binary the QUIC IPC +// file descriptor and CID prefix of the lingering worker process. +// The value must be comma separated parameters: +// ,,,... is the file +// descriptor. is the I-th CID prefix in hex encoded +// string. +constexpr auto ENV_QUIC_WORKER_PROCESS_PREFIX = + StringRef::from_lit("NGHTTPX_QUIC_WORKER_PROCESS_"); + +#ifndef _KERNEL_FASTOPEN +# define _KERNEL_FASTOPEN +// conditional define for TCP_FASTOPEN mostly on ubuntu +# ifndef TCP_FASTOPEN +# define TCP_FASTOPEN 23 +# endif + +// conditional define for SOL_TCP mostly on ubuntu +# ifndef SOL_TCP +# define SOL_TCP 6 +# endif +#endif + +// This configuration is fixed at the first startup of the main +// process, and does not change after subsequent reloadings. +struct StartupConfig { + // This contains all options given in command-line. + std::vector> cmdcfgs; + // The current working directory where this process started. + char *cwd; + // The pointer to original argv (not sure why we have this?) + char **original_argv; + // The pointer to argv, this is a deep copy of original argv. + char **argv; + // The number of elements in argv. + int argc; +}; + +namespace { +StartupConfig suconfig; +} // namespace + +struct InheritedAddr { + // IP address if TCP socket. Otherwise, UNIX domain socket path. + StringRef host; + uint16_t port; + // true if UNIX domain socket path + bool host_unix; + int fd; + bool used; +}; + +namespace { +void signal_cb(struct ev_loop *loop, ev_signal *w, int revents); +} // namespace + +namespace { +void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents); +} // namespace + +struct WorkerProcess { + WorkerProcess(struct ev_loop *loop, pid_t worker_pid, int ipc_fd +#ifdef ENABLE_HTTP3 + , + int quic_ipc_fd, + const std::vector> + &cid_prefixes +#endif // ENABLE_HTTP3 + ) + : loop(loop), + worker_pid(worker_pid), + ipc_fd(ipc_fd) +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd(quic_ipc_fd), + cid_prefixes(cid_prefixes) +#endif // ENABLE_HTTP3 + { + ev_child_init(&worker_process_childev, worker_process_child_cb, worker_pid, + 0); + worker_process_childev.data = this; + ev_child_start(loop, &worker_process_childev); + } + + ~WorkerProcess() { + ev_child_stop(loop, &worker_process_childev); + +#ifdef ENABLE_HTTP3 + if (quic_ipc_fd != -1) { + close(quic_ipc_fd); + } +#endif // ENABLE_HTTP3 + + if (ipc_fd != -1) { + shutdown(ipc_fd, SHUT_WR); + close(ipc_fd); + } + } + + ev_child worker_process_childev; + struct ev_loop *loop; + pid_t worker_pid; + int ipc_fd; + std::chrono::steady_clock::time_point termination_deadline; +#ifdef ENABLE_HTTP3 + int quic_ipc_fd; + std::vector> cid_prefixes; +#endif // ENABLE_HTTP3 +}; + +namespace { +void reload_config(); +} // namespace + +namespace { +std::deque> worker_processes; +} // namespace + +namespace { +ev_timer worker_process_grace_period_timer; +} // namespace + +namespace { +void worker_process_grace_period_timercb(struct ev_loop *loop, ev_timer *w, + int revents) { + auto now = std::chrono::steady_clock::now(); + auto next_repeat = std::chrono::steady_clock::duration::zero(); + + for (auto it = std::begin(worker_processes); + it != std::end(worker_processes);) { + auto &wp = *it; + if (wp->termination_deadline.time_since_epoch().count() == 0) { + ++it; + + continue; + } + + auto d = wp->termination_deadline - now; + if (d.count() > 0) { + if (next_repeat == std::chrono::steady_clock::duration::zero() || + d < next_repeat) { + next_repeat = d; + } + + ++it; + + continue; + } + + LOG(NOTICE) << "Deleting worker process pid=" << wp->worker_pid + << " because its grace shutdown period is over"; + + it = worker_processes.erase(it); + } + + if (next_repeat.count() > 0) { + w->repeat = util::ev_tstamp_from(next_repeat); + ev_timer_again(loop, w); + + return; + } + + ev_timer_stop(loop, w); +} +} // namespace + +namespace { +void worker_process_set_termination_deadline(WorkerProcess *wp, + struct ev_loop *loop) { + auto config = get_config(); + + if (!(config->worker_process_grace_shutdown_period > 0.)) { + return; + } + + wp->termination_deadline = + std::chrono::steady_clock::now() + + util::duration_from(config->worker_process_grace_shutdown_period); + + if (!ev_is_active(&worker_process_grace_period_timer)) { + worker_process_grace_period_timer.repeat = + config->worker_process_grace_shutdown_period; + + ev_timer_again(loop, &worker_process_grace_period_timer); + } +} +} // namespace + +namespace { +void worker_process_add(std::unique_ptr wp) { + worker_processes.push_back(std::move(wp)); +} +} // namespace + +namespace { +void worker_process_remove(const WorkerProcess *wp, struct ev_loop *loop) { + for (auto it = std::begin(worker_processes); it != std::end(worker_processes); + ++it) { + auto &s = *it; + + if (s.get() != wp) { + continue; + } + + worker_processes.erase(it); + + if (worker_processes.empty()) { + ev_timer_stop(loop, &worker_process_grace_period_timer); + } + + break; + } +} +} // namespace + +namespace { +void worker_process_adjust_limit() { + auto config = get_config(); + + if (config->max_worker_processes && + worker_processes.size() > config->max_worker_processes) { + worker_processes.pop_front(); + } +} +} // namespace + +namespace { +void worker_process_remove_all(struct ev_loop *loop) { + std::deque>().swap(worker_processes); + + ev_timer_stop(loop, &worker_process_grace_period_timer); +} +} // namespace + +namespace { +// Send signal |signum| to all worker processes, and clears +// worker_processes. +void worker_process_kill(int signum, struct ev_loop *loop) { + for (auto &s : worker_processes) { + if (s->worker_pid == -1) { + continue; + } + kill(s->worker_pid, signum); + } + worker_process_remove_all(loop); +} +} // namespace + +namespace { +int save_pid() { + std::array errbuf; + auto config = get_config(); + + constexpr auto SUFFIX = StringRef::from_lit(".XXXXXX"); + auto &pid_file = config->pid_file; + + auto len = config->pid_file.size() + SUFFIX.size(); + auto buf = std::make_unique(len + 1); + auto p = buf.get(); + + p = std::copy(std::begin(pid_file), std::end(pid_file), p); + p = std::copy(std::begin(SUFFIX), std::end(SUFFIX), p); + *p = '\0'; + + auto temp_path = buf.get(); + + auto fd = mkstemp(temp_path); + if (fd == -1) { + auto error = errno; + LOG(ERROR) << "Could not save PID to file " << pid_file << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + auto content = util::utos(config->pid) + '\n'; + + if (write(fd, content.c_str(), content.size()) == -1) { + auto error = errno; + LOG(ERROR) << "Could not save PID to file " << pid_file << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + if (fsync(fd) == -1) { + auto error = errno; + LOG(ERROR) << "Could not save PID to file " << pid_file << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + close(fd); + + if (rename(temp_path, pid_file.c_str()) == -1) { + auto error = errno; + LOG(ERROR) << "Could not save PID to file " << pid_file << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + unlink(temp_path); + + return -1; + } + + if (config->uid != 0) { + if (chown(pid_file.c_str(), config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of pid file " << pid_file << " failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + + return 0; +} +} // namespace + +namespace { +void shrpx_sd_notifyf(int unset_environment, const char *format, ...) { +#ifdef HAVE_LIBSYSTEMD + va_list args; + + va_start(args, format); + sd_notifyf(unset_environment, format, va_arg(args, char *)); + va_end(args); +#endif // HAVE_LIBSYSTEMD +} +} // namespace + +namespace { +void exec_binary() { + int rv; + sigset_t oldset; + std::array errbuf; + + LOG(NOTICE) << "Executing new binary"; + + shrpx_sd_notifyf(0, "RELOADING=1"); + + rv = shrpx_signal_block_all(&oldset); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Blocking all signals failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + return; + } + + auto pid = fork(); + + if (pid != 0) { + if (pid == -1) { + auto error = errno; + LOG(ERROR) << "fork() failed errno=" << error; + } else { + // update PID tracking information in systemd + shrpx_sd_notifyf(0, "MAINPID=%d\n", pid); + } + + rv = shrpx_signal_set(&oldset); + + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Restoring signal mask failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + exit(EXIT_FAILURE); + } + + return; + } + + // child process + + shrpx_signal_unset_main_proc_ign_handler(); + + rv = shrpx_signal_unblock_all(); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Unblocking all signals failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + nghttp2_Exit(EXIT_FAILURE); + } + + auto exec_path = + util::get_exec_path(suconfig.argc, suconfig.argv, suconfig.cwd); + + if (!exec_path) { + LOG(ERROR) << "Could not resolve the executable path"; + nghttp2_Exit(EXIT_FAILURE); + } + + auto argv = std::make_unique(suconfig.argc + 1); + + argv[0] = exec_path; + for (int i = 1; i < suconfig.argc; ++i) { + argv[i] = suconfig.argv[i]; + } + argv[suconfig.argc] = nullptr; + + size_t envlen = 0; + for (char **p = environ; *p; ++p, ++envlen) + ; + + auto config = get_config(); + auto &listenerconf = config->conn.listener; + + // 2 for ENV_ORIG_PID and terminal nullptr. + auto envp = std::make_unique(envlen + listenerconf.addrs.size() + + worker_processes.size() + 2); + size_t envidx = 0; + + std::vector fd_envs; + for (size_t i = 0; i < listenerconf.addrs.size(); ++i) { + auto &addr = listenerconf.addrs[i]; + auto s = ENV_ACCEPT_PREFIX.str(); + s += util::utos(i + 1); + s += '='; + if (addr.host_unix) { + s += "unix,"; + s += util::utos(addr.fd); + s += ','; + s += addr.host; + } else { + s += "tcp,"; + s += util::utos(addr.fd); + } + + fd_envs.emplace_back(s); + envp[envidx++] = const_cast(fd_envs.back().c_str()); + } + + auto ipc_fd_str = ENV_ORIG_PID.str(); + ipc_fd_str += '='; + ipc_fd_str += util::utos(config->pid); + envp[envidx++] = const_cast(ipc_fd_str.c_str()); + +#ifdef ENABLE_HTTP3 + std::vector quic_lwps; + for (size_t i = 0; i < worker_processes.size(); ++i) { + auto &wp = worker_processes[i]; + auto s = ENV_QUIC_WORKER_PROCESS_PREFIX.str(); + s += util::utos(i + 1); + s += '='; + s += util::utos(wp->quic_ipc_fd); + for (auto &cid_prefix : wp->cid_prefixes) { + s += ','; + s += util::format_hex(cid_prefix); + } + + quic_lwps.emplace_back(s); + envp[envidx++] = const_cast(quic_lwps.back().c_str()); + } +#endif // ENABLE_HTTP3 + + for (size_t i = 0; i < envlen; ++i) { + auto env = StringRef{environ[i]}; + if (util::starts_with(env, ENV_ACCEPT_PREFIX) || + util::starts_with(env, ENV_LISTENER4_FD) || + util::starts_with(env, ENV_LISTENER6_FD) || + util::starts_with(env, ENV_PORT) || + util::starts_with(env, ENV_UNIX_FD) || + util::starts_with(env, ENV_UNIX_PATH) || + util::starts_with(env, ENV_ORIG_PID) || + util::starts_with(env, ENV_QUIC_WORKER_PROCESS_PREFIX)) { + continue; + } + + envp[envidx++] = environ[i]; + } + + envp[envidx++] = nullptr; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "cmdline"; + for (int i = 0; argv[i]; ++i) { + LOG(INFO) << i << ": " << argv[i]; + } + LOG(INFO) << "environ"; + for (int i = 0; envp[i]; ++i) { + LOG(INFO) << i << ": " << envp[i]; + } + } + + // restores original stderr + restore_original_fds(); + + // reloading finished + shrpx_sd_notifyf(0, "READY=1"); + + if (execve(argv[0], argv.get(), envp.get()) == -1) { + auto error = errno; + LOG(ERROR) << "execve failed: errno=" << error; + nghttp2_Exit(EXIT_FAILURE); + } +} +} // namespace + +namespace { +void ipc_send(WorkerProcess *wp, uint8_t ipc_event) { + std::array errbuf; + ssize_t nwrite; + while ((nwrite = write(wp->ipc_fd, &ipc_event, 1)) == -1 && errno == EINTR) + ; + + if (nwrite < 0) { + auto error = errno; + LOG(ERROR) << "Could not send IPC event to worker process: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return; + } + + if (nwrite == 0) { + LOG(ERROR) << "Could not send IPC event due to pipe overflow"; + return; + } +} +} // namespace + +namespace { +void reopen_log(WorkerProcess *wp) { + LOG(NOTICE) << "Reopening log files: main process"; + + auto config = get_config(); + auto &loggingconf = config->logging; + + (void)reopen_log_files(loggingconf); + redirect_stderr_to_errorlog(loggingconf); + ipc_send(wp, SHRPX_IPC_REOPEN_LOG); +} +} // namespace + +namespace { +void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) { + switch (w->signum) { + case REOPEN_LOG_SIGNAL: + for (auto &wp : worker_processes) { + reopen_log(wp.get()); + } + + return; + case EXEC_BINARY_SIGNAL: + exec_binary(); + return; + case GRACEFUL_SHUTDOWN_SIGNAL: { + auto &listenerconf = get_config()->conn.listener; + for (auto &addr : listenerconf.addrs) { + close(addr.fd); + } + + for (auto &wp : worker_processes) { + ipc_send(wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN); + worker_process_set_termination_deadline(wp.get(), loop); + } + + return; + } + case RELOAD_SIGNAL: + reload_config(); + + return; + default: + worker_process_kill(w->signum, loop); + ev_break(loop); + return; + } +} +} // namespace + +namespace { +void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents) { + auto wp = static_cast(w->data); + + log_chld(w->rpid, w->rstatus, "Worker process"); + + worker_process_remove(wp, loop); + + if (worker_processes.empty()) { + ev_break(loop); + } +} +} // namespace + +namespace { +int create_unix_domain_server_socket(UpstreamAddr &faddr, + std::vector &iaddrs) { + std::array errbuf; + auto found = std::find_if( + std::begin(iaddrs), std::end(iaddrs), [&faddr](const InheritedAddr &ia) { + return !ia.used && ia.host_unix && ia.host == faddr.host; + }); + + if (found != std::end(iaddrs)) { + LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host + << (faddr.tls ? ", tls" : ""); + (*found).used = true; + faddr.fd = (*found).fd; + faddr.hostport = StringRef::from_lit("localhost"); + + return 0; + } + +#ifdef SOCK_NONBLOCK + auto fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (fd == -1) { + auto error = errno; + LOG(FATAL) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } +#else // !SOCK_NONBLOCK + auto fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + auto error = errno; + LOG(FATAL) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + util::make_socket_nonblocking(fd); +#endif // !SOCK_NONBLOCK + int val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(FATAL) << "Failed to set SO_REUSEADDR option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + sockaddr_union addr; + addr.un.sun_family = AF_UNIX; + if (faddr.host.size() + 1 > sizeof(addr.un.sun_path)) { + LOG(FATAL) << "UNIX domain socket path " << faddr.host << " is too long > " + << sizeof(addr.un.sun_path); + close(fd); + return -1; + } + // copy path including terminal NULL + std::copy_n(faddr.host.c_str(), faddr.host.size() + 1, addr.un.sun_path); + + // unlink (remove) already existing UNIX domain socket path + unlink(faddr.host.c_str()); + + if (bind(fd, &addr.sa, sizeof(addr.un)) != 0) { + auto error = errno; + LOG(FATAL) << "Failed to bind UNIX domain socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + auto &listenerconf = get_config()->conn.listener; + + if (listen(fd, listenerconf.backlog) != 0) { + auto error = errno; + LOG(FATAL) << "Failed to listen to UNIX domain socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host + << (faddr.tls ? ", tls" : ""); + + faddr.fd = fd; + faddr.hostport = StringRef::from_lit("localhost"); + + return 0; +} +} // namespace + +namespace { +int create_tcp_server_socket(UpstreamAddr &faddr, + std::vector &iaddrs) { + std::array errbuf; + int fd = -1; + int rv; + + auto &listenerconf = get_config()->conn.listener; + + auto service = util::utos(faddr.port); + addrinfo hints{}; + hints.ai_family = faddr.family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif // AI_ADDRCONFIG + + auto node = + faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str(); + + addrinfo *res, *rp; + rv = getaddrinfo(node, service.c_str(), &hints, &res); +#ifdef AI_ADDRCONFIG + if (rv != 0) { + // Retry without AI_ADDRCONFIG + hints.ai_flags &= ~AI_ADDRCONFIG; + rv = getaddrinfo(node, service.c_str(), &hints, &res); + } +#endif // AI_ADDRCONFIG + if (rv != 0) { + LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6") + << " address for " << faddr.host << ", port " << faddr.port + << ": " << gai_strerror(rv); + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + std::array host; + + for (rp = res; rp; rp = rp->ai_next) { + + rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(), + nullptr, 0, NI_NUMERICHOST); + + if (rv != 0) { + LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv); + continue; + } + + auto found = std::find_if(std::begin(iaddrs), std::end(iaddrs), + [&host, &faddr](const InheritedAddr &ia) { + return !ia.used && !ia.host_unix && + ia.host == host.data() && + ia.port == faddr.port; + }); + + if (found != std::end(iaddrs)) { + (*found).used = true; + fd = (*found).fd; + break; + } + +#ifdef SOCK_NONBLOCK + fd = + socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol); + if (fd == -1) { + auto error = errno; + LOG(WARN) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } +#else // !SOCK_NONBLOCK + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + auto error = errno; + LOG(WARN) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } + util::make_socket_nonblocking(fd); +#endif // !SOCK_NONBLOCK + int val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + +#ifdef IPV6_V6ONLY + if (faddr.family == AF_INET6) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + } +#endif // IPV6_V6ONLY + +#ifdef TCP_DEFER_ACCEPT + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set TCP_DEFER_ACCEPT option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } +#endif // TCP_DEFER_ACCEPT + + // When we are executing new binary, and the old binary did not + // bind privileged port (< 1024) for some reason, binding to those + // ports will fail with permission denied error. + if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) { + auto error = errno; + LOG(WARN) << "bind() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + if (listenerconf.fastopen > 0) { + val = listenerconf.fastopen; + if (setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set TCP_FASTOPEN option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + + if (listen(fd, listenerconf.backlog) == -1) { + auto error = errno; + LOG(WARN) << "listen() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + break; + } + + if (!rp) { + LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6") + << " socket failed"; + + return -1; + } + + faddr.fd = fd; + faddr.hostport = util::make_http_hostport(mod_config()->balloc, + StringRef{host.data()}, faddr.port); + + LOG(NOTICE) << "Listening on " << faddr.hostport + << (faddr.tls ? ", tls" : ""); + + return 0; +} +} // namespace + +namespace { +// Returns array of InheritedAddr constructed from |config|. This +// function is intended to be used when reloading configuration, and +// |config| is usually a current configuration. +std::vector +get_inherited_addr_from_config(BlockAllocator &balloc, Config *config) { + std::array errbuf; + int rv; + + auto &listenerconf = config->conn.listener; + + std::vector iaddrs(listenerconf.addrs.size()); + + size_t idx = 0; + for (auto &addr : listenerconf.addrs) { + auto &iaddr = iaddrs[idx++]; + + if (addr.host_unix) { + iaddr.host = addr.host; + iaddr.host_unix = true; + iaddr.fd = addr.fd; + + continue; + } + + iaddr.port = addr.port; + iaddr.fd = addr.fd; + + // We have to getsockname/getnameinfo for fd, since we may have + // '*' appear in addr.host, which makes comparison against "real" + // address fail. + + sockaddr_union su; + socklen_t salen = sizeof(su); + + // We already added entry to iaddrs. Even if we got errors, we + // don't remove it. This is required because we have to close the + // socket if it is not reused. The empty host name usually does + // not match anything. + + if (getsockname(addr.fd, &su.sa, &salen) != 0) { + auto error = errno; + LOG(WARN) << "getsockname() syscall failed (fd=" << addr.fd + << "): " << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } + + std::array host; + rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0, + NI_NUMERICHOST); + if (rv != 0) { + LOG(WARN) << "getnameinfo() failed (fd=" << addr.fd + << "): " << gai_strerror(rv); + continue; + } + + iaddr.host = make_string_ref(balloc, StringRef{host.data()}); + } + + return iaddrs; +} +} // namespace + +namespace { +// Returns array of InheritedAddr constructed from environment +// variables. This function handles the old environment variable +// names used in 1.7.0 or earlier. +std::vector get_inherited_addr_from_env(Config *config) { + std::array errbuf; + int rv; + std::vector iaddrs; + + { + // Upgrade from 1.7.0 or earlier + auto portenv = getenv(ENV_PORT.c_str()); + if (portenv) { + size_t i = 1; + for (const auto &env_name : {ENV_LISTENER4_FD, ENV_LISTENER6_FD}) { + auto fdenv = getenv(env_name.c_str()); + if (fdenv) { + auto name = ENV_ACCEPT_PREFIX.str(); + name += util::utos(i); + std::string value = "tcp,"; + value += fdenv; + setenv(name.c_str(), value.c_str(), 0); + ++i; + } + } + } else { + // The return value of getenv may be allocated statically. + if (getenv(ENV_UNIX_PATH.c_str()) && getenv(ENV_UNIX_FD.c_str())) { + auto name = ENV_ACCEPT_PREFIX.str(); + name += '1'; + std::string value = "unix,"; + value += getenv(ENV_UNIX_FD.c_str()); + value += ','; + value += getenv(ENV_UNIX_PATH.c_str()); + setenv(name.c_str(), value.c_str(), 0); + } + } + } + + for (size_t i = 1;; ++i) { + auto name = ENV_ACCEPT_PREFIX.str(); + name += util::utos(i); + auto env = getenv(name.c_str()); + if (!env) { + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Read env " << name << "=" << env; + } + + auto end_type = strchr(env, ','); + if (!end_type) { + continue; + } + + auto type = StringRef(env, end_type); + auto value = end_type + 1; + + if (type == StringRef::from_lit("unix")) { + auto endfd = strchr(value, ','); + if (!endfd) { + continue; + } + auto fd = util::parse_uint(reinterpret_cast(value), + endfd - value); + if (fd == -1) { + LOG(WARN) << "Could not parse file descriptor from " + << std::string(value, endfd - value); + continue; + } + + auto path = endfd + 1; + if (strlen(path) == 0) { + LOG(WARN) << "Empty UNIX domain socket path (fd=" << fd << ")"; + close(fd); + continue; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Inherit UNIX domain socket fd=" << fd + << ", path=" << path; + } + + InheritedAddr addr{}; + addr.host = make_string_ref(config->balloc, StringRef{path}); + addr.host_unix = true; + addr.fd = static_cast(fd); + iaddrs.push_back(std::move(addr)); + } + + if (type == StringRef::from_lit("tcp")) { + auto fd = util::parse_uint(value); + if (fd == -1) { + LOG(WARN) << "Could not parse file descriptor from " << value; + continue; + } + + sockaddr_union su; + socklen_t salen = sizeof(su); + + if (getsockname(fd, &su.sa, &salen) != 0) { + auto error = errno; + LOG(WARN) << "getsockname() syscall failed (fd=" << fd + << "): " << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + uint16_t port; + + switch (su.storage.ss_family) { + case AF_INET: + port = ntohs(su.in.sin_port); + break; + case AF_INET6: + port = ntohs(su.in6.sin6_port); + break; + default: + close(fd); + continue; + } + + std::array host; + rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0, + NI_NUMERICHOST); + if (rv != 0) { + LOG(WARN) << "getnameinfo() failed (fd=" << fd + << "): " << gai_strerror(rv); + close(fd); + continue; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Inherit TCP socket fd=" << fd + << ", address=" << host.data() << ", port=" << port; + } + + InheritedAddr addr{}; + addr.host = make_string_ref(config->balloc, StringRef{host.data()}); + addr.port = static_cast(port); + addr.fd = static_cast(fd); + iaddrs.push_back(std::move(addr)); + continue; + } + } + + return iaddrs; +} +} // namespace + +namespace { +// Closes all sockets which are not reused. +void close_unused_inherited_addr(const std::vector &iaddrs) { + for (auto &ia : iaddrs) { + if (ia.used) { + continue; + } + + close(ia.fd); + } +} +} // namespace + +namespace { +// Returns the PID of the original main process from environment +// variable ENV_ORIG_PID. +pid_t get_orig_pid_from_env() { + auto s = getenv(ENV_ORIG_PID.c_str()); + if (s == nullptr) { + return -1; + } + return util::parse_uint(s); +} +} // namespace + +#ifdef ENABLE_HTTP3 +namespace { +std::vector + inherited_quic_lingering_worker_processes; +} // namespace + +namespace { +std::vector +get_inherited_quic_lingering_worker_process_from_env() { + std::vector iwps; + + for (size_t i = 1;; ++i) { + auto name = ENV_QUIC_WORKER_PROCESS_PREFIX.str(); + name += util::utos(i); + auto env = getenv(name.c_str()); + if (!env) { + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Read env " << name << "=" << env; + } + + auto envend = env + strlen(env); + + auto end_fd = std::find(env, envend, ','); + if (end_fd == envend) { + continue; + } + + auto fd = + util::parse_uint(reinterpret_cast(env), end_fd - env); + if (fd == -1) { + LOG(WARN) << "Could not parse file descriptor from " + << StringRef{env, static_cast(end_fd - env)}; + continue; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Inherit worker process QUIC IPC socket fd=" << fd; + } + + util::make_socket_closeonexec(fd); + + std::vector> cid_prefixes; + + auto p = end_fd + 1; + for (;;) { + auto end = std::find(p, envend, ','); + + auto hex_cid_prefix = StringRef{p, end}; + if (hex_cid_prefix.size() != SHRPX_QUIC_CID_PREFIXLEN * 2 || + !util::is_hex_string(hex_cid_prefix)) { + LOG(WARN) << "Found invalid CID prefix=" << hex_cid_prefix; + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Inherit worker process CID prefix=" << hex_cid_prefix; + } + + cid_prefixes.emplace_back(); + + util::decode_hex(std::begin(cid_prefixes.back()), hex_cid_prefix); + + if (end == envend) { + break; + } + + p = end + 1; + } + + iwps.emplace_back(std::move(cid_prefixes), fd); + } + + return iwps; +} +} // namespace +#endif // ENABLE_HTTP3 + +namespace { +int create_acceptor_socket(Config *config, std::vector &iaddrs) { + std::array errbuf; + auto &listenerconf = config->conn.listener; + + for (auto &addr : listenerconf.addrs) { + if (addr.host_unix) { + if (create_unix_domain_server_socket(addr, iaddrs) != 0) { + return -1; + } + + if (config->uid != 0) { + // fd is not associated to inode, so we cannot use fchown(2) + // here. https://lkml.org/lkml/2004/11/1/84 + if (chown(addr.host.c_str(), config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of UNIX domain socket " << addr.host + << " failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + continue; + } + + if (create_tcp_server_socket(addr, iaddrs) != 0) { + return -1; + } + } + + return 0; +} +} // namespace + +namespace { +int call_daemon() { +#ifdef __sgi + return _daemonize(0, 0, 0, 0); +#else // !__sgi +# ifdef HAVE_LIBSYSTEMD + if (sd_booted() && (getenv("NOTIFY_SOCKET") != nullptr)) { + LOG(NOTICE) << "Daemonising disabled under systemd"; + chdir("/"); + return 0; + } +# endif // HAVE_LIBSYSTEMD + return util::daemonize(0, 0); +#endif // !__sgi +} +} // namespace + +namespace { +// Opens IPC socket used to communicate with worker proess. The +// communication is unidirectional; that is main process sends +// messages to the worker process. On success, ipc_fd[0] is for +// reading, and ipc_fd[1] for writing, just like pipe(2). +int create_ipc_socket(std::array &ipc_fd) { + std::array errbuf; + int rv; + + rv = pipe(ipc_fd.data()); + if (rv == -1) { + auto error = errno; + LOG(WARN) << "Failed to create pipe to communicate worker process: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + for (int i = 0; i < 2; ++i) { + auto fd = ipc_fd[i]; + util::make_socket_nonblocking(fd); + util::make_socket_closeonexec(fd); + } + + return 0; +} +} // namespace + +namespace { +int create_worker_process_ready_ipc_socket(std::array &ipc_fd) { + std::array errbuf; + int rv; + + rv = socketpair(AF_UNIX, SOCK_DGRAM, 0, ipc_fd.data()); + if (rv == -1) { + auto error = errno; + LOG(WARN) << "Failed to create socket pair to communicate worker process " + "readiness: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + for (auto fd : ipc_fd) { + util::make_socket_closeonexec(fd); + } + + util::make_socket_nonblocking(ipc_fd[0]); + + return 0; +} +} // namespace + +#ifdef ENABLE_HTTP3 +namespace { +int create_quic_ipc_socket(std::array &quic_ipc_fd) { + std::array errbuf; + int rv; + + rv = socketpair(AF_UNIX, SOCK_DGRAM, 0, quic_ipc_fd.data()); + if (rv == -1) { + auto error = errno; + LOG(WARN) << "Failed to create socket pair to communicate worker process: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + for (auto fd : quic_ipc_fd) { + util::make_socket_nonblocking(fd); + } + + return 0; +} +} // namespace + +namespace { +int generate_cid_prefix( + std::vector> &cid_prefixes, + const Config *config) { + auto &apiconf = config->api; + auto &quicconf = config->quic; + + size_t num_cid_prefix; + if (config->single_thread) { + num_cid_prefix = 1; + } else { + num_cid_prefix = config->num_worker; + + // API endpoint occupies the one dedicated worker thread. + // Although such worker never gets QUIC traffic, we create CID + // prefix for it to make code a bit simpler. + if (apiconf.enabled) { + ++num_cid_prefix; + } + } + + cid_prefixes.resize(num_cid_prefix); + + for (auto &cid_prefix : cid_prefixes) { + if (create_cid_prefix(cid_prefix.data(), quicconf.server_id.data()) != 0) { + return -1; + } + } + + return 0; +} +} // namespace + +namespace { +std::vector +collect_quic_lingering_worker_processes() { + std::vector quic_lwps{ + std::begin(inherited_quic_lingering_worker_processes), + std::end(inherited_quic_lingering_worker_processes)}; + + for (auto &wp : worker_processes) { + quic_lwps.emplace_back(wp->cid_prefixes, wp->quic_ipc_fd); + } + + return quic_lwps; +} +} // namespace +#endif // ENABLE_HTTP3 + +namespace { +ev_signal reopen_log_signalev; +ev_signal exec_binary_signalev; +ev_signal graceful_shutdown_signalev; +ev_signal reload_signalev; +} // namespace + +namespace { +void start_signal_watchers(struct ev_loop *loop) { + ev_signal_init(&reopen_log_signalev, signal_cb, REOPEN_LOG_SIGNAL); + ev_signal_start(loop, &reopen_log_signalev); + + ev_signal_init(&exec_binary_signalev, signal_cb, EXEC_BINARY_SIGNAL); + ev_signal_start(loop, &exec_binary_signalev); + + ev_signal_init(&graceful_shutdown_signalev, signal_cb, + GRACEFUL_SHUTDOWN_SIGNAL); + ev_signal_start(loop, &graceful_shutdown_signalev); + + ev_signal_init(&reload_signalev, signal_cb, RELOAD_SIGNAL); + ev_signal_start(loop, &reload_signalev); +} +} // namespace + +namespace { +void shutdown_signal_watchers(struct ev_loop *loop) { + ev_signal_stop(loop, &reload_signalev); + ev_signal_stop(loop, &graceful_shutdown_signalev); + ev_signal_stop(loop, &exec_binary_signalev); + ev_signal_stop(loop, &reopen_log_signalev); +} +} // namespace + +namespace { +// A pair of connected socket with which a worker process tells main +// process that it is ready for service. A worker process writes its +// PID to worker_process_ready_ipc_fd[1] and main process reads it +// from worker_process_ready_ipc_fd[0]. +std::array worker_process_ready_ipc_fd; +} // namespace + +namespace { +ev_io worker_process_ready_ipcev; +} // namespace + +namespace { +// PID received via NGHTTPX_ORIG_PID environment variable. +pid_t orig_pid = -1; +} // namespace + +namespace { +void worker_process_ready_ipc_readcb(struct ev_loop *loop, ev_io *w, + int revents) { + std::array buf; + ssize_t nread; + + while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR) + ; + + if (nread == -1) { + std::array errbuf; + auto error = errno; + + LOG(ERROR) << "Failed to read data from worker process ready IPC channel: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + return; + } + + if (nread == 0) { + return; + } + + if (nread != sizeof(pid_t)) { + LOG(ERROR) << "Read " << nread + << " bytes from worker process ready IPC channel"; + + return; + } + + pid_t pid; + + memcpy(&pid, buf.data(), sizeof(pid)); + + LOG(NOTICE) << "Worker process pid=" << pid << " is ready"; + + for (auto &wp : worker_processes) { + // Send graceful shutdown signal to all worker processes prior to + // pid. + if (wp->worker_pid == pid) { + break; + } + + LOG(INFO) << "Sending graceful shutdown event to worker process pid=" + << wp->worker_pid; + + ipc_send(wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN); + worker_process_set_termination_deadline(wp.get(), loop); + } + + if (orig_pid != -1) { + LOG(NOTICE) << "Send QUIT signal to the original main process to tell " + "that we are ready to serve requests."; + kill(orig_pid, SIGQUIT); + + orig_pid = -1; + } +} +} // namespace + +namespace { +void start_worker_process_ready_ipc_watcher(struct ev_loop *loop) { + ev_io_init(&worker_process_ready_ipcev, worker_process_ready_ipc_readcb, + worker_process_ready_ipc_fd[0], EV_READ); + ev_io_start(loop, &worker_process_ready_ipcev); +} +} // namespace + +namespace { +void shutdown_worker_process_ready_ipc_watcher(struct ev_loop *loop) { + ev_io_stop(loop, &worker_process_ready_ipcev); +} +} // namespace + +namespace { +// Creates worker process, and returns PID of worker process. On +// success, file descriptor for IPC (send only) is assigned to +// |main_ipc_fd|. In child process, we will close file descriptors +// which are inherited from previous configuration/process, but not +// used in the current configuration. +pid_t fork_worker_process( + int &main_ipc_fd +#ifdef ENABLE_HTTP3 + , + int &wp_quic_ipc_fd +#endif // ENABLE_HTTP3 + , + const std::vector &iaddrs +#ifdef ENABLE_HTTP3 + , + const std::vector> + &cid_prefixes, + const std::vector &quic_lwps +#endif // ENABLE_HTTP3 +) { + std::array errbuf; + int rv; + sigset_t oldset; + + std::array ipc_fd; + + rv = create_ipc_socket(ipc_fd); + if (rv != 0) { + return -1; + } + +#ifdef ENABLE_HTTP3 + std::array quic_ipc_fd; + + rv = create_quic_ipc_socket(quic_ipc_fd); + if (rv != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + rv = shrpx_signal_block_all(&oldset); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Blocking all signals failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + close(ipc_fd[0]); + close(ipc_fd[1]); + + return -1; + } + + auto config = get_config(); + + pid_t pid = 0; + + if (!config->single_process) { + pid = fork(); + } + + if (pid == 0) { + // We are in new process now, update pid for logger. + log_config()->pid = getpid(); + + ev_loop_fork(EV_DEFAULT); + + for (auto &addr : config->conn.listener.addrs) { + util::make_socket_closeonexec(addr.fd); + } + +#ifdef ENABLE_HTTP3 + util::make_socket_closeonexec(quic_ipc_fd[0]); + + for (auto &lwp : quic_lwps) { + util::make_socket_closeonexec(lwp.quic_ipc_fd); + } + + for (auto &wp : worker_processes) { + util::make_socket_closeonexec(wp->quic_ipc_fd); + // Do not close quic_ipc_fd. + wp->quic_ipc_fd = -1; + } +#endif // ENABLE_HTTP3 + + if (!config->single_process) { + close(worker_process_ready_ipc_fd[0]); + shutdown_worker_process_ready_ipc_watcher(EV_DEFAULT); + + shutdown_signal_watchers(EV_DEFAULT); + } + + // Remove all WorkerProcesses to stop any registered watcher on + // default loop. + worker_process_remove_all(EV_DEFAULT); + + close_unused_inherited_addr(iaddrs); + + shrpx_signal_set_worker_proc_ign_handler(); + + rv = shrpx_signal_unblock_all(); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Unblocking all signals failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + if (config->single_process) { + exit(EXIT_FAILURE); + } else { + nghttp2_Exit(EXIT_FAILURE); + } + } + + if (!config->single_process) { + close(ipc_fd[1]); +#ifdef ENABLE_HTTP3 + close(quic_ipc_fd[1]); +#endif // ENABLE_HTTP3 + } + + WorkerProcessConfig wpconf{ + .ipc_fd = ipc_fd[0], + .ready_ipc_fd = worker_process_ready_ipc_fd[1], +#ifdef ENABLE_HTTP3 + .cid_prefixes = cid_prefixes, + .quic_ipc_fd = quic_ipc_fd[0], + .quic_lingering_worker_processes = quic_lwps, +#endif // ENABLE_HTTP3 + }; + rv = worker_process_event_loop(&wpconf); + if (rv != 0) { + LOG(FATAL) << "Worker process returned error"; + + if (config->single_process) { + exit(EXIT_FAILURE); + } else { + nghttp2_Exit(EXIT_FAILURE); + } + } + + LOG(NOTICE) << "Worker process shutting down momentarily"; + + // call exit(...) instead of nghttp2_Exit to get leak sanitizer report + if (config->single_process) { + exit(EXIT_SUCCESS); + } else { + nghttp2_Exit(EXIT_SUCCESS); + } + } + + // parent process + if (pid == -1) { + auto error = errno; + LOG(ERROR) << "Could not spawn worker process: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + + rv = shrpx_signal_set(&oldset); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Restoring signal mask failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + exit(EXIT_FAILURE); + } + + if (pid == -1) { + close(ipc_fd[0]); + close(ipc_fd[1]); +#ifdef ENABLE_HTTP3 + close(quic_ipc_fd[0]); + close(quic_ipc_fd[1]); +#endif // ENABLE_HTTP3 + + return -1; + } + + close(ipc_fd[0]); +#ifdef ENABLE_HTTP3 + close(quic_ipc_fd[0]); +#endif // ENABLE_HTTP3 + + main_ipc_fd = ipc_fd[1]; +#ifdef ENABLE_HTTP3 + wp_quic_ipc_fd = quic_ipc_fd[1]; +#endif // ENABLE_HTTP3 + + LOG(NOTICE) << "Worker process [" << pid << "] spawned"; + + return pid; +} +} // namespace + +namespace { +int event_loop() { + std::array errbuf; + + shrpx_signal_set_main_proc_ign_handler(); + + auto config = mod_config(); + + if (config->daemon) { + if (call_daemon() == -1) { + auto error = errno; + LOG(FATAL) << "Failed to daemonize: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + // We get new PID after successful daemon(). + mod_config()->pid = getpid(); + + // daemon redirects stderr file descriptor to /dev/null, so we + // need this. + redirect_stderr_to_errorlog(config->logging); + } + + // update systemd PID tracking + shrpx_sd_notifyf(0, "MAINPID=%d\n", config->pid); + + { + auto iaddrs = get_inherited_addr_from_env(config); + + if (create_acceptor_socket(config, iaddrs) != 0) { + return -1; + } + + close_unused_inherited_addr(iaddrs); + } + + orig_pid = get_orig_pid_from_env(); + +#ifdef ENABLE_HTTP3 + inherited_quic_lingering_worker_processes = + get_inherited_quic_lingering_worker_process_from_env(); +#endif // ENABLE_HTTP3 + + auto loop = ev_default_loop(config->ev_loop_flags); + + int ipc_fd = 0; +#ifdef ENABLE_HTTP3 + int quic_ipc_fd = 0; + + auto quic_lwps = collect_quic_lingering_worker_processes(); + + std::vector> cid_prefixes; + + if (generate_cid_prefix(cid_prefixes, config) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + if (!config->single_process) { + start_signal_watchers(loop); + } + + create_worker_process_ready_ipc_socket(worker_process_ready_ipc_fd); + start_worker_process_ready_ipc_watcher(loop); + + auto pid = fork_worker_process(ipc_fd +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd +#endif // ENABLE_HTTP3 + , + {} +#ifdef ENABLE_HTTP3 + , + cid_prefixes, quic_lwps +#endif // ENABLE_HTTP3 + ); + + if (pid == -1) { + return -1; + } + + ev_timer_init(&worker_process_grace_period_timer, + worker_process_grace_period_timercb, 0., 0.); + + worker_process_add(std::make_unique(loop, pid, ipc_fd +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd, cid_prefixes +#endif // ENABLE_HTTP3 + )); + + // Write PID file when we are ready to accept connection from peer. + // This makes easier to write restart script for nghttpx. Because + // when we know that PID file is recreated, it means we can send + // QUIT signal to the old process to make it shutdown gracefully. + if (!config->pid_file.empty()) { + save_pid(); + } + + shrpx_sd_notifyf(0, "READY=1"); + + ev_run(loop, 0); + + ev_timer_stop(loop, &worker_process_grace_period_timer); + + shutdown_worker_process_ready_ipc_watcher(loop); + + // config is now stale if reload has happened. + if (!get_config()->single_process) { + shutdown_signal_watchers(loop); + } + + return 0; +} +} // namespace + +namespace { +// Returns true if regular file or symbolic link |path| exists. +bool conf_exists(const char *path) { + struct stat buf; + int rv = stat(path, &buf); + return rv == 0 && (buf.st_mode & (S_IFREG | S_IFLNK)); +} +} // namespace + +namespace { +constexpr auto DEFAULT_NPN_LIST = + StringRef::from_lit("h2,h2-16,h2-14,http/1.1"); +} // namespace + +namespace { +constexpr auto DEFAULT_TLS_MIN_PROTO_VERSION = StringRef::from_lit("TLSv1.2"); +#ifdef TLS1_3_VERSION +constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.3"); +#else // !TLS1_3_VERSION +constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.2"); +#endif // !TLS1_3_VERSION +} // namespace + +namespace { +constexpr auto DEFAULT_ACCESSLOG_FORMAT = + StringRef::from_lit(R"($remote_addr - - [$time_local] )" + R"("$request" $status $body_bytes_sent )" + R"("$http_referer" "$http_user_agent")"); +} // namespace + +namespace { +void fill_default_config(Config *config) { + config->num_worker = 1; + config->conf_path = StringRef::from_lit("/etc/nghttpx/nghttpx.conf"); + config->pid = getpid(); + +#ifdef NOTHREADS + config->single_thread = true; +#endif // NOTHREADS + + if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) { + config->ev_loop_flags = ev_recommended_backends() | EVBACKEND_KQUEUE; + } + + auto &tlsconf = config->tls; + { + auto &ticketconf = tlsconf.ticket; + { + auto &memcachedconf = ticketconf.memcached; + memcachedconf.max_retry = 3; + memcachedconf.max_fail = 2; + memcachedconf.interval = 10_min; + memcachedconf.family = AF_UNSPEC; + } + + auto &session_cacheconf = tlsconf.session_cache; + { + auto &memcachedconf = session_cacheconf.memcached; + memcachedconf.family = AF_UNSPEC; + } + + ticketconf.cipher = EVP_aes_128_cbc(); + } + + { + auto &ocspconf = tlsconf.ocsp; + // ocsp update interval = 14400 secs = 4 hours, borrowed from h2o + ocspconf.update_interval = 4_h; + ocspconf.fetch_ocsp_response_file = + StringRef::from_lit(PKGDATADIR "/fetch-ocsp-response"); + } + + { + auto &dyn_recconf = tlsconf.dyn_rec; + dyn_recconf.warmup_threshold = 1_m; + dyn_recconf.idle_timeout = 1_s; + } + + tlsconf.session_timeout = std::chrono::hours(12); + tlsconf.ciphers = StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST); + tlsconf.tls13_ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST); + tlsconf.client.ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST); + tlsconf.client.tls13_ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST); + tlsconf.min_proto_version = + tls::proto_version_from_string(DEFAULT_TLS_MIN_PROTO_VERSION); + tlsconf.max_proto_version = + tls::proto_version_from_string(DEFAULT_TLS_MAX_PROTO_VERSION); + tlsconf.max_early_data = 16_k; +#if OPENSSL_1_1_API || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521"); +#else // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + tlsconf.ecdh_curves = StringRef::from_lit("P-256:P-384:P-521"); +#endif // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + auto &httpconf = config->http; + httpconf.server_name = StringRef::from_lit("nghttpx"); + httpconf.no_host_rewrite = true; + httpconf.request_header_field_buffer = 64_k; + httpconf.max_request_header_fields = 100; + httpconf.response_header_field_buffer = 64_k; + httpconf.max_response_header_fields = 500; + httpconf.redirect_https_port = StringRef::from_lit("443"); + httpconf.max_requests = std::numeric_limits::max(); + httpconf.xfp.add = true; + httpconf.xfp.strip_incoming = true; + httpconf.early_data.strip_incoming = true; + + auto &http2conf = config->http2; + { + auto &upstreamconf = http2conf.upstream; + + { + auto &timeoutconf = upstreamconf.timeout; + timeoutconf.settings = 10_s; + } + + // window size for HTTP/2 upstream connection per stream. 2**16-1 + // = 64KiB-1, which is HTTP/2 default. + upstreamconf.window_size = 64_k - 1; + // HTTP/2 has connection-level flow control. The default window + // size for HTTP/2 is 64KiB - 1. + upstreamconf.connection_window_size = 64_k - 1; + upstreamconf.max_concurrent_streams = 100; + + upstreamconf.encoder_dynamic_table_size = 4_k; + upstreamconf.decoder_dynamic_table_size = 4_k; + + nghttp2_option_new(&upstreamconf.option); + nghttp2_option_set_no_auto_window_update(upstreamconf.option, 1); + nghttp2_option_set_no_recv_client_magic(upstreamconf.option, 1); + nghttp2_option_set_max_deflate_dynamic_table_size( + upstreamconf.option, upstreamconf.encoder_dynamic_table_size); + nghttp2_option_set_server_fallback_rfc7540_priorities(upstreamconf.option, + 1); + nghttp2_option_set_builtin_recv_extension_type(upstreamconf.option, + NGHTTP2_PRIORITY_UPDATE); + + // For API endpoint, we enable automatic window update. This is + // because we are a sink. + nghttp2_option_new(&upstreamconf.alt_mode_option); + nghttp2_option_set_no_recv_client_magic(upstreamconf.alt_mode_option, 1); + nghttp2_option_set_max_deflate_dynamic_table_size( + upstreamconf.alt_mode_option, upstreamconf.encoder_dynamic_table_size); + } + + http2conf.timeout.stream_write = 1_min; + + { + auto &downstreamconf = http2conf.downstream; + + { + auto &timeoutconf = downstreamconf.timeout; + timeoutconf.settings = 10_s; + } + + downstreamconf.window_size = 64_k - 1; + downstreamconf.connection_window_size = (1u << 31) - 1; + downstreamconf.max_concurrent_streams = 100; + + downstreamconf.encoder_dynamic_table_size = 4_k; + downstreamconf.decoder_dynamic_table_size = 4_k; + + nghttp2_option_new(&downstreamconf.option); + nghttp2_option_set_no_auto_window_update(downstreamconf.option, 1); + nghttp2_option_set_peer_max_concurrent_streams(downstreamconf.option, 100); + nghttp2_option_set_max_deflate_dynamic_table_size( + downstreamconf.option, downstreamconf.encoder_dynamic_table_size); + } + +#ifdef ENABLE_HTTP3 + auto &quicconf = config->quic; + { + auto &upstreamconf = quicconf.upstream; + + { + auto &timeoutconf = upstreamconf.timeout; + timeoutconf.idle = 30_s; + } + + auto &bpfconf = quicconf.bpf; + bpfconf.prog_file = StringRef::from_lit(PKGLIBDIR "/reuseport_kern.o"); + + upstreamconf.congestion_controller = NGTCP2_CC_ALGO_CUBIC; + + upstreamconf.initial_rtt = + static_cast(NGTCP2_DEFAULT_INITIAL_RTT) / NGTCP2_SECONDS; + } + + if (RAND_bytes(quicconf.server_id.data(), quicconf.server_id.size()) != 1) { + assert(0); + abort(); + } + + auto &http3conf = config->http3; + { + auto &upstreamconf = http3conf.upstream; + + upstreamconf.max_concurrent_streams = 100; + upstreamconf.window_size = 256_k; + upstreamconf.connection_window_size = 1_m; + upstreamconf.max_window_size = 6_m; + upstreamconf.max_connection_window_size = 8_m; + } +#endif // ENABLE_HTTP3 + + auto &loggingconf = config->logging; + { + auto &accessconf = loggingconf.access; + accessconf.format = + parse_log_format(config->balloc, DEFAULT_ACCESSLOG_FORMAT); + + auto &errorconf = loggingconf.error; + errorconf.file = StringRef::from_lit("/dev/stderr"); + } + + loggingconf.syslog_facility = LOG_DAEMON; + loggingconf.severity = NOTICE; + + auto &connconf = config->conn; + { + auto &listenerconf = connconf.listener; + { + // Default accept() backlog + listenerconf.backlog = 65536; + listenerconf.timeout.sleep = 30_s; + } + } + + { + auto &upstreamconf = connconf.upstream; + { + auto &timeoutconf = upstreamconf.timeout; + // Read timeout for HTTP2 upstream connection + timeoutconf.http2_read = 3_min; + + // Read timeout for HTTP3 upstream connection + timeoutconf.http3_read = 3_min; + + // Read timeout for non-HTTP2 upstream connection + timeoutconf.read = 1_min; + + // Write timeout for HTTP2/non-HTTP2 upstream connection + timeoutconf.write = 30_s; + + // Keep alive timeout for HTTP/1 upstream connection + timeoutconf.idle_read = 1_min; + } + } + + { + connconf.downstream = std::make_shared(); + auto &downstreamconf = *connconf.downstream; + { + auto &timeoutconf = downstreamconf.timeout; + // Read/Write timeouts for downstream connection + timeoutconf.read = 1_min; + timeoutconf.write = 30_s; + // Timeout for pooled (idle) connections + timeoutconf.idle_read = 2_s; + timeoutconf.connect = 30_s; + timeoutconf.max_backoff = 120_s; + } + + downstreamconf.connections_per_host = 8; + downstreamconf.request_buffer_size = 16_k; + downstreamconf.response_buffer_size = 128_k; + downstreamconf.family = AF_UNSPEC; + } + + auto &apiconf = config->api; + apiconf.max_request_body = 32_m; + + auto &dnsconf = config->dns; + { + auto &timeoutconf = dnsconf.timeout; + timeoutconf.cache = 10_s; + timeoutconf.lookup = 5_s; + } + dnsconf.max_try = 2; +} + +} // namespace + +namespace { +void print_version(std::ostream &out) { + out << "nghttpx nghttp2/" NGHTTP2_VERSION +#ifdef ENABLE_HTTP3 + " ngtcp2/" NGTCP2_VERSION " nghttp3/" NGHTTP3_VERSION +#endif // ENABLE_HTTP3 + << std::endl; +} +} // namespace + +namespace { +void print_usage(std::ostream &out) { + out << R"(Usage: nghttpx [OPTIONS]... [ ] +A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.)" + << std::endl; +} +} // namespace + +namespace { +void print_help(std::ostream &out) { + auto config = get_config(); + + print_usage(out); + out << R"( + + Set path to server's private key. Required unless + "no-tls" parameter is used in --frontend option. + Set path to server's certificate. Required unless + "no-tls" parameter is used in --frontend option. To + make OCSP stapling work, this must be an absolute path. + +Options: + The options are categorized into several groups. + +Connections: + -b, --backend=(,|unix:)[;[[:...]][[;]...] + + Set backend host and port. The multiple backend + addresses are accepted by repeating this option. UNIX + domain socket can be specified by prefixing path name + with "unix:" (e.g., unix:/var/run/backend.sock). + + Optionally, if s are given, the backend address + is only used if request matches the pattern. The + pattern matching is closely designed to ServeMux in + net/http package of Go programming language. + consists of path, host + path or just host. The path + must start with "/". If it ends with "/", it matches + all request path in its subtree. To deal with the + request to the directory without trailing slash, the + path which ends with "/" also matches the request path + which only lacks trailing '/' (e.g., path "/foo/" + matches request path "/foo"). If it does not end with + "/", it performs exact match against the request path. + If host is given, it performs a match against the + request host. For a request received on the frontend + listener with "sni-fwd" parameter enabled, SNI host is + used instead of a request host. If host alone is given, + "/" is appended to it, so that it matches all request + paths under the host (e.g., specifying "nghttp2.org" + equals to "nghttp2.org/"). CONNECT method is treated + specially. It does not have path, and we don't allow + empty path. To workaround this, we assume that CONNECT + method has "/" as path. + + Patterns with host take precedence over patterns with + just path. Then, longer patterns take precedence over + shorter ones. + + Host can include "*" in the left most position to + indicate wildcard match (only suffix match is done). + The "*" must match at least one character. For example, + host pattern "*.nghttp2.org" matches against + "www.nghttp2.org" and "git.ngttp2.org", but does not + match against "nghttp2.org". The exact hosts match + takes precedence over the wildcard hosts match. + + If path part ends with "*", it is treated as wildcard + path. The wildcard path behaves differently from the + normal path. For normal path, match is made around the + boundary of path component separator,"/". On the other + hand, the wildcard path does not take into account the + path component separator. All paths which include the + wildcard path without last "*" as prefix, and are + strictly longer than wildcard path without last "*" are + matched. "*" must match at least one character. For + example, the pattern "/foo*" matches "/foo/" and + "/foobar". But it does not match "/foo", or "/fo". + + If is omitted or empty string, "/" is used as + pattern, which matches all request paths (catch-all + pattern). The catch-all backend must be given. + + When doing a match, nghttpx made some normalization to + pattern, request host and path. For host part, they are + converted to lower case. For path part, percent-encoded + unreserved characters defined in RFC 3986 are decoded, + and any dot-segments (".." and ".") are resolved and + removed. + + For example, -b'127.0.0.1,8080;nghttp2.org/httpbin/' + matches the request host "nghttp2.org" and the request + path "/httpbin/get", but does not match the request host + "nghttp2.org" and the request path "/index.html". + + The multiple s can be specified, delimiting + them by ":". Specifying + -b'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the + same effect to specify -b'127.0.0.1,8080;nghttp2.org' + and -b'127.0.0.1,8080;www.nghttp2.org'. + + The backend addresses sharing same are grouped + together forming load balancing group. + + Several parameters are accepted after . + The parameters are delimited by ";". The available + parameters are: "proto=", "tls", + "sni=", "fall=", "rise=", + "affinity=", "dns", "redirect-if-not-tls", + "upgrade-scheme", "mruby=", + "read-timeout=", "write-timeout=", + "group=", "group-weight=", "weight=", and + "dnf". The parameter consists of keyword, and + optionally followed by "=" and value. For example, the + parameter "proto=h2" consists of the keyword "proto" and + value "h2". The parameter "tls" consists of the keyword + "tls" without value. Each parameter is described as + follows. + + The backend application protocol can be specified using + optional "proto" parameter, and in the form of + "proto=". should be one of the following + list without quotes: "h2", "http/1.1". The default + value of is "http/1.1". Note that usually "h2" + refers to HTTP/2 over TLS. But in this option, it may + mean HTTP/2 over cleartext TCP unless "tls" keyword is + used (see below). + + TLS can be enabled by specifying optional "tls" + parameter. TLS is not enabled by default. + + With "sni=" parameter, it can override the TLS + SNI field value with given . This will + default to the backend name + + The feature to detect whether backend is online or + offline can be enabled using optional "fall" and "rise" + parameters. Using "fall=" parameter, if nghttpx + cannot connect to a this backend times in a row, + this backend is assumed to be offline, and it is + excluded from load balancing. If is 0, this backend + never be excluded from load balancing whatever times + nghttpx cannot connect to it, and this is the default. + There is also "rise=" parameter. After backend was + excluded from load balancing group, nghttpx periodically + attempts to make a connection to the failed backend, and + if the connection is made successfully times in a + row, the backend is assumed to be online, and it is now + eligible for load balancing target. If is 0, a + backend is permanently offline, once it goes in that + state, and this is the default behaviour. + + The session affinity is enabled using + "affinity=" parameter. If "ip" is given in + , client IP based session affinity is enabled. + If "cookie" is given in , cookie based session + affinity is enabled. If "none" is given in , + session affinity is disabled, and this is the default. + The session affinity is enabled per . If at + least one backend has "affinity" parameter, and its + is not "none", session affinity is enabled for + all backend servers sharing the same . It is + advised to set "affinity" parameter to all backend + explicitly if session affinity is desired. The session + affinity may break if one of the backend gets + unreachable, or backend settings are reloaded or + replaced by API. + + If "affinity=cookie" is used, the additional + configuration is required. + "affinity-cookie-name=" must be used to specify a + name of cookie to use. Optionally, + "affinity-cookie-path=" can be used to specify a + path which cookie is applied. The optional + "affinity-cookie-secure=" controls the Secure + attribute of a cookie. The default value is "auto", and + the Secure attribute is determined by a request scheme. + If a request scheme is "https", then Secure attribute is + set. Otherwise, it is not set. If is "yes", + the Secure attribute is always set. If is + "no", the Secure attribute is always omitted. + "affinity-cookie-stickiness=" controls + stickiness of this affinity. If is + "loose", removing or adding a backend server might break + the affinity and the request might be forwarded to a + different backend server. If is "strict", + removing the designated backend server breaks affinity, + but adding new backend server does not cause breakage. + If the designated backend server becomes unavailable, + new backend server is chosen as if the request does not + have an affinity cookie. defaults to + "loose". + + By default, name resolution of backend host name is done + at start up, or reloading configuration. If "dns" + parameter is given, name resolution takes place + dynamically. This is useful if backend address changes + frequently. If "dns" is given, name resolution of + backend host name at start up, or reloading + configuration is skipped. + + If "redirect-if-not-tls" parameter is used, the matched + backend requires that frontend connection is TLS + encrypted. If it isn't, nghttpx responds to the request + with 308 status code, and https URI the client should + use instead is included in Location header field. The + port number in redirect URI is 443 by default, and can + be changed using --redirect-https-port option. If at + least one backend has "redirect-if-not-tls" parameter, + this feature is enabled for all backend servers sharing + the same . It is advised to set + "redirect-if-no-tls" parameter to all backends + explicitly if this feature is desired. + + If "upgrade-scheme" parameter is used along with "tls" + parameter, HTTP/2 :scheme pseudo header field is changed + to "https" from "http" when forwarding a request to this + particular backend. This is a workaround for a backend + server which requires "https" :scheme pseudo header + field on TLS encrypted connection. + + "mruby=" parameter specifies a path to mruby + script file which is invoked when this pattern is + matched. All backends which share the same pattern must + have the same mruby path. + + "read-timeout=" and "write-timeout=" + parameters specify the read and write timeout of the + backend connection when this pattern is matched. All + backends which share the same pattern must have the same + timeouts. If these timeouts are entirely omitted for a + pattern, --backend-read-timeout and + --backend-write-timeout are used. + + "group=" parameter specifies the name of group + this backend address belongs to. By default, it belongs + to the unnamed default group. The name of group is + unique per pattern. "group-weight=" parameter + specifies the weight of the group. The higher weight + gets more frequently selected by the load balancing + algorithm. must be [1, 256] inclusive. The weight + 8 has 4 times more weight than 2. must be the same + for all addresses which share the same . If + "group-weight" is omitted in an address, but the other + address which belongs to the same group specifies + "group-weight", its weight is used. If no + "group-weight" is specified for all addresses, the + weight of a group becomes 1. "group" and "group-weight" + are ignored if session affinity is enabled. + + "weight=" parameter specifies the weight of the + backend address inside a group which this address + belongs to. The higher weight gets more frequently + selected by the load balancing algorithm. must be + [1, 256] inclusive. The weight 8 has 4 times more + weight than weight 2. If this parameter is omitted, + weight becomes 1. "weight" is ignored if session + affinity is enabled. + + If "dnf" parameter is specified, an incoming request is + not forwarded to a backend and just consumed along with + the request body (actually a backend server never be + contacted). It is expected that the HTTP response is + generated by mruby script (see "mruby=" parameter + above). "dnf" is an abbreviation of "do not forward". + + Since ";" and ":" are used as delimiter, must + not contain these characters. In order to include ":" + in , one has to specify "%3A" (which is + percent-encoded from of ":") instead. Since ";" has + special meaning in shell, the option value must be + quoted. + + Default: )" + << DEFAULT_DOWNSTREAM_HOST << "," << DEFAULT_DOWNSTREAM_PORT << R"( + -f, --frontend=(,|unix:)[[;]...] + Set frontend host and port. If is '*', it + assumes all addresses including both IPv4 and IPv6. + UNIX domain socket can be specified by prefixing path + name with "unix:" (e.g., unix:/var/run/nghttpx.sock). + This option can be used multiple times to listen to + multiple addresses. + + This option can take 0 or more parameters, which are + described below. Note that "api" and "healthmon" + parameters are mutually exclusive. + + Optionally, TLS can be disabled by specifying "no-tls" + parameter. TLS is enabled by default. + + If "sni-fwd" parameter is used, when performing a match + to select a backend server, SNI host name received from + the client is used instead of the request host. See + --backend option about the pattern match. + + To make this frontend as API endpoint, specify "api" + parameter. This is disabled by default. It is + important to limit the access to the API frontend. + Otherwise, someone may change the backend server, and + break your services, or expose confidential information + to the outside the world. + + To make this frontend as health monitor endpoint, + specify "healthmon" parameter. This is disabled by + default. Any requests which come through this address + are replied with 200 HTTP status, without no body. + + To accept PROXY protocol version 1 and 2 on frontend + connection, specify "proxyproto" parameter. This is + disabled by default. + + To receive HTTP/3 (QUIC) traffic, specify "quic" + parameter. It makes nghttpx listen on UDP port rather + than TCP port. UNIX domain socket, "api", and + "healthmon" parameters cannot be used with "quic" + parameter. + + Default: *,3000 + --backlog= + Set listen backlog size. + Default: )" + << config->conn.listener.backlog << R"( + --backend-address-family=(auto|IPv4|IPv6) + Specify address family of backend connections. If + "auto" is given, both IPv4 and IPv6 are considered. If + "IPv4" is given, only IPv4 address is considered. If + "IPv6" is given, only IPv6 address is considered. + Default: auto + --backend-http-proxy-uri= + Specify proxy URI in the form + http://[:@]:. If a proxy + requires authentication, specify and . + Note that they must be properly percent-encoded. This + proxy is used when the backend connection is HTTP/2. + First, make a CONNECT request to the proxy and it + connects to the backend on behalf of nghttpx. This + forms tunnel. After that, nghttpx performs SSL/TLS + handshake with the downstream through the tunnel. The + timeouts when connecting and making CONNECT request can + be specified by --backend-read-timeout and + --backend-write-timeout options. + +Performance: + -n, --workers= + Set the number of worker threads. + Default: )" + << config->num_worker << R"( + --single-thread + Run everything in one thread inside the worker process. + This feature is provided for better debugging + experience, or for the platforms which lack thread + support. If threading is disabled, this option is + always enabled. + --read-rate= + Set maximum average read rate on frontend connection. + Setting 0 to this option means read rate is unlimited. + Default: )" + << config->conn.upstream.ratelimit.read.rate << R"( + --read-burst= + Set maximum read burst size on frontend connection. + Setting 0 to this option means read burst size is + unlimited. + Default: )" + << config->conn.upstream.ratelimit.read.burst << R"( + --write-rate= + Set maximum average write rate on frontend connection. + Setting 0 to this option means write rate is unlimited. + Default: )" + << config->conn.upstream.ratelimit.write.rate << R"( + --write-burst= + Set maximum write burst size on frontend connection. + Setting 0 to this option means write burst size is + unlimited. + Default: )" + << config->conn.upstream.ratelimit.write.burst << R"( + --worker-read-rate= + Set maximum average read rate on frontend connection per + worker. Setting 0 to this option means read rate is + unlimited. Not implemented yet. + Default: 0 + --worker-read-burst= + Set maximum read burst size on frontend connection per + worker. Setting 0 to this option means read burst size + is unlimited. Not implemented yet. + Default: 0 + --worker-write-rate= + Set maximum average write rate on frontend connection + per worker. Setting 0 to this option means write rate + is unlimited. Not implemented yet. + Default: 0 + --worker-write-burst= + Set maximum write burst size on frontend connection per + worker. Setting 0 to this option means write burst size + is unlimited. Not implemented yet. + Default: 0 + --worker-frontend-connections= + Set maximum number of simultaneous connections frontend + accepts. Setting 0 means unlimited. + Default: )" + << config->conn.upstream.worker_connections << R"( + --backend-connections-per-host= + Set maximum number of backend concurrent connections + (and/or streams in case of HTTP/2) per origin host. + This option is meaningful when --http2-proxy option is + used. The origin host is determined by authority + portion of request URI (or :authority header field for + HTTP/2). To limit the number of connections per + frontend for default mode, use + --backend-connections-per-frontend. + Default: )" + << config->conn.downstream->connections_per_host << R"( + --backend-connections-per-frontend= + Set maximum number of backend concurrent connections + (and/or streams in case of HTTP/2) per frontend. This + option is only used for default mode. 0 means + unlimited. To limit the number of connections per host + with --http2-proxy option, use + --backend-connections-per-host. + Default: )" + << config->conn.downstream->connections_per_frontend << R"( + --rlimit-nofile= + Set maximum number of open files (RLIMIT_NOFILE) to . + If 0 is given, nghttpx does not set the limit. + Default: )" + << config->rlimit_nofile << R"( + --rlimit-memlock= + Set maximum number of bytes of memory that may be locked + into RAM. If 0 is given, nghttpx does not set the + limit. + Default: )" + << config->rlimit_memlock << R"( + --backend-request-buffer= + Set buffer size used to store backend request. + Default: )" + << util::utos_unit(config->conn.downstream->request_buffer_size) << R"( + --backend-response-buffer= + Set buffer size used to store backend response. + Default: )" + << util::utos_unit(config->conn.downstream->response_buffer_size) << R"( + --fastopen= + Enables "TCP Fast Open" for the listening socket and + limits the maximum length for the queue of connections + that have not yet completed the three-way handshake. If + value is 0 then fast open is disabled. + Default: )" + << config->conn.listener.fastopen << R"( + --no-kqueue Don't use kqueue. This option is only applicable for + the platforms which have kqueue. For other platforms, + this option will be simply ignored. + +Timeout: + --frontend-http2-read-timeout= + Specify read timeout for HTTP/2 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.http2_read) << R"( + --frontend-http3-read-timeout= + Specify read timeout for HTTP/3 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.http3_read) << R"( + --frontend-read-timeout= + Specify read timeout for HTTP/1.1 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.read) << R"( + --frontend-write-timeout= + Specify write timeout for all frontend connections. + Default: )" + << util::duration_str(config->conn.upstream.timeout.write) << R"( + --frontend-keep-alive-timeout= + Specify keep-alive timeout for frontend HTTP/1 + connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.idle_read) << R"( + --stream-read-timeout= + Specify read timeout for HTTP/2 streams. 0 means no + timeout. + Default: )" + << util::duration_str(config->http2.timeout.stream_read) << R"( + --stream-write-timeout= + Specify write timeout for HTTP/2 streams. 0 means no + timeout. + Default: )" + << util::duration_str(config->http2.timeout.stream_write) << R"( + --backend-read-timeout= + Specify read timeout for backend connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.read) << R"( + --backend-write-timeout= + Specify write timeout for backend connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.write) << R"( + --backend-connect-timeout= + Specify timeout before establishing TCP connection to + backend. + Default: )" + << util::duration_str(config->conn.downstream->timeout.connect) << R"( + --backend-keep-alive-timeout= + Specify keep-alive timeout for backend HTTP/1 + connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.idle_read) << R"( + --listener-disable-timeout= + After accepting connection failed, connection listener + is disabled for a given amount of time. Specifying 0 + disables this feature. + Default: )" + << util::duration_str(config->conn.listener.timeout.sleep) << R"( + --frontend-http2-setting-timeout= + Specify timeout before SETTINGS ACK is received from + client. + Default: )" + << util::duration_str(config->http2.upstream.timeout.settings) << R"( + --backend-http2-settings-timeout= + Specify timeout before SETTINGS ACK is received from + backend server. + Default: )" + << util::duration_str(config->http2.downstream.timeout.settings) << R"( + --backend-max-backoff= + Specify maximum backoff interval. This is used when + doing health check against offline backend (see "fail" + parameter in --backend option). It is also used to + limit the maximum interval to temporarily disable + backend when nghttpx failed to connect to it. These + intervals are calculated using exponential backoff, and + consecutive failed attempts increase the interval. This + option caps its maximum value. + Default: )" + << util::duration_str(config->conn.downstream->timeout.max_backoff) << R"( + +SSL/TLS: + --ciphers= + Set allowed cipher list for frontend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.2 or earlier. + Use --tls13-ciphers for TLSv1.3. + Default: )" + << config->tls.ciphers << R"( + --tls13-ciphers= + Set allowed cipher list for frontend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.3. Use + --ciphers for TLSv1.2 or earlier. + Default: )" + << config->tls.tls13_ciphers << R"( + --client-ciphers= + Set allowed cipher list for backend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.2 or earlier. + Use --tls13-client-ciphers for TLSv1.3. + Default: )" + << config->tls.client.ciphers << R"( + --tls13-client-ciphers= + Set allowed cipher list for backend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.3. Use + --tls13-client-ciphers for TLSv1.2 or earlier. + Default: )" + << config->tls.client.tls13_ciphers << R"( + --ecdh-curves= + Set supported curve list for frontend connections. + is a colon separated list of curve NID or names + in the preference order. The supported curves depend on + the linked OpenSSL library. This function requires + OpenSSL >= 1.0.2. + Default: )" + << config->tls.ecdh_curves << R"( + -k, --insecure + Don't verify backend server's certificate if TLS is + enabled for backend connections. + --cacert= + Set path to trusted CA certificate file. It is used in + backend TLS connections to verify peer's certificate. + It is also used to verify OCSP response from the script + set by --fetch-ocsp-response-file. The file must be in + PEM format. It can contain multiple certificates. If + the linked OpenSSL is configured to load system wide + certificates, they are loaded at startup regardless of + this option. + --private-key-passwd-file= + Path to file that contains password for the server's + private key. If none is given and the private key is + password protected it'll be requested interactively. + --subcert=:[[;]...] + Specify additional certificate and private key file. + nghttpx will choose certificates based on the hostname + indicated by client using TLS SNI extension. If nghttpx + is built with OpenSSL >= 1.0.2, the shared elliptic + curves (e.g., P-256) between client and server are also + taken into consideration. This allows nghttpx to send + ECDSA certificate to modern clients, while sending RSA + based certificate to older clients. This option can be + used multiple times. To make OCSP stapling work, + must be absolute path. + + Additional parameter can be specified in . The + available is "sct-dir=". + + "sct-dir=" specifies the path to directory which + contains *.sct files for TLS + signed_certificate_timestamp extension (RFC 6962). This + feature requires OpenSSL >= 1.0.2. See also + --tls-sct-dir option. + --dh-param-file= + Path to file that contains DH parameters in PEM format. + Without this option, DHE cipher suites are not + available. + --npn-list= + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. This is used in both ALPN and + NPN. The parameter must be delimited by a single comma + only and any white spaces are treated as a part of + protocol string. + Default: )" + << DEFAULT_NPN_LIST + << R"( + --verify-client + Require and verify client certificate. + --verify-client-cacert= + Path to file that contains CA certificates to verify + client certificate. The file must be in PEM format. It + can contain multiple certificates. + --verify-client-tolerate-expired + Accept expired client certificate. Operator should + handle the expired client certificate by some means + (e.g., mruby script). Otherwise, this option might + cause a security risk. + --client-private-key-file= + Path to file that contains client private key used in + backend client authentication. + --client-cert-file= + Path to file that contains client certificate used in + backend client authentication. + --tls-min-proto-version= + Specify minimum SSL/TLS protocol. The name matching is + done in case-insensitive manner. The versions between + --tls-min-proto-version and --tls-max-proto-version are + enabled. If the protocol list advertised by client does + not overlap this range, you will receive the error + message "unknown protocol". If a protocol version lower + than TLSv1.2 is specified, make sure that the compatible + ciphers are included in --ciphers option. The default + cipher list only includes ciphers compatible with + TLSv1.2 or above. The available versions are: + )" +#ifdef TLS1_3_VERSION + "TLSv1.3, " +#endif // TLS1_3_VERSION + "TLSv1.2, TLSv1.1, and TLSv1.0" + R"( + Default: )" + << DEFAULT_TLS_MIN_PROTO_VERSION + << R"( + --tls-max-proto-version= + Specify maximum SSL/TLS protocol. The name matching is + done in case-insensitive manner. The versions between + --tls-min-proto-version and --tls-max-proto-version are + enabled. If the protocol list advertised by client does + not overlap this range, you will receive the error + message "unknown protocol". The available versions are: + )" +#ifdef TLS1_3_VERSION + "TLSv1.3, " +#endif // TLS1_3_VERSION + "TLSv1.2, TLSv1.1, and TLSv1.0" + R"( + Default: )" + << DEFAULT_TLS_MAX_PROTO_VERSION << R"( + --tls-ticket-key-file= + Path to file that contains random data to construct TLS + session ticket parameters. If aes-128-cbc is given in + --tls-ticket-key-cipher, the file must contain exactly + 48 bytes. If aes-256-cbc is given in + --tls-ticket-key-cipher, the file must contain exactly + 80 bytes. This options can be used repeatedly to + specify multiple ticket parameters. If several files + are given, only the first key is used to encrypt TLS + session tickets. Other keys are accepted but server + will issue new session ticket with first key. This + allows session key rotation. Please note that key + rotation does not occur automatically. User should + rearrange files or change options values and restart + nghttpx gracefully. If opening or reading given file + fails, all loaded keys are discarded and it is treated + as if none of this option is given. If this option is + not given or an error occurred while opening or reading + a file, key is generated every 1 hour internally and + they are valid for 12 hours. This is recommended if + ticket key sharing between nghttpx instances is not + required. + --tls-ticket-key-memcached=,[;tls] + Specify address of memcached server to get TLS ticket + keys for session resumption. This enables shared TLS + ticket key between multiple nghttpx instances. nghttpx + does not set TLS ticket key to memcached. The external + ticket key generator is required. nghttpx just gets TLS + ticket keys from memcached, and use them, possibly + replacing current set of keys. It is up to extern TLS + ticket key generator to rotate keys frequently. See + "TLS SESSION TICKET RESUMPTION" section in manual page + to know the data format in memcached entry. Optionally, + memcached connection can be encrypted with TLS by + specifying "tls" parameter. + --tls-ticket-key-memcached-address-family=(auto|IPv4|IPv6) + Specify address family of memcached connections to get + TLS ticket keys. If "auto" is given, both IPv4 and IPv6 + are considered. If "IPv4" is given, only IPv4 address + is considered. If "IPv6" is given, only IPv6 address is + considered. + Default: auto + --tls-ticket-key-memcached-interval= + Set interval to get TLS ticket keys from memcached. + Default: )" + << util::duration_str(config->tls.ticket.memcached.interval) << R"( + --tls-ticket-key-memcached-max-retry= + Set maximum number of consecutive retries before + abandoning TLS ticket key retrieval. If this number is + reached, the attempt is considered as failure, and + "failure" count is incremented by 1, which contributed + to the value controlled + --tls-ticket-key-memcached-max-fail option. + Default: )" + << config->tls.ticket.memcached.max_retry << R"( + --tls-ticket-key-memcached-max-fail= + Set maximum number of consecutive failure before + disabling TLS ticket until next scheduled key retrieval. + Default: )" + << config->tls.ticket.memcached.max_fail << R"( + --tls-ticket-key-cipher= + Specify cipher to encrypt TLS session ticket. Specify + either aes-128-cbc or aes-256-cbc. By default, + aes-128-cbc is used. + --tls-ticket-key-memcached-cert-file= + Path to client certificate for memcached connections to + get TLS ticket keys. + --tls-ticket-key-memcached-private-key-file= + Path to client private key for memcached connections to + get TLS ticket keys. + --fetch-ocsp-response-file= + Path to fetch-ocsp-response script file. It should be + absolute path. + Default: )" + << config->tls.ocsp.fetch_ocsp_response_file << R"( + --ocsp-update-interval= + Set interval to update OCSP response cache. + Default: )" + << util::duration_str(config->tls.ocsp.update_interval) << R"( + --ocsp-startup + Start accepting connections after initial attempts to + get OCSP responses finish. It does not matter some of + the attempts fail. This feature is useful if OCSP + responses must be available before accepting + connections. + --no-verify-ocsp + nghttpx does not verify OCSP response. + --no-ocsp Disable OCSP stapling. + --tls-session-cache-memcached=,[;tls] + Specify address of memcached server to store session + cache. This enables shared session cache between + multiple nghttpx instances. Optionally, memcached + connection can be encrypted with TLS by specifying "tls" + parameter. + --tls-session-cache-memcached-address-family=(auto|IPv4|IPv6) + Specify address family of memcached connections to store + session cache. If "auto" is given, both IPv4 and IPv6 + are considered. If "IPv4" is given, only IPv4 address + is considered. If "IPv6" is given, only IPv6 address is + considered. + Default: auto + --tls-session-cache-memcached-cert-file= + Path to client certificate for memcached connections to + store session cache. + --tls-session-cache-memcached-private-key-file= + Path to client private key for memcached connections to + store session cache. + --tls-dyn-rec-warmup-threshold= + Specify the threshold size for TLS dynamic record size + behaviour. During a TLS session, after the threshold + number of bytes have been written, the TLS record size + will be increased to the maximum allowed (16K). The max + record size will continue to be used on the active TLS + session. After --tls-dyn-rec-idle-timeout has elapsed, + the record size is reduced to 1300 bytes. Specify 0 to + always use the maximum record size, regardless of idle + period. This behaviour applies to all TLS based + frontends, and TLS HTTP/2 backends. + Default: )" + << util::utos_unit(config->tls.dyn_rec.warmup_threshold) << R"( + --tls-dyn-rec-idle-timeout= + Specify TLS dynamic record size behaviour timeout. See + --tls-dyn-rec-warmup-threshold for more information. + This behaviour applies to all TLS based frontends, and + TLS HTTP/2 backends. + Default: )" + << util::duration_str(config->tls.dyn_rec.idle_timeout) << R"( + --no-http2-cipher-block-list + Allow block listed cipher suite on frontend HTTP/2 + connection. See + https://tools.ietf.org/html/rfc7540#appendix-A for the + complete HTTP/2 cipher suites block list. + --client-no-http2-cipher-block-list + Allow block listed cipher suite on backend HTTP/2 + connection. See + https://tools.ietf.org/html/rfc7540#appendix-A for the + complete HTTP/2 cipher suites block list. + --tls-sct-dir= + Specifies the directory where *.sct files exist. All + *.sct files in are read, and sent as + extension_data of TLS signed_certificate_timestamp (RFC + 6962) to client. These *.sct files are for the + certificate specified in positional command-line + argument , or certificate option in configuration + file. For additional certificates, use --subcert + option. This option requires OpenSSL >= 1.0.2. + --psk-secrets= + Read list of PSK identity and secrets from . This + is used for frontend connection. The each line of input + file is formatted as :, where + is PSK identity, and is secret + in hex. An empty line, and line which starts with '#' + are skipped. The default enabled cipher list might not + contain any PSK cipher suite. In that case, desired PSK + cipher suites must be enabled using --ciphers option. + The desired PSK cipher suite may be block listed by + HTTP/2. To use those cipher suites with HTTP/2, + consider to use --no-http2-cipher-block-list option. + But be aware its implications. + --client-psk-secrets= + Read PSK identity and secrets from . This is used + for backend connection. The each line of input file is + formatted as :, where + is PSK identity, and is secret in hex. An + empty line, and line which starts with '#' are skipped. + The first identity and secret pair encountered is used. + The default enabled cipher list might not contain any + PSK cipher suite. In that case, desired PSK cipher + suites must be enabled using --client-ciphers option. + The desired PSK cipher suite may be block listed by + HTTP/2. To use those cipher suites with HTTP/2, + consider to use --client-no-http2-cipher-block-list + option. But be aware its implications. + --tls-no-postpone-early-data + By default, except for QUIC connections, nghttpx + postpones forwarding HTTP requests sent in early data, + including those sent in partially in it, until TLS + handshake finishes. If all backend server recognizes + "Early-Data" header field, using this option makes + nghttpx not postpone forwarding request and get full + potential of 0-RTT data. + --tls-max-early-data= + Sets the maximum amount of 0-RTT data that server + accepts. + Default: )" + << util::utos_unit(config->tls.max_early_data) << R"( + --tls-ktls Enable ktls. For server, ktls is enable if + --tls-session-cache-memcached is not configured. + +HTTP/2: + -c, --frontend-http2-max-concurrent-streams= + Set the maximum number of the concurrent streams in one + frontend HTTP/2 session. + Default: )" + << config->http2.upstream.max_concurrent_streams << R"( + --backend-http2-max-concurrent-streams= + Set the maximum number of the concurrent streams in one + backend HTTP/2 session. This sets maximum number of + concurrent opened pushed streams. The maximum number of + concurrent requests are set by a remote server. + Default: )" + << config->http2.downstream.max_concurrent_streams << R"( + --frontend-http2-window-size= + Sets the per-stream initial window size of HTTP/2 + frontend connection. + Default: )" + << config->http2.upstream.window_size << R"( + --frontend-http2-connection-window-size= + Sets the per-connection window size of HTTP/2 frontend + connection. + Default: )" + << config->http2.upstream.connection_window_size << R"( + --backend-http2-window-size= + Sets the initial window size of HTTP/2 backend + connection. + Default: )" + << config->http2.downstream.window_size << R"( + --backend-http2-connection-window-size= + Sets the per-connection window size of HTTP/2 backend + connection. + Default: )" + << config->http2.downstream.connection_window_size << R"( + --http2-no-cookie-crumbling + Don't crumble cookie header field. + --padding= + Add at most bytes to a HTTP/2 frame payload as + padding. Specify 0 to disable padding. This option is + meant for debugging purpose and not intended to enhance + protocol security. + --no-server-push + Disable HTTP/2 server push. Server push is supported by + default mode and HTTP/2 frontend via Link header field. + It is also supported if both frontend and backend are + HTTP/2 in default mode. In this case, server push from + backend session is relayed to frontend, and server push + via Link header field is also supported. + --frontend-http2-optimize-write-buffer-size + (Experimental) Enable write buffer size optimization in + frontend HTTP/2 TLS connection. This optimization aims + to reduce write buffer size so that it only contains + bytes which can send immediately. This makes server + more responsive to prioritized HTTP/2 stream because the + buffering of lower priority stream is reduced. This + option is only effective on recent Linux platform. + --frontend-http2-optimize-window-size + (Experimental) Automatically tune connection level + window size of frontend HTTP/2 TLS connection. If this + feature is enabled, connection window size starts with + the default window size, 65535 bytes. nghttpx + automatically adjusts connection window size based on + TCP receiving window size. The maximum window size is + capped by the value specified by + --frontend-http2-connection-window-size. Since the + stream is subject to stream level window size, it should + be adjusted using --frontend-http2-window-size option as + well. This option is only effective on recent Linux + platform. + --frontend-http2-encoder-dynamic-table-size= + Specify the maximum dynamic table size of HPACK encoder + in the frontend HTTP/2 connection. The decoder (client) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which client specified. + Default: )" + << util::utos_unit(config->http2.upstream.encoder_dynamic_table_size) + << R"( + --frontend-http2-decoder-dynamic-table-size= + Specify the maximum dynamic table size of HPACK decoder + in the frontend HTTP/2 connection. + Default: )" + << util::utos_unit(config->http2.upstream.decoder_dynamic_table_size) + << R"( + --backend-http2-encoder-dynamic-table-size= + Specify the maximum dynamic table size of HPACK encoder + in the backend HTTP/2 connection. The decoder (backend) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which backend specified. + Default: )" + << util::utos_unit(config->http2.downstream.encoder_dynamic_table_size) + << R"( + --backend-http2-decoder-dynamic-table-size= + Specify the maximum dynamic table size of HPACK decoder + in the backend HTTP/2 connection. + Default: )" + << util::utos_unit(config->http2.downstream.decoder_dynamic_table_size) + << R"( + +Mode: + (default mode) + Accept HTTP/2, and HTTP/1.1 over SSL/TLS. "no-tls" + parameter is used in --frontend option, accept HTTP/2 + and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1 + connection can be upgraded to HTTP/2 through HTTP + Upgrade. + -s, --http2-proxy + Like default mode, but enable forward proxy. This is so + called HTTP/2 proxy mode. + +Logging: + -L, --log-level= + Set the severity level of log output. must be + one of INFO, NOTICE, WARN, ERROR and FATAL. + Default: NOTICE + --accesslog-file= + Set path to write access log. To reopen file, send USR1 + signal to nghttpx. + --accesslog-syslog + Send access log to syslog. If this option is used, + --accesslog-file option is ignored. + --accesslog-format= + Specify format string for access log. The default + format is combined format. The following variables are + available: + + * $remote_addr: client IP address. + * $time_local: local time in Common Log format. + * $time_iso8601: local time in ISO 8601 format. + * $request: HTTP request line. + * $status: HTTP response status code. + * $body_bytes_sent: the number of bytes sent to client + as response body. + * $http_: value of HTTP request header where + '_' in is replaced with '-'. + * $remote_port: client port. + * $server_port: server port. + * $request_time: request processing time in seconds with + milliseconds resolution. + * $pid: PID of the running process. + * $alpn: ALPN identifier of the protocol which generates + the response. For HTTP/1, ALPN is always http/1.1, + regardless of minor version. + * $tls_cipher: cipher used for SSL/TLS connection. + * $tls_client_fingerprint_sha256: SHA-256 fingerprint of + client certificate. + * $tls_client_fingerprint_sha1: SHA-1 fingerprint of + client certificate. + * $tls_client_subject_name: subject name in client + certificate. + * $tls_client_issuer_name: issuer name in client + certificate. + * $tls_client_serial: serial number in client + certificate. + * $tls_protocol: protocol for SSL/TLS connection. + * $tls_session_id: session ID for SSL/TLS connection. + * $tls_session_reused: "r" if SSL/TLS session was + reused. Otherwise, "." + * $tls_sni: SNI server name for SSL/TLS connection. + * $backend_host: backend host used to fulfill the + request. "-" if backend host is not available. + * $backend_port: backend port used to fulfill the + request. "-" if backend host is not available. + * $method: HTTP method + * $path: Request path including query. For CONNECT + request, authority is recorded. + * $path_without_query: $path up to the first '?' + character. For CONNECT request, authority is + recorded. + * $protocol_version: HTTP version (e.g., HTTP/1.1, + HTTP/2) + + The variable can be enclosed by "{" and "}" for + disambiguation (e.g., ${remote_addr}). + + Default: )" + << DEFAULT_ACCESSLOG_FORMAT << R"( + --accesslog-write-early + Write access log when response header fields are + received from backend rather than when request + transaction finishes. + --errorlog-file= + Set path to write error log. To reopen file, send USR1 + signal to nghttpx. stderr will be redirected to the + error log file unless --errorlog-syslog is used. + Default: )" + << config->logging.error.file << R"( + --errorlog-syslog + Send error log to syslog. If this option is used, + --errorlog-file option is ignored. + --syslog-facility= + Set syslog facility to . + Default: )" + << str_syslog_facility(config->logging.syslog_facility) << R"( + +HTTP: + --add-x-forwarded-for + Append X-Forwarded-For header field to the downstream + request. + --strip-incoming-x-forwarded-for + Strip X-Forwarded-For header field from inbound client + requests. + --no-add-x-forwarded-proto + Don't append additional X-Forwarded-Proto header field + to the backend request. If inbound client sets + X-Forwarded-Proto, and + --no-strip-incoming-x-forwarded-proto option is used, + they are passed to the backend. + --no-strip-incoming-x-forwarded-proto + Don't strip X-Forwarded-Proto header field from inbound + client requests. + --add-forwarded= + Append RFC 7239 Forwarded header field with parameters + specified in comma delimited list . The supported + parameters are "by", "for", "host", and "proto". By + default, the value of "by" and "for" parameters are + obfuscated string. See --forwarded-by and + --forwarded-for options respectively. Note that nghttpx + does not translate non-standard X-Forwarded-* header + fields into Forwarded header field, and vice versa. + --strip-incoming-forwarded + Strip Forwarded header field from inbound client + requests. + --forwarded-by=(obfuscated|ip|) + Specify the parameter value sent out with "by" parameter + of Forwarded header field. If "obfuscated" is given, + the string is randomly generated at startup. If "ip" is + given, the interface address of the connection, + including port number, is sent with "by" parameter. In + case of UNIX domain socket, "localhost" is used instead + of address and port. User can also specify the static + obfuscated string. The limitation is that it must start + with "_", and only consists of character set + [A-Za-z0-9._-], as described in RFC 7239. + Default: obfuscated + --forwarded-for=(obfuscated|ip) + Specify the parameter value sent out with "for" + parameter of Forwarded header field. If "obfuscated" is + given, the string is randomly generated for each client + connection. If "ip" is given, the remote client address + of the connection, without port number, is sent with + "for" parameter. In case of UNIX domain socket, + "localhost" is used instead of address. + Default: obfuscated + --no-via Don't append to Via header field. If Via header field + is received, it is left unaltered. + --no-strip-incoming-early-data + Don't strip Early-Data header field from inbound client + requests. + --no-location-rewrite + Don't rewrite location header field in default mode. + When --http2-proxy is used, location header field will + not be altered regardless of this option. + --host-rewrite + Rewrite host and :authority header fields in default + mode. When --http2-proxy is used, these headers will + not be altered regardless of this option. + --altsvc= + Specify protocol ID, port, host and origin of + alternative service. , and are + optional. Empty and are allowed and + they are treated as nothing is specified. They are + advertised in alt-svc header field only in HTTP/1.1 + frontend. This option can be used multiple times to + specify multiple alternative services. + Example: --altsvc="h2,443,,,ma=3600; persist=1" + --http2-altsvc= + Just like --altsvc option, but this altsvc is only sent + in HTTP/2 frontend. + --add-request-header=
+ Specify additional header field to add to request header + set. The field name must be lowercase. This option + just appends header field and won't replace anything + already set. This option can be used several times to + specify multiple header fields. + Example: --add-request-header="foo: bar" + --add-response-header=
+ Specify additional header field to add to response + header set. The field name must be lowercase. This + option just appends header field and won't replace + anything already set. This option can be used several + times to specify multiple header fields. + Example: --add-response-header="foo: bar" + --request-header-field-buffer= + Set maximum buffer size for incoming HTTP request header + field list. This is the sum of header name and value in + bytes. If trailer fields exist, they are counted + towards this number. + Default: )" + << util::utos_unit(config->http.request_header_field_buffer) << R"( + --max-request-header-fields= + Set maximum number of incoming HTTP request header + fields. If trailer fields exist, they are counted + towards this number. + Default: )" + << config->http.max_request_header_fields << R"( + --response-header-field-buffer= + Set maximum buffer size for incoming HTTP response + header field list. This is the sum of header name and + value in bytes. If trailer fields exist, they are + counted towards this number. + Default: )" + << util::utos_unit(config->http.response_header_field_buffer) << R"( + --max-response-header-fields= + Set maximum number of incoming HTTP response header + fields. If trailer fields exist, they are counted + towards this number. + Default: )" + << config->http.max_response_header_fields << R"( + --error-page=(|*)= + Set file path to custom error page served when nghttpx + originally generates HTTP error status code . + must be greater than or equal to 400, and at most + 599. If "*" is used instead of , it matches all + HTTP status code. If error status code comes from + backend server, the custom error pages are not used. + --server-name= + Change server response header field value to . + Default: )" + << config->http.server_name << R"( + --no-server-rewrite + Don't rewrite server header field in default mode. When + --http2-proxy is used, these headers will not be altered + regardless of this option. + --redirect-https-port= + Specify the port number which appears in Location header + field when redirect to HTTPS URI is made due to + "redirect-if-not-tls" parameter in --backend option. + Default: )" + << config->http.redirect_https_port << R"( + --require-http-scheme + Always require http or https scheme in HTTP request. It + also requires that https scheme must be used for an + encrypted connection. Otherwise, http scheme must be + used. This option is recommended for a server + deployment which directly faces clients and the services + it provides only require http or https scheme. + +API: + --api-max-request-body= + Set the maximum size of request body for API request. + Default: )" + << util::utos_unit(config->api.max_request_body) << R"( + +DNS: + --dns-cache-timeout= + Set duration that cached DNS results remain valid. Note + that nghttpx caches the unsuccessful results as well. + Default: )" + << util::duration_str(config->dns.timeout.cache) << R"( + --dns-lookup-timeout= + Set timeout that DNS server is given to respond to the + initial DNS query. For the 2nd and later queries, + server is given time based on this timeout, and it is + scaled linearly. + Default: )" + << util::duration_str(config->dns.timeout.lookup) << R"( + --dns-max-try= + Set the number of DNS query before nghttpx gives up name + lookup. + Default: )" + << config->dns.max_try << R"( + --frontend-max-requests= + The number of requests that single frontend connection + can process. For HTTP/2, this is the number of streams + in one HTTP/2 connection. For HTTP/1, this is the + number of keep alive requests. This is hint to nghttpx, + and it may allow additional few requests. The default + value is unlimited. + +Debug: + --frontend-http2-dump-request-header= + Dumps request headers received by HTTP/2 frontend to the + file denoted in . The output is done in HTTP/1 + header field format and each header block is followed by + an empty line. This option is not thread safe and MUST + NOT be used with option -n, where >= 2. + --frontend-http2-dump-response-header= + Dumps response headers sent from HTTP/2 frontend to the + file denoted in . The output is done in HTTP/1 + header field format and each header block is followed by + an empty line. This option is not thread safe and MUST + NOT be used with option -n, where >= 2. + -o, --frontend-frame-debug + Print HTTP/2 frames in frontend to stderr. This option + is not thread safe and MUST NOT be used with option + -n=N, where N >= 2. + +Process: + -D, --daemon + Run in a background. If -D is used, the current working + directory is changed to '/'. + --pid-file= + Set path to save PID of this program. + --user= + Run this program as . This option is intended to + be used to drop root privileges. + --single-process + Run this program in a single process mode for debugging + purpose. Without this option, nghttpx creates at least + 2 processes: main and worker processes. If this option + is used, main and worker are unified into a single + process. nghttpx still spawns additional process if + neverbleed is used. In the single process mode, the + signal handling feature is disabled. + --max-worker-processes= + The maximum number of worker processes. nghttpx spawns + new worker process when it reloads its configuration. + The previous worker process enters graceful termination + period and will terminate when it finishes handling the + existing connections. However, if reloading + configurations happen very frequently, the worker + processes might be piled up if they take a bit long time + to finish the existing connections. With this option, + if the number of worker processes exceeds the given + value, the oldest worker process is terminated + immediately. Specifying 0 means no limit and it is the + default behaviour. + --worker-process-grace-shutdown-period= + Maximum period for a worker process to terminate + gracefully. When a worker process enters in graceful + shutdown period (e.g., when nghttpx reloads its + configuration) and it does not finish handling the + existing connections in the given period of time, it is + immediately terminated. Specifying 0 means no limit and + it is the default behaviour. + +Scripting: + --mruby-file= + Set mruby script file + --ignore-per-pattern-mruby-error + Ignore mruby compile error for per-pattern mruby script + file. If error occurred, it is treated as if no mruby + file were specified for the pattern. +)"; + +#ifdef ENABLE_HTTP3 + out << R"( +HTTP/3 and QUIC: + --frontend-quic-idle-timeout= + Specify an idle timeout for QUIC connection. + Default: )" + << util::duration_str(config->quic.upstream.timeout.idle) << R"( + --frontend-quic-debug-log + Output QUIC debug log to /dev/stderr. + --quic-bpf-program-file= + Specify a path to eBPF program file reuseport_kern.o to + direct an incoming QUIC UDP datagram to a correct + socket. + Default: )" + << config->quic.bpf.prog_file << R"( + --frontend-quic-early-data + Enable early data on frontend QUIC connections. nghttpx + sends "Early-Data" header field to a backend server if a + request is received in early data and handshake has not + finished. All backend servers should deal with possibly + replayed requests. + --frontend-quic-qlog-dir= + Specify a directory where a qlog file is written for + frontend QUIC connections. A qlog file is created per + each QUIC connection. The file name is ISO8601 basic + format, followed by "-", server Source Connection ID and + ".sqlog". + --frontend-quic-require-token + Require an address validation token for a frontend QUIC + connection. Server sends a token in Retry packet or + NEW_TOKEN frame in the previous connection. + --frontend-quic-congestion-controller= + Specify a congestion controller algorithm for a frontend + QUIC connection. should be either "cubic" or + "bbr". + Default: )" + << (config->quic.upstream.congestion_controller == NGTCP2_CC_ALGO_CUBIC + ? "cubic" + : "bbr") + << R"( + --frontend-quic-secret-file= + Path to file that contains secure random data to be used + as QUIC keying materials. It is used to derive keys for + encrypting tokens and Connection IDs. It is not used to + encrypt QUIC packets. Each line of this file must + contain exactly 136 bytes hex-encoded string (when + decoded the byte string is 68 bytes long). The first 2 + bits of decoded byte string are used to identify the + keying material. An empty line or a line which starts + '#' is ignored. The file can contain more than one + keying materials. Because the identifier is 2 bits, at + most 4 keying materials are read and the remaining data + is discarded. The first keying material in the file is + primarily used for encryption and decryption for new + connection. The other ones are used to decrypt data for + the existing connections. Specifying multiple keying + materials enables key rotation. Please note that key + rotation does not occur automatically. User should + update files or change options values and restart + nghttpx gracefully. If opening or reading given file + fails, all loaded keying materials are discarded and it + is treated as if none of this option is given. If this + option is not given or an error occurred while opening + or reading a file, a keying material is generated + internally on startup and reload. + --quic-server-id= + Specify server ID encoded in Connection ID to identify + this particular server instance. Connection ID is + encrypted and this part is not visible in public. It + must be 4 bytes long and must be encoded in hex string + (which is 8 bytes long). If this option is omitted, a + random server ID is generated on startup and + configuration reload. + --frontend-quic-initial-rtt= + Specify the initial RTT of the frontend QUIC connection. + Default: )" + << util::duration_str(config->quic.upstream.initial_rtt) << R"( + --no-quic-bpf + Disable eBPF. + --frontend-http3-window-size= + Sets the per-stream initial window size of HTTP/3 + frontend connection. + Default: )" + << util::utos_unit(config->http3.upstream.window_size) << R"( + --frontend-http3-connection-window-size= + Sets the per-connection window size of HTTP/3 frontend + connection. + Default: )" + << util::utos_unit(config->http3.upstream.connection_window_size) << R"( + --frontend-http3-max-window-size= + Sets the maximum per-stream window size of HTTP/3 + frontend connection. The window size is adjusted based + on the receiving rate of stream data. The initial value + is the value specified by --frontend-http3-window-size + and the window size grows up to bytes. + Default: )" + << util::utos_unit(config->http3.upstream.max_window_size) << R"( + --frontend-http3-max-connection-window-size= + Sets the maximum per-connection window size of HTTP/3 + frontend connection. The window size is adjusted based + on the receiving rate of stream data. The initial value + is the value specified by + --frontend-http3-connection-window-size and the window + size grows up to bytes. + Default: )" + << util::utos_unit(config->http3.upstream.max_connection_window_size) + << R"( + --frontend-http3-max-concurrent-streams= + Set the maximum number of the concurrent streams in one + frontend HTTP/3 connection. + Default: )" + << config->http3.upstream.max_concurrent_streams << R"( +)"; +#endif // ENABLE_HTTP3 + + out << R"( +Misc: + --conf= + Load configuration from . Please note that + nghttpx always tries to read the default configuration + file if --conf is not given. + Default: )" + << config->conf_path << R"( + --include= + Load additional configurations from . File + is read when configuration parser encountered this + option. This option can be used multiple times, or even + recursively. + -v, --version + Print version and exit. + -h, --help Print this help and exit. + +-- + + The argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The argument is an integer and an optional unit (e.g., 1s + is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms + (hours, minutes, seconds and milliseconds, respectively). If a unit + is omitted, a second is used as unit.)" + << std::endl; +} +} // namespace + +namespace { +int process_options(Config *config, + std::vector> &cmdcfgs) { + std::array errbuf; + std::map pattern_addr_indexer; + if (conf_exists(config->conf_path.c_str())) { + LOG(NOTICE) << "Loading configuration from " << config->conf_path; + std::set include_set; + if (load_config(config, config->conf_path.c_str(), include_set, + pattern_addr_indexer) == -1) { + LOG(FATAL) << "Failed to load configuration from " << config->conf_path; + return -1; + } + assert(include_set.empty()); + } + + // Reopen log files using configurations in file + reopen_log_files(config->logging); + + { + std::set include_set; + + for (auto &p : cmdcfgs) { + if (parse_config(config, p.first, p.second, include_set, + pattern_addr_indexer) == -1) { + LOG(FATAL) << "Failed to parse command-line argument."; + return -1; + } + } + + assert(include_set.empty()); + } + + Log::set_severity_level(config->logging.severity); + + auto &loggingconf = config->logging; + + if (loggingconf.access.syslog || loggingconf.error.syslog) { + openlog("nghttpx", LOG_NDELAY | LOG_NOWAIT | LOG_PID, + loggingconf.syslog_facility); + } + + if (reopen_log_files(config->logging) != 0) { + LOG(FATAL) << "Failed to open log file"; + return -1; + } + + redirect_stderr_to_errorlog(loggingconf); + + if (config->uid != 0) { + if (log_config()->accesslog_fd != -1 && + fchown(log_config()->accesslog_fd, config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of access log file failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + if (log_config()->errorlog_fd != -1 && + fchown(log_config()->errorlog_fd, config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of error log file failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + + if (config->single_thread) { + LOG(WARN) << "single-thread: Set workers to 1"; + config->num_worker = 1; + } + + auto &http2conf = config->http2; + { + auto &dumpconf = http2conf.upstream.debug.dump; + + if (!dumpconf.request_header_file.empty()) { + auto path = dumpconf.request_header_file.c_str(); + auto f = open_file_for_write(path); + + if (f == nullptr) { + LOG(FATAL) << "Failed to open http2 upstream request header file: " + << path; + return -1; + } + + dumpconf.request_header = f; + + if (config->uid != 0) { + if (chown(path, config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of http2 upstream request header file " + << path << " failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + } + + if (!dumpconf.response_header_file.empty()) { + auto path = dumpconf.response_header_file.c_str(); + auto f = open_file_for_write(path); + + if (f == nullptr) { + LOG(FATAL) << "Failed to open http2 upstream response header file: " + << path; + return -1; + } + + dumpconf.response_header = f; + + if (config->uid != 0) { + if (chown(path, config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of http2 upstream response header file" + << " " << path << " failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + } + } + + auto &tlsconf = config->tls; + + if (tlsconf.npn_list.empty()) { + tlsconf.npn_list = util::split_str(DEFAULT_NPN_LIST, ','); + } + + if (!tlsconf.tls_proto_list.empty()) { + tlsconf.tls_proto_mask = tls::create_tls_proto_mask(tlsconf.tls_proto_list); + } + + // TODO We depends on the ordering of protocol version macro in + // OpenSSL. + if (tlsconf.min_proto_version > tlsconf.max_proto_version) { + LOG(ERROR) << "tls-max-proto-version must be equal to or larger than " + "tls-min-proto-version"; + return -1; + } + + if (tls::set_alpn_prefs(tlsconf.alpn_prefs, tlsconf.npn_list) != 0) { + return -1; + } + + tlsconf.bio_method = create_bio_method(); + + auto &listenerconf = config->conn.listener; + auto &upstreamconf = config->conn.upstream; + + if (listenerconf.addrs.empty()) { + UpstreamAddr addr{}; + addr.host = StringRef::from_lit("*"); + addr.port = 3000; + addr.tls = true; + addr.family = AF_INET; + addr.index = 0; + listenerconf.addrs.push_back(addr); + addr.family = AF_INET6; + addr.index = 1; + listenerconf.addrs.push_back(std::move(addr)); + } + + if (upstreamconf.worker_connections == 0) { + upstreamconf.worker_connections = std::numeric_limits::max(); + } + + if (tls::upstream_tls_enabled(config->conn) && + (tlsconf.private_key_file.empty() || tlsconf.cert_file.empty())) { + LOG(FATAL) << "TLS private key and certificate files are required. " + "Specify them in command-line, or in configuration file " + "using private-key-file and certificate-file options."; + return -1; + } + + if (tls::upstream_tls_enabled(config->conn) && !tlsconf.ocsp.disabled) { + struct stat buf; + if (stat(tlsconf.ocsp.fetch_ocsp_response_file.c_str(), &buf) != 0) { + tlsconf.ocsp.disabled = true; + LOG(WARN) << "--fetch-ocsp-response-file: " + << tlsconf.ocsp.fetch_ocsp_response_file + << " not found. OCSP stapling has been disabled."; + } + } + + if (configure_downstream_group(config, config->http2_proxy, false, tlsconf) != + 0) { + return -1; + } + + std::array hostport_buf; + + auto &proxy = config->downstream_http_proxy; + if (!proxy.host.empty()) { + auto hostport = util::make_hostport(std::begin(hostport_buf), + StringRef{proxy.host}, proxy.port); + if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port, + AF_UNSPEC) == -1) { + LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport; + return -1; + } + LOG(NOTICE) << "Backend HTTP proxy address: " << hostport << " -> " + << util::to_numeric_addr(&proxy.addr); + } + + { + auto &memcachedconf = tlsconf.session_cache.memcached; + if (!memcachedconf.host.empty()) { + auto hostport = util::make_hostport(std::begin(hostport_buf), + StringRef{memcachedconf.host}, + memcachedconf.port); + if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(), + memcachedconf.port, memcachedconf.family) == -1) { + LOG(FATAL) + << "Resolving memcached address for TLS session cache failed: " + << hostport; + return -1; + } + LOG(NOTICE) << "Memcached address for TLS session cache: " << hostport + << " -> " << util::to_numeric_addr(&memcachedconf.addr); + if (memcachedconf.tls) { + LOG(NOTICE) << "Connection to memcached for TLS session cache will be " + "encrypted by TLS"; + } + } + } + + { + auto &memcachedconf = tlsconf.ticket.memcached; + if (!memcachedconf.host.empty()) { + auto hostport = util::make_hostport(std::begin(hostport_buf), + StringRef{memcachedconf.host}, + memcachedconf.port); + if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(), + memcachedconf.port, memcachedconf.family) == -1) { + LOG(FATAL) << "Resolving memcached address for TLS ticket key failed: " + << hostport; + return -1; + } + LOG(NOTICE) << "Memcached address for TLS ticket key: " << hostport + << " -> " << util::to_numeric_addr(&memcachedconf.addr); + if (memcachedconf.tls) { + LOG(NOTICE) << "Connection to memcached for TLS ticket key will be " + "encrypted by TLS"; + } + } + } + + if (config->rlimit_nofile) { + struct rlimit lim = {static_cast(config->rlimit_nofile), + static_cast(config->rlimit_nofile)}; + if (setrlimit(RLIMIT_NOFILE, &lim) != 0) { + auto error = errno; + LOG(WARN) << "Setting rlimit-nofile failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + +#ifdef RLIMIT_MEMLOCK + if (config->rlimit_memlock) { + struct rlimit lim = {static_cast(config->rlimit_memlock), + static_cast(config->rlimit_memlock)}; + if (setrlimit(RLIMIT_MEMLOCK, &lim) != 0) { + auto error = errno; + LOG(WARN) << "Setting rlimit-memlock failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } +#endif // RLIMIT_MEMLOCK + + auto &fwdconf = config->http.forwarded; + + if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED && + fwdconf.by_obfuscated.empty()) { + // 2 for '_' and terminal NULL + auto iov = make_byte_ref(config->balloc, SHRPX_OBFUSCATED_NODE_LENGTH + 2); + auto p = iov.base; + *p++ = '_'; + auto gen = util::make_mt19937(); + p = util::random_alpha_digit(p, p + SHRPX_OBFUSCATED_NODE_LENGTH, gen); + *p = '\0'; + fwdconf.by_obfuscated = StringRef{iov.base, p}; + } + + if (config->http2.upstream.debug.frame_debug) { + // To make it sync to logging + set_output(stderr); + if (isatty(fileno(stdout))) { + set_color_output(true); + } + reset_timer(); + } + + config->http2.upstream.callbacks = create_http2_upstream_callbacks(); + config->http2.downstream.callbacks = create_http2_downstream_callbacks(); + + if (!config->http.altsvcs.empty()) { + config->http.altsvc_header_value = + http::create_altsvc_header_value(config->balloc, config->http.altsvcs); + } + + if (!config->http.http2_altsvcs.empty()) { + config->http.http2_altsvc_header_value = http::create_altsvc_header_value( + config->balloc, config->http.http2_altsvcs); + } + + return 0; +} +} // namespace + +namespace { +// Closes file descriptor which are opened for listeners in config, +// and are not inherited from |iaddrs|. +void close_not_inherited_fd(Config *config, + const std::vector &iaddrs) { + auto &listenerconf = config->conn.listener; + + for (auto &addr : listenerconf.addrs) { + auto inherited = std::find_if( + std::begin(iaddrs), std::end(iaddrs), + [&addr](const InheritedAddr &iaddr) { return addr.fd == iaddr.fd; }); + + if (inherited != std::end(iaddrs)) { + continue; + } + + close(addr.fd); + } +} +} // namespace + +namespace { +void reload_config() { + int rv; + + LOG(NOTICE) << "Reloading configuration"; + + auto cur_config = mod_config(); + auto new_config = std::make_unique(); + + fill_default_config(new_config.get()); + + new_config->conf_path = + make_string_ref(new_config->balloc, cur_config->conf_path); + // daemon option is ignored here. + new_config->daemon = cur_config->daemon; + // loop is reused, and ev_loop_flags gets ignored + new_config->ev_loop_flags = cur_config->ev_loop_flags; + new_config->config_revision = cur_config->config_revision + 1; + + rv = process_options(new_config.get(), suconfig.cmdcfgs); + if (rv != 0) { + LOG(ERROR) << "Failed to process new configuration"; + return; + } + + auto iaddrs = get_inherited_addr_from_config(new_config->balloc, cur_config); + + if (create_acceptor_socket(new_config.get(), iaddrs) != 0) { + close_not_inherited_fd(new_config.get(), iaddrs); + return; + } + + // According to libev documentation, flags are ignored since we have + // already created first default loop. + auto loop = ev_default_loop(new_config->ev_loop_flags); + + int ipc_fd = 0; +#ifdef ENABLE_HTTP3 + int quic_ipc_fd = 0; + + auto quic_lwps = collect_quic_lingering_worker_processes(); + + std::vector> cid_prefixes; + + if (generate_cid_prefix(cid_prefixes, new_config.get()) != 0) { + close_not_inherited_fd(new_config.get(), iaddrs); + return; + } +#endif // ENABLE_HTTP3 + + // fork_worker_process and forked child process assumes new + // configuration can be obtained from get_config(). + + auto old_config = replace_config(std::move(new_config)); + + auto pid = fork_worker_process(ipc_fd +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd +#endif // ENABLE_HTTP3 + + , + iaddrs +#ifdef ENABLE_HTTP3 + , + cid_prefixes, quic_lwps +#endif // ENABLE_HTTP3 + ); + + if (pid == -1) { + LOG(ERROR) << "Failed to process new configuration"; + + new_config = replace_config(std::move(old_config)); + close_not_inherited_fd(new_config.get(), iaddrs); + + return; + } + + close_unused_inherited_addr(iaddrs); + + worker_process_add(std::make_unique(loop, pid, ipc_fd +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd, cid_prefixes +#endif // ENABLE_HTTP3 + )); + + worker_process_adjust_limit(); + + if (!get_config()->pid_file.empty()) { + save_pid(); + } +} +} // namespace + +int main(int argc, char **argv) { + int rv; + std::array errbuf; + + nghttp2::tls::libssl_init(); + +#ifdef HAVE_LIBBPF + libbpf_set_strict_mode(LIBBPF_STRICT_ALL); +#endif // HAVE_LIBBPF + +#ifndef NOTHREADS + nghttp2::tls::LibsslGlobalLock lock; +#endif // NOTHREADS + + Log::set_severity_level(NOTICE); + create_config(); + fill_default_config(mod_config()); + + // make copy of stderr + store_original_fds(); + + // First open log files with default configuration, so that we can + // log errors/warnings while reading configuration files. + reopen_log_files(get_config()->logging); + + suconfig.original_argv = argv; + + // We have to copy argv, since getopt_long may change its content. + suconfig.argc = argc; + suconfig.argv = new char *[argc]; + + for (int i = 0; i < argc; ++i) { + suconfig.argv[i] = strdup(argv[i]); + if (suconfig.argv[i] == nullptr) { + auto error = errno; + LOG(FATAL) << "failed to copy argv: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + exit(EXIT_FAILURE); + } + } + + suconfig.cwd = getcwd(nullptr, 0); + if (suconfig.cwd == nullptr) { + auto error = errno; + LOG(FATAL) << "failed to get current working directory: errno=" << error; + exit(EXIT_FAILURE); + } + + auto &cmdcfgs = suconfig.cmdcfgs; + + while (1) { + static int flag = 0; + static constexpr option long_options[] = { + {SHRPX_OPT_DAEMON.c_str(), no_argument, nullptr, 'D'}, + {SHRPX_OPT_LOG_LEVEL.c_str(), required_argument, nullptr, 'L'}, + {SHRPX_OPT_BACKEND.c_str(), required_argument, nullptr, 'b'}, + {SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), required_argument, + nullptr, 'c'}, + {SHRPX_OPT_FRONTEND.c_str(), required_argument, nullptr, 'f'}, + {"help", no_argument, nullptr, 'h'}, + {SHRPX_OPT_INSECURE.c_str(), no_argument, nullptr, 'k'}, + {SHRPX_OPT_WORKERS.c_str(), required_argument, nullptr, 'n'}, + {SHRPX_OPT_CLIENT_PROXY.c_str(), no_argument, nullptr, 'p'}, + {SHRPX_OPT_HTTP2_PROXY.c_str(), no_argument, nullptr, 's'}, + {"version", no_argument, nullptr, 'v'}, + {SHRPX_OPT_FRONTEND_FRAME_DEBUG.c_str(), no_argument, nullptr, 'o'}, + {SHRPX_OPT_ADD_X_FORWARDED_FOR.c_str(), no_argument, &flag, 1}, + {SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT.c_str(), required_argument, + &flag, 2}, + {SHRPX_OPT_FRONTEND_READ_TIMEOUT.c_str(), required_argument, &flag, 3}, + {SHRPX_OPT_FRONTEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 4}, + {SHRPX_OPT_BACKEND_READ_TIMEOUT.c_str(), required_argument, &flag, 5}, + {SHRPX_OPT_BACKEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 6}, + {SHRPX_OPT_ACCESSLOG_FILE.c_str(), required_argument, &flag, 7}, + {SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, &flag, + 8}, + {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag, + 9}, + {SHRPX_OPT_PID_FILE.c_str(), required_argument, &flag, 10}, + {SHRPX_OPT_USER.c_str(), required_argument, &flag, 11}, + {"conf", required_argument, &flag, 12}, + {SHRPX_OPT_SYSLOG_FACILITY.c_str(), required_argument, &flag, 14}, + {SHRPX_OPT_BACKLOG.c_str(), required_argument, &flag, 15}, + {SHRPX_OPT_CIPHERS.c_str(), required_argument, &flag, 16}, + {SHRPX_OPT_CLIENT.c_str(), no_argument, &flag, 17}, + {SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag, + 18}, + {SHRPX_OPT_CACERT.c_str(), required_argument, &flag, 19}, + {SHRPX_OPT_BACKEND_IPV4.c_str(), no_argument, &flag, 20}, + {SHRPX_OPT_BACKEND_IPV6.c_str(), no_argument, &flag, 21}, + {SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE.c_str(), required_argument, &flag, + 22}, + {SHRPX_OPT_NO_VIA.c_str(), no_argument, &flag, 23}, + {SHRPX_OPT_SUBCERT.c_str(), required_argument, &flag, 24}, + {SHRPX_OPT_HTTP2_BRIDGE.c_str(), no_argument, &flag, 25}, + {SHRPX_OPT_BACKEND_HTTP_PROXY_URI.c_str(), required_argument, &flag, + 26}, + {SHRPX_OPT_BACKEND_NO_TLS.c_str(), no_argument, &flag, 27}, + {SHRPX_OPT_OCSP_STARTUP.c_str(), no_argument, &flag, 28}, + {SHRPX_OPT_FRONTEND_NO_TLS.c_str(), no_argument, &flag, 29}, + {SHRPX_OPT_NO_VERIFY_OCSP.c_str(), no_argument, &flag, 30}, + {SHRPX_OPT_BACKEND_TLS_SNI_FIELD.c_str(), required_argument, &flag, 31}, + {SHRPX_OPT_DH_PARAM_FILE.c_str(), required_argument, &flag, 33}, + {SHRPX_OPT_READ_RATE.c_str(), required_argument, &flag, 34}, + {SHRPX_OPT_READ_BURST.c_str(), required_argument, &flag, 35}, + {SHRPX_OPT_WRITE_RATE.c_str(), required_argument, &flag, 36}, + {SHRPX_OPT_WRITE_BURST.c_str(), required_argument, &flag, 37}, + {SHRPX_OPT_NPN_LIST.c_str(), required_argument, &flag, 38}, + {SHRPX_OPT_VERIFY_CLIENT.c_str(), no_argument, &flag, 39}, + {SHRPX_OPT_VERIFY_CLIENT_CACERT.c_str(), required_argument, &flag, 40}, + {SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE.c_str(), required_argument, &flag, + 41}, + {SHRPX_OPT_CLIENT_CERT_FILE.c_str(), required_argument, &flag, 42}, + {SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER.c_str(), + required_argument, &flag, 43}, + {SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER.c_str(), + required_argument, &flag, 44}, + {SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING.c_str(), no_argument, &flag, 45}, + {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(), + required_argument, &flag, 46}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(), + required_argument, &flag, 47}, + {SHRPX_OPT_TLS_PROTO_LIST.c_str(), required_argument, &flag, 48}, + {SHRPX_OPT_PADDING.c_str(), required_argument, &flag, 49}, + {SHRPX_OPT_WORKER_READ_RATE.c_str(), required_argument, &flag, 50}, + {SHRPX_OPT_WORKER_READ_BURST.c_str(), required_argument, &flag, 51}, + {SHRPX_OPT_WORKER_WRITE_RATE.c_str(), required_argument, &flag, 52}, + {SHRPX_OPT_WORKER_WRITE_BURST.c_str(), required_argument, &flag, 53}, + {SHRPX_OPT_ALTSVC.c_str(), required_argument, &flag, 54}, + {SHRPX_OPT_ADD_RESPONSE_HEADER.c_str(), required_argument, &flag, 55}, + {SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS.c_str(), required_argument, + &flag, 56}, + {SHRPX_OPT_ACCESSLOG_SYSLOG.c_str(), no_argument, &flag, 57}, + {SHRPX_OPT_ERRORLOG_FILE.c_str(), required_argument, &flag, 58}, + {SHRPX_OPT_ERRORLOG_SYSLOG.c_str(), no_argument, &flag, 59}, + {SHRPX_OPT_STREAM_READ_TIMEOUT.c_str(), required_argument, &flag, 60}, + {SHRPX_OPT_STREAM_WRITE_TIMEOUT.c_str(), required_argument, &flag, 61}, + {SHRPX_OPT_NO_LOCATION_REWRITE.c_str(), no_argument, &flag, 62}, + {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST.c_str(), + required_argument, &flag, 63}, + {SHRPX_OPT_LISTENER_DISABLE_TIMEOUT.c_str(), required_argument, &flag, + 64}, + {SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR.c_str(), no_argument, &flag, + 65}, + {SHRPX_OPT_ACCESSLOG_FORMAT.c_str(), required_argument, &flag, 66}, + {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND.c_str(), + required_argument, &flag, 67}, + {SHRPX_OPT_TLS_TICKET_KEY_FILE.c_str(), required_argument, &flag, 68}, + {SHRPX_OPT_RLIMIT_NOFILE.c_str(), required_argument, &flag, 69}, + {SHRPX_OPT_BACKEND_RESPONSE_BUFFER.c_str(), required_argument, &flag, + 71}, + {SHRPX_OPT_BACKEND_REQUEST_BUFFER.c_str(), required_argument, &flag, + 72}, + {SHRPX_OPT_NO_HOST_REWRITE.c_str(), no_argument, &flag, 73}, + {SHRPX_OPT_NO_SERVER_PUSH.c_str(), no_argument, &flag, 74}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER.c_str(), + required_argument, &flag, 76}, + {SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE.c_str(), required_argument, &flag, + 77}, + {SHRPX_OPT_OCSP_UPDATE_INTERVAL.c_str(), required_argument, &flag, 78}, + {SHRPX_OPT_NO_OCSP.c_str(), no_argument, &flag, 79}, + {SHRPX_OPT_HEADER_FIELD_BUFFER.c_str(), required_argument, &flag, 80}, + {SHRPX_OPT_MAX_HEADER_FIELDS.c_str(), required_argument, &flag, 81}, + {SHRPX_OPT_ADD_REQUEST_HEADER.c_str(), required_argument, &flag, 82}, + {SHRPX_OPT_INCLUDE.c_str(), required_argument, &flag, 83}, + {SHRPX_OPT_TLS_TICKET_KEY_CIPHER.c_str(), required_argument, &flag, 84}, + {SHRPX_OPT_HOST_REWRITE.c_str(), no_argument, &flag, 85}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED.c_str(), required_argument, + &flag, 86}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED.c_str(), required_argument, &flag, + 87}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL.c_str(), required_argument, + &flag, 88}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY.c_str(), + required_argument, &flag, 89}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL.c_str(), required_argument, + &flag, 90}, + {SHRPX_OPT_MRUBY_FILE.c_str(), required_argument, &flag, 91}, + {SHRPX_OPT_ACCEPT_PROXY_PROTOCOL.c_str(), no_argument, &flag, 93}, + {SHRPX_OPT_FASTOPEN.c_str(), required_argument, &flag, 94}, + {SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD.c_str(), required_argument, + &flag, 95}, + {SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT.c_str(), required_argument, &flag, + 96}, + {SHRPX_OPT_ADD_FORWARDED.c_str(), required_argument, &flag, 97}, + {SHRPX_OPT_STRIP_INCOMING_FORWARDED.c_str(), no_argument, &flag, 98}, + {SHRPX_OPT_FORWARDED_BY.c_str(), required_argument, &flag, 99}, + {SHRPX_OPT_FORWARDED_FOR.c_str(), required_argument, &flag, 100}, + {SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER.c_str(), required_argument, + &flag, 101}, + {SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS.c_str(), required_argument, &flag, + 102}, + {SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument, &flag, 103}, + {SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER.c_str(), required_argument, + &flag, 104}, + {SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS.c_str(), required_argument, &flag, + 105}, + {SHRPX_OPT_BACKEND_HTTP1_TLS.c_str(), no_argument, &flag, 106}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS.c_str(), no_argument, &flag, + 108}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE.c_str(), + required_argument, &flag, 109}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE.c_str(), + required_argument, &flag, 110}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS.c_str(), no_argument, &flag, + 111}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE.c_str(), + required_argument, &flag, 112}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE.c_str(), + required_argument, &flag, 113}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY.c_str(), + required_argument, &flag, 114}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY.c_str(), + required_argument, &flag, 115}, + {SHRPX_OPT_BACKEND_ADDRESS_FAMILY.c_str(), required_argument, &flag, + 116}, + {SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 117}, + {SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 118}, + {SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND.c_str(), required_argument, + &flag, 119}, + {SHRPX_OPT_BACKEND_TLS.c_str(), no_argument, &flag, 120}, + {SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST.c_str(), required_argument, + &flag, 121}, + {SHRPX_OPT_ERROR_PAGE.c_str(), required_argument, &flag, 122}, + {SHRPX_OPT_NO_KQUEUE.c_str(), no_argument, &flag, 123}, + {SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument, + &flag, 124}, + {SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument, + &flag, 125}, + {SHRPX_OPT_API_MAX_REQUEST_BODY.c_str(), required_argument, &flag, 126}, + {SHRPX_OPT_BACKEND_MAX_BACKOFF.c_str(), required_argument, &flag, 127}, + {SHRPX_OPT_SERVER_NAME.c_str(), required_argument, &flag, 128}, + {SHRPX_OPT_NO_SERVER_REWRITE.c_str(), no_argument, &flag, 129}, + {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE.c_str(), + no_argument, &flag, 130}, + {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE.c_str(), no_argument, + &flag, 131}, + {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag, + 132}, + {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 133}, + {SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag, + 134}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 135}, + {SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 136}, + {SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 137}, + {SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 138}, + {SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 139}, + {SHRPX_OPT_ECDH_CURVES.c_str(), required_argument, &flag, 140}, + {SHRPX_OPT_TLS_SCT_DIR.c_str(), required_argument, &flag, 141}, + {SHRPX_OPT_BACKEND_CONNECT_TIMEOUT.c_str(), required_argument, &flag, + 142}, + {SHRPX_OPT_DNS_CACHE_TIMEOUT.c_str(), required_argument, &flag, 143}, + {SHRPX_OPT_DNS_LOOKUP_TIMEOUT.c_str(), required_argument, &flag, 144}, + {SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145}, + {SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, + &flag, 146}, + {SHRPX_OPT_PSK_SECRETS.c_str(), required_argument, &flag, 147}, + {SHRPX_OPT_CLIENT_PSK_SECRETS.c_str(), required_argument, &flag, 148}, + {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument, + &flag, 149}, + {SHRPX_OPT_CLIENT_CIPHERS.c_str(), required_argument, &flag, 150}, + {SHRPX_OPT_ACCESSLOG_WRITE_EARLY.c_str(), no_argument, &flag, 151}, + {SHRPX_OPT_TLS_MIN_PROTO_VERSION.c_str(), required_argument, &flag, + 152}, + {SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag, + 153}, + {SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154}, + {SHRPX_OPT_FRONTEND_MAX_REQUESTS.c_str(), required_argument, &flag, + 155}, + {SHRPX_OPT_SINGLE_THREAD.c_str(), no_argument, &flag, 156}, + {SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO.c_str(), no_argument, &flag, 157}, + {SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO.c_str(), no_argument, + &flag, 158}, + {SHRPX_OPT_SINGLE_PROCESS.c_str(), no_argument, &flag, 159}, + {SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED.c_str(), no_argument, &flag, + 160}, + {SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR.c_str(), no_argument, &flag, + 161}, + {SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA.c_str(), no_argument, &flag, 162}, + {SHRPX_OPT_TLS_MAX_EARLY_DATA.c_str(), required_argument, &flag, 163}, + {SHRPX_OPT_TLS13_CIPHERS.c_str(), required_argument, &flag, 164}, + {SHRPX_OPT_TLS13_CLIENT_CIPHERS.c_str(), required_argument, &flag, 165}, + {SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA.c_str(), no_argument, &flag, + 166}, + {SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument, &flag, 167}, + {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument, + &flag, 168}, + {SHRPX_OPT_QUIC_BPF_PROGRAM_FILE.c_str(), required_argument, &flag, + 169}, + {SHRPX_OPT_NO_QUIC_BPF.c_str(), no_argument, &flag, 170}, + {SHRPX_OPT_HTTP2_ALTSVC.c_str(), required_argument, &flag, 171}, + {SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT.c_str(), required_argument, + &flag, 172}, + {SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT.c_str(), required_argument, &flag, + 173}, + {SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG.c_str(), no_argument, &flag, 174}, + {SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE.c_str(), required_argument, &flag, + 175}, + {SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 176}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE.c_str(), required_argument, + &flag, 177}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 178}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 179}, + {SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180}, + {SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR.c_str(), required_argument, &flag, + 181}, + {SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN.c_str(), no_argument, &flag, + 182}, + {SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER.c_str(), + required_argument, &flag, 183}, + {SHRPX_OPT_QUIC_SERVER_ID.c_str(), required_argument, &flag, 185}, + {SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE.c_str(), required_argument, &flag, + 186}, + {SHRPX_OPT_RLIMIT_MEMLOCK.c_str(), required_argument, &flag, 187}, + {SHRPX_OPT_MAX_WORKER_PROCESSES.c_str(), required_argument, &flag, 188}, + {SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD.c_str(), + required_argument, &flag, 189}, + {SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT.c_str(), required_argument, &flag, + 190}, + {SHRPX_OPT_REQUIRE_HTTP_SCHEME.c_str(), no_argument, &flag, 191}, + {SHRPX_OPT_TLS_KTLS.c_str(), no_argument, &flag, 192}, + {nullptr, 0, nullptr, 0}}; + + int option_index = 0; + int c = getopt_long(argc, argv, "DL:b:c:f:hkn:opsv", long_options, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'D': + cmdcfgs.emplace_back(SHRPX_OPT_DAEMON, StringRef::from_lit("yes")); + break; + case 'L': + cmdcfgs.emplace_back(SHRPX_OPT_LOG_LEVEL, StringRef{optarg}); + break; + case 'b': + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND, StringRef{optarg}); + break; + case 'c': + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 'f': + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND, StringRef{optarg}); + break; + case 'h': + print_help(std::cout); + exit(EXIT_SUCCESS); + case 'k': + cmdcfgs.emplace_back(SHRPX_OPT_INSECURE, StringRef::from_lit("yes")); + break; + case 'n': + cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, StringRef{optarg}); + break; + case 'o': + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_FRAME_DEBUG, + StringRef::from_lit("yes")); + break; + case 'p': + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PROXY, StringRef::from_lit("yes")); + break; + case 's': + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_PROXY, StringRef::from_lit("yes")); + break; + case 'v': + print_version(std::cout); + exit(EXIT_SUCCESS); + case '?': + util::show_candidates(argv[optind - 1], long_options); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // --add-x-forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_ADD_X_FORWARDED_FOR, + StringRef::from_lit("yes")); + break; + case 2: + // --frontend-http2-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT, + StringRef{optarg}); + break; + case 3: + // --frontend-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_READ_TIMEOUT, + StringRef{optarg}); + break; + case 4: + // --frontend-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_WRITE_TIMEOUT, + StringRef{optarg}); + break; + case 5: + // --backend-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_READ_TIMEOUT, StringRef{optarg}); + break; + case 6: + // --backend-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_WRITE_TIMEOUT, + StringRef{optarg}); + break; + case 7: + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FILE, StringRef{optarg}); + break; + case 8: + // --backend-keep-alive-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT, + StringRef{optarg}); + break; + case 9: + // --frontend-http2-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS, + StringRef{optarg}); + break; + case 10: + cmdcfgs.emplace_back(SHRPX_OPT_PID_FILE, StringRef{optarg}); + break; + case 11: + cmdcfgs.emplace_back(SHRPX_OPT_USER, StringRef{optarg}); + break; + case 12: + // --conf + mod_config()->conf_path = + make_string_ref(mod_config()->balloc, StringRef{optarg}); + break; + case 14: + // --syslog-facility + cmdcfgs.emplace_back(SHRPX_OPT_SYSLOG_FACILITY, StringRef{optarg}); + break; + case 15: + // --backlog + cmdcfgs.emplace_back(SHRPX_OPT_BACKLOG, StringRef{optarg}); + break; + case 16: + // --ciphers + cmdcfgs.emplace_back(SHRPX_OPT_CIPHERS, StringRef{optarg}); + break; + case 17: + // --client + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT, StringRef::from_lit("yes")); + break; + case 18: + // --backend-http2-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS, + StringRef{optarg}); + break; + case 19: + // --cacert + cmdcfgs.emplace_back(SHRPX_OPT_CACERT, StringRef{optarg}); + break; + case 20: + // --backend-ipv4 + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV4, + StringRef::from_lit("yes")); + break; + case 21: + // --backend-ipv6 + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV6, + StringRef::from_lit("yes")); + break; + case 22: + // --private-key-passwd-file + cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE, + StringRef{optarg}); + break; + case 23: + // --no-via + cmdcfgs.emplace_back(SHRPX_OPT_NO_VIA, StringRef::from_lit("yes")); + break; + case 24: + // --subcert + cmdcfgs.emplace_back(SHRPX_OPT_SUBCERT, StringRef{optarg}); + break; + case 25: + // --http2-bridge + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_BRIDGE, + StringRef::from_lit("yes")); + break; + case 26: + // --backend-http-proxy-uri + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP_PROXY_URI, + StringRef{optarg}); + break; + case 27: + // --backend-no-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_NO_TLS, + StringRef::from_lit("yes")); + break; + case 28: + // --ocsp-startup + cmdcfgs.emplace_back(SHRPX_OPT_OCSP_STARTUP, + StringRef::from_lit("yes")); + break; + case 29: + // --frontend-no-tls + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_NO_TLS, + StringRef::from_lit("yes")); + break; + case 30: + // --no-verify-ocsp + cmdcfgs.emplace_back(SHRPX_OPT_NO_VERIFY_OCSP, + StringRef::from_lit("yes")); + break; + case 31: + // --backend-tls-sni-field + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS_SNI_FIELD, + StringRef{optarg}); + break; + case 33: + // --dh-param-file + cmdcfgs.emplace_back(SHRPX_OPT_DH_PARAM_FILE, StringRef{optarg}); + break; + case 34: + // --read-rate + cmdcfgs.emplace_back(SHRPX_OPT_READ_RATE, StringRef{optarg}); + break; + case 35: + // --read-burst + cmdcfgs.emplace_back(SHRPX_OPT_READ_BURST, StringRef{optarg}); + break; + case 36: + // --write-rate + cmdcfgs.emplace_back(SHRPX_OPT_WRITE_RATE, StringRef{optarg}); + break; + case 37: + // --write-burst + cmdcfgs.emplace_back(SHRPX_OPT_WRITE_BURST, StringRef{optarg}); + break; + case 38: + // --npn-list + cmdcfgs.emplace_back(SHRPX_OPT_NPN_LIST, StringRef{optarg}); + break; + case 39: + // --verify-client + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT, + StringRef::from_lit("yes")); + break; + case 40: + // --verify-client-cacert + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_CACERT, StringRef{optarg}); + break; + case 41: + // --client-private-key-file + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 42: + // --client-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CERT_FILE, StringRef{optarg}); + break; + case 43: + // --frontend-http2-dump-request-header + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER, + StringRef{optarg}); + break; + case 44: + // --frontend-http2-dump-response-header + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER, + StringRef{optarg}); + break; + case 45: + // --http2-no-cookie-crumbling + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING, + StringRef::from_lit("yes")); + break; + case 46: + // --frontend-http2-connection-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS, + StringRef{optarg}); + break; + case 47: + // --backend-http2-connection-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS, + StringRef{optarg}); + break; + case 48: + // --tls-proto-list + cmdcfgs.emplace_back(SHRPX_OPT_TLS_PROTO_LIST, StringRef{optarg}); + break; + case 49: + // --padding + cmdcfgs.emplace_back(SHRPX_OPT_PADDING, StringRef{optarg}); + break; + case 50: + // --worker-read-rate + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_RATE, StringRef{optarg}); + break; + case 51: + // --worker-read-burst + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_BURST, StringRef{optarg}); + break; + case 52: + // --worker-write-rate + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_RATE, StringRef{optarg}); + break; + case 53: + // --worker-write-burst + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_BURST, StringRef{optarg}); + break; + case 54: + // --altsvc + cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, StringRef{optarg}); + break; + case 55: + // --add-response-header + cmdcfgs.emplace_back(SHRPX_OPT_ADD_RESPONSE_HEADER, StringRef{optarg}); + break; + case 56: + // --worker-frontend-connections + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS, + StringRef{optarg}); + break; + case 57: + // --accesslog-syslog + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_SYSLOG, + StringRef::from_lit("yes")); + break; + case 58: + // --errorlog-file + cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_FILE, StringRef{optarg}); + break; + case 59: + // --errorlog-syslog + cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_SYSLOG, + StringRef::from_lit("yes")); + break; + case 60: + // --stream-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_STREAM_READ_TIMEOUT, StringRef{optarg}); + break; + case 61: + // --stream-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_STREAM_WRITE_TIMEOUT, StringRef{optarg}); + break; + case 62: + // --no-location-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_LOCATION_REWRITE, + StringRef::from_lit("yes")); + break; + case 63: + // --backend-http1-connections-per-host + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST, + StringRef{optarg}); + break; + case 64: + // --listener-disable-timeout + cmdcfgs.emplace_back(SHRPX_OPT_LISTENER_DISABLE_TIMEOUT, + StringRef{optarg}); + break; + case 65: + // --strip-incoming-x-forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR, + StringRef::from_lit("yes")); + break; + case 66: + // --accesslog-format + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, StringRef{optarg}); + break; + case 67: + // --backend-http1-connections-per-frontend + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND, + StringRef{optarg}); + break; + case 68: + // --tls-ticket-key-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_FILE, StringRef{optarg}); + break; + case 69: + // --rlimit-nofile + cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_NOFILE, StringRef{optarg}); + break; + case 71: + // --backend-response-buffer + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_RESPONSE_BUFFER, + StringRef{optarg}); + break; + case 72: + // --backend-request-buffer + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_REQUEST_BUFFER, + StringRef{optarg}); + break; + case 73: + // --no-host-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_HOST_REWRITE, + StringRef::from_lit("yes")); + break; + case 74: + // --no-server-push + cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_PUSH, + StringRef::from_lit("yes")); + break; + case 76: + // --backend-http2-connections-per-worker + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER, + StringRef{optarg}); + break; + case 77: + // --fetch-ocsp-response-file + cmdcfgs.emplace_back(SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE, + StringRef{optarg}); + break; + case 78: + // --ocsp-update-interval + cmdcfgs.emplace_back(SHRPX_OPT_OCSP_UPDATE_INTERVAL, StringRef{optarg}); + break; + case 79: + // --no-ocsp + cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, StringRef::from_lit("yes")); + break; + case 80: + // --header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_HEADER_FIELD_BUFFER, StringRef{optarg}); + break; + case 81: + // --max-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_HEADER_FIELDS, StringRef{optarg}); + break; + case 82: + // --add-request-header + cmdcfgs.emplace_back(SHRPX_OPT_ADD_REQUEST_HEADER, StringRef{optarg}); + break; + case 83: + // --include + cmdcfgs.emplace_back(SHRPX_OPT_INCLUDE, StringRef{optarg}); + break; + case 84: + // --tls-ticket-key-cipher + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_CIPHER, + StringRef{optarg}); + break; + case 85: + // --host-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_HOST_REWRITE, + StringRef::from_lit("yes")); + break; + case 86: + // --tls-session-cache-memcached + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED, + StringRef{optarg}); + break; + case 87: + // --tls-ticket-key-memcached + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED, + StringRef{optarg}); + break; + case 88: + // --tls-ticket-key-memcached-interval + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL, + StringRef{optarg}); + break; + case 89: + // --tls-ticket-key-memcached-max-retry + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY, + StringRef{optarg}); + break; + case 90: + // --tls-ticket-key-memcached-max-fail + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, + StringRef{optarg}); + break; + case 91: + // --mruby-file + cmdcfgs.emplace_back(SHRPX_OPT_MRUBY_FILE, StringRef{optarg}); + break; + case 93: + // --accept-proxy-protocol + cmdcfgs.emplace_back(SHRPX_OPT_ACCEPT_PROXY_PROTOCOL, + StringRef::from_lit("yes")); + break; + case 94: + // --fastopen + cmdcfgs.emplace_back(SHRPX_OPT_FASTOPEN, StringRef{optarg}); + break; + case 95: + // --tls-dyn-rec-warmup-threshold + cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD, + StringRef{optarg}); + break; + case 96: + // --tls-dyn-rec-idle-timeout + cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT, + StringRef{optarg}); + break; + case 97: + // --add-forwarded + cmdcfgs.emplace_back(SHRPX_OPT_ADD_FORWARDED, StringRef{optarg}); + break; + case 98: + // --strip-incoming-forwarded + cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_FORWARDED, + StringRef::from_lit("yes")); + break; + case 99: + // --forwarded-by + cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_BY, StringRef{optarg}); + break; + case 100: + // --forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_FOR, StringRef{optarg}); + break; + case 101: + // --response-header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER, + StringRef{optarg}); + break; + case 102: + // --max-response-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS, + StringRef{optarg}); + break; + case 103: + // --no-http2-cipher-black-list + cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST, + StringRef::from_lit("yes")); + break; + case 104: + // --request-header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER, + StringRef{optarg}); + break; + case 105: + // --max-request-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS, + StringRef{optarg}); + break; + case 106: + // --backend-http1-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_TLS, + StringRef::from_lit("yes")); + break; + case 108: + // --tls-session-cache-memcached-tls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS, + StringRef::from_lit("yes")); + break; + case 109: + // --tls-session-cache-memcached-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE, + StringRef{optarg}); + break; + case 110: + // --tls-session-cache-memcached-private-key-file + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 111: + // --tls-ticket-key-memcached-tls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS, + StringRef::from_lit("yes")); + break; + case 112: + // --tls-ticket-key-memcached-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE, + StringRef{optarg}); + break; + case 113: + // --tls-ticket-key-memcached-private-key-file + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 114: + // --tls-ticket-key-memcached-address-family + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 115: + // --tls-session-cache-memcached-address-family + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 116: + // --backend-address-family + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 117: + // --frontend-http2-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 118: + // --backend-http2-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 119: + // --backend-connections-per-frontend + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND, + StringRef{optarg}); + break; + case 120: + // --backend-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS, StringRef::from_lit("yes")); + break; + case 121: + // --backend-connections-per-host + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST, + StringRef{optarg}); + break; + case 122: + // --error-page + cmdcfgs.emplace_back(SHRPX_OPT_ERROR_PAGE, StringRef{optarg}); + break; + case 123: + // --no-kqueue + cmdcfgs.emplace_back(SHRPX_OPT_NO_KQUEUE, StringRef::from_lit("yes")); + break; + case 124: + // --frontend-http2-settings-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT, + StringRef{optarg}); + break; + case 125: + // --backend-http2-settings-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT, + StringRef{optarg}); + break; + case 126: + // --api-max-request-body + cmdcfgs.emplace_back(SHRPX_OPT_API_MAX_REQUEST_BODY, StringRef{optarg}); + break; + case 127: + // --backend-max-backoff + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_MAX_BACKOFF, StringRef{optarg}); + break; + case 128: + // --server-name + cmdcfgs.emplace_back(SHRPX_OPT_SERVER_NAME, StringRef{optarg}); + break; + case 129: + // --no-server-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_REWRITE, + StringRef::from_lit("yes")); + break; + case 130: + // --frontend-http2-optimize-write-buffer-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE, + StringRef::from_lit("yes")); + break; + case 131: + // --frontend-http2-optimize-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE, + StringRef::from_lit("yes")); + break; + case 132: + // --frontend-http2-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE, + StringRef{optarg}); + break; + case 133: + // --frontend-http2-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 134: + // --backend-http2-window-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE, + StringRef{optarg}); + break; + case 135: + // --backend-http2-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 136: + // --frontend-http2-encoder-dynamic-table-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 137: + // --frontend-http2-decoder-dynamic-table-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 138: + // --backend-http2-encoder-dynamic-table-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 139: + // --backend-http2-decoder-dynamic-table-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 140: + // --ecdh-curves + cmdcfgs.emplace_back(SHRPX_OPT_ECDH_CURVES, StringRef{optarg}); + break; + case 141: + // --tls-sct-dir + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SCT_DIR, StringRef{optarg}); + break; + case 142: + // --backend-connect-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECT_TIMEOUT, + StringRef{optarg}); + break; + case 143: + // --dns-cache-timeout + cmdcfgs.emplace_back(SHRPX_OPT_DNS_CACHE_TIMEOUT, StringRef{optarg}); + break; + case 144: + // --dns-lookup-timeou + cmdcfgs.emplace_back(SHRPX_OPT_DNS_LOOKUP_TIMEOUT, StringRef{optarg}); + break; + case 145: + // --dns-max-try + cmdcfgs.emplace_back(SHRPX_OPT_DNS_MAX_TRY, StringRef{optarg}); + break; + case 146: + // --frontend-keep-alive-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT, + StringRef{optarg}); + break; + case 147: + // --psk-secrets + cmdcfgs.emplace_back(SHRPX_OPT_PSK_SECRETS, StringRef{optarg}); + break; + case 148: + // --client-psk-secrets + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PSK_SECRETS, StringRef{optarg}); + break; + case 149: + // --client-no-http2-cipher-black-list + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST, + StringRef::from_lit("yes")); + break; + case 150: + // --client-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CIPHERS, StringRef{optarg}); + break; + case 151: + // --accesslog-write-early + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_WRITE_EARLY, + StringRef::from_lit("yes")); + break; + case 152: + // --tls-min-proto-version + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MIN_PROTO_VERSION, + StringRef{optarg}); + break; + case 153: + // --tls-max-proto-version + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION, + StringRef{optarg}); + break; + case 154: + // --redirect-https-port + cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg}); + break; + case 155: + // --frontend-max-requests + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_MAX_REQUESTS, + StringRef{optarg}); + break; + case 156: + // --single-thread + cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_THREAD, + StringRef::from_lit("yes")); + break; + case 157: + // --no-add-x-forwarded-proto + cmdcfgs.emplace_back(SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO, + StringRef::from_lit("yes")); + break; + case 158: + // --no-strip-incoming-x-forwarded-proto + cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO, + StringRef::from_lit("yes")); + break; + case 159: + // --single-process + cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_PROCESS, + StringRef::from_lit("yes")); + break; + case 160: + // --verify-client-tolerate-expired + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED, + StringRef::from_lit("yes")); + break; + case 161: + // --ignore-per-pattern-mruby-error + cmdcfgs.emplace_back(SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR, + StringRef::from_lit("yes")); + break; + case 162: + // --tls-no-postpone-early-data + cmdcfgs.emplace_back(SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 163: + // --tls-max-early-data + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_EARLY_DATA, StringRef{optarg}); + break; + case 164: + // --tls13-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CIPHERS, StringRef{optarg}); + break; + case 165: + // --tls13-client-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CLIENT_CIPHERS, StringRef{optarg}); + break; + case 166: + // --no-strip-incoming-early-data + cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 167: + // --no-http2-cipher-block-list + cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST, + StringRef::from_lit("yes")); + break; + case 168: + // --client-no-http2-cipher-block-list + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST, + StringRef::from_lit("yes")); + break; + case 169: + // --quic-bpf-program-file + cmdcfgs.emplace_back(SHRPX_OPT_QUIC_BPF_PROGRAM_FILE, + StringRef{optarg}); + break; + case 170: + // --no-quic-bpf + cmdcfgs.emplace_back(SHRPX_OPT_NO_QUIC_BPF, StringRef::from_lit("yes")); + break; + case 171: + // --http2-altsvc + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_ALTSVC, StringRef{optarg}); + break; + case 172: + // --frontend-http3-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT, + StringRef{optarg}); + break; + case 173: + // --frontend-quic-idle-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT, + StringRef{optarg}); + break; + case 174: + // --frontend-quic-debug-log + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG, + StringRef::from_lit("yes")); + break; + case 175: + // --frontend-http3-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE, + StringRef{optarg}); + break; + case 176: + // --frontend-http3-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 177: + // --frontend-http3-max-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE, + StringRef{optarg}); + break; + case 178: + // --frontend-http3-max-connection-window-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 179: + // --frontend-http3-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 180: + // --frontend-quic-early-data + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 181: + // --frontend-quic-qlog-dir + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR, + StringRef{optarg}); + break; + case 182: + // --frontend-quic-require-token + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN, + StringRef::from_lit("yes")); + break; + case 183: + // --frontend-quic-congestion-controller + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER, + StringRef{optarg}); + break; + case 185: + // --quic-server-id + cmdcfgs.emplace_back(SHRPX_OPT_QUIC_SERVER_ID, StringRef{optarg}); + break; + case 186: + // --frontend-quic-secret-file + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE, + StringRef{optarg}); + break; + case 187: + // --rlimit-memlock + cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_MEMLOCK, StringRef{optarg}); + break; + case 188: + // --max-worker-processes + cmdcfgs.emplace_back(SHRPX_OPT_MAX_WORKER_PROCESSES, StringRef{optarg}); + break; + case 189: + // --worker-process-grace-shutdown-period + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD, + StringRef{optarg}); + break; + case 190: + // --frontend-quic-initial-rtt + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT, + StringRef{optarg}); + break; + case 191: + // --require-http-scheme + cmdcfgs.emplace_back(SHRPX_OPT_REQUIRE_HTTP_SCHEME, + StringRef::from_lit("yes")); + break; + case 192: + // --tls-ktls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_KTLS, StringRef::from_lit("yes")); + break; + default: + break; + } + break; + default: + break; + } + } + + if (argc - optind >= 2) { + cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_FILE, StringRef{argv[optind++]}); + cmdcfgs.emplace_back(SHRPX_OPT_CERTIFICATE_FILE, StringRef{argv[optind++]}); + } + + rv = process_options(mod_config(), cmdcfgs); + if (rv != 0) { + return -1; + } + + if (event_loop() != 0) { + return -1; + } + + LOG(NOTICE) << "Shutdown momentarily"; + + delete_log_config(); + + return 0; +} + +} // namespace shrpx + +int main(int argc, char **argv) { return run_app(shrpx::main, argc, argv); } diff --git a/lib/nghttp2/src/shrpx.h b/lib/nghttp2/src/shrpx.h new file mode 100644 index 00000000000..d881ef5d9d4 --- /dev/null +++ b/lib/nghttp2/src/shrpx.h @@ -0,0 +1,58 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_H +#define SHRPX_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H + +#include + +#ifndef HAVE__EXIT +# define nghttp2_Exit(status) _exit(status) +#else // HAVE__EXIT +# define nghttp2_Exit(status) _Exit(status) +#endif // HAVE__EXIT + +#define DIE() nghttp2_Exit(EXIT_FAILURE) + +#if defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS +inline int initgroups(const char *user, gid_t group) { return 0; } +#endif // defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS + +#ifndef HAVE_BPF_STATS_TYPE +/* Newer kernel should have this defined in linux/bpf.h */ +enum bpf_stats_type { + BPF_STATS_RUN_TIME = 0, +}; +#endif // !HAVE_BPF_STATS_TYPE + +#endif // SHRPX_H diff --git a/lib/nghttp2/src/shrpx_accept_handler.cc b/lib/nghttp2/src/shrpx_accept_handler.cc new file mode 100644 index 00000000000..01b641580c1 --- /dev/null +++ b/lib/nghttp2/src/shrpx_accept_handler.cc @@ -0,0 +1,111 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "shrpx_accept_handler.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H + +#include + +#include "shrpx_connection_handler.h" +#include "shrpx_config.h" +#include "shrpx_log.h" +#include "util.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +void acceptcb(struct ev_loop *loop, ev_io *w, int revent) { + auto h = static_cast(w->data); + h->accept_connection(); +} +} // namespace + +AcceptHandler::AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h) + : conn_hnr_(h), faddr_(faddr) { + ev_io_init(&wev_, acceptcb, faddr_->fd, EV_READ); + wev_.data = this; + ev_io_start(conn_hnr_->get_loop(), &wev_); +} + +AcceptHandler::~AcceptHandler() { + ev_io_stop(conn_hnr_->get_loop(), &wev_); + close(faddr_->fd); +} + +void AcceptHandler::accept_connection() { + sockaddr_union sockaddr; + socklen_t addrlen = sizeof(sockaddr); + +#ifdef HAVE_ACCEPT4 + auto cfd = + accept4(faddr_->fd, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC); +#else // !HAVE_ACCEPT4 + auto cfd = accept(faddr_->fd, &sockaddr.sa, &addrlen); +#endif // !HAVE_ACCEPT4 + + if (cfd == -1) { + switch (errno) { + case EINTR: + case ENETDOWN: + case EPROTO: + case ENOPROTOOPT: + case EHOSTDOWN: +#ifdef ENONET + case ENONET: +#endif // ENONET + case EHOSTUNREACH: + case EOPNOTSUPP: + case ENETUNREACH: + return; + case EMFILE: + case ENFILE: + LOG(WARN) << "acceptor: running out file descriptor; disable acceptor " + "temporarily"; + conn_hnr_->sleep_acceptor(get_config()->conn.listener.timeout.sleep); + return; + default: + return; + } + } + +#ifndef HAVE_ACCEPT4 + util::make_socket_nonblocking(cfd); + util::make_socket_closeonexec(cfd); +#endif // !HAVE_ACCEPT4 + + conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen, faddr_); +} + +void AcceptHandler::enable() { ev_io_start(conn_hnr_->get_loop(), &wev_); } + +void AcceptHandler::disable() { ev_io_stop(conn_hnr_->get_loop(), &wev_); } + +int AcceptHandler::get_fd() const { return faddr_->fd; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_accept_handler.h b/lib/nghttp2/src/shrpx_accept_handler.h new file mode 100644 index 00000000000..853e9a2b80f --- /dev/null +++ b/lib/nghttp2/src/shrpx_accept_handler.h @@ -0,0 +1,54 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 SHRPX_ACCEPT_HANDLER_H +#define SHRPX_ACCEPT_HANDLER_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +class ConnectionHandler; +struct UpstreamAddr; + +class AcceptHandler { +public: + AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h); + ~AcceptHandler(); + void accept_connection(); + void enable(); + void disable(); + int get_fd() const; + +private: + ev_io wev_; + ConnectionHandler *conn_hnr_; + const UpstreamAddr *faddr_; +}; + +} // namespace shrpx + +#endif // SHRPX_ACCEPT_HANDLER_H diff --git a/lib/nghttp2/src/shrpx_api_downstream_connection.cc b/lib/nghttp2/src/shrpx_api_downstream_connection.cc new file mode 100644 index 00000000000..254ab59e2bb --- /dev/null +++ b/lib/nghttp2/src/shrpx_api_downstream_connection.cc @@ -0,0 +1,478 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_api_downstream_connection.h" + +#include +#include +#include +#include + +#include "shrpx_client_handler.h" +#include "shrpx_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_worker.h" +#include "shrpx_connection_handler.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +// List of API endpoints +const std::array &apis() { + static const auto apis = new std::array{ + APIEndpoint{ + StringRef::from_lit("/api/v1beta1/backendconfig"), + true, + (1 << API_METHOD_POST) | (1 << API_METHOD_PUT), + &APIDownstreamConnection::handle_backendconfig, + }, + APIEndpoint{ + StringRef::from_lit("/api/v1beta1/configrevision"), + true, + (1 << API_METHOD_GET), + &APIDownstreamConnection::handle_configrevision, + }, + }; + + return *apis; +} +} // namespace + +namespace { +// The method string. This must be same order of APIMethod. +constexpr StringRef API_METHOD_STRING[] = { + StringRef::from_lit("GET"), + StringRef::from_lit("POST"), + StringRef::from_lit("PUT"), +}; +} // namespace + +APIDownstreamConnection::APIDownstreamConnection(Worker *worker) + : worker_(worker), api_(nullptr), fd_(-1), shutdown_read_(false) {} + +APIDownstreamConnection::~APIDownstreamConnection() { + if (fd_ != -1) { + close(fd_); + } +} + +int APIDownstreamConnection::attach_downstream(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; + } + + downstream_ = downstream; + + return 0; +} + +void APIDownstreamConnection::detach_downstream(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream; + } + downstream_ = nullptr; +} + +int APIDownstreamConnection::send_reply(unsigned int http_status, + APIStatusCode api_status, + const StringRef &data) { + shutdown_read_ = true; + + auto upstream = downstream_->get_upstream(); + + auto &resp = downstream_->response(); + + resp.http_status = http_status; + + auto &balloc = downstream_->get_block_allocator(); + + StringRef api_status_str; + + switch (api_status) { + case APIStatusCode::SUCCESS: + api_status_str = StringRef::from_lit("Success"); + break; + case APIStatusCode::FAILURE: + api_status_str = StringRef::from_lit("Failure"); + break; + default: + assert(0); + } + + constexpr auto M1 = StringRef::from_lit("{\"status\":\""); + constexpr auto M2 = StringRef::from_lit("\",\"code\":"); + constexpr auto M3 = StringRef::from_lit("}"); + + // 3 is the number of digits in http_status, assuming it is 3 digits + // number. + auto buflen = M1.size() + M2.size() + M3.size() + data.size() + + api_status_str.size() + 3; + + auto buf = make_byte_ref(balloc, buflen); + auto p = buf.base; + + p = std::copy(std::begin(M1), std::end(M1), p); + p = std::copy(std::begin(api_status_str), std::end(api_status_str), p); + p = std::copy(std::begin(M2), std::end(M2), p); + p = util::utos(p, http_status); + p = std::copy(std::begin(data), std::end(data), p); + p = std::copy(std::begin(M3), std::end(M3), p); + + buf.len = p - buf.base; + + auto content_length = util::make_string_ref_uint(balloc, buf.len); + + resp.fs.add_header_token(StringRef::from_lit("content-length"), + content_length, false, http2::HD_CONTENT_LENGTH); + + switch (http_status) { + case 400: + case 405: + case 413: + resp.fs.add_header_token(StringRef::from_lit("connection"), + StringRef::from_lit("close"), false, + http2::HD_CONNECTION); + break; + } + + if (upstream->send_reply(downstream_, buf.base, buf.len) != 0) { + return -1; + } + + return 0; +} + +namespace { +const APIEndpoint *lookup_api(const StringRef &path) { + switch (path.size()) { + case 26: + switch (path[25]) { + case 'g': + if (util::streq_l("/api/v1beta1/backendconfi", std::begin(path), 25)) { + return &apis()[0]; + } + break; + } + break; + case 27: + switch (path[26]) { + case 'n': + if (util::streq_l("/api/v1beta1/configrevisio", std::begin(path), 26)) { + return &apis()[1]; + } + break; + } + break; + } + return nullptr; +} +} // namespace + +int APIDownstreamConnection::push_request_headers() { + auto &req = downstream_->request(); + + auto path = + StringRef{std::begin(req.path), + std::find(std::begin(req.path), std::end(req.path), '?')}; + + api_ = lookup_api(path); + + if (!api_) { + send_reply(404, APIStatusCode::FAILURE); + + return 0; + } + + switch (req.method) { + case HTTP_GET: + if (!(api_->allowed_methods & (1 << API_METHOD_GET))) { + error_method_not_allowed(); + return 0; + } + break; + case HTTP_POST: + if (!(api_->allowed_methods & (1 << API_METHOD_POST))) { + error_method_not_allowed(); + return 0; + } + break; + case HTTP_PUT: + if (!(api_->allowed_methods & (1 << API_METHOD_PUT))) { + error_method_not_allowed(); + return 0; + } + break; + default: + error_method_not_allowed(); + return 0; + } + + // This works with req.fs.content_length == -1 + if (req.fs.content_length > + static_cast(get_config()->api.max_request_body)) { + send_reply(413, APIStatusCode::FAILURE); + + return 0; + } + + switch (req.method) { + case HTTP_POST: + case HTTP_PUT: { + char tempname[] = "/tmp/nghttpx-api.XXXXXX"; +#ifdef HAVE_MKOSTEMP + fd_ = mkostemp(tempname, O_CLOEXEC); +#else // !HAVE_MKOSTEMP + fd_ = mkstemp(tempname); +#endif // !HAVE_MKOSTEMP + if (fd_ == -1) { + send_reply(500, APIStatusCode::FAILURE); + + return 0; + } +#ifndef HAVE_MKOSTEMP + util::make_socket_closeonexec(fd_); +#endif // HAVE_MKOSTEMP + unlink(tempname); + break; + } + } + + downstream_->set_request_header_sent(true); + auto src = downstream_->get_blocked_request_buf(); + auto dest = downstream_->get_request_buf(); + src->remove(*dest); + + return 0; +} + +int APIDownstreamConnection::error_method_not_allowed() { + auto &resp = downstream_->response(); + + size_t len = 0; + for (uint8_t i = 0; i < API_METHOD_MAX; ++i) { + if (api_->allowed_methods & (1 << i)) { + // The length of method + ", " + len += API_METHOD_STRING[i].size() + 2; + } + } + + assert(len > 0); + + auto &balloc = downstream_->get_block_allocator(); + + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + for (uint8_t i = 0; i < API_METHOD_MAX; ++i) { + if (api_->allowed_methods & (1 << i)) { + auto &s = API_METHOD_STRING[i]; + p = std::copy(std::begin(s), std::end(s), p); + p = std::copy_n(", ", 2, p); + } + } + + p -= 2; + *p = '\0'; + + resp.fs.add_header_token(StringRef::from_lit("allow"), StringRef{iov.base, p}, + false, -1); + return send_reply(405, APIStatusCode::FAILURE); +} + +int APIDownstreamConnection::push_upload_data_chunk(const uint8_t *data, + size_t datalen) { + if (shutdown_read_ || !api_->require_body) { + return 0; + } + + auto &req = downstream_->request(); + auto &apiconf = get_config()->api; + + if (static_cast(req.recv_body_length) > apiconf.max_request_body) { + send_reply(413, APIStatusCode::FAILURE); + + return 0; + } + + ssize_t nwrite; + while ((nwrite = write(fd_, data, datalen)) == -1 && errno == EINTR) + ; + if (nwrite == -1) { + auto error = errno; + LOG(ERROR) << "Could not write API request body: errno=" << error; + send_reply(500, APIStatusCode::FAILURE); + + return 0; + } + + // We don't have to call Upstream::resume_read() here, because + // request buffer is effectively unlimited. Actually, we cannot + // call it here since it could recursively call this function again. + + return 0; +} + +int APIDownstreamConnection::end_upload_data() { + if (shutdown_read_) { + return 0; + } + + return api_->handler(*this); +} + +int APIDownstreamConnection::handle_backendconfig() { + auto &req = downstream_->request(); + + if (req.recv_body_length == 0) { + send_reply(200, APIStatusCode::SUCCESS); + + return 0; + } + + auto rp = mmap(nullptr, req.recv_body_length, PROT_READ, MAP_SHARED, fd_, 0); + if (rp == reinterpret_cast(-1)) { + send_reply(500, APIStatusCode::FAILURE); + return 0; + } + + auto unmapper = defer(munmap, rp, req.recv_body_length); + + Config new_config{}; + new_config.conn.downstream = std::make_shared(); + const auto &downstreamconf = new_config.conn.downstream; + + auto config = get_config(); + auto &src = config->conn.downstream; + + downstreamconf->timeout = src->timeout; + downstreamconf->connections_per_host = src->connections_per_host; + downstreamconf->connections_per_frontend = src->connections_per_frontend; + downstreamconf->request_buffer_size = src->request_buffer_size; + downstreamconf->response_buffer_size = src->response_buffer_size; + downstreamconf->family = src->family; + + std::set include_set; + std::map pattern_addr_indexer; + + for (auto first = reinterpret_cast(rp), + last = first + req.recv_body_length; + first != last;) { + auto eol = std::find(first, last, '\n'); + if (eol == last) { + break; + } + + if (first == eol || *first == '#') { + first = ++eol; + continue; + } + + auto eq = std::find(first, eol, '='); + if (eq == eol) { + send_reply(400, APIStatusCode::FAILURE); + return 0; + } + + auto opt = StringRef{first, eq}; + auto optval = StringRef{eq + 1, eol}; + + auto optid = option_lookup_token(opt.c_str(), opt.size()); + + switch (optid) { + case SHRPX_OPTID_BACKEND: + break; + default: + first = ++eol; + continue; + } + + if (parse_config(&new_config, optid, opt, optval, include_set, + pattern_addr_indexer) != 0) { + send_reply(400, APIStatusCode::FAILURE); + return 0; + } + + first = ++eol; + } + + auto &tlsconf = config->tls; + if (configure_downstream_group(&new_config, config->http2_proxy, true, + tlsconf) != 0) { + send_reply(400, APIStatusCode::FAILURE); + return 0; + } + + auto conn_handler = worker_->get_connection_handler(); + + conn_handler->send_replace_downstream(downstreamconf); + + send_reply(200, APIStatusCode::SUCCESS); + + return 0; +} + +int APIDownstreamConnection::handle_configrevision() { + auto config = get_config(); + auto &balloc = downstream_->get_block_allocator(); + + // Construct the following string: + // , + // "data":{ + // "configRevision": N + // } + auto data = concat_string_ref( + balloc, StringRef::from_lit(R"(,"data":{"configRevision":)"), + util::make_string_ref_uint(balloc, config->config_revision), + StringRef::from_lit("}")); + + send_reply(200, APIStatusCode::SUCCESS, data); + + return 0; +} + +void APIDownstreamConnection::pause_read(IOCtrlReason reason) {} + +int APIDownstreamConnection::resume_read(IOCtrlReason reason, size_t consumed) { + return 0; +} + +void APIDownstreamConnection::force_resume_read() {} + +int APIDownstreamConnection::on_read() { return 0; } + +int APIDownstreamConnection::on_write() { return 0; } + +void APIDownstreamConnection::on_upstream_change(Upstream *upstream) {} + +bool APIDownstreamConnection::poolable() const { return false; } + +const std::shared_ptr & +APIDownstreamConnection::get_downstream_addr_group() const { + static std::shared_ptr s; + return s; +} + +DownstreamAddr *APIDownstreamConnection::get_addr() const { return nullptr; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_api_downstream_connection.h b/lib/nghttp2/src/shrpx_api_downstream_connection.h new file mode 100644 index 00000000000..5d4182f0306 --- /dev/null +++ b/lib/nghttp2/src/shrpx_api_downstream_connection.h @@ -0,0 +1,114 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_API_DOWNSTREAM_CONNECTION_H +#define SHRPX_API_DOWNSTREAM_CONNECTION_H + +#include "shrpx_downstream_connection.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +class Worker; + +// If new method is added, don't forget to update API_METHOD_STRING as +// well. +enum APIMethod { + API_METHOD_GET, + API_METHOD_POST, + API_METHOD_PUT, + API_METHOD_MAX, +}; + +// API status code, which is independent from HTTP status code. But +// generally, 2xx code for SUCCESS, and otherwise FAILURE. +enum class APIStatusCode { + SUCCESS, + FAILURE, +}; + +class APIDownstreamConnection; + +struct APIEndpoint { + // Endpoint path. It must start with "/api/". + StringRef path; + // true if we evaluate request body. + bool require_body; + // Allowed methods. This is bitwise OR of one or more of (1 << + // APIMethod value). + uint8_t allowed_methods; + std::function handler; +}; + +class APIDownstreamConnection : public DownstreamConnection { +public: + APIDownstreamConnection(Worker *worker); + virtual ~APIDownstreamConnection(); + virtual int attach_downstream(Downstream *downstream); + virtual void detach_downstream(Downstream *downstream); + + virtual int push_request_headers(); + virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen); + virtual int end_upload_data(); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, size_t consumed); + virtual void force_resume_read(); + + virtual int on_read(); + virtual int on_write(); + + virtual void on_upstream_change(Upstream *upstream); + + // true if this object is poolable. + virtual bool poolable() const; + + virtual const std::shared_ptr & + get_downstream_addr_group() const; + virtual DownstreamAddr *get_addr() const; + + int send_reply(unsigned int http_status, APIStatusCode api_status, + const StringRef &data = StringRef{}); + int error_method_not_allowed(); + + // Handles backendconfig API request. + int handle_backendconfig(); + // Handles configrevision API request. + int handle_configrevision(); + +private: + Worker *worker_; + // This points to the requested APIEndpoint struct. + const APIEndpoint *api_; + // The file descriptor for temporary file to store request body. + int fd_; + // true if we stop reading request body. + bool shutdown_read_; +}; + +} // namespace shrpx + +#endif // SHRPX_API_DOWNSTREAM_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_client_handler.cc b/lib/nghttp2/src/shrpx_client_handler.cc new file mode 100644 index 00000000000..ac7a65ec94a --- /dev/null +++ b/lib/nghttp2/src/shrpx_client_handler.cc @@ -0,0 +1,1710 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_client_handler.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H + +#include + +#include "shrpx_upstream.h" +#include "shrpx_http2_upstream.h" +#include "shrpx_https_upstream.h" +#include "shrpx_config.h" +#include "shrpx_http_downstream_connection.h" +#include "shrpx_http2_downstream_connection.h" +#include "shrpx_tls.h" +#include "shrpx_worker.h" +#include "shrpx_downstream_connection_pool.h" +#include "shrpx_downstream.h" +#include "shrpx_http2_session.h" +#include "shrpx_connect_blocker.h" +#include "shrpx_api_downstream_connection.h" +#include "shrpx_health_monitor_downstream_connection.h" +#include "shrpx_null_downstream_connection.h" +#ifdef ENABLE_HTTP3 +# include "shrpx_http3_upstream.h" +#endif // ENABLE_HTTP3 +#include "shrpx_log.h" +#include "util.h" +#include "template.h" +#include "tls.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto conn = static_cast(w->data); + auto handler = static_cast(conn->data); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "Time out"; + } + + delete handler; +} +} // namespace + +namespace { +void shutdowncb(struct ev_loop *loop, ev_timer *w, int revents) { + auto handler = static_cast(w->data); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "Close connection due to TLS renegotiation"; + } + + delete handler; +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn = static_cast(w->data); + auto handler = static_cast(conn->data); + + if (handler->do_read() != 0) { + delete handler; + return; + } +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn = static_cast(w->data); + auto handler = static_cast(conn->data); + + if (handler->do_write() != 0) { + delete handler; + return; + } +} +} // namespace + +int ClientHandler::noop() { return 0; } + +int ClientHandler::read_clear() { + auto should_break = false; + rb_.ensure_chunk(); + for (;;) { + if (rb_.rleft() && on_read() != 0) { + return -1; + } + if (rb_.rleft() == 0) { + rb_.reset(); + } else if (rb_.wleft() == 0) { + conn_.rlimit.stopw(); + return 0; + } + + if (!ev_is_active(&conn_.rev) || should_break) { + return 0; + } + + auto nread = conn_.read_clear(rb_.last(), rb_.wleft()); + + if (nread == 0) { + if (rb_.rleft() == 0) { + rb_.release_chunk(); + } + return 0; + } + + if (nread < 0) { + return -1; + } + + rb_.write(nread); + should_break = true; + } +} + +int ClientHandler::write_clear() { + std::array iov; + + for (;;) { + if (on_write() != 0) { + return -1; + } + + auto iovcnt = upstream_->response_riovec(iov.data(), iov.size()); + if (iovcnt == 0) { + break; + } + + auto nwrite = conn_.writev_clear(iov.data(), iovcnt); + if (nwrite < 0) { + return -1; + } + + if (nwrite == 0) { + return 0; + } + + upstream_->response_drain(nwrite); + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + return 0; +} + +int ClientHandler::proxy_protocol_peek_clear() { + rb_.ensure_chunk(); + + assert(rb_.rleft() == 0); + + auto nread = conn_.peek_clear(rb_.last(), rb_.wleft()); + if (nread < 0) { + return -1; + } + if (nread == 0) { + return 0; + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol: Peek " << nread + << " bytes from socket"; + } + + rb_.write(nread); + + if (on_read() != 0) { + return -1; + } + + rb_.reset(); + + return 0; +} + +int ClientHandler::tls_handshake() { + ev_timer_again(conn_.loop, &conn_.rt); + + ERR_clear_error(); + + auto rv = conn_.tls_handshake(); + + if (rv == SHRPX_ERR_INPROGRESS) { + return 0; + } + + if (rv < 0) { + return -1; + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "SSL/TLS handshake completed"; + } + + if (validate_next_proto() != 0) { + return -1; + } + + read_ = &ClientHandler::read_tls; + write_ = &ClientHandler::write_tls; + + return 0; +} + +int ClientHandler::read_tls() { + auto should_break = false; + + ERR_clear_error(); + + rb_.ensure_chunk(); + + for (;;) { + // we should process buffered data first before we read EOF. + if (rb_.rleft() && on_read() != 0) { + return -1; + } + if (rb_.rleft() == 0) { + rb_.reset(); + } else if (rb_.wleft() == 0) { + conn_.rlimit.stopw(); + return 0; + } + + if (!ev_is_active(&conn_.rev) || should_break) { + return 0; + } + + auto nread = conn_.read_tls(rb_.last(), rb_.wleft()); + + if (nread == 0) { + if (rb_.rleft() == 0) { + rb_.release_chunk(); + } + return 0; + } + + if (nread < 0) { + return -1; + } + + rb_.write(nread); + should_break = true; + } +} + +int ClientHandler::write_tls() { + struct iovec iov; + + ERR_clear_error(); + + if (on_write() != 0) { + return -1; + } + + auto iovcnt = upstream_->response_riovec(&iov, 1); + if (iovcnt == 0) { + conn_.start_tls_write_idle(); + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + return 0; + } + + for (;;) { + auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len); + if (nwrite < 0) { + return -1; + } + + if (nwrite == 0) { + return 0; + } + + upstream_->response_drain(nwrite); + + iovcnt = upstream_->response_riovec(&iov, 1); + if (iovcnt == 0) { + return 0; + } + } +} + +#ifdef ENABLE_HTTP3 +int ClientHandler::read_quic(const UpstreamAddr *faddr, + const Address &remote_addr, + const Address &local_addr, + const ngtcp2_pkt_info &pi, const uint8_t *data, + size_t datalen) { + auto upstream = static_cast(upstream_.get()); + + return upstream->on_read(faddr, remote_addr, local_addr, pi, data, datalen); +} + +int ClientHandler::write_quic() { return upstream_->on_write(); } +#endif // ENABLE_HTTP3 + +int ClientHandler::upstream_noop() { return 0; } + +int ClientHandler::upstream_read() { + assert(upstream_); + if (upstream_->on_read() != 0) { + return -1; + } + return 0; +} + +int ClientHandler::upstream_write() { + assert(upstream_); + if (upstream_->on_write() != 0) { + return -1; + } + + if (get_should_close_after_write() && upstream_->response_empty()) { + return -1; + } + + return 0; +} + +int ClientHandler::upstream_http2_connhd_read() { + auto nread = std::min(left_connhd_len_, rb_.rleft()); + if (memcmp(&NGHTTP2_CLIENT_MAGIC[NGHTTP2_CLIENT_MAGIC_LEN - left_connhd_len_], + rb_.pos(), nread) != 0) { + // There is no downgrade path here. Just drop the connection. + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "invalid client connection header"; + } + + return -1; + } + + left_connhd_len_ -= nread; + rb_.drain(nread); + conn_.rlimit.startw(); + + if (left_connhd_len_ == 0) { + on_read_ = &ClientHandler::upstream_read; + // Run on_read to process data left in buffer since they are not + // notified further + if (on_read() != 0) { + return -1; + } + return 0; + } + + return 0; +} + +int ClientHandler::upstream_http1_connhd_read() { + auto nread = std::min(left_connhd_len_, rb_.rleft()); + if (memcmp(&NGHTTP2_CLIENT_MAGIC[NGHTTP2_CLIENT_MAGIC_LEN - left_connhd_len_], + rb_.pos(), nread) != 0) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "This is HTTP/1.1 connection, " + << "but may be upgraded to HTTP/2 later."; + } + + // Reset header length for later HTTP/2 upgrade + left_connhd_len_ = NGHTTP2_CLIENT_MAGIC_LEN; + on_read_ = &ClientHandler::upstream_read; + on_write_ = &ClientHandler::upstream_write; + + if (on_read() != 0) { + return -1; + } + + return 0; + } + + left_connhd_len_ -= nread; + rb_.drain(nread); + conn_.rlimit.startw(); + + if (left_connhd_len_ == 0) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "direct HTTP/2 connection"; + } + + direct_http2_upgrade(); + on_read_ = &ClientHandler::upstream_read; + on_write_ = &ClientHandler::upstream_write; + + // Run on_read to process data left in buffer since they are not + // notified further + if (on_read() != 0) { + return -1; + } + + return 0; + } + + return 0; +} + +ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl, + const StringRef &ipaddr, const StringRef &port, + int family, const UpstreamAddr *faddr) + : // We use balloc_ for TLS session ID (64), ipaddr (IPv6) (39), + // port (5), forwarded-for (IPv6) (41), alpn (5), proxyproto + // ipaddr (15), proxyproto port (5), sni (32, estimated). we + // need terminal NULL byte for each. We also require 8 bytes + // header for each allocation. We align at 16 bytes boundary, + // so the required space is 64 + 48 + 16 + 48 + 16 + 16 + 16 + + // 32 + 8 + 8 * 8 = 328. + balloc_(512, 512), + rb_(worker->get_mcpool()), + conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(), + get_config()->conn.upstream.timeout.write, + get_config()->conn.upstream.timeout.read, + get_config()->conn.upstream.ratelimit.write, + get_config()->conn.upstream.ratelimit.read, writecb, readcb, + timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold, + get_config()->tls.dyn_rec.idle_timeout, + faddr->quic ? Proto::HTTP3 : Proto::NONE), + ipaddr_(make_string_ref(balloc_, ipaddr)), + port_(make_string_ref(balloc_, port)), + faddr_(faddr), + worker_(worker), + left_connhd_len_(NGHTTP2_CLIENT_MAGIC_LEN), + affinity_hash_(0), + should_close_after_write_(false), + affinity_hash_computed_(false) { + + ++worker_->get_worker_stat()->num_connections; + + ev_timer_init(&reneg_shutdown_timer_, shutdowncb, 0., 0.); + + reneg_shutdown_timer_.data = this; + + if (!faddr->quic) { + conn_.rlimit.startw(); + } + ev_timer_again(conn_.loop, &conn_.rt); + + auto config = get_config(); + + if (!faddr->quic) { + if (faddr_->accept_proxy_protocol || + config->conn.upstream.accept_proxy_protocol) { + read_ = &ClientHandler::proxy_protocol_peek_clear; + write_ = &ClientHandler::noop; + on_read_ = &ClientHandler::proxy_protocol_read; + on_write_ = &ClientHandler::upstream_noop; + } else { + setup_upstream_io_callback(); + } + } + + auto &fwdconf = config->http.forwarded; + + if (fwdconf.params & FORWARDED_FOR) { + if (fwdconf.for_node_type == ForwardedNode::OBFUSCATED) { + // 1 for '_' + auto len = SHRPX_OBFUSCATED_NODE_LENGTH + 1; + // 1 for terminating NUL. + auto buf = make_byte_ref(balloc_, len + 1); + auto p = buf.base; + *p++ = '_'; + p = util::random_alpha_digit(p, p + SHRPX_OBFUSCATED_NODE_LENGTH, + worker_->get_randgen()); + *p = '\0'; + + forwarded_for_ = StringRef{buf.base, p}; + } else { + init_forwarded_for(family, ipaddr_); + } + } +} + +void ClientHandler::init_forwarded_for(int family, const StringRef &ipaddr) { + if (family == AF_INET6) { + // 2 for '[' and ']' + auto len = 2 + ipaddr.size(); + // 1 for terminating NUL. + auto buf = make_byte_ref(balloc_, len + 1); + auto p = buf.base; + *p++ = '['; + p = std::copy(std::begin(ipaddr), std::end(ipaddr), p); + *p++ = ']'; + *p = '\0'; + + forwarded_for_ = StringRef{buf.base, p}; + } else { + // family == AF_INET or family == AF_UNIX + forwarded_for_ = ipaddr; + } +} + +void ClientHandler::setup_upstream_io_callback() { + if (conn_.tls.ssl) { + conn_.prepare_server_handshake(); + read_ = write_ = &ClientHandler::tls_handshake; + on_read_ = &ClientHandler::upstream_noop; + on_write_ = &ClientHandler::upstream_write; + } else { + // For non-TLS version, first create HttpsUpstream. It may be + // upgraded to HTTP/2 through HTTP Upgrade or direct HTTP/2 + // connection. + upstream_ = std::make_unique(this); + alpn_ = StringRef::from_lit("http/1.1"); + read_ = &ClientHandler::read_clear; + write_ = &ClientHandler::write_clear; + on_read_ = &ClientHandler::upstream_http1_connhd_read; + on_write_ = &ClientHandler::upstream_noop; + } +} + +#ifdef ENABLE_HTTP3 +void ClientHandler::setup_http3_upstream( + std::unique_ptr &&upstream) { + upstream_ = std::move(upstream); + write_ = &ClientHandler::write_quic; + + auto config = get_config(); + + reset_upstream_read_timeout(config->conn.upstream.timeout.http3_read); +} +#endif // ENABLE_HTTP3 + +ClientHandler::~ClientHandler() { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Deleting"; + } + + if (upstream_) { + upstream_->on_handler_delete(); + } + + auto worker_stat = worker_->get_worker_stat(); + --worker_stat->num_connections; + + if (worker_stat->num_connections == 0) { + worker_->schedule_clear_mcpool(); + } + + ev_timer_stop(conn_.loop, &reneg_shutdown_timer_); + + // TODO If backend is http/2, and it is in CONNECTED state, signal + // it and make it loopbreak when output is zero. + if (worker_->get_graceful_shutdown() && worker_stat->num_connections == 0 && + worker_stat->num_close_waits == 0) { + ev_break(conn_.loop); + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Deleted"; + } +} + +Upstream *ClientHandler::get_upstream() { return upstream_.get(); } + +struct ev_loop *ClientHandler::get_loop() const { return conn_.loop; } + +void ClientHandler::reset_upstream_read_timeout(ev_tstamp t) { + conn_.rt.repeat = t; + if (ev_is_active(&conn_.rt)) { + ev_timer_again(conn_.loop, &conn_.rt); + } +} + +void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) { + conn_.wt.repeat = t; + if (ev_is_active(&conn_.wt)) { + ev_timer_again(conn_.loop, &conn_.wt); + } +} + +void ClientHandler::repeat_read_timer() { + ev_timer_again(conn_.loop, &conn_.rt); +} + +void ClientHandler::stop_read_timer() { ev_timer_stop(conn_.loop, &conn_.rt); } + +int ClientHandler::validate_next_proto() { + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len = 0; + + // First set callback for catch all cases + on_read_ = &ClientHandler::upstream_read; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + StringRef proto; + + if (next_proto) { + proto = StringRef{next_proto, next_proto_len}; + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "The negotiated next protocol: " << proto; + } + } else { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "No protocol negotiated. Fallback to HTTP/1.1"; + } + + proto = StringRef::from_lit("http/1.1"); + } + + if (!tls::in_proto_list(get_config()->tls.npn_list, proto)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "The negotiated protocol is not supported: " << proto; + } + return -1; + } + + if (util::check_h2_is_selected(proto)) { + on_read_ = &ClientHandler::upstream_http2_connhd_read; + + auto http2_upstream = std::make_unique(this); + + upstream_ = std::move(http2_upstream); + alpn_ = make_string_ref(balloc_, proto); + + // At this point, input buffer is already filled with some bytes. + // The read callback is not called until new data come. So consume + // input buffer here. + if (on_read() != 0) { + return -1; + } + + return 0; + } + + if (proto == StringRef::from_lit("http/1.1")) { + upstream_ = std::make_unique(this); + alpn_ = StringRef::from_lit("http/1.1"); + + // At this point, input buffer is already filled with some bytes. + // The read callback is not called until new data come. So consume + // input buffer here. + if (on_read() != 0) { + return -1; + } + + return 0; + } + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "The negotiated protocol is not supported"; + } + return -1; +} + +int ClientHandler::do_read() { return read_(*this); } +int ClientHandler::do_write() { return write_(*this); } + +int ClientHandler::on_read() { + if (rb_.chunk_avail()) { + auto rv = on_read_(*this); + if (rv != 0) { + return rv; + } + } + conn_.handle_tls_pending_read(); + return 0; +} +int ClientHandler::on_write() { return on_write_(*this); } + +const StringRef &ClientHandler::get_ipaddr() const { return ipaddr_; } + +bool ClientHandler::get_should_close_after_write() const { + return should_close_after_write_; +} + +void ClientHandler::set_should_close_after_write(bool f) { + should_close_after_write_ = f; +} + +void ClientHandler::pool_downstream_connection( + std::unique_ptr dconn) { + if (!dconn->poolable()) { + return; + } + + dconn->set_client_handler(nullptr); + + auto &group = dconn->get_downstream_addr_group(); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get() + << " in group " << group; + } + + auto addr = dconn->get_addr(); + auto &dconn_pool = addr->dconn_pool; + dconn_pool->add_downstream_connection(std::move(dconn)); +} + +namespace { +// Computes 32bits hash for session affinity for IP address |ip|. +uint32_t compute_affinity_from_ip(const StringRef &ip) { + int rv; + std::array buf; + + rv = util::sha256(buf.data(), ip); + if (rv != 0) { + // Not sure when sha256 failed. Just fall back to another + // function. + return util::hash32(ip); + } + + return (static_cast(buf[0]) << 24) | + (static_cast(buf[1]) << 16) | + (static_cast(buf[2]) << 8) | static_cast(buf[3]); +} +} // namespace + +Http2Session *ClientHandler::get_http2_session( + const std::shared_ptr &group, DownstreamAddr *addr) { + auto &shared_addr = group->shared_addr; + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Selected DownstreamAddr=" << addr + << ", index=" << (addr - shared_addr->addrs.data()); + } + + for (auto session = addr->http2_extra_freelist.head; session;) { + auto next = session->dlnext; + + if (session->max_concurrency_reached(0)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) + << "Maximum streams have been reached for Http2Session(" << session + << "). Skip it"; + } + + session->remove_from_freelist(); + session = next; + + continue; + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Use Http2Session " << session + << " from http2_extra_freelist"; + } + + if (session->max_concurrency_reached(1)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Maximum streams are reached for Http2Session(" + << session << ")."; + } + + session->remove_from_freelist(); + } + return session; + } + + auto session = new Http2Session(conn_.loop, worker_->get_cl_ssl_ctx(), + worker_, group, addr); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Create new Http2Session " << session; + } + + session->add_to_extra_freelist(); + + return session; +} + +uint32_t ClientHandler::get_affinity_cookie(Downstream *downstream, + const StringRef &cookie_name) { + auto h = downstream->find_affinity_cookie(cookie_name); + if (h) { + return h; + } + + auto d = std::uniform_int_distribution(1); + auto rh = d(worker_->get_randgen()); + h = util::hash32(StringRef{reinterpret_cast(&rh), + reinterpret_cast(&rh) + sizeof(rh)}); + + downstream->renew_affinity_cookie(h); + + return h; +} + +namespace { +void reschedule_addr( + std::priority_queue, + DownstreamAddrEntryGreater> &pq, + DownstreamAddr *addr) { + auto penalty = MAX_DOWNSTREAM_ADDR_WEIGHT + addr->pending_penalty; + addr->cycle += penalty / addr->weight; + addr->pending_penalty = penalty % addr->weight; + + pq.push(DownstreamAddrEntry{addr, addr->seq, addr->cycle}); + addr->queued = true; +} +} // namespace + +namespace { +void reschedule_wg( + std::priority_queue, + WeightGroupEntryGreater> &pq, + WeightGroup *wg) { + auto penalty = MAX_DOWNSTREAM_ADDR_WEIGHT + wg->pending_penalty; + wg->cycle += penalty / wg->weight; + wg->pending_penalty = penalty % wg->weight; + + pq.push(WeightGroupEntry{wg, wg->seq, wg->cycle}); + wg->queued = true; +} +} // namespace + +DownstreamAddr *ClientHandler::get_downstream_addr(int &err, + DownstreamAddrGroup *group, + Downstream *downstream) { + err = 0; + + switch (faddr_->alt_mode) { + case UpstreamAltMode::API: + case UpstreamAltMode::HEALTHMON: + assert(0); + default: + break; + } + + auto &shared_addr = group->shared_addr; + + if (shared_addr->affinity.type != SessionAffinity::NONE) { + uint32_t hash; + switch (shared_addr->affinity.type) { + case SessionAffinity::IP: + if (!affinity_hash_computed_) { + affinity_hash_ = compute_affinity_from_ip(ipaddr_); + affinity_hash_computed_ = true; + } + hash = affinity_hash_; + break; + case SessionAffinity::COOKIE: + if (shared_addr->affinity.cookie.stickiness == + SessionAffinityCookieStickiness::STRICT) { + return get_downstream_addr_strict_affinity(err, shared_addr, + downstream); + } + + hash = get_affinity_cookie(downstream, shared_addr->affinity.cookie.name); + break; + default: + assert(0); + } + + const auto &affinity_hash = shared_addr->affinity_hash; + + auto it = std::lower_bound( + std::begin(affinity_hash), std::end(affinity_hash), hash, + [](const AffinityHash &lhs, uint32_t rhs) { return lhs.hash < rhs; }); + + if (it == std::end(affinity_hash)) { + it = std::begin(affinity_hash); + } + + auto aff_idx = + static_cast(std::distance(std::begin(affinity_hash), it)); + auto idx = (*it).idx; + auto addr = &shared_addr->addrs[idx]; + + if (addr->connect_blocker->blocked()) { + size_t i; + for (i = aff_idx + 1; i != aff_idx; ++i) { + if (i == shared_addr->affinity_hash.size()) { + i = 0; + } + addr = &shared_addr->addrs[shared_addr->affinity_hash[i].idx]; + if (addr->connect_blocker->blocked()) { + continue; + } + break; + } + if (i == aff_idx) { + err = -1; + return nullptr; + } + } + + return addr; + } + + auto &wgpq = shared_addr->pq; + + for (;;) { + if (wgpq.empty()) { + CLOG(INFO, this) << "No working downstream address found"; + err = -1; + return nullptr; + } + + auto wg = wgpq.top().wg; + wgpq.pop(); + wg->queued = false; + + for (;;) { + if (wg->pq.empty()) { + break; + } + + auto addr = wg->pq.top().addr; + wg->pq.pop(); + addr->queued = false; + + if (addr->connect_blocker->blocked()) { + continue; + } + + reschedule_addr(wg->pq, addr); + reschedule_wg(wgpq, wg); + + return addr; + } + } +} + +DownstreamAddr *ClientHandler::get_downstream_addr_strict_affinity( + int &err, const std::shared_ptr &shared_addr, + Downstream *downstream) { + const auto &affinity_hash = shared_addr->affinity_hash; + + auto h = downstream->find_affinity_cookie(shared_addr->affinity.cookie.name); + if (h) { + auto it = shared_addr->affinity_hash_map.find(h); + if (it != std::end(shared_addr->affinity_hash_map)) { + auto addr = &shared_addr->addrs[(*it).second]; + if (!addr->connect_blocker->blocked()) { + return addr; + } + } + } else { + auto d = std::uniform_int_distribution(1); + auto rh = d(worker_->get_randgen()); + h = util::hash32(StringRef{reinterpret_cast(&rh), + reinterpret_cast(&rh) + sizeof(rh)}); + } + + // Client is not bound to a particular backend, or the bound backend + // is not found, or is blocked. Find new backend using h. Using + // existing h allows us to find new server in a deterministic way. + // It is preferable because multiple concurrent requests with the + // stale cookie might be in-flight. + auto it = std::lower_bound( + std::begin(affinity_hash), std::end(affinity_hash), h, + [](const AffinityHash &lhs, uint32_t rhs) { return lhs.hash < rhs; }); + + if (it == std::end(affinity_hash)) { + it = std::begin(affinity_hash); + } + + auto aff_idx = + static_cast(std::distance(std::begin(affinity_hash), it)); + auto idx = (*it).idx; + auto addr = &shared_addr->addrs[idx]; + + if (addr->connect_blocker->blocked()) { + size_t i; + for (i = aff_idx + 1; i != aff_idx; ++i) { + if (i == shared_addr->affinity_hash.size()) { + i = 0; + } + addr = &shared_addr->addrs[shared_addr->affinity_hash[i].idx]; + if (addr->connect_blocker->blocked()) { + continue; + } + break; + } + if (i == aff_idx) { + err = -1; + return nullptr; + } + } + + downstream->renew_affinity_cookie(addr->affinity_hash); + + return addr; +} + +std::unique_ptr +ClientHandler::get_downstream_connection(int &err, Downstream *downstream) { + size_t group_idx; + auto &downstreamconf = *worker_->get_downstream_config(); + auto &routerconf = downstreamconf.router; + + auto catch_all = downstreamconf.addr_group_catch_all; + auto &groups = worker_->get_downstream_addr_groups(); + + auto &req = downstream->request(); + + err = 0; + + switch (faddr_->alt_mode) { + case UpstreamAltMode::API: { + auto dconn = std::make_unique(worker_); + dconn->set_client_handler(this); + return dconn; + } + case UpstreamAltMode::HEALTHMON: { + auto dconn = std::make_unique(); + dconn->set_client_handler(this); + return dconn; + } + default: + break; + } + + auto &balloc = downstream->get_block_allocator(); + + StringRef authority, path; + + if (req.forwarded_once) { + if (groups.size() != 1) { + authority = req.orig_authority; + path = req.orig_path; + } + } else { + if (faddr_->sni_fwd) { + authority = sni_; + } else if (!req.authority.empty()) { + authority = req.authority; + } else { + auto h = req.fs.header(http2::HD_HOST); + if (h) { + authority = h->value; + } + } + + // CONNECT method does not have path. But we requires path in + // host-path mapping. As workaround, we assume that path is + // "/". + if (!req.regular_connect_method()) { + path = req.path; + } + + // Cache the authority and path used for the first-time backend + // selection because per-pattern mruby script can change them. + req.orig_authority = authority; + req.orig_path = path; + req.forwarded_once = true; + } + + // Fast path. If we have one group, it must be catch-all group. + if (groups.size() == 1) { + group_idx = 0; + } else { + group_idx = match_downstream_addr_group(routerconf, authority, path, groups, + catch_all, balloc); + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Downstream address group_idx: " << group_idx; + } + + if (groups[group_idx]->shared_addr->redirect_if_not_tls && !conn_.tls.ssl) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Downstream address group " << group_idx + << " requires frontend TLS connection."; + } + err = SHRPX_ERR_TLS_REQUIRED; + return nullptr; + } + + auto &group = groups[group_idx]; + + if (group->shared_addr->dnf) { + auto dconn = std::make_unique(group); + dconn->set_client_handler(this); + return dconn; + } + + auto addr = get_downstream_addr(err, group.get(), downstream); + if (addr == nullptr) { + return nullptr; + } + + if (addr->proto == Proto::HTTP1) { + auto dconn = addr->dconn_pool->pop_downstream_connection(); + if (dconn) { + dconn->set_client_handler(this); + return dconn; + } + + if (worker_->get_connect_blocker()->blocked()) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) + << "Worker wide backend connection was blocked temporarily"; + } + return nullptr; + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Downstream connection pool is empty." + << " Create new one"; + } + + dconn = std::make_unique(group, addr, conn_.loop, + worker_); + dconn->set_client_handler(this); + return dconn; + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Downstream connection pool is empty." + << " Create new one"; + } + + auto http2session = get_http2_session(group, addr); + auto dconn = std::make_unique(http2session); + dconn->set_client_handler(this); + return dconn; +} + +MemchunkPool *ClientHandler::get_mcpool() { return worker_->get_mcpool(); } + +SSL *ClientHandler::get_ssl() const { return conn_.tls.ssl; } + +void ClientHandler::direct_http2_upgrade() { + upstream_ = std::make_unique(this); + alpn_ = StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID); + on_read_ = &ClientHandler::upstream_read; + write_ = &ClientHandler::write_clear; +} + +int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) { + auto upstream = std::make_unique(this); + + auto output = upstream->get_response_buf(); + + // We might have written non-final header in response_buf, in this + // case, response_state is still INITIAL. If this non-final header + // and upgrade header fit in output buffer, do upgrade. Otherwise, + // to avoid to send this non-final header as response body in HTTP/2 + // upstream, fail upgrade. + auto downstream = http->get_downstream(); + auto input = downstream->get_response_buf(); + + if (upstream->upgrade_upstream(http) != 0) { + return -1; + } + // http pointer is now owned by upstream. + upstream_.release(); + // TODO We might get other version id in HTTP2-settings, if we + // support aliasing for h2, but we just use library default for now. + alpn_ = StringRef::from_lit(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID); + on_read_ = &ClientHandler::upstream_http2_connhd_read; + write_ = &ClientHandler::write_clear; + + input->remove(*output, input->rleft()); + + constexpr auto res = + StringRef::from_lit("HTTP/1.1 101 Switching Protocols\r\n" + "Connection: Upgrade\r\n" + "Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n" + "\r\n"); + + output->append(res); + upstream_ = std::move(upstream); + + signal_write(); + return 0; +} + +bool ClientHandler::get_http2_upgrade_allowed() const { return !conn_.tls.ssl; } + +StringRef ClientHandler::get_upstream_scheme() const { + if (conn_.tls.ssl) { + return StringRef::from_lit("https"); + } else { + return StringRef::from_lit("http"); + } +} + +void ClientHandler::start_immediate_shutdown() { + ev_timer_start(conn_.loop, &reneg_shutdown_timer_); +} + +void ClientHandler::write_accesslog(Downstream *downstream) { + auto &req = downstream->request(); + + auto config = get_config(); + + if (!req.tstamp) { + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + req.tstamp = lgconf->tstamp; + } + + upstream_accesslog( + config->logging.access.format, + LogSpec{ + downstream, + ipaddr_, + alpn_, + sni_, + conn_.tls.ssl, + std::chrono::high_resolution_clock::now(), // request_end_time + port_, + faddr_->port, + config->pid, + }); +} + +ClientHandler::ReadBuf *ClientHandler::get_rb() { return &rb_; } + +void ClientHandler::signal_write() { conn_.wlimit.startw(); } + +RateLimit *ClientHandler::get_rlimit() { return &conn_.rlimit; } +RateLimit *ClientHandler::get_wlimit() { return &conn_.wlimit; } + +ev_io *ClientHandler::get_wev() { return &conn_.wev; } + +Worker *ClientHandler::get_worker() const { return worker_; } + +namespace { +ssize_t parse_proxy_line_port(const uint8_t *first, const uint8_t *last) { + auto p = first; + int32_t port = 0; + + if (p == last) { + return -1; + } + + if (*p == '0') { + if (p + 1 != last && util::is_digit(*(p + 1))) { + return -1; + } + return 1; + } + + for (; p != last && util::is_digit(*p); ++p) { + port *= 10; + port += *p - '0'; + + if (port > 65535) { + return -1; + } + } + + return p - first; +} +} // namespace + +int ClientHandler::on_proxy_protocol_finish() { + auto len = rb_.pos() - rb_.begin(); + + assert(len); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol: Draining " << len + << " bytes from socket"; + } + + rb_.reset(); + + if (conn_.read_nolim_clear(rb_.pos(), len) < 0) { + return -1; + } + + rb_.reset(); + + setup_upstream_io_callback(); + + return 0; +} + +namespace { +// PROXY-protocol v2 header signature +constexpr uint8_t PROXY_PROTO_V2_SIG[] = + "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; + +// PROXY-protocol v2 header length +constexpr size_t PROXY_PROTO_V2_HDLEN = + str_size(PROXY_PROTO_V2_SIG) + /* ver_cmd(1) + fam(1) + len(2) = */ 4; +} // namespace + +// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt +int ClientHandler::proxy_protocol_read() { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol: Started"; + } + + auto first = rb_.pos(); + + if (rb_.rleft() >= PROXY_PROTO_V2_HDLEN && + (*(first + str_size(PROXY_PROTO_V2_SIG)) & 0xf0) == 0x20) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol: Detected v2 header signature"; + } + return proxy_protocol_v2_read(); + } + + // NULL character really destroys functions which expects NULL + // terminated string. We won't expect it in PROXY protocol line, so + // find it here. + auto chrs = std::array{'\n', '\0'}; + + constexpr size_t MAX_PROXY_LINELEN = 107; + + auto bufend = rb_.pos() + std::min(MAX_PROXY_LINELEN, rb_.rleft()); + + auto end = + std::find_first_of(rb_.pos(), bufend, std::begin(chrs), std::end(chrs)); + + if (end == bufend || *end == '\0' || end == rb_.pos() || *(end - 1) != '\r') { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: No ending CR LF sequence found"; + } + return -1; + } + + --end; + + constexpr auto HEADER = StringRef::from_lit("PROXY "); + + if (static_cast(end - rb_.pos()) < HEADER.size()) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: PROXY version 1 ID not found"; + } + return -1; + } + + if (!util::streq(HEADER, StringRef{rb_.pos(), HEADER.size()})) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Bad PROXY protocol version 1 ID"; + } + return -1; + } + + rb_.drain(HEADER.size()); + + int family; + + if (rb_.pos()[0] == 'T') { + if (end - rb_.pos() < 5) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found"; + } + return -1; + } + + if (rb_.pos()[1] != 'C' || rb_.pos()[2] != 'P') { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family"; + } + return -1; + } + + switch (rb_.pos()[3]) { + case '4': + family = AF_INET; + break; + case '6': + family = AF_INET6; + break; + default: + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family"; + } + return -1; + } + + rb_.drain(5); + } else { + if (end - rb_.pos() < 7) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found"; + } + return -1; + } + if (!util::streq_l("UNKNOWN", rb_.pos(), 7)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family"; + } + return -1; + } + + rb_.drain(end + 2 - rb_.pos()); + + return on_proxy_protocol_finish(); + } + + // source address + auto token_end = std::find(rb_.pos(), end, ' '); + if (token_end == end) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Source address not found"; + } + return -1; + } + + *token_end = '\0'; + if (!util::numeric_host(reinterpret_cast(rb_.pos()), family)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source address"; + } + return -1; + } + + auto src_addr = rb_.pos(); + auto src_addrlen = token_end - rb_.pos(); + + rb_.drain(token_end - rb_.pos() + 1); + + // destination address + token_end = std::find(rb_.pos(), end, ' '); + if (token_end == end) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Destination address not found"; + } + return -1; + } + + *token_end = '\0'; + if (!util::numeric_host(reinterpret_cast(rb_.pos()), family)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination address"; + } + return -1; + } + + // Currently we don't use destination address + + rb_.drain(token_end - rb_.pos() + 1); + + // source port + auto n = parse_proxy_line_port(rb_.pos(), end); + if (n <= 0 || *(rb_.pos() + n) != ' ') { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source port"; + } + return -1; + } + + rb_.pos()[n] = '\0'; + auto src_port = rb_.pos(); + auto src_portlen = n; + + rb_.drain(n + 1); + + // destination port + n = parse_proxy_line_port(rb_.pos(), end); + if (n <= 0 || rb_.pos() + n != end) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination port"; + } + return -1; + } + + // Currently we don't use destination port + + rb_.drain(end + 2 - rb_.pos()); + + ipaddr_ = + make_string_ref(balloc_, StringRef{src_addr, src_addr + src_addrlen}); + port_ = make_string_ref(balloc_, StringRef{src_port, src_port + src_portlen}); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Finished, " << (rb_.pos() - first) + << " bytes read"; + } + + auto config = get_config(); + auto &fwdconf = config->http.forwarded; + + if ((fwdconf.params & FORWARDED_FOR) && + fwdconf.for_node_type == ForwardedNode::IP) { + init_forwarded_for(family, ipaddr_); + } + + return on_proxy_protocol_finish(); +} + +int ClientHandler::proxy_protocol_v2_read() { + // Assume that first str_size(PROXY_PROTO_V2_SIG) octets match v2 + // protocol signature and followed by the bytes which indicates v2. + assert(rb_.rleft() >= PROXY_PROTO_V2_HDLEN); + + auto p = rb_.pos() + str_size(PROXY_PROTO_V2_SIG); + + assert(((*p) & 0xf0) == 0x20); + + enum { LOCAL, PROXY } cmd; + + auto cmd_bits = (*p++) & 0xf; + switch (cmd_bits) { + case 0x0: + cmd = LOCAL; + break; + case 0x01: + cmd = PROXY; + break; + default: + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Unknown command " << log::hex + << cmd_bits; + } + return -1; + } + + auto fam = *p++; + uint16_t len; + memcpy(&len, p, sizeof(len)); + len = ntohs(len); + + p += sizeof(len); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Detected family=" << log::hex << fam + << ", len=" << log::dec << len; + } + + if (rb_.last() - p < len) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) + << "PROXY-protocol-v2: Prematurely truncated header block; require " + << len << " bytes, " << rb_.last() - p << " bytes left"; + } + return -1; + } + + int family; + std::array src_addr, + dst_addr; + size_t addrlen; + + switch (fam) { + case 0x11: + case 0x12: + if (len < 12) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_INET addresses"; + } + return -1; + } + family = AF_INET; + addrlen = 4; + break; + case 0x21: + case 0x22: + if (len < 36) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_INET6 addresses"; + } + return -1; + } + family = AF_INET6; + addrlen = 16; + break; + case 0x31: + case 0x32: + if (len < 216) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Too short AF_UNIX addresses"; + } + return -1; + } + // fall through + case 0x00: { + // UNSPEC and UNIX are just ignored. + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Ignore combination of address " + "family and protocol " + << log::hex << fam; + } + rb_.drain(PROXY_PROTO_V2_HDLEN + len); + return on_proxy_protocol_finish(); + } + default: + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Unknown combination of address " + "family and protocol " + << log::hex << fam; + } + return -1; + } + + if (cmd != PROXY) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Ignore non-PROXY command"; + } + rb_.drain(PROXY_PROTO_V2_HDLEN + len); + return on_proxy_protocol_finish(); + } + + if (inet_ntop(family, p, src_addr.data(), src_addr.size()) == nullptr) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Unable to parse source address"; + } + return -1; + } + + p += addrlen; + + if (inet_ntop(family, p, dst_addr.data(), dst_addr.size()) == nullptr) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) + << "PROXY-protocol-v2: Unable to parse destination address"; + } + return -1; + } + + p += addrlen; + + uint16_t src_port; + + memcpy(&src_port, p, sizeof(src_port)); + src_port = ntohs(src_port); + + // We don't use destination port. + p += 4; + + ipaddr_ = make_string_ref(balloc_, StringRef{src_addr.data()}); + port_ = util::make_string_ref_uint(balloc_, src_port); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v2: Finished reading proxy addresses, " + << p - rb_.pos() << " bytes read, " + << PROXY_PROTO_V2_HDLEN + len - (p - rb_.pos()) + << " bytes left"; + } + + auto config = get_config(); + auto &fwdconf = config->http.forwarded; + + if ((fwdconf.params & FORWARDED_FOR) && + fwdconf.for_node_type == ForwardedNode::IP) { + init_forwarded_for(family, ipaddr_); + } + + rb_.drain(PROXY_PROTO_V2_HDLEN + len); + return on_proxy_protocol_finish(); +} + +StringRef ClientHandler::get_forwarded_by() const { + auto &fwdconf = get_config()->http.forwarded; + + if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED) { + return fwdconf.by_obfuscated; + } + + return faddr_->hostport; +} + +StringRef ClientHandler::get_forwarded_for() const { return forwarded_for_; } + +const UpstreamAddr *ClientHandler::get_upstream_addr() const { return faddr_; } + +Connection *ClientHandler::get_connection() { return &conn_; }; + +void ClientHandler::set_tls_sni(const StringRef &sni) { + sni_ = make_string_ref(balloc_, sni); +} + +StringRef ClientHandler::get_tls_sni() const { return sni_; } + +StringRef ClientHandler::get_alpn() const { return alpn_; } + +BlockAllocator &ClientHandler::get_block_allocator() { return balloc_; } + +void ClientHandler::set_alpn_from_conn() { + const unsigned char *alpn; + unsigned int alpnlen; + + SSL_get0_alpn_selected(conn_.tls.ssl, &alpn, &alpnlen); + + alpn_ = make_string_ref(balloc_, StringRef{alpn, alpnlen}); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_client_handler.h b/lib/nghttp2/src/shrpx_client_handler.h new file mode 100644 index 00000000000..511dd9108e8 --- /dev/null +++ b/lib/nghttp2/src/shrpx_client_handler.h @@ -0,0 +1,236 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_CLIENT_HANDLER_H +#define SHRPX_CLIENT_HANDLER_H + +#include "shrpx.h" + +#include + +#include + +#include + +#include "shrpx_rate_limit.h" +#include "shrpx_connection.h" +#include "buffer.h" +#include "memchunk.h" +#include "allocator.h" + +using namespace nghttp2; + +namespace shrpx { + +class Upstream; +class DownstreamConnection; +class HttpsUpstream; +class ConnectBlocker; +class DownstreamConnectionPool; +class Worker; +class Downstream; +struct WorkerStat; +struct DownstreamAddrGroup; +struct SharedDownstreamAddr; +struct DownstreamAddr; +#ifdef ENABLE_HTTP3 +class Http3Upstream; +#endif // ENABLE_HTTP3 + +class ClientHandler { +public: + ClientHandler(Worker *worker, int fd, SSL *ssl, const StringRef &ipaddr, + const StringRef &port, int family, const UpstreamAddr *faddr); + ~ClientHandler(); + + int noop(); + // Performs clear text I/O + int read_clear(); + int write_clear(); + // Specialized for PROXY-protocol use; peek data from socket. + int proxy_protocol_peek_clear(); + // Performs TLS handshake + int tls_handshake(); + // Performs TLS I/O + int read_tls(); + int write_tls(); + + int upstream_noop(); + int upstream_read(); + int upstream_http2_connhd_read(); + int upstream_http1_connhd_read(); + int upstream_write(); + + int proxy_protocol_read(); + int proxy_protocol_v2_read(); + int on_proxy_protocol_finish(); + + // Performs I/O operation. Internally calls on_read()/on_write(). + int do_read(); + int do_write(); + + // Processes buffers. No underlying I/O operation will be done. + int on_read(); + int on_write(); + + struct ev_loop *get_loop() const; + void reset_upstream_read_timeout(ev_tstamp t); + void reset_upstream_write_timeout(ev_tstamp t); + + int validate_next_proto(); + const StringRef &get_ipaddr() const; + bool get_should_close_after_write() const; + void set_should_close_after_write(bool f); + Upstream *get_upstream(); + + void pool_downstream_connection(std::unique_ptr dconn); + void remove_downstream_connection(DownstreamConnection *dconn); + DownstreamAddr *get_downstream_addr(int &err, DownstreamAddrGroup *group, + Downstream *downstream); + // Returns DownstreamConnection object based on request path. This + // function returns non-null DownstreamConnection, and assigns 0 to + // |err| if it succeeds, or returns nullptr, and assigns negative + // error code to |err|. + std::unique_ptr + get_downstream_connection(int &err, Downstream *downstream); + MemchunkPool *get_mcpool(); + SSL *get_ssl() const; + // Call this function when HTTP/2 connection header is received at + // the start of the connection. + void direct_http2_upgrade(); + // Performs HTTP/2 Upgrade from the connection managed by + // |http|. If this function fails, the connection must be + // terminated. This function returns 0 if it succeeds, or -1. + int perform_http2_upgrade(HttpsUpstream *http); + bool get_http2_upgrade_allowed() const; + // Returns upstream scheme, either "http" or "https" + StringRef get_upstream_scheme() const; + void start_immediate_shutdown(); + + // Writes upstream accesslog using |downstream|. The |downstream| + // must not be nullptr. + void write_accesslog(Downstream *downstream); + + Worker *get_worker() const; + + // Initializes forwarded_for_. + void init_forwarded_for(int family, const StringRef &ipaddr); + + using ReadBuf = DefaultMemchunkBuffer; + + ReadBuf *get_rb(); + + RateLimit *get_rlimit(); + RateLimit *get_wlimit(); + + void signal_write(); + ev_io *get_wev(); + + void setup_upstream_io_callback(); + +#ifdef ENABLE_HTTP3 + void setup_http3_upstream(std::unique_ptr &&upstream); + int read_quic(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen); + int write_quic(); +#endif // ENABLE_HTTP3 + + // Returns string suitable for use in "by" parameter of Forwarded + // header field. + StringRef get_forwarded_by() const; + // Returns string suitable for use in "for" parameter of Forwarded + // header field. + StringRef get_forwarded_for() const; + + Http2Session * + get_http2_session(const std::shared_ptr &group, + DownstreamAddr *addr); + + // Returns an affinity cookie value for |downstream|. |cookie_name| + // is used to inspect cookie header field in request header fields. + uint32_t get_affinity_cookie(Downstream *downstream, + const StringRef &cookie_name); + + DownstreamAddr *get_downstream_addr_strict_affinity( + int &err, const std::shared_ptr &shared_addr, + Downstream *downstream); + + const UpstreamAddr *get_upstream_addr() const; + + void repeat_read_timer(); + void stop_read_timer(); + + Connection *get_connection(); + + // Stores |sni| which is TLS SNI extension value client sent in this + // connection. + void set_tls_sni(const StringRef &sni); + // Returns TLS SNI extension value client sent in this connection. + StringRef get_tls_sni() const; + + // Returns ALPN negotiated in this connection. + StringRef get_alpn() const; + + BlockAllocator &get_block_allocator(); + + void set_alpn_from_conn(); + +private: + // Allocator to allocate memory for connection-wide objects. Make + // sure that the allocations must be bounded, and not proportional + // to the number of requests. + BlockAllocator balloc_; + DefaultMemchunkBuffer rb_; + Connection conn_; + ev_timer reneg_shutdown_timer_; + std::unique_ptr upstream_; + // IP address of client. If UNIX domain socket is used, this is + // "localhost". + StringRef ipaddr_; + StringRef port_; + // The ALPN identifier negotiated for this connection. + StringRef alpn_; + // The client address used in "for" parameter of Forwarded header + // field. + StringRef forwarded_for_; + // lowercased TLS SNI which client sent. + StringRef sni_; + std::function read_, write_; + std::function on_read_, on_write_; + // Address of frontend listening socket + const UpstreamAddr *faddr_; + Worker *worker_; + // The number of bytes of HTTP/2 client connection header to read + size_t left_connhd_len_; + // hash for session affinity using client IP + uint32_t affinity_hash_; + bool should_close_after_write_; + // true if affinity_hash_ is computed + bool affinity_hash_computed_; +}; + +} // namespace shrpx + +#endif // SHRPX_CLIENT_HANDLER_H diff --git a/lib/nghttp2/src/shrpx_config.cc b/lib/nghttp2/src/shrpx_config.cc new file mode 100644 index 00000000000..bcb48ebccef --- /dev/null +++ b/lib/nghttp2/src/shrpx_config.cc @@ -0,0 +1,4687 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_config.h" + +#ifdef HAVE_PWD_H +# include +#endif // HAVE_PWD_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#ifdef HAVE_SYSLOG_H +# include +#endif // HAVE_SYSLOG_H +#include +#include +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include + +#include +#include +#include +#include +#include + +#include + +#include "url-parser/url_parser.h" + +#include "shrpx_log.h" +#include "shrpx_tls.h" +#include "shrpx_http.h" +#ifdef HAVE_MRUBY +# include "shrpx_mruby.h" +#endif // HAVE_MRUBY +#include "util.h" +#include "base64.h" +#include "ssl_compat.h" +#include "xsi_strerror.h" + +namespace shrpx { + +namespace { +Config *config; +} // namespace + +constexpr auto SHRPX_UNIX_PATH_PREFIX = StringRef::from_lit("unix:"); + +const Config *get_config() { return config; } + +Config *mod_config() { return config; } + +std::unique_ptr replace_config(std::unique_ptr another) { + auto p = config; + config = another.release(); + return std::unique_ptr(p); +} + +void create_config() { config = new Config(); } + +Config::~Config() { + auto &upstreamconf = http2.upstream; + + nghttp2_option_del(upstreamconf.option); + nghttp2_option_del(upstreamconf.alt_mode_option); + nghttp2_session_callbacks_del(upstreamconf.callbacks); + + auto &downstreamconf = http2.downstream; + + nghttp2_option_del(downstreamconf.option); + nghttp2_session_callbacks_del(downstreamconf.callbacks); + + auto &dumpconf = http2.upstream.debug.dump; + + if (dumpconf.request_header) { + fclose(dumpconf.request_header); + } + + if (dumpconf.response_header) { + fclose(dumpconf.response_header); + } +} + +TicketKeys::~TicketKeys() { + /* Erase keys from memory */ + for (auto &key : keys) { + memset(&key, 0, sizeof(key)); + } +} + +namespace { +int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, + const StringRef &hostport, const StringRef &opt) { + // host and port in |hostport| is separated by single ','. + auto sep = std::find(std::begin(hostport), std::end(hostport), ','); + if (sep == std::end(hostport)) { + LOG(ERROR) << opt << ": Invalid host, port: " << hostport; + return -1; + } + size_t len = sep - std::begin(hostport); + if (hostlen < len + 1) { + LOG(ERROR) << opt << ": Hostname too long: " << hostport; + return -1; + } + std::copy(std::begin(hostport), sep, host); + host[len] = '\0'; + + auto portstr = StringRef{sep + 1, std::end(hostport)}; + auto d = util::parse_uint(portstr); + if (1 <= d && d <= std::numeric_limits::max()) { + *port_ptr = d; + return 0; + } + + LOG(ERROR) << opt << ": Port is invalid: " << portstr; + return -1; +} +} // namespace + +namespace { +bool is_secure(const StringRef &filename) { + struct stat buf; + int rv = stat(filename.c_str(), &buf); + if (rv == 0) { + if ((buf.st_mode & S_IRWXU) && !(buf.st_mode & S_IRWXG) && + !(buf.st_mode & S_IRWXO)) { + return true; + } + } + + return false; +} +} // namespace + +std::unique_ptr +read_tls_ticket_key_file(const std::vector &files, + const EVP_CIPHER *cipher, const EVP_MD *hmac) { + auto ticket_keys = std::make_unique(); + auto &keys = ticket_keys->keys; + keys.resize(files.size()); + auto enc_keylen = EVP_CIPHER_key_length(cipher); + auto hmac_keylen = EVP_MD_size(hmac); + if (cipher == EVP_aes_128_cbc()) { + // backward compatibility, as a legacy of using same file format + // with nginx and apache. + hmac_keylen = 16; + } + auto expectedlen = keys[0].data.name.size() + enc_keylen + hmac_keylen; + char buf[256]; + assert(sizeof(buf) >= expectedlen); + + size_t i = 0; + for (auto &file : files) { + struct stat fst {}; + + if (stat(file.c_str(), &fst) == -1) { + auto error = errno; + LOG(ERROR) << "tls-ticket-key-file: could not stat file " << file + << ", errno=" << error; + return nullptr; + } + + if (static_cast(fst.st_size) != expectedlen) { + LOG(ERROR) << "tls-ticket-key-file: the expected file size is " + << expectedlen << ", the actual file size is " << fst.st_size; + return nullptr; + } + + std::ifstream f(file.c_str()); + if (!f) { + LOG(ERROR) << "tls-ticket-key-file: could not open file " << file; + return nullptr; + } + + f.read(buf, expectedlen); + if (static_cast(f.gcount()) != expectedlen) { + LOG(ERROR) << "tls-ticket-key-file: want to read " << expectedlen + << " bytes but only read " << f.gcount() << " bytes from " + << file; + return nullptr; + } + + auto &key = keys[i++]; + key.cipher = cipher; + key.hmac = hmac; + key.hmac_keylen = hmac_keylen; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "enc_keylen=" << enc_keylen + << ", hmac_keylen=" << key.hmac_keylen; + } + + auto p = buf; + std::copy_n(p, key.data.name.size(), std::begin(key.data.name)); + p += key.data.name.size(); + std::copy_n(p, enc_keylen, std::begin(key.data.enc_key)); + p += enc_keylen; + std::copy_n(p, hmac_keylen, std::begin(key.data.hmac_key)); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "session ticket key: " << util::format_hex(key.data.name); + } + } + return ticket_keys; +} + +#ifdef ENABLE_HTTP3 +std::shared_ptr +read_quic_secret_file(const StringRef &path) { + constexpr size_t expectedlen = + SHRPX_QUIC_SECRET_RESERVEDLEN + SHRPX_QUIC_SECRETLEN + SHRPX_QUIC_SALTLEN; + + auto qkms = std::make_shared(); + auto &kms = qkms->keying_materials; + + std::ifstream f(path.c_str()); + if (!f) { + LOG(ERROR) << "frontend-quic-secret-file: could not open file " << path; + return nullptr; + } + + std::array buf; + + while (f.getline(buf.data(), buf.size())) { + auto len = strlen(buf.data()); + if (len == 0 || buf[0] == '#') { + continue; + } + + auto s = StringRef{std::begin(buf), std::begin(buf) + len}; + if (s.size() != expectedlen * 2 || !util::is_hex_string(s)) { + LOG(ERROR) << "frontend-quic-secret-file: each line must be a " + << expectedlen * 2 << " bytes hex encoded string"; + return nullptr; + } + + kms.emplace_back(); + auto &qkm = kms.back(); + + auto p = std::begin(s); + + util::decode_hex(std::begin(qkm.reserved), + StringRef{p, p + qkm.reserved.size()}); + p += qkm.reserved.size() * 2; + util::decode_hex(std::begin(qkm.secret), + StringRef{p, p + qkm.secret.size()}); + p += qkm.secret.size() * 2; + util::decode_hex(std::begin(qkm.salt), StringRef{p, p + qkm.salt.size()}); + p += qkm.salt.size() * 2; + + assert(static_cast(p - std::begin(s)) == expectedlen * 2); + + qkm.id = qkm.reserved[0] & 0xc0; + + if (kms.size() == 4) { + break; + } + } + + if (f.bad() || (!f.eof() && f.fail())) { + LOG(ERROR) + << "frontend-quic-secret-file: error occurred while reading file " + << path; + return nullptr; + } + + if (kms.empty()) { + LOG(WARN) + << "frontend-quic-secret-file: no keying materials are present in file " + << path; + return nullptr; + } + + return qkms; +} +#endif // ENABLE_HTTP3 + +FILE *open_file_for_write(const char *filename) { + std::array errbuf; + +#ifdef O_CLOEXEC + auto fd = open(filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); +#else + auto fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + + // We get race condition if execve is called at the same time. + if (fd != -1) { + util::make_socket_closeonexec(fd); + } +#endif + if (fd == -1) { + auto error = errno; + LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return nullptr; + } + auto f = fdopen(fd, "wb"); + if (f == nullptr) { + auto error = errno; + LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return nullptr; + } + + return f; +} + +namespace { +// Read passwd from |filename| +std::string read_passwd_from_file(const StringRef &opt, + const StringRef &filename) { + std::string line; + + if (!is_secure(filename)) { + LOG(ERROR) << opt << ": Private key passwd file " << filename + << " has insecure mode."; + return line; + } + + std::ifstream in(filename.c_str(), std::ios::binary); + if (!in) { + LOG(ERROR) << opt << ": Could not open key passwd file " << filename; + return line; + } + + std::getline(in, line); + return line; +} +} // namespace + +HeaderRefs::value_type parse_header(BlockAllocator &balloc, + const StringRef &optarg) { + auto colon = std::find(std::begin(optarg), std::end(optarg), ':'); + + if (colon == std::end(optarg) || colon == std::begin(optarg)) { + return {}; + } + + auto value = colon + 1; + for (; *value == '\t' || *value == ' '; ++value) + ; + + auto name_iov = + make_byte_ref(balloc, std::distance(std::begin(optarg), colon) + 1); + auto p = name_iov.base; + p = std::copy(std::begin(optarg), colon, p); + util::inp_strlower(name_iov.base, p); + *p = '\0'; + + auto nv = + HeaderRef(StringRef{name_iov.base, p}, + make_string_ref(balloc, StringRef{value, std::end(optarg)})); + + if (!nghttp2_check_header_name(nv.name.byte(), nv.name.size()) || + !nghttp2_check_header_value_rfc9113(nv.value.byte(), nv.value.size())) { + return {}; + } + + return nv; +} + +template +int parse_uint(T *dest, const StringRef &opt, const StringRef &optarg) { + auto val = util::parse_uint(optarg); + if (val == -1) { + LOG(ERROR) << opt << ": bad value. Specify an integer >= 0."; + return -1; + } + + *dest = val; + + return 0; +} + +namespace { +template +int parse_uint_with_unit(T *dest, const StringRef &opt, + const StringRef &optarg) { + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + LOG(ERROR) << opt << ": bad value: '" << optarg << "'"; + return -1; + } + + if (static_cast(std::numeric_limits::max()) < + static_cast(n)) { + LOG(ERROR) << opt + << ": too large. The value should be less than or equal to " + << std::numeric_limits::max(); + return -1; + } + + *dest = n; + + return 0; +} +} // namespace + +namespace { +int parse_altsvc(AltSvc &altsvc, const StringRef &opt, + const StringRef &optarg) { + // PROTOID, PORT, HOST, ORIGIN, PARAMS. + auto tokens = util::split_str(optarg, ',', 5); + + if (tokens.size() < 2) { + // Requires at least protocol_id and port + LOG(ERROR) << opt << ": too few parameters: " << optarg; + return -1; + } + + int port; + + if (parse_uint(&port, opt, tokens[1]) != 0) { + return -1; + } + + if (port < 1 || + port > static_cast(std::numeric_limits::max())) { + LOG(ERROR) << opt << ": port is invalid: " << tokens[1]; + return -1; + } + + altsvc.protocol_id = make_string_ref(config->balloc, tokens[0]); + + altsvc.port = port; + altsvc.service = make_string_ref(config->balloc, tokens[1]); + + if (tokens.size() > 2) { + if (!tokens[2].empty()) { + altsvc.host = make_string_ref(config->balloc, tokens[2]); + } + + if (tokens.size() > 3) { + if (!tokens[3].empty()) { + altsvc.origin = make_string_ref(config->balloc, tokens[3]); + } + + if (tokens.size() > 4) { + if (!tokens[4].empty()) { + altsvc.params = make_string_ref(config->balloc, tokens[4]); + } + } + } + } + + return 0; +} +} // namespace + +namespace { +// generated by gennghttpxfun.py +LogFragmentType log_var_lookup_token(const char *name, size_t namelen) { + switch (namelen) { + case 3: + switch (name[2]) { + case 'd': + if (util::strieq_l("pi", name, 2)) { + return LogFragmentType::PID; + } + break; + } + break; + case 4: + switch (name[3]) { + case 'h': + if (util::strieq_l("pat", name, 3)) { + return LogFragmentType::PATH; + } + break; + case 'n': + if (util::strieq_l("alp", name, 3)) { + return LogFragmentType::ALPN; + } + break; + } + break; + case 6: + switch (name[5]) { + case 'd': + if (util::strieq_l("metho", name, 5)) { + return LogFragmentType::METHOD; + } + break; + case 's': + if (util::strieq_l("statu", name, 5)) { + return LogFragmentType::STATUS; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'i': + if (util::strieq_l("tls_sn", name, 6)) { + return LogFragmentType::TLS_SNI; + } + break; + case 't': + if (util::strieq_l("reques", name, 6)) { + return LogFragmentType::REQUEST; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'l': + if (util::strieq_l("time_loca", name, 9)) { + return LogFragmentType::TIME_LOCAL; + } + break; + case 'r': + if (util::strieq_l("ssl_ciphe", name, 9)) { + return LogFragmentType::SSL_CIPHER; + } + if (util::strieq_l("tls_ciphe", name, 9)) { + return LogFragmentType::TLS_CIPHER; + } + break; + } + break; + case 11: + switch (name[10]) { + case 'r': + if (util::strieq_l("remote_add", name, 10)) { + return LogFragmentType::REMOTE_ADDR; + } + break; + case 't': + if (util::strieq_l("remote_por", name, 10)) { + return LogFragmentType::REMOTE_PORT; + } + if (util::strieq_l("server_por", name, 10)) { + return LogFragmentType::SERVER_PORT; + } + break; + } + break; + case 12: + switch (name[11]) { + case '1': + if (util::strieq_l("time_iso860", name, 11)) { + return LogFragmentType::TIME_ISO8601; + } + break; + case 'e': + if (util::strieq_l("request_tim", name, 11)) { + return LogFragmentType::REQUEST_TIME; + } + break; + case 'l': + if (util::strieq_l("ssl_protoco", name, 11)) { + return LogFragmentType::SSL_PROTOCOL; + } + if (util::strieq_l("tls_protoco", name, 11)) { + return LogFragmentType::TLS_PROTOCOL; + } + break; + case 't': + if (util::strieq_l("backend_hos", name, 11)) { + return LogFragmentType::BACKEND_HOST; + } + if (util::strieq_l("backend_por", name, 11)) { + return LogFragmentType::BACKEND_PORT; + } + break; + } + break; + case 14: + switch (name[13]) { + case 'd': + if (util::strieq_l("ssl_session_i", name, 13)) { + return LogFragmentType::SSL_SESSION_ID; + } + if (util::strieq_l("tls_session_i", name, 13)) { + return LogFragmentType::TLS_SESSION_ID; + } + break; + } + break; + case 15: + switch (name[14]) { + case 't': + if (util::strieq_l("body_bytes_sen", name, 14)) { + return LogFragmentType::BODY_BYTES_SENT; + } + break; + } + break; + case 16: + switch (name[15]) { + case 'n': + if (util::strieq_l("protocol_versio", name, 15)) { + return LogFragmentType::PROTOCOL_VERSION; + } + break; + } + break; + case 17: + switch (name[16]) { + case 'l': + if (util::strieq_l("tls_client_seria", name, 16)) { + return LogFragmentType::TLS_CLIENT_SERIAL; + } + break; + } + break; + case 18: + switch (name[17]) { + case 'd': + if (util::strieq_l("ssl_session_reuse", name, 17)) { + return LogFragmentType::SSL_SESSION_REUSED; + } + if (util::strieq_l("tls_session_reuse", name, 17)) { + return LogFragmentType::TLS_SESSION_REUSED; + } + break; + case 'y': + if (util::strieq_l("path_without_quer", name, 17)) { + return LogFragmentType::PATH_WITHOUT_QUERY; + } + break; + } + break; + case 22: + switch (name[21]) { + case 'e': + if (util::strieq_l("tls_client_issuer_nam", name, 21)) { + return LogFragmentType::TLS_CLIENT_ISSUER_NAME; + } + break; + } + break; + case 23: + switch (name[22]) { + case 'e': + if (util::strieq_l("tls_client_subject_nam", name, 22)) { + return LogFragmentType::TLS_CLIENT_SUBJECT_NAME; + } + break; + } + break; + case 27: + switch (name[26]) { + case '1': + if (util::strieq_l("tls_client_fingerprint_sha", name, 26)) { + return LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA1; + } + break; + } + break; + case 29: + switch (name[28]) { + case '6': + if (util::strieq_l("tls_client_fingerprint_sha25", name, 28)) { + return LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256; + } + break; + } + break; + } + return LogFragmentType::NONE; +} +} // namespace + +namespace { +bool var_token(char c) { + return util::is_alpha(c) || util::is_digit(c) || c == '_'; +} +} // namespace + +std::vector parse_log_format(BlockAllocator &balloc, + const StringRef &optarg) { + auto literal_start = std::begin(optarg); + auto p = literal_start; + auto eop = std::end(optarg); + + auto res = std::vector(); + + for (; p != eop;) { + if (*p != '$') { + ++p; + continue; + } + + auto var_start = p; + + ++p; + + const char *var_name; + size_t var_namelen; + if (p != eop && *p == '{') { + var_name = ++p; + for (; p != eop && var_token(*p); ++p) + ; + + if (p == eop || *p != '}') { + LOG(WARN) << "Missing '}' after " << StringRef{var_start, p}; + continue; + } + + var_namelen = p - var_name; + ++p; + } else { + var_name = p; + for (; p != eop && var_token(*p); ++p) + ; + + var_namelen = p - var_name; + } + + const char *value = nullptr; + + auto type = log_var_lookup_token(var_name, var_namelen); + + if (type == LogFragmentType::NONE) { + if (util::istarts_with_l(StringRef{var_name, var_namelen}, "http_")) { + if (util::streq_l("host", StringRef{var_name + str_size("http_"), + var_namelen - str_size("http_")})) { + // Special handling of host header field. We will use + // :authority header field if host header is missing. This + // is a typical case in HTTP/2. + type = LogFragmentType::AUTHORITY; + } else { + type = LogFragmentType::HTTP; + value = var_name + str_size("http_"); + } + } else { + LOG(WARN) << "Unrecognized log format variable: " + << StringRef{var_name, var_namelen}; + continue; + } + } + + if (literal_start < var_start) { + res.emplace_back( + LogFragmentType::LITERAL, + make_string_ref(balloc, StringRef{literal_start, var_start})); + } + + literal_start = p; + + if (value == nullptr) { + res.emplace_back(type); + continue; + } + + { + auto iov = make_byte_ref( + balloc, std::distance(value, var_name + var_namelen) + 1); + auto p = iov.base; + p = std::copy(value, var_name + var_namelen, p); + for (auto cp = iov.base; cp != p; ++cp) { + if (*cp == '_') { + *cp = '-'; + } + } + *p = '\0'; + res.emplace_back(type, StringRef{iov.base, p}); + } + } + + if (literal_start != eop) { + res.emplace_back(LogFragmentType::LITERAL, + make_string_ref(balloc, StringRef{literal_start, eop})); + } + + return res; +} + +namespace { +int parse_address_family(int *dest, const StringRef &opt, + const StringRef &optarg) { + if (util::strieq_l("auto", optarg)) { + *dest = AF_UNSPEC; + return 0; + } + if (util::strieq_l("IPv4", optarg)) { + *dest = AF_INET; + return 0; + } + if (util::strieq_l("IPv6", optarg)) { + *dest = AF_INET6; + return 0; + } + + LOG(ERROR) << opt << ": bad value: '" << optarg << "'"; + return -1; +} +} // namespace + +namespace { +int parse_duration(ev_tstamp *dest, const StringRef &opt, + const StringRef &optarg) { + auto t = util::parse_duration_with_unit(optarg); + if (t == std::numeric_limits::infinity()) { + LOG(ERROR) << opt << ": bad value: '" << optarg << "'"; + return -1; + } + + *dest = t; + + return 0; +} +} // namespace + +namespace { +int parse_tls_proto_version(int &dest, const StringRef &opt, + const StringRef &optarg) { + auto v = tls::proto_version_from_string(optarg); + if (v == -1) { + LOG(ERROR) << opt << ": invalid TLS protocol version: " << optarg; + return -1; + } + + dest = v; + + return 0; +} +} // namespace + +struct MemcachedConnectionParams { + bool tls; +}; + +namespace { +// Parses memcached connection configuration parameter |src_params|, +// and stores parsed results into |out|. This function returns 0 if +// it succeeds, or -1. +int parse_memcached_connection_params(MemcachedConnectionParams &out, + const StringRef &src_params, + const StringRef &opt) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::strieq_l("tls", param)) { + out.tls = true; + } else if (util::strieq_l("no-tls", param)) { + out.tls = false; + } else if (!param.empty()) { + LOG(ERROR) << opt << ": " << param << ": unknown keyword"; + return -1; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +struct UpstreamParams { + UpstreamAltMode alt_mode; + bool tls; + bool sni_fwd; + bool proxyproto; + bool quic; +}; + +namespace { +// Parses upstream configuration parameter |src_params|, and stores +// parsed results into |out|. This function returns 0 if it succeeds, +// or -1. +int parse_upstream_params(UpstreamParams &out, const StringRef &src_params) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::strieq_l("tls", param)) { + out.tls = true; + } else if (util::strieq_l("sni-fwd", param)) { + out.sni_fwd = true; + } else if (util::strieq_l("no-tls", param)) { + out.tls = false; + } else if (util::strieq_l("api", param)) { + if (out.alt_mode != UpstreamAltMode::NONE && + out.alt_mode != UpstreamAltMode::API) { + LOG(ERROR) << "frontend: api and healthmon are mutually exclusive"; + return -1; + } + out.alt_mode = UpstreamAltMode::API; + } else if (util::strieq_l("healthmon", param)) { + if (out.alt_mode != UpstreamAltMode::NONE && + out.alt_mode != UpstreamAltMode::HEALTHMON) { + LOG(ERROR) << "frontend: api and healthmon are mutually exclusive"; + return -1; + } + out.alt_mode = UpstreamAltMode::HEALTHMON; + } else if (util::strieq_l("proxyproto", param)) { + out.proxyproto = true; + } else if (util::strieq_l("quic", param)) { +#ifdef ENABLE_HTTP3 + out.quic = true; +#else // !ENABLE_HTTP3 + LOG(ERROR) << "quic: QUIC is disabled at compile time"; + return -1; +#endif // !ENABLE_HTTP3 + } else if (!param.empty()) { + LOG(ERROR) << "frontend: " << param << ": unknown keyword"; + return -1; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +struct DownstreamParams { + StringRef sni; + StringRef mruby; + StringRef group; + AffinityConfig affinity; + ev_tstamp read_timeout; + ev_tstamp write_timeout; + size_t fall; + size_t rise; + uint32_t weight; + uint32_t group_weight; + Proto proto; + bool tls; + bool dns; + bool redirect_if_not_tls; + bool upgrade_scheme; + bool dnf; +}; + +namespace { +// Parses |value| of parameter named |name| as duration. This +// function returns 0 if it succeeds and the parsed value is assigned +// to |dest|, or -1. +int parse_downstream_param_duration(ev_tstamp &dest, const StringRef &name, + const StringRef &value) { + auto t = util::parse_duration_with_unit(value); + if (t == std::numeric_limits::infinity()) { + LOG(ERROR) << "backend: " << name << ": bad value: '" << value << "'"; + return -1; + } + dest = t; + return 0; +} +} // namespace + +namespace { +// Parses downstream configuration parameter |src_params|, and stores +// parsed results into |out|. This function returns 0 if it succeeds, +// or -1. +int parse_downstream_params(DownstreamParams &out, + const StringRef &src_params) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::istarts_with_l(param, "proto=")) { + auto protostr = StringRef{first + str_size("proto="), end}; + if (protostr.empty()) { + LOG(ERROR) << "backend: proto: protocol is empty"; + return -1; + } + + if (util::streq_l("h2", std::begin(protostr), protostr.size())) { + out.proto = Proto::HTTP2; + } else if (util::streq_l("http/1.1", std::begin(protostr), + protostr.size())) { + out.proto = Proto::HTTP1; + } else { + LOG(ERROR) << "backend: proto: unknown protocol " << protostr; + return -1; + } + } else if (util::istarts_with_l(param, "fall=")) { + auto valstr = StringRef{first + str_size("fall="), end}; + if (valstr.empty()) { + LOG(ERROR) << "backend: fall: non-negative integer is expected"; + return -1; + } + + auto n = util::parse_uint(valstr); + if (n == -1) { + LOG(ERROR) << "backend: fall: non-negative integer is expected"; + return -1; + } + + out.fall = n; + } else if (util::istarts_with_l(param, "rise=")) { + auto valstr = StringRef{first + str_size("rise="), end}; + if (valstr.empty()) { + LOG(ERROR) << "backend: rise: non-negative integer is expected"; + return -1; + } + + auto n = util::parse_uint(valstr); + if (n == -1) { + LOG(ERROR) << "backend: rise: non-negative integer is expected"; + return -1; + } + + out.rise = n; + } else if (util::strieq_l("tls", param)) { + out.tls = true; + } else if (util::strieq_l("no-tls", param)) { + out.tls = false; + } else if (util::istarts_with_l(param, "sni=")) { + out.sni = StringRef{first + str_size("sni="), end}; + } else if (util::istarts_with_l(param, "affinity=")) { + auto valstr = StringRef{first + str_size("affinity="), end}; + if (util::strieq_l("none", valstr)) { + out.affinity.type = SessionAffinity::NONE; + } else if (util::strieq_l("ip", valstr)) { + out.affinity.type = SessionAffinity::IP; + } else if (util::strieq_l("cookie", valstr)) { + out.affinity.type = SessionAffinity::COOKIE; + } else { + LOG(ERROR) + << "backend: affinity: value must be one of none, ip, and cookie"; + return -1; + } + } else if (util::istarts_with_l(param, "affinity-cookie-name=")) { + auto val = StringRef{first + str_size("affinity-cookie-name="), end}; + if (val.empty()) { + LOG(ERROR) + << "backend: affinity-cookie-name: non empty string is expected"; + return -1; + } + out.affinity.cookie.name = val; + } else if (util::istarts_with_l(param, "affinity-cookie-path=")) { + out.affinity.cookie.path = + StringRef{first + str_size("affinity-cookie-path="), end}; + } else if (util::istarts_with_l(param, "affinity-cookie-secure=")) { + auto valstr = StringRef{first + str_size("affinity-cookie-secure="), end}; + if (util::strieq_l("auto", valstr)) { + out.affinity.cookie.secure = SessionAffinityCookieSecure::AUTO; + } else if (util::strieq_l("yes", valstr)) { + out.affinity.cookie.secure = SessionAffinityCookieSecure::YES; + } else if (util::strieq_l("no", valstr)) { + out.affinity.cookie.secure = SessionAffinityCookieSecure::NO; + } else { + LOG(ERROR) << "backend: affinity-cookie-secure: value must be one of " + "auto, yes, and no"; + return -1; + } + } else if (util::istarts_with_l(param, "affinity-cookie-stickiness=")) { + auto valstr = + StringRef{first + str_size("affinity-cookie-stickiness="), end}; + if (util::strieq_l("loose", valstr)) { + out.affinity.cookie.stickiness = SessionAffinityCookieStickiness::LOOSE; + } else if (util::strieq_l("strict", valstr)) { + out.affinity.cookie.stickiness = + SessionAffinityCookieStickiness::STRICT; + } else { + LOG(ERROR) << "backend: affinity-cookie-stickiness: value must be " + "either loose or strict"; + return -1; + } + } else if (util::strieq_l("dns", param)) { + out.dns = true; + } else if (util::strieq_l("redirect-if-not-tls", param)) { + out.redirect_if_not_tls = true; + } else if (util::strieq_l("upgrade-scheme", param)) { + out.upgrade_scheme = true; + } else if (util::istarts_with_l(param, "mruby=")) { + auto valstr = StringRef{first + str_size("mruby="), end}; + out.mruby = valstr; + } else if (util::istarts_with_l(param, "read-timeout=")) { + if (parse_downstream_param_duration( + out.read_timeout, StringRef::from_lit("read-timeout"), + StringRef{first + str_size("read-timeout="), end}) == -1) { + return -1; + } + } else if (util::istarts_with_l(param, "write-timeout=")) { + if (parse_downstream_param_duration( + out.write_timeout, StringRef::from_lit("write-timeout"), + StringRef{first + str_size("write-timeout="), end}) == -1) { + return -1; + } + } else if (util::istarts_with_l(param, "weight=")) { + auto valstr = StringRef{first + str_size("weight="), end}; + if (valstr.empty()) { + LOG(ERROR) + << "backend: weight: non-negative integer [1, 256] is expected"; + return -1; + } + + auto n = util::parse_uint(valstr); + if (n < 1 || n > 256) { + LOG(ERROR) + << "backend: weight: non-negative integer [1, 256] is expected"; + return -1; + } + out.weight = n; + } else if (util::istarts_with_l(param, "group=")) { + auto valstr = StringRef{first + str_size("group="), end}; + if (valstr.empty()) { + LOG(ERROR) << "backend: group: empty string is not allowed"; + return -1; + } + out.group = valstr; + } else if (util::istarts_with_l(param, "group-weight=")) { + auto valstr = StringRef{first + str_size("group-weight="), end}; + if (valstr.empty()) { + LOG(ERROR) << "backend: group-weight: non-negative integer [1, 256] is " + "expected"; + return -1; + } + + auto n = util::parse_uint(valstr); + if (n < 1 || n > 256) { + LOG(ERROR) << "backend: group-weight: non-negative integer [1, 256] is " + "expected"; + return -1; + } + out.group_weight = n; + } else if (util::strieq_l("dnf", param)) { + out.dnf = true; + } else if (!param.empty()) { + LOG(ERROR) << "backend: " << param << ": unknown keyword"; + return -1; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +namespace { +// Parses host-path mapping patterns in |src_pattern|, and stores +// mappings in config. We will store each host-path pattern found in +// |src| with |addr|. |addr| will be copied accordingly. Also we +// make a group based on the pattern. The "/" pattern is considered +// as catch-all. We also parse protocol specified in |src_proto|. +// +// This function returns 0 if it succeeds, or -1. +int parse_mapping(Config *config, DownstreamAddrConfig &addr, + std::map &pattern_addr_indexer, + const StringRef &src_pattern, const StringRef &src_params) { + // This returns at least 1 element (it could be empty string). We + // will append '/' to all patterns, so it becomes catch-all pattern. + auto mapping = util::split_str(src_pattern, ':'); + assert(!mapping.empty()); + auto &downstreamconf = *config->conn.downstream; + auto &addr_groups = downstreamconf.addr_groups; + + DownstreamParams params{}; + params.proto = Proto::HTTP1; + params.weight = 1; + + if (parse_downstream_params(params, src_params) != 0) { + return -1; + } + + if (addr.host_unix && params.dns) { + LOG(ERROR) << "backend: dns: cannot be used for UNIX domain socket"; + return -1; + } + + if (params.affinity.type == SessionAffinity::COOKIE && + params.affinity.cookie.name.empty()) { + LOG(ERROR) << "backend: affinity-cookie-name is mandatory if " + "affinity=cookie is specified"; + return -1; + } + + addr.fall = params.fall; + addr.rise = params.rise; + addr.weight = params.weight; + addr.group = make_string_ref(downstreamconf.balloc, params.group); + addr.group_weight = params.group_weight; + addr.proto = params.proto; + addr.tls = params.tls; + addr.sni = make_string_ref(downstreamconf.balloc, params.sni); + addr.dns = params.dns; + addr.upgrade_scheme = params.upgrade_scheme; + addr.dnf = params.dnf; + + auto &routerconf = downstreamconf.router; + auto &router = routerconf.router; + auto &rw_router = routerconf.rev_wildcard_router; + auto &wildcard_patterns = routerconf.wildcard_patterns; + + for (const auto &raw_pattern : mapping) { + StringRef pattern; + auto slash = std::find(std::begin(raw_pattern), std::end(raw_pattern), '/'); + if (slash == std::end(raw_pattern)) { + // This effectively makes empty pattern to "/". 2 for '/' and + // terminal NULL character. + auto iov = make_byte_ref(downstreamconf.balloc, raw_pattern.size() + 2); + auto p = iov.base; + p = std::copy(std::begin(raw_pattern), std::end(raw_pattern), p); + util::inp_strlower(iov.base, p); + *p++ = '/'; + *p = '\0'; + pattern = StringRef{iov.base, p}; + } else { + auto path = http2::normalize_path_colon( + downstreamconf.balloc, StringRef{slash, std::end(raw_pattern)}, + StringRef{}); + auto iov = make_byte_ref(downstreamconf.balloc, + std::distance(std::begin(raw_pattern), slash) + + path.size() + 1); + auto p = iov.base; + p = std::copy(std::begin(raw_pattern), slash, p); + util::inp_strlower(iov.base, p); + p = std::copy(std::begin(path), std::end(path), p); + *p = '\0'; + pattern = StringRef{iov.base, p}; + } + auto it = pattern_addr_indexer.find(pattern); + if (it != std::end(pattern_addr_indexer)) { + auto &g = addr_groups[(*it).second]; + // Last value wins if we have multiple different affinity + // value under one group. + if (params.affinity.type != SessionAffinity::NONE) { + if (g.affinity.type == SessionAffinity::NONE) { + g.affinity.type = params.affinity.type; + if (params.affinity.type == SessionAffinity::COOKIE) { + g.affinity.cookie.name = make_string_ref( + downstreamconf.balloc, params.affinity.cookie.name); + if (!params.affinity.cookie.path.empty()) { + g.affinity.cookie.path = make_string_ref( + downstreamconf.balloc, params.affinity.cookie.path); + } + g.affinity.cookie.secure = params.affinity.cookie.secure; + g.affinity.cookie.stickiness = params.affinity.cookie.stickiness; + } + } else if (g.affinity.type != params.affinity.type || + g.affinity.cookie.name != params.affinity.cookie.name || + g.affinity.cookie.path != params.affinity.cookie.path || + g.affinity.cookie.secure != params.affinity.cookie.secure || + g.affinity.cookie.stickiness != + params.affinity.cookie.stickiness) { + LOG(ERROR) << "backend: affinity: multiple different affinity " + "configurations found in a single group"; + return -1; + } + } + // If at least one backend requires frontend TLS connection, + // enable it for all backends sharing the same pattern. + if (params.redirect_if_not_tls) { + g.redirect_if_not_tls = true; + } + // All backends in the same group must have the same mruby path. + // If some backends do not specify mruby file, and there is at + // least one backend with mruby file, it is used for all + // backends in the group. + if (!params.mruby.empty()) { + if (g.mruby_file.empty()) { + g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby); + } else if (g.mruby_file != params.mruby) { + LOG(ERROR) << "backend: mruby: multiple different mruby file found " + "in a single group"; + return -1; + } + } + // All backends in the same group must have the same read/write + // timeout. If some backends do not specify read/write timeout, + // and there is at least one backend with read/write timeout, it + // is used for all backends in the group. + if (params.read_timeout > 1e-9) { + if (g.timeout.read < 1e-9) { + g.timeout.read = params.read_timeout; + } else if (fabs(g.timeout.read - params.read_timeout) > 1e-9) { + LOG(ERROR) + << "backend: read-timeout: multiple different read-timeout " + "found in a single group"; + return -1; + } + } + if (params.write_timeout > 1e-9) { + if (g.timeout.write < 1e-9) { + g.timeout.write = params.write_timeout; + } else if (fabs(g.timeout.write - params.write_timeout) > 1e-9) { + LOG(ERROR) << "backend: write-timeout: multiple different " + "write-timeout found in a single group"; + return -1; + } + } + // All backends in the same group must have the same dnf + // setting. If some backends do not specify dnf, and there is + // at least one backend with dnf, it is used for all backends in + // the group. In general, multiple backends are not necessary + // for dnf because there is no need for load balancing. + if (params.dnf) { + g.dnf = true; + } + + g.addrs.push_back(addr); + continue; + } + + auto idx = addr_groups.size(); + pattern_addr_indexer.emplace(pattern, idx); + addr_groups.emplace_back(pattern); + auto &g = addr_groups.back(); + g.addrs.push_back(addr); + g.affinity.type = params.affinity.type; + if (params.affinity.type == SessionAffinity::COOKIE) { + g.affinity.cookie.name = + make_string_ref(downstreamconf.balloc, params.affinity.cookie.name); + if (!params.affinity.cookie.path.empty()) { + g.affinity.cookie.path = + make_string_ref(downstreamconf.balloc, params.affinity.cookie.path); + } + g.affinity.cookie.secure = params.affinity.cookie.secure; + g.affinity.cookie.stickiness = params.affinity.cookie.stickiness; + } + g.redirect_if_not_tls = params.redirect_if_not_tls; + g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby); + g.timeout.read = params.read_timeout; + g.timeout.write = params.write_timeout; + g.dnf = params.dnf; + + if (pattern[0] == '*') { + // wildcard pattern + auto path_first = + std::find(std::begin(g.pattern), std::end(g.pattern), '/'); + + auto host = StringRef{std::begin(g.pattern) + 1, path_first}; + auto path = StringRef{path_first, std::end(g.pattern)}; + + auto path_is_wildcard = false; + if (path[path.size() - 1] == '*') { + path = StringRef{std::begin(path), std::begin(path) + path.size() - 1}; + path_is_wildcard = true; + } + + auto it = std::find_if( + std::begin(wildcard_patterns), std::end(wildcard_patterns), + [&host](const WildcardPattern &wp) { return wp.host == host; }); + + if (it == std::end(wildcard_patterns)) { + wildcard_patterns.emplace_back(host); + + auto &router = wildcard_patterns.back().router; + router.add_route(path, idx, path_is_wildcard); + + auto iov = make_byte_ref(downstreamconf.balloc, host.size() + 1); + auto p = iov.base; + p = std::reverse_copy(std::begin(host), std::end(host), p); + *p = '\0'; + auto rev_host = StringRef{iov.base, p}; + + rw_router.add_route(rev_host, wildcard_patterns.size() - 1); + } else { + (*it).router.add_route(path, idx, path_is_wildcard); + } + + continue; + } + + auto path_is_wildcard = false; + if (pattern[pattern.size() - 1] == '*') { + pattern = StringRef{std::begin(pattern), + std::begin(pattern) + pattern.size() - 1}; + path_is_wildcard = true; + } + + router.add_route(pattern, idx, path_is_wildcard); + } + return 0; +} +} // namespace + +namespace { +ForwardedNode parse_forwarded_node_type(const StringRef &optarg) { + if (util::strieq_l("obfuscated", optarg)) { + return ForwardedNode::OBFUSCATED; + } + + if (util::strieq_l("ip", optarg)) { + return ForwardedNode::IP; + } + + if (optarg.size() < 2 || optarg[0] != '_') { + return static_cast(-1); + } + + if (std::find_if_not(std::begin(optarg), std::end(optarg), [](char c) { + return util::is_alpha(c) || util::is_digit(c) || c == '.' || c == '_' || + c == '-'; + }) != std::end(optarg)) { + return static_cast(-1); + } + + return ForwardedNode::OBFUSCATED; +} +} // namespace + +namespace { +int parse_error_page(std::vector &error_pages, const StringRef &opt, + const StringRef &optarg) { + std::array errbuf; + + auto eq = std::find(std::begin(optarg), std::end(optarg), '='); + if (eq == std::end(optarg) || eq + 1 == std::end(optarg)) { + LOG(ERROR) << opt << ": bad value: '" << optarg << "'"; + return -1; + } + + auto codestr = StringRef{std::begin(optarg), eq}; + unsigned int code; + + if (codestr == StringRef::from_lit("*")) { + code = 0; + } else { + auto n = util::parse_uint(codestr); + + if (n == -1 || n < 400 || n > 599) { + LOG(ERROR) << opt << ": bad code: '" << codestr << "'"; + return -1; + } + + code = static_cast(n); + } + + auto path = StringRef{eq + 1, std::end(optarg)}; + + std::vector content; + auto fd = open(path.c_str(), O_RDONLY); + if (fd == -1) { + auto error = errno; + LOG(ERROR) << opt << ": " << optarg << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + auto fd_closer = defer(close, fd); + + std::array buf; + for (;;) { + auto n = read(fd, buf.data(), buf.size()); + if (n == -1) { + auto error = errno; + LOG(ERROR) << opt << ": " << optarg << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + if (n == 0) { + break; + } + content.insert(std::end(content), std::begin(buf), std::begin(buf) + n); + } + + error_pages.push_back(ErrorPage{std::move(content), code}); + + return 0; +} +} // namespace + +namespace { +// Maximum size of SCT extension payload length. +constexpr size_t MAX_SCT_EXT_LEN = 16_k; +} // namespace + +struct SubcertParams { + StringRef sct_dir; +}; + +namespace { +// Parses subcert parameter |src_params|, and stores parsed results +// into |out|. This function returns 0 if it succeeds, or -1. +int parse_subcert_params(SubcertParams &out, const StringRef &src_params) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::istarts_with_l(param, "sct-dir=")) { +#if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + auto sct_dir = + StringRef{std::begin(param) + str_size("sct-dir="), std::end(param)}; + if (sct_dir.empty()) { + LOG(ERROR) << "subcert: " << param << ": empty sct-dir"; + return -1; + } + out.sct_dir = sct_dir; +#else // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + LOG(WARN) << "subcert: sct-dir requires OpenSSL >= 1.0.2"; +#endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + } else if (!param.empty()) { + LOG(ERROR) << "subcert: " << param << ": unknown keyword"; + return -1; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +namespace { +// Reads *.sct files from directory denoted by |dir_path|. |dir_path| +// must be NULL-terminated string. +int read_tls_sct_from_dir(std::vector &dst, const StringRef &opt, + const StringRef &dir_path) { + std::array errbuf; + + auto dir = opendir(dir_path.c_str()); + if (dir == nullptr) { + auto error = errno; + LOG(ERROR) << opt << ": " << dir_path << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + auto closer = defer(closedir, dir); + + // 2 bytes total length field + auto len_idx = std::distance(std::begin(dst), std::end(dst)); + dst.insert(std::end(dst), 2, 0); + + for (;;) { + errno = 0; + auto ent = readdir(dir); + if (ent == nullptr) { + if (errno != 0) { + auto error = errno; + LOG(ERROR) << opt << ": failed to read directory " << dir_path << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + break; + } + + auto name = StringRef{ent->d_name}; + + if (name[0] == '.' || !util::iends_with_l(name, ".sct")) { + continue; + } + + std::string path; + path.resize(dir_path.size() + 1 + name.size()); + { + auto p = std::begin(path); + p = std::copy(std::begin(dir_path), std::end(dir_path), p); + *p++ = '/'; + std::copy(std::begin(name), std::end(name), p); + } + + auto fd = open(path.c_str(), O_RDONLY); + if (fd == -1) { + auto error = errno; + LOG(ERROR) << opt << ": failed to read SCT from " << path << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + auto closer = defer(close, fd); + + // 2 bytes length field for this SCT. + auto len_idx = std::distance(std::begin(dst), std::end(dst)); + dst.insert(std::end(dst), 2, 0); + + // *.sct file tends to be small; around 110+ bytes. + std::array buf; + for (;;) { + ssize_t nread; + while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR) + ; + + if (nread == -1) { + auto error = errno; + LOG(ERROR) << opt << ": failed to read SCT data from " << path << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + if (nread == 0) { + break; + } + + dst.insert(std::end(dst), std::begin(buf), std::begin(buf) + nread); + + if (dst.size() > MAX_SCT_EXT_LEN) { + LOG(ERROR) << opt << ": the concatenated SCT data from " << dir_path + << " is too large. Max " << MAX_SCT_EXT_LEN; + return -1; + } + } + + auto len = dst.size() - len_idx - 2; + + if (len == 0) { + dst.resize(dst.size() - 2); + continue; + } + + dst[len_idx] = len >> 8; + dst[len_idx + 1] = len; + } + + auto len = dst.size() - len_idx - 2; + + if (len == 0) { + dst.resize(dst.size() - 2); + return 0; + } + + dst[len_idx] = len >> 8; + dst[len_idx + 1] = len; + + return 0; +} +} // namespace + +#if !LIBRESSL_LEGACY_API +namespace { +// Reads PSK secrets from path, and parses each line. The result is +// directly stored into config->tls.psk_secrets. This function +// returns 0 if it succeeds, or -1. +int parse_psk_secrets(Config *config, const StringRef &path) { + auto &tlsconf = config->tls; + + std::ifstream f(path.c_str(), std::ios::binary); + if (!f) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": could not open file " << path; + return -1; + } + + size_t lineno = 0; + std::string line; + while (std::getline(f, line)) { + ++lineno; + if (line.empty() || line[0] == '#') { + continue; + } + + auto sep_it = std::find(std::begin(line), std::end(line), ':'); + if (sep_it == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": could not fine separator at line " << lineno; + return -1; + } + + if (sep_it == std::begin(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty identity at line " + << lineno; + return -1; + } + + if (sep_it + 1 == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty secret at line " + << lineno; + return -1; + } + + if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": secret must be hex string at line " << lineno; + return -1; + } + + auto identity = + make_string_ref(config->balloc, StringRef{std::begin(line), sep_it}); + + auto secret = + util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)}); + + auto rv = tlsconf.psk_secrets.emplace(identity, secret); + if (!rv.second) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": identity has already been registered at line " << lineno; + return -1; + } + } + + return 0; +} +} // namespace +#endif // !LIBRESSL_LEGACY_API + +#if !LIBRESSL_LEGACY_API +namespace { +// Reads PSK secrets from path, and parses each line. The result is +// directly stored into config->tls.client.psk. This function returns +// 0 if it succeeds, or -1. +int parse_client_psk_secrets(Config *config, const StringRef &path) { + auto &tlsconf = config->tls; + + std::ifstream f(path.c_str(), std::ios::binary); + if (!f) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": could not open file " + << path; + return -1; + } + + size_t lineno = 0; + std::string line; + while (std::getline(f, line)) { + ++lineno; + if (line.empty() || line[0] == '#') { + continue; + } + + auto sep_it = std::find(std::begin(line), std::end(line), ':'); + if (sep_it == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS + << ": could not find separator at line " << lineno; + return -1; + } + + if (sep_it == std::begin(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty identity at line " + << lineno; + return -1; + } + + if (sep_it + 1 == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty secret at line " + << lineno; + return -1; + } + + if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS + << ": secret must be hex string at line " << lineno; + return -1; + } + + tlsconf.client.psk.identity = + make_string_ref(config->balloc, StringRef{std::begin(line), sep_it}); + + tlsconf.client.psk.secret = + util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)}); + + return 0; + } + + return 0; +} +} // namespace +#endif // !LIBRESSL_LEGACY_API + +// generated by gennghttpxfun.py +int option_lookup_token(const char *name, size_t namelen) { + switch (namelen) { + case 4: + switch (name[3]) { + case 'f': + if (util::strieq_l("con", name, 3)) { + return SHRPX_OPTID_CONF; + } + break; + case 'r': + if (util::strieq_l("use", name, 3)) { + return SHRPX_OPTID_USER; + } + break; + } + break; + case 6: + switch (name[5]) { + case 'a': + if (util::strieq_l("no-vi", name, 5)) { + return SHRPX_OPTID_NO_VIA; + } + break; + case 'c': + if (util::strieq_l("altsv", name, 5)) { + return SHRPX_OPTID_ALTSVC; + } + break; + case 'n': + if (util::strieq_l("daemo", name, 5)) { + return SHRPX_OPTID_DAEMON; + } + break; + case 't': + if (util::strieq_l("cacer", name, 5)) { + return SHRPX_OPTID_CACERT; + } + if (util::strieq_l("clien", name, 5)) { + return SHRPX_OPTID_CLIENT; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'd': + if (util::strieq_l("backen", name, 6)) { + return SHRPX_OPTID_BACKEND; + } + break; + case 'e': + if (util::strieq_l("includ", name, 6)) { + return SHRPX_OPTID_INCLUDE; + } + break; + case 'g': + if (util::strieq_l("backlo", name, 6)) { + return SHRPX_OPTID_BACKLOG; + } + if (util::strieq_l("paddin", name, 6)) { + return SHRPX_OPTID_PADDING; + } + break; + case 'p': + if (util::strieq_l("no-ocs", name, 6)) { + return SHRPX_OPTID_NO_OCSP; + } + break; + case 's': + if (util::strieq_l("cipher", name, 6)) { + return SHRPX_OPTID_CIPHERS; + } + if (util::strieq_l("worker", name, 6)) { + return SHRPX_OPTID_WORKERS; + } + break; + case 't': + if (util::strieq_l("subcer", name, 6)) { + return SHRPX_OPTID_SUBCERT; + } + break; + } + break; + case 8: + switch (name[7]) { + case 'd': + if (util::strieq_l("fronten", name, 7)) { + return SHRPX_OPTID_FRONTEND; + } + break; + case 'e': + if (util::strieq_l("insecur", name, 7)) { + return SHRPX_OPTID_INSECURE; + } + if (util::strieq_l("pid-fil", name, 7)) { + return SHRPX_OPTID_PID_FILE; + } + break; + case 'n': + if (util::strieq_l("fastope", name, 7)) { + return SHRPX_OPTID_FASTOPEN; + } + break; + case 's': + if (util::strieq_l("tls-ktl", name, 7)) { + return SHRPX_OPTID_TLS_KTLS; + } + break; + case 't': + if (util::strieq_l("npn-lis", name, 7)) { + return SHRPX_OPTID_NPN_LIST; + } + break; + } + break; + case 9: + switch (name[8]) { + case 'e': + if (util::strieq_l("no-kqueu", name, 8)) { + return SHRPX_OPTID_NO_KQUEUE; + } + if (util::strieq_l("read-rat", name, 8)) { + return SHRPX_OPTID_READ_RATE; + } + break; + case 'l': + if (util::strieq_l("log-leve", name, 8)) { + return SHRPX_OPTID_LOG_LEVEL; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'e': + if (util::strieq_l("error-pag", name, 9)) { + return SHRPX_OPTID_ERROR_PAGE; + } + if (util::strieq_l("mruby-fil", name, 9)) { + return SHRPX_OPTID_MRUBY_FILE; + } + if (util::strieq_l("write-rat", name, 9)) { + return SHRPX_OPTID_WRITE_RATE; + } + break; + case 't': + if (util::strieq_l("read-burs", name, 9)) { + return SHRPX_OPTID_READ_BURST; + } + break; + } + break; + case 11: + switch (name[10]) { + case 'e': + if (util::strieq_l("server-nam", name, 10)) { + return SHRPX_OPTID_SERVER_NAME; + } + break; + case 'f': + if (util::strieq_l("no-quic-bp", name, 10)) { + return SHRPX_OPTID_NO_QUIC_BPF; + } + break; + case 'r': + if (util::strieq_l("tls-sct-di", name, 10)) { + return SHRPX_OPTID_TLS_SCT_DIR; + } + break; + case 's': + if (util::strieq_l("backend-tl", name, 10)) { + return SHRPX_OPTID_BACKEND_TLS; + } + if (util::strieq_l("ecdh-curve", name, 10)) { + return SHRPX_OPTID_ECDH_CURVES; + } + if (util::strieq_l("psk-secret", name, 10)) { + return SHRPX_OPTID_PSK_SECRETS; + } + break; + case 't': + if (util::strieq_l("write-burs", name, 10)) { + return SHRPX_OPTID_WRITE_BURST; + } + break; + case 'y': + if (util::strieq_l("dns-max-tr", name, 10)) { + return SHRPX_OPTID_DNS_MAX_TRY; + } + if (util::strieq_l("http2-prox", name, 10)) { + return SHRPX_OPTID_HTTP2_PROXY; + } + break; + } + break; + case 12: + switch (name[11]) { + case '4': + if (util::strieq_l("backend-ipv", name, 11)) { + return SHRPX_OPTID_BACKEND_IPV4; + } + break; + case '6': + if (util::strieq_l("backend-ipv", name, 11)) { + return SHRPX_OPTID_BACKEND_IPV6; + } + break; + case 'c': + if (util::strieq_l("http2-altsv", name, 11)) { + return SHRPX_OPTID_HTTP2_ALTSVC; + } + break; + case 'e': + if (util::strieq_l("host-rewrit", name, 11)) { + return SHRPX_OPTID_HOST_REWRITE; + } + if (util::strieq_l("http2-bridg", name, 11)) { + return SHRPX_OPTID_HTTP2_BRIDGE; + } + break; + case 'p': + if (util::strieq_l("ocsp-startu", name, 11)) { + return SHRPX_OPTID_OCSP_STARTUP; + } + break; + case 'y': + if (util::strieq_l("client-prox", name, 11)) { + return SHRPX_OPTID_CLIENT_PROXY; + } + if (util::strieq_l("forwarded-b", name, 11)) { + return SHRPX_OPTID_FORWARDED_BY; + } + break; + } + break; + case 13: + switch (name[12]) { + case 'd': + if (util::strieq_l("add-forwarde", name, 12)) { + return SHRPX_OPTID_ADD_FORWARDED; + } + if (util::strieq_l("single-threa", name, 12)) { + return SHRPX_OPTID_SINGLE_THREAD; + } + break; + case 'e': + if (util::strieq_l("dh-param-fil", name, 12)) { + return SHRPX_OPTID_DH_PARAM_FILE; + } + if (util::strieq_l("errorlog-fil", name, 12)) { + return SHRPX_OPTID_ERRORLOG_FILE; + } + if (util::strieq_l("rlimit-nofil", name, 12)) { + return SHRPX_OPTID_RLIMIT_NOFILE; + } + break; + case 'r': + if (util::strieq_l("forwarded-fo", name, 12)) { + return SHRPX_OPTID_FORWARDED_FOR; + } + break; + case 's': + if (util::strieq_l("tls13-cipher", name, 12)) { + return SHRPX_OPTID_TLS13_CIPHERS; + } + break; + case 't': + if (util::strieq_l("verify-clien", name, 12)) { + return SHRPX_OPTID_VERIFY_CLIENT; + } + break; + } + break; + case 14: + switch (name[13]) { + case 'd': + if (util::strieq_l("quic-server-i", name, 13)) { + return SHRPX_OPTID_QUIC_SERVER_ID; + } + break; + case 'e': + if (util::strieq_l("accesslog-fil", name, 13)) { + return SHRPX_OPTID_ACCESSLOG_FILE; + } + break; + case 'h': + if (util::strieq_l("no-server-pus", name, 13)) { + return SHRPX_OPTID_NO_SERVER_PUSH; + } + break; + case 'k': + if (util::strieq_l("rlimit-memloc", name, 13)) { + return SHRPX_OPTID_RLIMIT_MEMLOCK; + } + break; + case 'p': + if (util::strieq_l("no-verify-ocs", name, 13)) { + return SHRPX_OPTID_NO_VERIFY_OCSP; + } + break; + case 's': + if (util::strieq_l("backend-no-tl", name, 13)) { + return SHRPX_OPTID_BACKEND_NO_TLS; + } + if (util::strieq_l("client-cipher", name, 13)) { + return SHRPX_OPTID_CLIENT_CIPHERS; + } + if (util::strieq_l("single-proces", name, 13)) { + return SHRPX_OPTID_SINGLE_PROCESS; + } + break; + case 't': + if (util::strieq_l("tls-proto-lis", name, 13)) { + return SHRPX_OPTID_TLS_PROTO_LIST; + } + break; + } + break; + case 15: + switch (name[14]) { + case 'e': + if (util::strieq_l("no-host-rewrit", name, 14)) { + return SHRPX_OPTID_NO_HOST_REWRITE; + } + break; + case 'g': + if (util::strieq_l("errorlog-syslo", name, 14)) { + return SHRPX_OPTID_ERRORLOG_SYSLOG; + } + break; + case 's': + if (util::strieq_l("frontend-no-tl", name, 14)) { + return SHRPX_OPTID_FRONTEND_NO_TLS; + } + break; + case 'y': + if (util::strieq_l("syslog-facilit", name, 14)) { + return SHRPX_OPTID_SYSLOG_FACILITY; + } + break; + } + break; + case 16: + switch (name[15]) { + case 'e': + if (util::strieq_l("certificate-fil", name, 15)) { + return SHRPX_OPTID_CERTIFICATE_FILE; + } + if (util::strieq_l("client-cert-fil", name, 15)) { + return SHRPX_OPTID_CLIENT_CERT_FILE; + } + if (util::strieq_l("private-key-fil", name, 15)) { + return SHRPX_OPTID_PRIVATE_KEY_FILE; + } + if (util::strieq_l("worker-read-rat", name, 15)) { + return SHRPX_OPTID_WORKER_READ_RATE; + } + break; + case 'g': + if (util::strieq_l("accesslog-syslo", name, 15)) { + return SHRPX_OPTID_ACCESSLOG_SYSLOG; + } + break; + case 't': + if (util::strieq_l("accesslog-forma", name, 15)) { + return SHRPX_OPTID_ACCESSLOG_FORMAT; + } + break; + } + break; + case 17: + switch (name[16]) { + case 'e': + if (util::strieq_l("no-server-rewrit", name, 16)) { + return SHRPX_OPTID_NO_SERVER_REWRITE; + } + if (util::strieq_l("worker-write-rat", name, 16)) { + return SHRPX_OPTID_WORKER_WRITE_RATE; + } + break; + case 's': + if (util::strieq_l("backend-http1-tl", name, 16)) { + return SHRPX_OPTID_BACKEND_HTTP1_TLS; + } + if (util::strieq_l("max-header-field", name, 16)) { + return SHRPX_OPTID_MAX_HEADER_FIELDS; + } + break; + case 't': + if (util::strieq_l("dns-cache-timeou", name, 16)) { + return SHRPX_OPTID_DNS_CACHE_TIMEOUT; + } + if (util::strieq_l("worker-read-burs", name, 16)) { + return SHRPX_OPTID_WORKER_READ_BURST; + } + break; + } + break; + case 18: + switch (name[17]) { + case 'a': + if (util::strieq_l("tls-max-early-dat", name, 17)) { + return SHRPX_OPTID_TLS_MAX_EARLY_DATA; + } + break; + case 'r': + if (util::strieq_l("add-request-heade", name, 17)) { + return SHRPX_OPTID_ADD_REQUEST_HEADER; + } + break; + case 's': + if (util::strieq_l("client-psk-secret", name, 17)) { + return SHRPX_OPTID_CLIENT_PSK_SECRETS; + } + break; + case 't': + if (util::strieq_l("dns-lookup-timeou", name, 17)) { + return SHRPX_OPTID_DNS_LOOKUP_TIMEOUT; + } + if (util::strieq_l("worker-write-burs", name, 17)) { + return SHRPX_OPTID_WORKER_WRITE_BURST; + } + break; + } + break; + case 19: + switch (name[18]) { + case 'e': + if (util::strieq_l("no-location-rewrit", name, 18)) { + return SHRPX_OPTID_NO_LOCATION_REWRITE; + } + if (util::strieq_l("require-http-schem", name, 18)) { + return SHRPX_OPTID_REQUIRE_HTTP_SCHEME; + } + if (util::strieq_l("tls-ticket-key-fil", name, 18)) { + return SHRPX_OPTID_TLS_TICKET_KEY_FILE; + } + break; + case 'f': + if (util::strieq_l("backend-max-backof", name, 18)) { + return SHRPX_OPTID_BACKEND_MAX_BACKOFF; + } + break; + case 'r': + if (util::strieq_l("add-response-heade", name, 18)) { + return SHRPX_OPTID_ADD_RESPONSE_HEADER; + } + if (util::strieq_l("add-x-forwarded-fo", name, 18)) { + return SHRPX_OPTID_ADD_X_FORWARDED_FOR; + } + if (util::strieq_l("header-field-buffe", name, 18)) { + return SHRPX_OPTID_HEADER_FIELD_BUFFER; + } + break; + case 't': + if (util::strieq_l("redirect-https-por", name, 18)) { + return SHRPX_OPTID_REDIRECT_HTTPS_PORT; + } + if (util::strieq_l("stream-read-timeou", name, 18)) { + return SHRPX_OPTID_STREAM_READ_TIMEOUT; + } + break; + } + break; + case 20: + switch (name[19]) { + case 'g': + if (util::strieq_l("frontend-frame-debu", name, 19)) { + return SHRPX_OPTID_FRONTEND_FRAME_DEBUG; + } + break; + case 'l': + if (util::strieq_l("ocsp-update-interva", name, 19)) { + return SHRPX_OPTID_OCSP_UPDATE_INTERVAL; + } + break; + case 's': + if (util::strieq_l("max-worker-processe", name, 19)) { + return SHRPX_OPTID_MAX_WORKER_PROCESSES; + } + if (util::strieq_l("tls13-client-cipher", name, 19)) { + return SHRPX_OPTID_TLS13_CLIENT_CIPHERS; + } + break; + case 't': + if (util::strieq_l("backend-read-timeou", name, 19)) { + return SHRPX_OPTID_BACKEND_READ_TIMEOUT; + } + if (util::strieq_l("stream-write-timeou", name, 19)) { + return SHRPX_OPTID_STREAM_WRITE_TIMEOUT; + } + if (util::strieq_l("verify-client-cacer", name, 19)) { + return SHRPX_OPTID_VERIFY_CLIENT_CACERT; + } + break; + case 'y': + if (util::strieq_l("api-max-request-bod", name, 19)) { + return SHRPX_OPTID_API_MAX_REQUEST_BODY; + } + break; + } + break; + case 21: + switch (name[20]) { + case 'd': + if (util::strieq_l("backend-tls-sni-fiel", name, 20)) { + return SHRPX_OPTID_BACKEND_TLS_SNI_FIELD; + } + break; + case 'e': + if (util::strieq_l("quic-bpf-program-fil", name, 20)) { + return SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE; + } + break; + case 'l': + if (util::strieq_l("accept-proxy-protoco", name, 20)) { + return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL; + } + break; + case 'n': + if (util::strieq_l("tls-max-proto-versio", name, 20)) { + return SHRPX_OPTID_TLS_MAX_PROTO_VERSION; + } + if (util::strieq_l("tls-min-proto-versio", name, 20)) { + return SHRPX_OPTID_TLS_MIN_PROTO_VERSION; + } + break; + case 'r': + if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) { + return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER; + } + break; + case 's': + if (util::strieq_l("frontend-max-request", name, 20)) { + return SHRPX_OPTID_FRONTEND_MAX_REQUESTS; + } + break; + case 't': + if (util::strieq_l("backend-write-timeou", name, 20)) { + return SHRPX_OPTID_BACKEND_WRITE_TIMEOUT; + } + if (util::strieq_l("frontend-read-timeou", name, 20)) { + return SHRPX_OPTID_FRONTEND_READ_TIMEOUT; + } + break; + case 'y': + if (util::strieq_l("accesslog-write-earl", name, 20)) { + return SHRPX_OPTID_ACCESSLOG_WRITE_EARLY; + } + break; + } + break; + case 22: + switch (name[21]) { + case 'i': + if (util::strieq_l("backend-http-proxy-ur", name, 21)) { + return SHRPX_OPTID_BACKEND_HTTP_PROXY_URI; + } + break; + case 'r': + if (util::strieq_l("backend-request-buffe", name, 21)) { + return SHRPX_OPTID_BACKEND_REQUEST_BUFFER; + } + if (util::strieq_l("frontend-quic-qlog-di", name, 21)) { + return SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR; + } + break; + case 't': + if (util::strieq_l("frontend-write-timeou", name, 21)) { + return SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT; + } + break; + case 'y': + if (util::strieq_l("backend-address-famil", name, 21)) { + return SHRPX_OPTID_BACKEND_ADDRESS_FAMILY; + } + break; + } + break; + case 23: + switch (name[22]) { + case 'e': + if (util::strieq_l("client-private-key-fil", name, 22)) { + return SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE; + } + if (util::strieq_l("private-key-passwd-fil", name, 22)) { + return SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE; + } + break; + case 'g': + if (util::strieq_l("frontend-quic-debug-lo", name, 22)) { + return SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG; + } + break; + case 'r': + if (util::strieq_l("backend-response-buffe", name, 22)) { + return SHRPX_OPTID_BACKEND_RESPONSE_BUFFER; + } + break; + case 't': + if (util::strieq_l("backend-connect-timeou", name, 22)) { + return SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT; + } + break; + } + break; + case 24: + switch (name[23]) { + case 'a': + if (util::strieq_l("frontend-quic-early-dat", name, 23)) { + return SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA; + } + break; + case 'd': + if (util::strieq_l("strip-incoming-forwarde", name, 23)) { + return SHRPX_OPTID_STRIP_INCOMING_FORWARDED; + } + if (util::strieq_l("tls-ticket-key-memcache", name, 23)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED; + } + break; + case 'e': + if (util::strieq_l("fetch-ocsp-response-fil", name, 23)) { + return SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE; + } + break; + case 'o': + if (util::strieq_l("no-add-x-forwarded-prot", name, 23)) { + return SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO; + } + break; + case 't': + if (util::strieq_l("listener-disable-timeou", name, 23)) { + return SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT; + } + if (util::strieq_l("tls-dyn-rec-idle-timeou", name, 23)) { + return SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT; + } + break; + } + break; + case 25: + switch (name[24]) { + case 'e': + if (util::strieq_l("backend-http2-window-siz", name, 24)) { + return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE; + } + if (util::strieq_l("frontend-quic-secret-fil", name, 24)) { + return SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE; + } + break; + case 'g': + if (util::strieq_l("http2-no-cookie-crumblin", name, 24)) { + return SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING; + } + break; + case 's': + if (util::strieq_l("backend-http2-window-bit", name, 24)) { + return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS; + } + if (util::strieq_l("max-request-header-field", name, 24)) { + return SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS; + } + break; + case 't': + if (util::strieq_l("frontend-quic-initial-rt", name, 24)) { + return SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT; + } + break; + } + break; + case 26: + switch (name[25]) { + case 'a': + if (util::strieq_l("tls-no-postpone-early-dat", name, 25)) { + return SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA; + } + break; + case 'e': + if (util::strieq_l("frontend-http2-window-siz", name, 25)) { + return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE; + } + if (util::strieq_l("frontend-http3-window-siz", name, 25)) { + return SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE; + } + break; + case 's': + if (util::strieq_l("frontend-http2-window-bit", name, 25)) { + return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS; + } + if (util::strieq_l("max-response-header-field", name, 25)) { + return SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS; + } + break; + case 't': + if (util::strieq_l("backend-keep-alive-timeou", name, 25)) { + return SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT; + } + if (util::strieq_l("frontend-quic-idle-timeou", name, 25)) { + return SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT; + } + if (util::strieq_l("no-http2-cipher-black-lis", name, 25)) { + return SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST; + } + if (util::strieq_l("no-http2-cipher-block-lis", name, 25)) { + return SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST; + } + break; + } + break; + case 27: + switch (name[26]) { + case 'd': + if (util::strieq_l("tls-session-cache-memcache", name, 26)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED; + } + break; + case 'n': + if (util::strieq_l("frontend-quic-require-toke", name, 26)) { + return SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN; + } + break; + case 'r': + if (util::strieq_l("request-header-field-buffe", name, 26)) { + return SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER; + } + break; + case 's': + if (util::strieq_l("worker-frontend-connection", name, 26)) { + return SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS; + } + break; + case 't': + if (util::strieq_l("frontend-http2-read-timeou", name, 26)) { + return SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT; + } + if (util::strieq_l("frontend-http3-read-timeou", name, 26)) { + return SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT; + } + if (util::strieq_l("frontend-keep-alive-timeou", name, 26)) { + return SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT; + } + break; + } + break; + case 28: + switch (name[27]) { + case 'a': + if (util::strieq_l("no-strip-incoming-early-dat", name, 27)) { + return SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA; + } + break; + case 'd': + if (util::strieq_l("tls-dyn-rec-warmup-threshol", name, 27)) { + return SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD; + } + break; + case 'r': + if (util::strieq_l("response-header-field-buffe", name, 27)) { + return SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER; + } + break; + case 's': + if (util::strieq_l("http2-max-concurrent-stream", name, 27)) { + return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS; + } + if (util::strieq_l("tls-ticket-key-memcached-tl", name, 27)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS; + } + break; + case 't': + if (util::strieq_l("backend-connections-per-hos", name, 27)) { + return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST; + } + break; + } + break; + case 30: + switch (name[29]) { + case 'd': + if (util::strieq_l("verify-client-tolerate-expire", name, 29)) { + return SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED; + } + break; + case 'e': + if (util::strieq_l("frontend-http3-max-window-siz", name, 29)) { + return SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE; + } + break; + case 'r': + if (util::strieq_l("ignore-per-pattern-mruby-erro", name, 29)) { + return SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR; + } + if (util::strieq_l("strip-incoming-x-forwarded-fo", name, 29)) { + return SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR; + } + break; + case 't': + if (util::strieq_l("backend-http2-settings-timeou", name, 29)) { + return SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT; + } + break; + } + break; + case 31: + switch (name[30]) { + case 's': + if (util::strieq_l("tls-session-cache-memcached-tl", name, 30)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS; + } + break; + case 't': + if (util::strieq_l("frontend-http2-settings-timeou", name, 30)) { + return SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT; + } + break; + } + break; + case 32: + switch (name[31]) { + case 'd': + if (util::strieq_l("backend-connections-per-fronten", name, 31)) { + return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND; + } + break; + } + break; + case 33: + switch (name[32]) { + case 'l': + if (util::strieq_l("tls-ticket-key-memcached-interva", name, 32)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL; + } + if (util::strieq_l("tls-ticket-key-memcached-max-fai", name, 32)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL; + } + break; + case 't': + if (util::strieq_l("client-no-http2-cipher-black-lis", name, 32)) { + return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST; + } + if (util::strieq_l("client-no-http2-cipher-block-lis", name, 32)) { + return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST; + } + break; + } + break; + case 34: + switch (name[33]) { + case 'e': + if (util::strieq_l("tls-ticket-key-memcached-cert-fil", name, 33)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE; + } + break; + case 'r': + if (util::strieq_l("frontend-http2-dump-request-heade", name, 33)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER; + } + break; + case 't': + if (util::strieq_l("backend-http1-connections-per-hos", name, 33)) { + return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST; + } + break; + case 'y': + if (util::strieq_l("tls-ticket-key-memcached-max-retr", name, 33)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY; + } + break; + } + break; + case 35: + switch (name[34]) { + case 'e': + if (util::strieq_l("frontend-http2-optimize-window-siz", name, 34)) { + return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE; + } + break; + case 'o': + if (util::strieq_l("no-strip-incoming-x-forwarded-prot", name, 34)) { + return SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO; + } + break; + case 'r': + if (util::strieq_l("frontend-http2-dump-response-heade", name, 34)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER; + } + if (util::strieq_l("frontend-quic-congestion-controlle", name, 34)) { + return SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER; + } + break; + } + break; + case 36: + switch (name[35]) { + case 'd': + if (util::strieq_l("worker-process-grace-shutdown-perio", name, 35)) { + return SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD; + } + break; + case 'e': + if (util::strieq_l("backend-http2-connection-window-siz", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE; + } + break; + case 'r': + if (util::strieq_l("backend-http2-connections-per-worke", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER; + } + break; + case 's': + if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS; + } + if (util::strieq_l("backend-http2-max-concurrent-stream", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS; + } + break; + } + break; + case 37: + switch (name[36]) { + case 'e': + if (util::strieq_l("frontend-http2-connection-window-siz", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE; + } + if (util::strieq_l("frontend-http3-connection-window-siz", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE; + } + if (util::strieq_l("tls-session-cache-memcached-cert-fil", name, 36)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE; + } + break; + case 's': + if (util::strieq_l("frontend-http2-connection-window-bit", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS; + } + if (util::strieq_l("frontend-http2-max-concurrent-stream", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS; + } + if (util::strieq_l("frontend-http3-max-concurrent-stream", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS; + } + break; + } + break; + case 38: + switch (name[37]) { + case 'd': + if (util::strieq_l("backend-http1-connections-per-fronten", name, 37)) { + return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND; + } + break; + } + break; + case 39: + switch (name[38]) { + case 'y': + if (util::strieq_l("tls-ticket-key-memcached-address-famil", name, 38)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY; + } + break; + } + break; + case 40: + switch (name[39]) { + case 'e': + if (util::strieq_l("backend-http2-decoder-dynamic-table-siz", name, 39)) { + return SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("backend-http2-encoder-dynamic-table-siz", name, 39)) { + return SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; + } + break; + } + break; + case 41: + switch (name[40]) { + case 'e': + if (util::strieq_l("frontend-http2-decoder-dynamic-table-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("frontend-http2-encoder-dynamic-table-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("frontend-http2-optimize-write-buffer-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE; + } + if (util::strieq_l("frontend-http3-max-connection-window-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE; + } + if (util::strieq_l("tls-ticket-key-memcached-private-key-fil", name, + 40)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE; + } + break; + } + break; + case 42: + switch (name[41]) { + case 'y': + if (util::strieq_l("tls-session-cache-memcached-address-famil", name, + 41)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY; + } + break; + } + break; + case 44: + switch (name[43]) { + case 'e': + if (util::strieq_l("tls-session-cache-memcached-private-key-fil", name, + 43)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE; + } + break; + } + break; + } + return -1; +} + +int parse_config(Config *config, const StringRef &opt, const StringRef &optarg, + std::set &included_set, + std::map &pattern_addr_indexer) { + auto optid = option_lookup_token(opt.c_str(), opt.size()); + return parse_config(config, optid, opt, optarg, included_set, + pattern_addr_indexer); +} + +int parse_config(Config *config, int optid, const StringRef &opt, + const StringRef &optarg, std::set &included_set, + std::map &pattern_addr_indexer) { + std::array errbuf; + char host[NI_MAXHOST]; + uint16_t port; + + switch (optid) { + case SHRPX_OPTID_BACKEND: { + auto &downstreamconf = *config->conn.downstream; + auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';'); + + DownstreamAddrConfig addr{}; + if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) { + auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size(); + addr.host = + make_string_ref(downstreamconf.balloc, StringRef{path, addr_end}); + addr.host_unix = true; + } else { + if (split_host_port(host, sizeof(host), &port, + StringRef{std::begin(optarg), addr_end}, opt) == -1) { + return -1; + } + + addr.host = make_string_ref(downstreamconf.balloc, StringRef{host}); + addr.port = port; + } + + auto mapping = addr_end == std::end(optarg) ? addr_end : addr_end + 1; + auto mapping_end = std::find(mapping, std::end(optarg), ';'); + + auto params = + mapping_end == std::end(optarg) ? mapping_end : mapping_end + 1; + + if (parse_mapping(config, addr, pattern_addr_indexer, + StringRef{mapping, mapping_end}, + StringRef{params, std::end(optarg)}) != 0) { + return -1; + } + + return 0; + } + case SHRPX_OPTID_FRONTEND: { + auto &apiconf = config->api; + + auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';'); + auto src_params = StringRef{addr_end, std::end(optarg)}; + + UpstreamParams params{}; + params.tls = true; + + if (parse_upstream_params(params, src_params) != 0) { + return -1; + } + + if (params.sni_fwd && !params.tls) { + LOG(ERROR) << "frontend: sni_fwd requires tls"; + return -1; + } + + if (params.quic) { + if (params.alt_mode != UpstreamAltMode::NONE) { + LOG(ERROR) << "frontend: api or healthmon cannot be used with quic"; + return -1; + } + + if (!params.tls) { + LOG(ERROR) << "frontend: quic requires TLS"; + return -1; + } + } + + UpstreamAddr addr{}; + addr.fd = -1; + addr.tls = params.tls; + addr.sni_fwd = params.sni_fwd; + addr.alt_mode = params.alt_mode; + addr.accept_proxy_protocol = params.proxyproto; + addr.quic = params.quic; + + if (addr.alt_mode == UpstreamAltMode::API) { + apiconf.enabled = true; + } + +#ifdef ENABLE_HTTP3 + auto &addrs = params.quic ? config->conn.quic_listener.addrs + : config->conn.listener.addrs; +#else // !ENABLE_HTTP3 + auto &addrs = config->conn.listener.addrs; +#endif // !ENABLE_HTTP3 + + if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) { + if (addr.quic) { + LOG(ERROR) << "frontend: quic cannot be used on UNIX domain socket"; + return -1; + } + + auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size(); + addr.host = make_string_ref(config->balloc, StringRef{path, addr_end}); + addr.host_unix = true; + addr.index = addrs.size(); + + addrs.push_back(std::move(addr)); + + return 0; + } + + if (split_host_port(host, sizeof(host), &port, + StringRef{std::begin(optarg), addr_end}, opt) == -1) { + return -1; + } + + addr.host = make_string_ref(config->balloc, StringRef{host}); + addr.port = port; + + if (util::numeric_host(host, AF_INET)) { + addr.family = AF_INET; + addr.index = addrs.size(); + addrs.push_back(std::move(addr)); + return 0; + } + + if (util::numeric_host(host, AF_INET6)) { + addr.family = AF_INET6; + addr.index = addrs.size(); + addrs.push_back(std::move(addr)); + return 0; + } + + addr.family = AF_INET; + addr.index = addrs.size(); + addrs.push_back(addr); + + addr.family = AF_INET6; + addr.index = addrs.size(); + addrs.push_back(std::move(addr)); + + return 0; + } + case SHRPX_OPTID_WORKERS: +#ifdef NOTHREADS + LOG(WARN) << "Threading disabled at build time, no threads created."; + return 0; +#else // !NOTHREADS + return parse_uint(&config->num_worker, opt, optarg); +#endif // !NOTHREADS + case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS: { + LOG(WARN) << opt << ": deprecated. Use " + << SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS << " and " + << SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS << " instead."; + size_t n; + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + auto &http2conf = config->http2; + http2conf.upstream.max_concurrent_streams = n; + http2conf.downstream.max_concurrent_streams = n; + + return 0; + } + case SHRPX_OPTID_LOG_LEVEL: { + auto level = Log::get_severity_level_by_name(optarg); + if (level == -1) { + LOG(ERROR) << opt << ": Invalid severity level: " << optarg; + return -1; + } + config->logging.severity = level; + + return 0; + } + case SHRPX_OPTID_DAEMON: + config->daemon = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_HTTP2_PROXY: + config->http2_proxy = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_HTTP2_BRIDGE: + LOG(ERROR) << opt + << ": deprecated. Use backend=,;;proto=h2;tls"; + return -1; + case SHRPX_OPTID_CLIENT_PROXY: + LOG(ERROR) + << opt + << ": deprecated. Use http2-proxy, frontend=,;no-tls " + "and backend=,;;proto=h2;tls"; + return -1; + case SHRPX_OPTID_ADD_X_FORWARDED_FOR: + config->http.xff.add = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR: + config->http.xff.strip_incoming = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_VIA: + config->http.no_via = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.http2_read, opt, + optarg); + case SHRPX_OPTID_FRONTEND_READ_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.read, opt, optarg); + case SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.write, opt, optarg); + case SHRPX_OPTID_BACKEND_READ_TIMEOUT: + return parse_duration(&config->conn.downstream->timeout.read, opt, optarg); + case SHRPX_OPTID_BACKEND_WRITE_TIMEOUT: + return parse_duration(&config->conn.downstream->timeout.write, opt, optarg); + case SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT: + return parse_duration(&config->conn.downstream->timeout.connect, opt, + optarg); + case SHRPX_OPTID_STREAM_READ_TIMEOUT: + return parse_duration(&config->http2.timeout.stream_read, opt, optarg); + case SHRPX_OPTID_STREAM_WRITE_TIMEOUT: + return parse_duration(&config->http2.timeout.stream_write, opt, optarg); + case SHRPX_OPTID_ACCESSLOG_FILE: + config->logging.access.file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ACCESSLOG_SYSLOG: + config->logging.access.syslog = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_ACCESSLOG_FORMAT: + config->logging.access.format = parse_log_format(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ERRORLOG_FILE: + config->logging.error.file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ERRORLOG_SYSLOG: + config->logging.error.syslog = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FASTOPEN: + return parse_uint(&config->conn.listener.fastopen, opt, optarg); + case SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT: + return parse_duration(&config->conn.downstream->timeout.idle_read, opt, + optarg); + case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS: + case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS: { + LOG(WARN) << opt << ": deprecated. Use " + << (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS + ? SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE + : SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE); + int32_t *resp; + + if (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS) { + resp = &config->http2.upstream.window_size; + } else { + resp = &config->http2.downstream.window_size; + } + + errno = 0; + + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n >= 31) { + LOG(ERROR) << opt + << ": specify the integer in the range [0, 30], inclusive"; + return -1; + } + + // Make 16 bits to the HTTP/2 default 64KiB - 1. This is the same + // behaviour of previous code. + *resp = (1 << n) - 1; + + return 0; + } + case SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS: + case SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS: { + LOG(WARN) << opt << ": deprecated. Use " + << (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS + ? SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE + : SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE); + int32_t *resp; + + if (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS) { + resp = &config->http2.upstream.connection_window_size; + } else { + resp = &config->http2.downstream.connection_window_size; + } + + errno = 0; + + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n < 16 || n >= 31) { + LOG(ERROR) << opt + << ": specify the integer in the range [16, 30], inclusive"; + return -1; + } + + *resp = (1 << n) - 1; + + return 0; + } + case SHRPX_OPTID_FRONTEND_NO_TLS: + LOG(WARN) << opt << ": deprecated. Use no-tls keyword in " + << SHRPX_OPT_FRONTEND; + return 0; + case SHRPX_OPTID_BACKEND_NO_TLS: + LOG(WARN) << opt + << ": deprecated. backend connection is not encrypted by " + "default. See also " + << SHRPX_OPT_BACKEND_TLS; + return 0; + case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD: + LOG(WARN) << opt + << ": deprecated. Use sni keyword in --backend option. " + "For now, all sni values of all backends are " + "overridden by the given value " + << optarg; + config->tls.backend_sni_name = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_PID_FILE: + config->pid_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_USER: { + auto pwd = getpwnam(optarg.c_str()); + if (!pwd) { + LOG(ERROR) << opt << ": failed to get uid from " << optarg << ": " + << xsi_strerror(errno, errbuf.data(), errbuf.size()); + return -1; + } + config->user = make_string_ref(config->balloc, StringRef{pwd->pw_name}); + config->uid = pwd->pw_uid; + config->gid = pwd->pw_gid; + + return 0; + } + case SHRPX_OPTID_PRIVATE_KEY_FILE: + config->tls.private_key_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE: { + auto passwd = read_passwd_from_file(opt, optarg); + if (passwd.empty()) { + LOG(ERROR) << opt << ": Couldn't read key file's passwd from " << optarg; + return -1; + } + config->tls.private_key_passwd = + make_string_ref(config->balloc, StringRef{passwd}); + + return 0; + } + case SHRPX_OPTID_CERTIFICATE_FILE: + config->tls.cert_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_DH_PARAM_FILE: + config->tls.dh_param_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_SUBCERT: { + auto end_keys = std::find(std::begin(optarg), std::end(optarg), ';'); + auto src_params = StringRef{end_keys, std::end(optarg)}; + + SubcertParams params; + if (parse_subcert_params(params, src_params) != 0) { + return -1; + } + + std::vector sct_data; + + if (!params.sct_dir.empty()) { + // Make sure that dir_path is NULL terminated string. + if (read_tls_sct_from_dir(sct_data, opt, + StringRef{params.sct_dir.str()}) != 0) { + return -1; + } + } + + // Private Key file and certificate file separated by ':'. + auto sp = std::find(std::begin(optarg), end_keys, ':'); + if (sp == end_keys) { + LOG(ERROR) << opt << ": missing ':' in " + << StringRef{std::begin(optarg), end_keys}; + return -1; + } + + auto private_key_file = StringRef{std::begin(optarg), sp}; + + if (private_key_file.empty()) { + LOG(ERROR) << opt << ": missing private key file: " + << StringRef{std::begin(optarg), end_keys}; + return -1; + } + + auto cert_file = StringRef{sp + 1, end_keys}; + + if (cert_file.empty()) { + LOG(ERROR) << opt << ": missing certificate file: " + << StringRef{std::begin(optarg), end_keys}; + return -1; + } + + config->tls.subcerts.emplace_back( + make_string_ref(config->balloc, private_key_file), + make_string_ref(config->balloc, cert_file), std::move(sct_data)); + + return 0; + } + case SHRPX_OPTID_SYSLOG_FACILITY: { + int facility = int_syslog_facility(optarg); + if (facility == -1) { + LOG(ERROR) << opt << ": Unknown syslog facility: " << optarg; + return -1; + } + config->logging.syslog_facility = facility; + + return 0; + } + case SHRPX_OPTID_BACKLOG: + return parse_uint(&config->conn.listener.backlog, opt, optarg); + case SHRPX_OPTID_CIPHERS: + config->tls.ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS13_CIPHERS: + config->tls.tls13_ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_CLIENT: + LOG(ERROR) << opt + << ": deprecated. Use frontend=,;no-tls, " + "backend=,;;proto=h2;tls"; + return -1; + case SHRPX_OPTID_INSECURE: + config->tls.insecure = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_CACERT: + config->tls.cacert = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_BACKEND_IPV4: + LOG(WARN) << opt + << ": deprecated. Use backend-address-family=IPv4 instead."; + + config->conn.downstream->family = AF_INET; + + return 0; + case SHRPX_OPTID_BACKEND_IPV6: + LOG(WARN) << opt + << ": deprecated. Use backend-address-family=IPv6 instead."; + + config->conn.downstream->family = AF_INET6; + + return 0; + case SHRPX_OPTID_BACKEND_HTTP_PROXY_URI: { + auto &proxy = config->downstream_http_proxy; + // Reset here so that multiple option occurrence does not merge + // the results. + proxy = {}; + // parse URI and get hostname, port and optionally userinfo. + http_parser_url u{}; + int rv = http_parser_parse_url(optarg.c_str(), optarg.size(), 0, &u); + if (rv == 0) { + if (u.field_set & UF_USERINFO) { + auto uf = util::get_uri_field(optarg.c_str(), u, UF_USERINFO); + // Surprisingly, u.field_set & UF_USERINFO is nonzero even if + // userinfo component is empty string. + if (!uf.empty()) { + proxy.userinfo = util::percent_decode(config->balloc, uf); + } + } + if (u.field_set & UF_HOST) { + proxy.host = make_string_ref( + config->balloc, util::get_uri_field(optarg.c_str(), u, UF_HOST)); + } else { + LOG(ERROR) << opt << ": no hostname specified"; + return -1; + } + if (u.field_set & UF_PORT) { + proxy.port = u.port; + } else { + LOG(ERROR) << opt << ": no port specified"; + return -1; + } + } else { + LOG(ERROR) << opt << ": parse error"; + return -1; + } + + return 0; + } + case SHRPX_OPTID_READ_RATE: + return parse_uint_with_unit(&config->conn.upstream.ratelimit.read.rate, opt, + optarg); + case SHRPX_OPTID_READ_BURST: + return parse_uint_with_unit(&config->conn.upstream.ratelimit.read.burst, + opt, optarg); + case SHRPX_OPTID_WRITE_RATE: + return parse_uint_with_unit(&config->conn.upstream.ratelimit.write.rate, + opt, optarg); + case SHRPX_OPTID_WRITE_BURST: + return parse_uint_with_unit(&config->conn.upstream.ratelimit.write.burst, + opt, optarg); + case SHRPX_OPTID_WORKER_READ_RATE: + LOG(WARN) << opt << ": not implemented yet"; + return 0; + case SHRPX_OPTID_WORKER_READ_BURST: + LOG(WARN) << opt << ": not implemented yet"; + return 0; + case SHRPX_OPTID_WORKER_WRITE_RATE: + LOG(WARN) << opt << ": not implemented yet"; + return 0; + case SHRPX_OPTID_WORKER_WRITE_BURST: + LOG(WARN) << opt << ": not implemented yet"; + return 0; + case SHRPX_OPTID_NPN_LIST: { + auto list = util::split_str(optarg, ','); + config->tls.npn_list.resize(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + config->tls.npn_list[i] = make_string_ref(config->balloc, list[i]); + } + + return 0; + } + case SHRPX_OPTID_TLS_PROTO_LIST: { + LOG(WARN) << opt + << ": deprecated. Use tls-min-proto-version and " + "tls-max-proto-version instead."; + auto list = util::split_str(optarg, ','); + config->tls.tls_proto_list.resize(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + config->tls.tls_proto_list[i] = make_string_ref(config->balloc, list[i]); + } + + return 0; + } + case SHRPX_OPTID_VERIFY_CLIENT: + config->tls.client_verify.enabled = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_VERIFY_CLIENT_CACERT: + config->tls.client_verify.cacert = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE: + config->tls.client.private_key_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_CLIENT_CERT_FILE: + config->tls.client.cert_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER: + config->http2.upstream.debug.dump.request_header_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER: + config->http2.upstream.debug.dump.response_header_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING: + config->http2.no_cookie_crumbling = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_FRAME_DEBUG: + config->http2.upstream.debug.frame_debug = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_PADDING: + return parse_uint(&config->padding, opt, optarg); + case SHRPX_OPTID_ALTSVC: { + AltSvc altsvc{}; + + if (parse_altsvc(altsvc, opt, optarg) != 0) { + return -1; + } + + config->http.altsvcs.push_back(std::move(altsvc)); + + return 0; + } + case SHRPX_OPTID_ADD_REQUEST_HEADER: + case SHRPX_OPTID_ADD_RESPONSE_HEADER: { + auto p = parse_header(config->balloc, optarg); + if (p.name.empty()) { + LOG(ERROR) << opt << ": invalid header field: " << optarg; + return -1; + } + if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) { + config->http.add_request_headers.push_back(std::move(p)); + } else { + config->http.add_response_headers.push_back(std::move(p)); + } + return 0; + } + case SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS: + return parse_uint(&config->conn.upstream.worker_connections, opt, optarg); + case SHRPX_OPTID_NO_LOCATION_REWRITE: + config->http.no_location_rewrite = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_HOST_REWRITE: + LOG(WARN) << SHRPX_OPT_NO_HOST_REWRITE + << ": deprecated. :authority and host header fields are NOT " + "altered by default. To rewrite these headers, use " + "--host-rewrite option."; + + return 0; + case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST: + LOG(WARN) << opt + << ": deprecated. Use backend-connections-per-host instead."; + // fall through + case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST: { + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n == 0) { + LOG(ERROR) << opt << ": specify an integer strictly more than 0"; + + return -1; + } + + config->conn.downstream->connections_per_host = n; + + return 0; + } + case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND: + LOG(WARN) << opt << ": deprecated. Use " + << SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND << " instead."; + // fall through + case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND: + return parse_uint(&config->conn.downstream->connections_per_frontend, opt, + optarg); + case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT: + return parse_duration(&config->conn.listener.timeout.sleep, opt, optarg); + case SHRPX_OPTID_TLS_TICKET_KEY_FILE: + config->tls.ticket.files.emplace_back( + make_string_ref(config->balloc, optarg)); + return 0; + case SHRPX_OPTID_RLIMIT_NOFILE: { + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n < 0) { + LOG(ERROR) << opt << ": specify the integer more than or equal to 0"; + + return -1; + } + + config->rlimit_nofile = n; + + return 0; + } + case SHRPX_OPTID_BACKEND_REQUEST_BUFFER: + case SHRPX_OPTID_BACKEND_RESPONSE_BUFFER: { + size_t n; + if (parse_uint_with_unit(&n, opt, optarg) != 0) { + return -1; + } + + if (n == 0) { + LOG(ERROR) << opt << ": specify an integer strictly more than 0"; + + return -1; + } + + if (optid == SHRPX_OPTID_BACKEND_REQUEST_BUFFER) { + config->conn.downstream->request_buffer_size = n; + } else { + config->conn.downstream->response_buffer_size = n; + } + + return 0; + } + + case SHRPX_OPTID_NO_SERVER_PUSH: + config->http2.no_server_push = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER: + LOG(WARN) << opt << ": deprecated."; + return 0; + case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE: + config->tls.ocsp.fetch_ocsp_response_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_OCSP_UPDATE_INTERVAL: + return parse_duration(&config->tls.ocsp.update_interval, opt, optarg); + case SHRPX_OPTID_NO_OCSP: + config->tls.ocsp.disabled = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_HEADER_FIELD_BUFFER: + LOG(WARN) << opt + << ": deprecated. Use request-header-field-buffer instead."; + // fall through + case SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER: + return parse_uint_with_unit(&config->http.request_header_field_buffer, opt, + optarg); + case SHRPX_OPTID_MAX_HEADER_FIELDS: + LOG(WARN) << opt << ": deprecated. Use max-request-header-fields instead."; + // fall through + case SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS: + return parse_uint(&config->http.max_request_header_fields, opt, optarg); + case SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER: + return parse_uint_with_unit(&config->http.response_header_field_buffer, opt, + optarg); + case SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS: + return parse_uint(&config->http.max_response_header_fields, opt, optarg); + case SHRPX_OPTID_INCLUDE: { + if (included_set.count(optarg)) { + LOG(ERROR) << opt << ": " << optarg << " has already been included"; + return -1; + } + + included_set.insert(optarg); + auto rv = + load_config(config, optarg.c_str(), included_set, pattern_addr_indexer); + included_set.erase(optarg); + + if (rv != 0) { + return -1; + } + + return 0; + } + case SHRPX_OPTID_TLS_TICKET_KEY_CIPHER: + if (util::strieq_l("aes-128-cbc", optarg)) { + config->tls.ticket.cipher = EVP_aes_128_cbc(); + } else if (util::strieq_l("aes-256-cbc", optarg)) { + config->tls.ticket.cipher = EVP_aes_256_cbc(); + } else { + LOG(ERROR) << opt + << ": unsupported cipher for ticket encryption: " << optarg; + return -1; + } + config->tls.ticket.cipher_given = true; + + return 0; + case SHRPX_OPTID_HOST_REWRITE: + config->http.no_host_rewrite = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: { + auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';'); + auto src_params = StringRef{addr_end, std::end(optarg)}; + + MemcachedConnectionParams params{}; + if (parse_memcached_connection_params(params, src_params, StringRef{opt}) != + 0) { + return -1; + } + + if (split_host_port(host, sizeof(host), &port, + StringRef{std::begin(optarg), addr_end}, opt) == -1) { + return -1; + } + + switch (optid) { + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: { + auto &memcachedconf = config->tls.session_cache.memcached; + memcachedconf.host = make_string_ref(config->balloc, StringRef{host}); + memcachedconf.port = port; + memcachedconf.tls = params.tls; + break; + } + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: { + auto &memcachedconf = config->tls.ticket.memcached; + memcachedconf.host = make_string_ref(config->balloc, StringRef{host}); + memcachedconf.port = port; + memcachedconf.tls = params.tls; + break; + } + }; + + return 0; + } + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL: + return parse_duration(&config->tls.ticket.memcached.interval, opt, optarg); + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY: { + int n; + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n > 30) { + LOG(ERROR) << opt << ": must be smaller than or equal to 30"; + return -1; + } + + config->tls.ticket.memcached.max_retry = n; + return 0; + } + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL: + return parse_uint(&config->tls.ticket.memcached.max_fail, opt, optarg); + case SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD: { + size_t n; + if (parse_uint_with_unit(&n, opt, optarg) != 0) { + return -1; + } + + config->tls.dyn_rec.warmup_threshold = n; + + return 0; + } + + case SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT: + return parse_duration(&config->tls.dyn_rec.idle_timeout, opt, optarg); + + case SHRPX_OPTID_MRUBY_FILE: +#ifdef HAVE_MRUBY + config->mruby_file = make_string_ref(config->balloc, optarg); +#else // !HAVE_MRUBY + LOG(WARN) << opt + << ": ignored because mruby support is disabled at build time."; +#endif // !HAVE_MRUBY + return 0; + case SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL: + LOG(WARN) << opt << ": deprecated. Use proxyproto keyword in " + << SHRPX_OPT_FRONTEND << " instead."; + config->conn.upstream.accept_proxy_protocol = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_ADD_FORWARDED: { + auto &fwdconf = config->http.forwarded; + fwdconf.params = FORWARDED_NONE; + for (const auto ¶m : util::split_str(optarg, ',')) { + if (util::strieq_l("by", param)) { + fwdconf.params |= FORWARDED_BY; + continue; + } + if (util::strieq_l("for", param)) { + fwdconf.params |= FORWARDED_FOR; + continue; + } + if (util::strieq_l("host", param)) { + fwdconf.params |= FORWARDED_HOST; + continue; + } + if (util::strieq_l("proto", param)) { + fwdconf.params |= FORWARDED_PROTO; + continue; + } + + LOG(ERROR) << opt << ": unknown parameter " << optarg; + + return -1; + } + + return 0; + } + case SHRPX_OPTID_STRIP_INCOMING_FORWARDED: + config->http.forwarded.strip_incoming = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FORWARDED_BY: + case SHRPX_OPTID_FORWARDED_FOR: { + auto type = parse_forwarded_node_type(optarg); + + if (type == static_cast(-1) || + (optid == SHRPX_OPTID_FORWARDED_FOR && optarg[0] == '_')) { + LOG(ERROR) << opt << ": unknown node type or illegal obfuscated string " + << optarg; + return -1; + } + + auto &fwdconf = config->http.forwarded; + + switch (optid) { + case SHRPX_OPTID_FORWARDED_BY: + fwdconf.by_node_type = type; + if (optarg[0] == '_') { + fwdconf.by_obfuscated = make_string_ref(config->balloc, optarg); + } else { + fwdconf.by_obfuscated = StringRef::from_lit(""); + } + break; + case SHRPX_OPTID_FORWARDED_FOR: + fwdconf.for_node_type = type; + break; + } + + return 0; + } + case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST: + LOG(WARN) << opt << ": deprecated. Use " + << SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead."; + // fall through + case SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST: + config->tls.no_http2_cipher_block_list = util::strieq_l("yes", optarg); + return 0; + case SHRPX_OPTID_BACKEND_HTTP1_TLS: + case SHRPX_OPTID_BACKEND_TLS: + LOG(WARN) << opt << ": deprecated. Use tls keyword in " + << SHRPX_OPT_BACKEND << " instead."; + return 0; + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS: + LOG(WARN) << opt << ": deprecated. Use tls keyword in " + << SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED; + return 0; + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE: + config->tls.session_cache.memcached.cert_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE: + config->tls.session_cache.memcached.private_key_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS: + LOG(WARN) << opt << ": deprecated. Use tls keyword in " + << SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED; + return 0; + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE: + config->tls.ticket.memcached.cert_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE: + config->tls.ticket.memcached.private_key_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY: + return parse_address_family(&config->tls.ticket.memcached.family, opt, + optarg); + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY: + return parse_address_family(&config->tls.session_cache.memcached.family, + opt, optarg); + case SHRPX_OPTID_BACKEND_ADDRESS_FAMILY: + return parse_address_family(&config->conn.downstream->family, opt, optarg); + case SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS: + return parse_uint(&config->http2.upstream.max_concurrent_streams, opt, + optarg); + case SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS: + return parse_uint(&config->http2.downstream.max_concurrent_streams, opt, + optarg); + case SHRPX_OPTID_ERROR_PAGE: + return parse_error_page(config->http.error_pages, opt, optarg); + case SHRPX_OPTID_NO_KQUEUE: + if ((ev_supported_backends() & EVBACKEND_KQUEUE) == 0) { + LOG(WARN) << opt << ": kqueue is not supported on this platform"; + return 0; + } + + config->ev_loop_flags = ev_recommended_backends() & ~EVBACKEND_KQUEUE; + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT: + return parse_duration(&config->http2.upstream.timeout.settings, opt, + optarg); + case SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT: + return parse_duration(&config->http2.downstream.timeout.settings, opt, + optarg); + case SHRPX_OPTID_API_MAX_REQUEST_BODY: + return parse_uint_with_unit(&config->api.max_request_body, opt, optarg); + case SHRPX_OPTID_BACKEND_MAX_BACKOFF: + return parse_duration(&config->conn.downstream->timeout.max_backoff, opt, + optarg); + case SHRPX_OPTID_SERVER_NAME: + config->http.server_name = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_NO_SERVER_REWRITE: + config->http.no_server_rewrite = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE: + config->http2.upstream.optimize_write_buffer_size = + util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE: + config->http2.upstream.optimize_window_size = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE: + if (parse_uint_with_unit(&config->http2.upstream.window_size, opt, + optarg) != 0) { + return -1; + } + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE: + if (parse_uint_with_unit(&config->http2.upstream.connection_window_size, + opt, optarg) != 0) { + return -1; + } + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE: + if (parse_uint_with_unit(&config->http2.downstream.window_size, opt, + optarg) != 0) { + return -1; + } + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE: + if (parse_uint_with_unit(&config->http2.downstream.connection_window_size, + opt, optarg) != 0) { + return -1; + } + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE: + if (parse_uint_with_unit(&config->http2.upstream.encoder_dynamic_table_size, + opt, optarg) != 0) { + return -1; + } + + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.upstream.option, + config->http2.upstream.encoder_dynamic_table_size); + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.upstream.alt_mode_option, + config->http2.upstream.encoder_dynamic_table_size); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE: + return parse_uint_with_unit( + &config->http2.upstream.decoder_dynamic_table_size, opt, optarg); + case SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE: + if (parse_uint_with_unit( + &config->http2.downstream.encoder_dynamic_table_size, opt, + optarg) != 0) { + return -1; + } + + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.downstream.option, + config->http2.downstream.encoder_dynamic_table_size); + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE: + return parse_uint_with_unit( + &config->http2.downstream.decoder_dynamic_table_size, opt, optarg); + case SHRPX_OPTID_ECDH_CURVES: +#if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + config->tls.ecdh_curves = make_string_ref(config->balloc, optarg); +#else // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + LOG(WARN) << opt << ": This option requires OpenSSL >= 1.0.2"; +#endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + return 0; + case SHRPX_OPTID_TLS_SCT_DIR: +#if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + return read_tls_sct_from_dir(config->tls.sct_data, opt, optarg); +#else // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + LOG(WARN) << opt << ": This option requires OpenSSL >= 1.0.2"; + return 0; +#endif // !(!LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L) + case SHRPX_OPTID_DNS_CACHE_TIMEOUT: + return parse_duration(&config->dns.timeout.cache, opt, optarg); + case SHRPX_OPTID_DNS_LOOKUP_TIMEOUT: + return parse_duration(&config->dns.timeout.lookup, opt, optarg); + case SHRPX_OPTID_DNS_MAX_TRY: { + int n; + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n > 5) { + LOG(ERROR) << opt << ": must be smaller than or equal to 5"; + return -1; + } + + config->dns.max_try = n; + return 0; + } + case SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.idle_read, opt, + optarg); + case SHRPX_OPTID_PSK_SECRETS: +#if !LIBRESSL_LEGACY_API + return parse_psk_secrets(config, optarg); +#else // LIBRESSL_LEGACY_API + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support PSK"; + return 0; +#endif // LIBRESSL_LEGACY_API + case SHRPX_OPTID_CLIENT_PSK_SECRETS: +#if !LIBRESSL_LEGACY_API + return parse_client_psk_secrets(config, optarg); +#else // LIBRESSL_LEGACY_API + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support PSK"; + return 0; +#endif // LIBRESSL_LEGACY_API + case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST: + LOG(WARN) << opt << ": deprecated. Use " + << SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead."; + // fall through + case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST: + config->tls.client.no_http2_cipher_block_list = + util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_CLIENT_CIPHERS: + config->tls.client.ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS13_CLIENT_CIPHERS: + config->tls.client.tls13_ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ACCESSLOG_WRITE_EARLY: + config->logging.access.write_early = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_MIN_PROTO_VERSION: + return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg); + case SHRPX_OPTID_TLS_MAX_PROTO_VERSION: + return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg); + case SHRPX_OPTID_REDIRECT_HTTPS_PORT: { + auto n = util::parse_uint(optarg); + if (n == -1 || n < 0 || n > 65535) { + LOG(ERROR) << opt + << ": bad value. Specify an integer in the range [0, " + "65535], inclusive"; + return -1; + } + config->http.redirect_https_port = make_string_ref(config->balloc, optarg); + return 0; + } + case SHRPX_OPTID_FRONTEND_MAX_REQUESTS: + return parse_uint(&config->http.max_requests, opt, optarg); + case SHRPX_OPTID_SINGLE_THREAD: + config->single_thread = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_SINGLE_PROCESS: + config->single_process = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO: + config->http.xfp.add = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO: + config->http.xfp.strip_incoming = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_OCSP_STARTUP: + config->tls.ocsp.startup = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_VERIFY_OCSP: + config->tls.ocsp.no_verify = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED: + config->tls.client_verify.tolerate_expired = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR: + config->ignore_per_pattern_mruby_error = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA: + config->tls.no_postpone_early_data = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_MAX_EARLY_DATA: { + return parse_uint_with_unit(&config->tls.max_early_data, opt, optarg); + } + case SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA: + config->http.early_data.strip_incoming = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE: +#ifdef ENABLE_HTTP3 + config->quic.bpf.prog_file = make_string_ref(config->balloc, optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_NO_QUIC_BPF: +#ifdef ENABLE_HTTP3 + config->quic.bpf.disabled = util::strieq_l("yes", optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_HTTP2_ALTSVC: { + AltSvc altsvc{}; + + if (parse_altsvc(altsvc, opt, optarg) != 0) { + return -1; + } + + config->http.http2_altsvcs.push_back(std::move(altsvc)); + + return 0; + } + case SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT: +#ifdef ENABLE_HTTP3 + return parse_duration(&config->conn.upstream.timeout.http3_read, opt, + optarg); +#else // !ENABLE_HTTP3 + return 0; +#endif // !ENABLE_HTTP3 + case SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT: +#ifdef ENABLE_HTTP3 + return parse_duration(&config->quic.upstream.timeout.idle, opt, optarg); +#else // !ENABLE_HTTP3 + return 0; +#endif // !ENABLE_HTTP3 + case SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG: +#ifdef ENABLE_HTTP3 + config->quic.upstream.debug.log = util::strieq_l("yes", optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE: +#ifdef ENABLE_HTTP3 + if (parse_uint_with_unit(&config->http3.upstream.window_size, opt, + optarg) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE: +#ifdef ENABLE_HTTP3 + if (parse_uint_with_unit(&config->http3.upstream.connection_window_size, + opt, optarg) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE: +#ifdef ENABLE_HTTP3 + if (parse_uint_with_unit(&config->http3.upstream.max_window_size, opt, + optarg) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE: +#ifdef ENABLE_HTTP3 + if (parse_uint_with_unit(&config->http3.upstream.max_connection_window_size, + opt, optarg) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS: +#ifdef ENABLE_HTTP3 + return parse_uint(&config->http3.upstream.max_concurrent_streams, opt, + optarg); +#else // !ENABLE_HTTP3 + return 0; +#endif // !ENABLE_HTTP3 + case SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA: +#ifdef ENABLE_HTTP3 + config->quic.upstream.early_data = util::strieq_l("yes", optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR: +#ifdef ENABLE_HTTP3 + config->quic.upstream.qlog.dir = make_string_ref(config->balloc, optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN: +#ifdef ENABLE_HTTP3 + config->quic.upstream.require_token = util::strieq_l("yes", optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER: +#ifdef ENABLE_HTTP3 + if (util::strieq_l("cubic", optarg)) { + config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_CUBIC; + } else if (util::strieq_l("bbr", optarg)) { + config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_BBR; + } else { + LOG(ERROR) << opt << ": must be either cubic or bbr"; + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_QUIC_SERVER_ID: +#ifdef ENABLE_HTTP3 + if (optarg.size() != config->quic.server_id.size() * 2 || + !util::is_hex_string(optarg)) { + LOG(ERROR) << opt << ": must be a hex-string"; + return -1; + } + util::decode_hex(std::begin(config->quic.server_id), optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE: +#ifdef ENABLE_HTTP3 + config->quic.upstream.secret_file = make_string_ref(config->balloc, optarg); +#endif // ENABLE_HTTP3 + + return 0; + case SHRPX_OPTID_RLIMIT_MEMLOCK: { + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n < 0) { + LOG(ERROR) << opt << ": specify the integer more than or equal to 0"; + + return -1; + } + + config->rlimit_memlock = n; + + return 0; + } + case SHRPX_OPTID_MAX_WORKER_PROCESSES: + return parse_uint(&config->max_worker_processes, opt, optarg); + case SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD: + return parse_duration(&config->worker_process_grace_shutdown_period, opt, + optarg); + case SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT: { +#ifdef ENABLE_HTTP3 + return parse_duration(&config->quic.upstream.initial_rtt, opt, optarg); +#endif // ENABLE_HTTP3 + + return 0; + } + case SHRPX_OPTID_REQUIRE_HTTP_SCHEME: + config->http.require_http_scheme = util::strieq_l("yes", optarg); + return 0; + case SHRPX_OPTID_TLS_KTLS: + config->tls.ktls = util::strieq_l("yes", optarg); + return 0; + case SHRPX_OPTID_CONF: + LOG(WARN) << "conf: ignored"; + + return 0; + } + + LOG(ERROR) << "Unknown option: " << opt; + + return -1; +} + +int load_config(Config *config, const char *filename, + std::set &include_set, + std::map &pattern_addr_indexer) { + std::ifstream in(filename, std::ios::binary); + if (!in) { + LOG(ERROR) << "Could not open config file " << filename; + return -1; + } + std::string line; + int linenum = 0; + while (std::getline(in, line)) { + ++linenum; + if (line.empty() || line[0] == '#') { + continue; + } + auto eq = std::find(std::begin(line), std::end(line), '='); + if (eq == std::end(line)) { + LOG(ERROR) << "Bad configuration format in " << filename << " at line " + << linenum; + return -1; + } + *eq = '\0'; + + if (parse_config(config, StringRef{std::begin(line), eq}, + StringRef{eq + 1, std::end(line)}, include_set, + pattern_addr_indexer) != 0) { + return -1; + } + } + return 0; +} + +StringRef str_syslog_facility(int facility) { + switch (facility) { + case (LOG_AUTH): + return StringRef::from_lit("auth"); +#ifdef LOG_AUTHPRIV + case (LOG_AUTHPRIV): + return StringRef::from_lit("authpriv"); +#endif // LOG_AUTHPRIV + case (LOG_CRON): + return StringRef::from_lit("cron"); + case (LOG_DAEMON): + return StringRef::from_lit("daemon"); +#ifdef LOG_FTP + case (LOG_FTP): + return StringRef::from_lit("ftp"); +#endif // LOG_FTP + case (LOG_KERN): + return StringRef::from_lit("kern"); + case (LOG_LOCAL0): + return StringRef::from_lit("local0"); + case (LOG_LOCAL1): + return StringRef::from_lit("local1"); + case (LOG_LOCAL2): + return StringRef::from_lit("local2"); + case (LOG_LOCAL3): + return StringRef::from_lit("local3"); + case (LOG_LOCAL4): + return StringRef::from_lit("local4"); + case (LOG_LOCAL5): + return StringRef::from_lit("local5"); + case (LOG_LOCAL6): + return StringRef::from_lit("local6"); + case (LOG_LOCAL7): + return StringRef::from_lit("local7"); + case (LOG_LPR): + return StringRef::from_lit("lpr"); + case (LOG_MAIL): + return StringRef::from_lit("mail"); + case (LOG_SYSLOG): + return StringRef::from_lit("syslog"); + case (LOG_USER): + return StringRef::from_lit("user"); + case (LOG_UUCP): + return StringRef::from_lit("uucp"); + default: + return StringRef::from_lit("(unknown)"); + } +} + +int int_syslog_facility(const StringRef &strfacility) { + if (util::strieq_l("auth", strfacility)) { + return LOG_AUTH; + } + +#ifdef LOG_AUTHPRIV + if (util::strieq_l("authpriv", strfacility)) { + return LOG_AUTHPRIV; + } +#endif // LOG_AUTHPRIV + + if (util::strieq_l("cron", strfacility)) { + return LOG_CRON; + } + + if (util::strieq_l("daemon", strfacility)) { + return LOG_DAEMON; + } + +#ifdef LOG_FTP + if (util::strieq_l("ftp", strfacility)) { + return LOG_FTP; + } +#endif // LOG_FTP + + if (util::strieq_l("kern", strfacility)) { + return LOG_KERN; + } + + if (util::strieq_l("local0", strfacility)) { + return LOG_LOCAL0; + } + + if (util::strieq_l("local1", strfacility)) { + return LOG_LOCAL1; + } + + if (util::strieq_l("local2", strfacility)) { + return LOG_LOCAL2; + } + + if (util::strieq_l("local3", strfacility)) { + return LOG_LOCAL3; + } + + if (util::strieq_l("local4", strfacility)) { + return LOG_LOCAL4; + } + + if (util::strieq_l("local5", strfacility)) { + return LOG_LOCAL5; + } + + if (util::strieq_l("local6", strfacility)) { + return LOG_LOCAL6; + } + + if (util::strieq_l("local7", strfacility)) { + return LOG_LOCAL7; + } + + if (util::strieq_l("lpr", strfacility)) { + return LOG_LPR; + } + + if (util::strieq_l("mail", strfacility)) { + return LOG_MAIL; + } + + if (util::strieq_l("news", strfacility)) { + return LOG_NEWS; + } + + if (util::strieq_l("syslog", strfacility)) { + return LOG_SYSLOG; + } + + if (util::strieq_l("user", strfacility)) { + return LOG_USER; + } + + if (util::strieq_l("uucp", strfacility)) { + return LOG_UUCP; + } + + return -1; +} + +StringRef strproto(Proto proto) { + switch (proto) { + case Proto::NONE: + return StringRef::from_lit("none"); + case Proto::HTTP1: + return StringRef::from_lit("http/1.1"); + case Proto::HTTP2: + return StringRef::from_lit("h2"); + case Proto::HTTP3: + return StringRef::from_lit("h3"); + case Proto::MEMCACHED: + return StringRef::from_lit("memcached"); + } + + // gcc needs this. + assert(0); + abort(); +} + +namespace { +// Consistent hashing method described in +// https://github.com/RJ/ketama. Generate 160 32-bit hashes per |s|, +// which is usually backend address. The each hash is associated to +// index of backend address. When all hashes for every backend +// address are calculated, sort it in ascending order of hash. To +// choose the index, compute 32-bit hash based on client IP address, +// and do lower bound search in the array. The returned index is the +// backend to use. +int compute_affinity_hash(std::vector &res, size_t idx, + const StringRef &s) { + int rv; + std::array buf; + + for (auto i = 0; i < 20; ++i) { + auto t = s.str(); + t += i; + + rv = util::sha256(buf.data(), StringRef{t}); + if (rv != 0) { + return -1; + } + + for (int i = 0; i < 8; ++i) { + auto h = (static_cast(buf[4 * i]) << 24) | + (static_cast(buf[4 * i + 1]) << 16) | + (static_cast(buf[4 * i + 2]) << 8) | + static_cast(buf[4 * i + 3]); + + res.emplace_back(idx, h); + } + } + + return 0; +} +} // namespace + +// Configures the following member in |config|: +// conn.downstream_router, conn.downstream.addr_groups, +// conn.downstream.addr_group_catch_all. +int configure_downstream_group(Config *config, bool http2_proxy, + bool numeric_addr_only, + const TLSConfig &tlsconf) { + int rv; + + auto &downstreamconf = *config->conn.downstream; + auto &addr_groups = downstreamconf.addr_groups; + auto &routerconf = downstreamconf.router; + auto &router = routerconf.router; + + if (addr_groups.empty()) { + DownstreamAddrConfig addr{}; + addr.host = StringRef::from_lit(DEFAULT_DOWNSTREAM_HOST); + addr.port = DEFAULT_DOWNSTREAM_PORT; + addr.proto = Proto::HTTP1; + addr.weight = 1; + addr.group_weight = 1; + + DownstreamAddrGroupConfig g(StringRef::from_lit("/")); + g.addrs.push_back(std::move(addr)); + router.add_route(g.pattern, addr_groups.size()); + addr_groups.push_back(std::move(g)); + } + + // backward compatibility: override all SNI fields with the option + // value --backend-tls-sni-field + if (!tlsconf.backend_sni_name.empty()) { + auto &sni = tlsconf.backend_sni_name; + for (auto &addr_group : addr_groups) { + for (auto &addr : addr_group.addrs) { + addr.sni = sni; + } + } + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Resolving backend address"; + } + + ssize_t catch_all_group = -1; + for (size_t i = 0; i < addr_groups.size(); ++i) { + auto &g = addr_groups[i]; + if (g.pattern == StringRef::from_lit("/")) { + catch_all_group = i; + } + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern + << "'"; + for (auto &addr : g.addrs) { + LOG(INFO) << "group " << i << " -> " << addr.host.c_str() + << (addr.host_unix ? "" : ":" + util::utos(addr.port)) + << ", proto=" << strproto(addr.proto) + << (addr.tls ? ", tls" : ""); + } + } +#ifdef HAVE_MRUBY + // Try compile mruby script and catch compile error early. + if (!g.mruby_file.empty()) { + if (mruby::create_mruby_context(g.mruby_file) == nullptr) { + LOG(config->ignore_per_pattern_mruby_error ? ERROR : FATAL) + << "backend: Could not compile mruby file for pattern " + << g.pattern; + if (!config->ignore_per_pattern_mruby_error) { + return -1; + } + g.mruby_file = StringRef{}; + } + } +#endif // HAVE_MRUBY + } + +#ifdef HAVE_MRUBY + // Try compile mruby script (--mruby-file) here to catch compile + // error early. + if (!config->mruby_file.empty()) { + if (mruby::create_mruby_context(config->mruby_file) == nullptr) { + LOG(FATAL) << "mruby-file: Could not compile mruby file"; + return -1; + } + } +#endif // HAVE_MRUBY + + if (catch_all_group == -1) { + LOG(FATAL) << "backend: No catch-all backend address is configured"; + return -1; + } + + downstreamconf.addr_group_catch_all = catch_all_group; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Catch-all pattern is group " << catch_all_group; + } + + auto resolve_flags = numeric_addr_only ? AI_NUMERICHOST | AI_NUMERICSERV : 0; + + std::array hostport_buf; + + for (auto &g : addr_groups) { + std::unordered_map wgchk; + for (auto &addr : g.addrs) { + if (addr.group_weight) { + auto it = wgchk.find(addr.group); + if (it == std::end(wgchk)) { + wgchk.emplace(addr.group, addr.group_weight); + } else if ((*it).second != addr.group_weight) { + LOG(FATAL) << "backend: inconsistent group-weight for a single group"; + return -1; + } + } + + if (addr.host_unix) { + // for AF_UNIX socket, we use "localhost" as host for backend + // hostport. This is used as Host header field to backend and + // not going to be passed to any syscalls. + addr.hostport = StringRef::from_lit("localhost"); + + auto path = addr.host.c_str(); + auto pathlen = addr.host.size(); + + if (pathlen + 1 > sizeof(addr.addr.su.un.sun_path)) { + LOG(FATAL) << "UNIX domain socket path " << path << " is too long > " + << sizeof(addr.addr.su.un.sun_path); + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Use UNIX domain socket path " << path + << " for backend connection"; + } + + addr.addr.su.un.sun_family = AF_UNIX; + // copy path including terminal NULL + std::copy_n(path, pathlen + 1, addr.addr.su.un.sun_path); + addr.addr.len = sizeof(addr.addr.su.un); + + continue; + } + + addr.hostport = + util::make_http_hostport(downstreamconf.balloc, addr.host, addr.port); + + auto hostport = + util::make_hostport(std::begin(hostport_buf), addr.host, addr.port); + + if (!addr.dns) { + if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port, + downstreamconf.family, resolve_flags) == -1) { + LOG(FATAL) << "Resolving backend address failed: " << hostport; + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Resolved backend address: " << hostport << " -> " + << util::to_numeric_addr(&addr.addr); + } + } else { + LOG(INFO) << "Resolving backend address " << hostport + << " takes place dynamically"; + } + } + + for (auto &addr : g.addrs) { + if (addr.group_weight == 0) { + auto it = wgchk.find(addr.group); + if (it == std::end(wgchk)) { + addr.group_weight = 1; + } else { + addr.group_weight = (*it).second; + } + } + } + + if (g.affinity.type != SessionAffinity::NONE) { + size_t idx = 0; + for (auto &addr : g.addrs) { + StringRef key; + if (addr.dns) { + if (addr.host_unix) { + key = addr.host; + } else { + key = addr.hostport; + } + } else { + auto p = reinterpret_cast(&addr.addr.su); + key = StringRef{p, addr.addr.len}; + } + rv = compute_affinity_hash(g.affinity_hash, idx, key); + if (rv != 0) { + return -1; + } + + if (g.affinity.cookie.stickiness == + SessionAffinityCookieStickiness::STRICT) { + addr.affinity_hash = util::hash32(key); + g.affinity_hash_map.emplace(addr.affinity_hash, idx); + } + + ++idx; + } + + std::sort(std::begin(g.affinity_hash), std::end(g.affinity_hash), + [](const AffinityHash &lhs, const AffinityHash &rhs) { + return lhs.hash < rhs.hash; + }); + } + + auto &timeout = g.timeout; + if (timeout.read < 1e-9) { + timeout.read = downstreamconf.timeout.read; + } + if (timeout.write < 1e-9) { + timeout.write = downstreamconf.timeout.write; + } + } + + return 0; +} + +int resolve_hostname(Address *addr, const char *hostname, uint16_t port, + int family, int additional_flags) { + int rv; + + auto service = util::utos(port); + + addrinfo hints{}; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= additional_flags; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif // AI_ADDRCONFIG + addrinfo *res; + + rv = getaddrinfo(hostname, service.c_str(), &hints, &res); +#ifdef AI_ADDRCONFIG + if (rv != 0) { + // Retry without AI_ADDRCONFIG + hints.ai_flags &= ~AI_ADDRCONFIG; + rv = getaddrinfo(hostname, service.c_str(), &hints, &res); + } +#endif // AI_ADDRCONFIG + if (rv != 0) { + LOG(FATAL) << "Unable to resolve address for " << hostname << ": " + << gai_strerror(rv); + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + char host[NI_MAXHOST]; + rv = getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof(host), nullptr, + 0, NI_NUMERICHOST); + if (rv != 0) { + LOG(FATAL) << "Address resolution for " << hostname + << " failed: " << gai_strerror(rv); + + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Address resolution for " << hostname + << " succeeded: " << host; + } + + memcpy(&addr->su, res->ai_addr, res->ai_addrlen); + addr->len = res->ai_addrlen; + + return 0; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_config.h b/lib/nghttp2/src/shrpx_config.h new file mode 100644 index 00000000000..0c43a3c267d --- /dev/null +++ b/lib/nghttp2/src/shrpx_config.h @@ -0,0 +1,1448 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_CONFIG_H +#define SHRPX_CONFIG_H + +#include "shrpx.h" + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#include +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "shrpx_router.h" +#if ENABLE_HTTP3 +# include "shrpx_quic.h" +#endif // ENABLE_HTTP3 +#include "template.h" +#include "http2.h" +#include "network.h" +#include "allocator.h" + +using namespace nghttp2; + +namespace shrpx { + +struct LogFragment; +class ConnectBlocker; +class Http2Session; + +namespace tls { + +class CertLookupTree; + +} // namespace tls + +constexpr auto SHRPX_OPT_PRIVATE_KEY_FILE = + StringRef::from_lit("private-key-file"); +constexpr auto SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE = + StringRef::from_lit("private-key-passwd-file"); +constexpr auto SHRPX_OPT_CERTIFICATE_FILE = + StringRef::from_lit("certificate-file"); +constexpr auto SHRPX_OPT_DH_PARAM_FILE = StringRef::from_lit("dh-param-file"); +constexpr auto SHRPX_OPT_SUBCERT = StringRef::from_lit("subcert"); +constexpr auto SHRPX_OPT_BACKEND = StringRef::from_lit("backend"); +constexpr auto SHRPX_OPT_FRONTEND = StringRef::from_lit("frontend"); +constexpr auto SHRPX_OPT_WORKERS = StringRef::from_lit("workers"); +constexpr auto SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS = + StringRef::from_lit("http2-max-concurrent-streams"); +constexpr auto SHRPX_OPT_LOG_LEVEL = StringRef::from_lit("log-level"); +constexpr auto SHRPX_OPT_DAEMON = StringRef::from_lit("daemon"); +constexpr auto SHRPX_OPT_HTTP2_PROXY = StringRef::from_lit("http2-proxy"); +constexpr auto SHRPX_OPT_HTTP2_BRIDGE = StringRef::from_lit("http2-bridge"); +constexpr auto SHRPX_OPT_CLIENT_PROXY = StringRef::from_lit("client-proxy"); +constexpr auto SHRPX_OPT_ADD_X_FORWARDED_FOR = + StringRef::from_lit("add-x-forwarded-for"); +constexpr auto SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR = + StringRef::from_lit("strip-incoming-x-forwarded-for"); +constexpr auto SHRPX_OPT_NO_VIA = StringRef::from_lit("no-via"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT = + StringRef::from_lit("frontend-http2-read-timeout"); +constexpr auto SHRPX_OPT_FRONTEND_READ_TIMEOUT = + StringRef::from_lit("frontend-read-timeout"); +constexpr auto SHRPX_OPT_FRONTEND_WRITE_TIMEOUT = + StringRef::from_lit("frontend-write-timeout"); +constexpr auto SHRPX_OPT_BACKEND_READ_TIMEOUT = + StringRef::from_lit("backend-read-timeout"); +constexpr auto SHRPX_OPT_BACKEND_WRITE_TIMEOUT = + StringRef::from_lit("backend-write-timeout"); +constexpr auto SHRPX_OPT_STREAM_READ_TIMEOUT = + StringRef::from_lit("stream-read-timeout"); +constexpr auto SHRPX_OPT_STREAM_WRITE_TIMEOUT = + StringRef::from_lit("stream-write-timeout"); +constexpr auto SHRPX_OPT_ACCESSLOG_FILE = StringRef::from_lit("accesslog-file"); +constexpr auto SHRPX_OPT_ACCESSLOG_SYSLOG = + StringRef::from_lit("accesslog-syslog"); +constexpr auto SHRPX_OPT_ACCESSLOG_FORMAT = + StringRef::from_lit("accesslog-format"); +constexpr auto SHRPX_OPT_ERRORLOG_FILE = StringRef::from_lit("errorlog-file"); +constexpr auto SHRPX_OPT_ERRORLOG_SYSLOG = + StringRef::from_lit("errorlog-syslog"); +constexpr auto SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT = + StringRef::from_lit("backend-keep-alive-timeout"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS = + StringRef::from_lit("frontend-http2-window-bits"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS = + StringRef::from_lit("backend-http2-window-bits"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS = + StringRef::from_lit("frontend-http2-connection-window-bits"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS = + StringRef::from_lit("backend-http2-connection-window-bits"); +constexpr auto SHRPX_OPT_FRONTEND_NO_TLS = + StringRef::from_lit("frontend-no-tls"); +constexpr auto SHRPX_OPT_BACKEND_NO_TLS = StringRef::from_lit("backend-no-tls"); +constexpr auto SHRPX_OPT_BACKEND_TLS_SNI_FIELD = + StringRef::from_lit("backend-tls-sni-field"); +constexpr auto SHRPX_OPT_PID_FILE = StringRef::from_lit("pid-file"); +constexpr auto SHRPX_OPT_USER = StringRef::from_lit("user"); +constexpr auto SHRPX_OPT_SYSLOG_FACILITY = + StringRef::from_lit("syslog-facility"); +constexpr auto SHRPX_OPT_BACKLOG = StringRef::from_lit("backlog"); +constexpr auto SHRPX_OPT_CIPHERS = StringRef::from_lit("ciphers"); +constexpr auto SHRPX_OPT_CLIENT = StringRef::from_lit("client"); +constexpr auto SHRPX_OPT_INSECURE = StringRef::from_lit("insecure"); +constexpr auto SHRPX_OPT_CACERT = StringRef::from_lit("cacert"); +constexpr auto SHRPX_OPT_BACKEND_IPV4 = StringRef::from_lit("backend-ipv4"); +constexpr auto SHRPX_OPT_BACKEND_IPV6 = StringRef::from_lit("backend-ipv6"); +constexpr auto SHRPX_OPT_BACKEND_HTTP_PROXY_URI = + StringRef::from_lit("backend-http-proxy-uri"); +constexpr auto SHRPX_OPT_READ_RATE = StringRef::from_lit("read-rate"); +constexpr auto SHRPX_OPT_READ_BURST = StringRef::from_lit("read-burst"); +constexpr auto SHRPX_OPT_WRITE_RATE = StringRef::from_lit("write-rate"); +constexpr auto SHRPX_OPT_WRITE_BURST = StringRef::from_lit("write-burst"); +constexpr auto SHRPX_OPT_WORKER_READ_RATE = + StringRef::from_lit("worker-read-rate"); +constexpr auto SHRPX_OPT_WORKER_READ_BURST = + StringRef::from_lit("worker-read-burst"); +constexpr auto SHRPX_OPT_WORKER_WRITE_RATE = + StringRef::from_lit("worker-write-rate"); +constexpr auto SHRPX_OPT_WORKER_WRITE_BURST = + StringRef::from_lit("worker-write-burst"); +constexpr auto SHRPX_OPT_NPN_LIST = StringRef::from_lit("npn-list"); +constexpr auto SHRPX_OPT_TLS_PROTO_LIST = StringRef::from_lit("tls-proto-list"); +constexpr auto SHRPX_OPT_VERIFY_CLIENT = StringRef::from_lit("verify-client"); +constexpr auto SHRPX_OPT_VERIFY_CLIENT_CACERT = + StringRef::from_lit("verify-client-cacert"); +constexpr auto SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE = + StringRef::from_lit("client-private-key-file"); +constexpr auto SHRPX_OPT_CLIENT_CERT_FILE = + StringRef::from_lit("client-cert-file"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER = + StringRef::from_lit("frontend-http2-dump-request-header"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER = + StringRef::from_lit("frontend-http2-dump-response-header"); +constexpr auto SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING = + StringRef::from_lit("http2-no-cookie-crumbling"); +constexpr auto SHRPX_OPT_FRONTEND_FRAME_DEBUG = + StringRef::from_lit("frontend-frame-debug"); +constexpr auto SHRPX_OPT_PADDING = StringRef::from_lit("padding"); +constexpr auto SHRPX_OPT_ALTSVC = StringRef::from_lit("altsvc"); +constexpr auto SHRPX_OPT_ADD_REQUEST_HEADER = + StringRef::from_lit("add-request-header"); +constexpr auto SHRPX_OPT_ADD_RESPONSE_HEADER = + StringRef::from_lit("add-response-header"); +constexpr auto SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS = + StringRef::from_lit("worker-frontend-connections"); +constexpr auto SHRPX_OPT_NO_LOCATION_REWRITE = + StringRef::from_lit("no-location-rewrite"); +constexpr auto SHRPX_OPT_NO_HOST_REWRITE = + StringRef::from_lit("no-host-rewrite"); +constexpr auto SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST = + StringRef::from_lit("backend-http1-connections-per-host"); +constexpr auto SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND = + StringRef::from_lit("backend-http1-connections-per-frontend"); +constexpr auto SHRPX_OPT_LISTENER_DISABLE_TIMEOUT = + StringRef::from_lit("listener-disable-timeout"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_FILE = + StringRef::from_lit("tls-ticket-key-file"); +constexpr auto SHRPX_OPT_RLIMIT_NOFILE = StringRef::from_lit("rlimit-nofile"); +constexpr auto SHRPX_OPT_BACKEND_REQUEST_BUFFER = + StringRef::from_lit("backend-request-buffer"); +constexpr auto SHRPX_OPT_BACKEND_RESPONSE_BUFFER = + StringRef::from_lit("backend-response-buffer"); +constexpr auto SHRPX_OPT_NO_SERVER_PUSH = StringRef::from_lit("no-server-push"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER = + StringRef::from_lit("backend-http2-connections-per-worker"); +constexpr auto SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE = + StringRef::from_lit("fetch-ocsp-response-file"); +constexpr auto SHRPX_OPT_OCSP_UPDATE_INTERVAL = + StringRef::from_lit("ocsp-update-interval"); +constexpr auto SHRPX_OPT_NO_OCSP = StringRef::from_lit("no-ocsp"); +constexpr auto SHRPX_OPT_HEADER_FIELD_BUFFER = + StringRef::from_lit("header-field-buffer"); +constexpr auto SHRPX_OPT_MAX_HEADER_FIELDS = + StringRef::from_lit("max-header-fields"); +constexpr auto SHRPX_OPT_INCLUDE = StringRef::from_lit("include"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_CIPHER = + StringRef::from_lit("tls-ticket-key-cipher"); +constexpr auto SHRPX_OPT_HOST_REWRITE = StringRef::from_lit("host-rewrite"); +constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED = + StringRef::from_lit("tls-session-cache-memcached"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED = + StringRef::from_lit("tls-ticket-key-memcached"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL = + StringRef::from_lit("tls-ticket-key-memcached-interval"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY = + StringRef::from_lit("tls-ticket-key-memcached-max-retry"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL = + StringRef::from_lit("tls-ticket-key-memcached-max-fail"); +constexpr auto SHRPX_OPT_MRUBY_FILE = StringRef::from_lit("mruby-file"); +constexpr auto SHRPX_OPT_ACCEPT_PROXY_PROTOCOL = + StringRef::from_lit("accept-proxy-protocol"); +constexpr auto SHRPX_OPT_FASTOPEN = StringRef::from_lit("fastopen"); +constexpr auto SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD = + StringRef::from_lit("tls-dyn-rec-warmup-threshold"); +constexpr auto SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT = + StringRef::from_lit("tls-dyn-rec-idle-timeout"); +constexpr auto SHRPX_OPT_ADD_FORWARDED = StringRef::from_lit("add-forwarded"); +constexpr auto SHRPX_OPT_STRIP_INCOMING_FORWARDED = + StringRef::from_lit("strip-incoming-forwarded"); +constexpr auto SHRPX_OPT_FORWARDED_BY = StringRef::from_lit("forwarded-by"); +constexpr auto SHRPX_OPT_FORWARDED_FOR = StringRef::from_lit("forwarded-for"); +constexpr auto SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER = + StringRef::from_lit("request-header-field-buffer"); +constexpr auto SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS = + StringRef::from_lit("max-request-header-fields"); +constexpr auto SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER = + StringRef::from_lit("response-header-field-buffer"); +constexpr auto SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS = + StringRef::from_lit("max-response-header-fields"); +constexpr auto SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST = + StringRef::from_lit("no-http2-cipher-block-list"); +constexpr auto SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST = + StringRef::from_lit("no-http2-cipher-black-list"); +constexpr auto SHRPX_OPT_BACKEND_HTTP1_TLS = + StringRef::from_lit("backend-http1-tls"); +constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS = + StringRef::from_lit("tls-session-cache-memcached-tls"); +constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE = + StringRef::from_lit("tls-session-cache-memcached-cert-file"); +constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE = + StringRef::from_lit("tls-session-cache-memcached-private-key-file"); +constexpr auto SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY = + StringRef::from_lit("tls-session-cache-memcached-address-family"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS = + StringRef::from_lit("tls-ticket-key-memcached-tls"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE = + StringRef::from_lit("tls-ticket-key-memcached-cert-file"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE = + StringRef::from_lit("tls-ticket-key-memcached-private-key-file"); +constexpr auto SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY = + StringRef::from_lit("tls-ticket-key-memcached-address-family"); +constexpr auto SHRPX_OPT_BACKEND_ADDRESS_FAMILY = + StringRef::from_lit("backend-address-family"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS = + StringRef::from_lit("frontend-http2-max-concurrent-streams"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS = + StringRef::from_lit("backend-http2-max-concurrent-streams"); +constexpr auto SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND = + StringRef::from_lit("backend-connections-per-frontend"); +constexpr auto SHRPX_OPT_BACKEND_TLS = StringRef::from_lit("backend-tls"); +constexpr auto SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST = + StringRef::from_lit("backend-connections-per-host"); +constexpr auto SHRPX_OPT_ERROR_PAGE = StringRef::from_lit("error-page"); +constexpr auto SHRPX_OPT_NO_KQUEUE = StringRef::from_lit("no-kqueue"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT = + StringRef::from_lit("frontend-http2-settings-timeout"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT = + StringRef::from_lit("backend-http2-settings-timeout"); +constexpr auto SHRPX_OPT_API_MAX_REQUEST_BODY = + StringRef::from_lit("api-max-request-body"); +constexpr auto SHRPX_OPT_BACKEND_MAX_BACKOFF = + StringRef::from_lit("backend-max-backoff"); +constexpr auto SHRPX_OPT_SERVER_NAME = StringRef::from_lit("server-name"); +constexpr auto SHRPX_OPT_NO_SERVER_REWRITE = + StringRef::from_lit("no-server-rewrite"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE = + StringRef::from_lit("frontend-http2-optimize-write-buffer-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE = + StringRef::from_lit("frontend-http2-optimize-window-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE = + StringRef::from_lit("frontend-http2-window-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE = + StringRef::from_lit("frontend-http2-connection-window-size"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE = + StringRef::from_lit("backend-http2-window-size"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE = + StringRef::from_lit("backend-http2-connection-window-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE = + StringRef::from_lit("frontend-http2-encoder-dynamic-table-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE = + StringRef::from_lit("frontend-http2-decoder-dynamic-table-size"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE = + StringRef::from_lit("backend-http2-encoder-dynamic-table-size"); +constexpr auto SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE = + StringRef::from_lit("backend-http2-decoder-dynamic-table-size"); +constexpr auto SHRPX_OPT_ECDH_CURVES = StringRef::from_lit("ecdh-curves"); +constexpr auto SHRPX_OPT_TLS_SCT_DIR = StringRef::from_lit("tls-sct-dir"); +constexpr auto SHRPX_OPT_BACKEND_CONNECT_TIMEOUT = + StringRef::from_lit("backend-connect-timeout"); +constexpr auto SHRPX_OPT_DNS_CACHE_TIMEOUT = + StringRef::from_lit("dns-cache-timeout"); +constexpr auto SHRPX_OPT_DNS_LOOKUP_TIMEOUT = + StringRef::from_lit("dns-lookup-timeout"); +constexpr auto SHRPX_OPT_DNS_MAX_TRY = StringRef::from_lit("dns-max-try"); +constexpr auto SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT = + StringRef::from_lit("frontend-keep-alive-timeout"); +constexpr auto SHRPX_OPT_PSK_SECRETS = StringRef::from_lit("psk-secrets"); +constexpr auto SHRPX_OPT_CLIENT_PSK_SECRETS = + StringRef::from_lit("client-psk-secrets"); +constexpr auto SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST = + StringRef::from_lit("client-no-http2-cipher-block-list"); +constexpr auto SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST = + StringRef::from_lit("client-no-http2-cipher-black-list"); +constexpr auto SHRPX_OPT_CLIENT_CIPHERS = StringRef::from_lit("client-ciphers"); +constexpr auto SHRPX_OPT_ACCESSLOG_WRITE_EARLY = + StringRef::from_lit("accesslog-write-early"); +constexpr auto SHRPX_OPT_TLS_MIN_PROTO_VERSION = + StringRef::from_lit("tls-min-proto-version"); +constexpr auto SHRPX_OPT_TLS_MAX_PROTO_VERSION = + StringRef::from_lit("tls-max-proto-version"); +constexpr auto SHRPX_OPT_REDIRECT_HTTPS_PORT = + StringRef::from_lit("redirect-https-port"); +constexpr auto SHRPX_OPT_FRONTEND_MAX_REQUESTS = + StringRef::from_lit("frontend-max-requests"); +constexpr auto SHRPX_OPT_SINGLE_THREAD = StringRef::from_lit("single-thread"); +constexpr auto SHRPX_OPT_SINGLE_PROCESS = StringRef::from_lit("single-process"); +constexpr auto SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO = + StringRef::from_lit("no-add-x-forwarded-proto"); +constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO = + StringRef::from_lit("no-strip-incoming-x-forwarded-proto"); +constexpr auto SHRPX_OPT_OCSP_STARTUP = StringRef::from_lit("ocsp-startup"); +constexpr auto SHRPX_OPT_NO_VERIFY_OCSP = StringRef::from_lit("no-verify-ocsp"); +constexpr auto SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED = + StringRef::from_lit("verify-client-tolerate-expired"); +constexpr auto SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR = + StringRef::from_lit("ignore-per-pattern-mruby-error"); +constexpr auto SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA = + StringRef::from_lit("tls-no-postpone-early-data"); +constexpr auto SHRPX_OPT_TLS_MAX_EARLY_DATA = + StringRef::from_lit("tls-max-early-data"); +constexpr auto SHRPX_OPT_TLS13_CIPHERS = StringRef::from_lit("tls13-ciphers"); +constexpr auto SHRPX_OPT_TLS13_CLIENT_CIPHERS = + StringRef::from_lit("tls13-client-ciphers"); +constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA = + StringRef::from_lit("no-strip-incoming-early-data"); +constexpr auto SHRPX_OPT_QUIC_BPF_PROGRAM_FILE = + StringRef::from_lit("quic-bpf-program-file"); +constexpr auto SHRPX_OPT_NO_QUIC_BPF = StringRef::from_lit("no-quic-bpf"); +constexpr auto SHRPX_OPT_HTTP2_ALTSVC = StringRef::from_lit("http2-altsvc"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT = + StringRef::from_lit("frontend-http3-read-timeout"); +constexpr auto SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT = + StringRef::from_lit("frontend-quic-idle-timeout"); +constexpr auto SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG = + StringRef::from_lit("frontend-quic-debug-log"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE = + StringRef::from_lit("frontend-http3-window-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE = + StringRef::from_lit("frontend-http3-connection-window-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE = + StringRef::from_lit("frontend-http3-max-window-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE = + StringRef::from_lit("frontend-http3-max-connection-window-size"); +constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS = + StringRef::from_lit("frontend-http3-max-concurrent-streams"); +constexpr auto SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA = + StringRef::from_lit("frontend-quic-early-data"); +constexpr auto SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR = + StringRef::from_lit("frontend-quic-qlog-dir"); +constexpr auto SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN = + StringRef::from_lit("frontend-quic-require-token"); +constexpr auto SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER = + StringRef::from_lit("frontend-quic-congestion-controller"); +constexpr auto SHRPX_OPT_QUIC_SERVER_ID = StringRef::from_lit("quic-server-id"); +constexpr auto SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE = + StringRef::from_lit("frontend-quic-secret-file"); +constexpr auto SHRPX_OPT_RLIMIT_MEMLOCK = StringRef::from_lit("rlimit-memlock"); +constexpr auto SHRPX_OPT_MAX_WORKER_PROCESSES = + StringRef::from_lit("max-worker-processes"); +constexpr auto SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD = + StringRef::from_lit("worker-process-grace-shutdown-period"); +constexpr auto SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT = + StringRef::from_lit("frontend-quic-initial-rtt"); +constexpr auto SHRPX_OPT_REQUIRE_HTTP_SCHEME = + StringRef::from_lit("require-http-scheme"); +constexpr auto SHRPX_OPT_TLS_KTLS = StringRef::from_lit("tls-ktls"); + +constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; + +constexpr char DEFAULT_DOWNSTREAM_HOST[] = "127.0.0.1"; +constexpr int16_t DEFAULT_DOWNSTREAM_PORT = 80; + +enum class Proto { + NONE, + HTTP1, + HTTP2, + HTTP3, + MEMCACHED, +}; + +enum class SessionAffinity { + // No session affinity + NONE, + // Client IP affinity + IP, + // Cookie based affinity + COOKIE, +}; + +enum class SessionAffinityCookieSecure { + // Secure attribute of session affinity cookie is determined by the + // request scheme. + AUTO, + // Secure attribute of session affinity cookie is always set. + YES, + // Secure attribute of session affinity cookie is always unset. + NO, +}; + +enum class SessionAffinityCookieStickiness { + // Backend server might be changed when an existing backend server + // is removed, or new backend server is added. + LOOSE, + // Backend server might be changed when a designated backend server + // is removed, but adding new backend server does not cause + // breakage. + STRICT, +}; + +struct AffinityConfig { + // Type of session affinity. + SessionAffinity type; + struct { + // Name of a cookie to use. + StringRef name; + // Path which a cookie is applied to. + StringRef path; + // Secure attribute + SessionAffinityCookieSecure secure; + // Affinity Stickiness + SessionAffinityCookieStickiness stickiness; + } cookie; +}; + +enum shrpx_forwarded_param { + FORWARDED_NONE = 0, + FORWARDED_BY = 0x1, + FORWARDED_FOR = 0x2, + FORWARDED_HOST = 0x4, + FORWARDED_PROTO = 0x8, +}; + +enum class ForwardedNode { + OBFUSCATED, + IP, +}; + +struct AltSvc { + StringRef protocol_id, host, origin, service, params; + + uint16_t port; +}; + +enum class UpstreamAltMode { + // No alternative mode + NONE, + // API processing mode + API, + // Health monitor mode + HEALTHMON, +}; + +struct UpstreamAddr { + // The unique index of this address. + size_t index; + // The frontend address (e.g., FQDN, hostname, IP address). If + // |host_unix| is true, this is UNIX domain socket path. This must + // be NULL terminated string. + StringRef host; + // For TCP socket, this is :. For IPv6 address, + // address is surrounded by square brackets. If socket is UNIX + // domain socket, this is "localhost". + StringRef hostport; + // frontend port. 0 if |host_unix| is true. + uint16_t port; + // For TCP socket, this is either AF_INET or AF_INET6. For UNIX + // domain socket, this is 0. + int family; + // Alternate mode + UpstreamAltMode alt_mode; + // true if |host| contains UNIX domain socket path. + bool host_unix; + // true if TLS is enabled. + bool tls; + // true if SNI host should be used as a host when selecting backend + // server. + bool sni_fwd; + // true if client is supposed to send PROXY protocol v1 header. + bool accept_proxy_protocol; + bool quic; + int fd; +}; + +struct DownstreamAddrConfig { + // Resolved address if |dns| is false + Address addr; + // backend address. If |host_unix| is true, this is UNIX domain + // socket path. This must be NULL terminated string. + StringRef host; + // :. This does not treat 80 and 443 specially. If + // |host_unix| is true, this is "localhost". + StringRef hostport; + // hostname sent as SNI field + StringRef sni; + // name of group which this address belongs to. + StringRef group; + size_t fall; + size_t rise; + // weight of this address inside a weight group. Its range is [1, + // 256], inclusive. + uint32_t weight; + // weight of the weight group. Its range is [1, 256], inclusive. + uint32_t group_weight; + // affinity hash for this address. It is assigned when strict + // stickiness is enabled. + uint32_t affinity_hash; + // Application protocol used in this group + Proto proto; + // backend port. 0 if |host_unix| is true. + uint16_t port; + // true if |host| contains UNIX domain socket path. + bool host_unix; + bool tls; + // true if dynamic DNS is enabled + bool dns; + // true if :scheme pseudo header field should be upgraded to secure + // variant (e.g., "https") when forwarding request to a backend + // connected by TLS connection. + bool upgrade_scheme; + // true if a request should not be forwarded to a backend. + bool dnf; +}; + +// Mapping hash to idx which is an index into +// DownstreamAddrGroupConfig::addrs. +struct AffinityHash { + AffinityHash(size_t idx, uint32_t hash) : idx(idx), hash(hash) {} + + size_t idx; + uint32_t hash; +}; + +struct DownstreamAddrGroupConfig { + DownstreamAddrGroupConfig(const StringRef &pattern) + : pattern(pattern), + affinity{SessionAffinity::NONE}, + redirect_if_not_tls(false), + dnf{false}, + timeout{} {} + + StringRef pattern; + StringRef mruby_file; + std::vector addrs; + // Bunch of session affinity hash. Only used if affinity == + // SessionAffinity::IP. + std::vector affinity_hash; + // Maps affinity hash of each DownstreamAddrConfig to its index in + // addrs. It is only assigned when strict stickiness is enabled. + std::unordered_map affinity_hash_map; + // Cookie based session affinity configuration. + AffinityConfig affinity; + // true if this group requires that client connection must be TLS, + // and the request must be redirected to https URI. + bool redirect_if_not_tls; + // true if a request should not be forwarded to a backend. + bool dnf; + // Timeouts for backend connection. + struct { + ev_tstamp read; + ev_tstamp write; + } timeout; +}; + +struct TicketKey { + const EVP_CIPHER *cipher; + const EVP_MD *hmac; + size_t hmac_keylen; + struct { + // name of this ticket configuration + std::array name; + // encryption key for |cipher| + std::array enc_key; + // hmac key for |hmac| + std::array hmac_key; + } data; +}; + +struct TicketKeys { + ~TicketKeys(); + std::vector keys; +}; + +struct TLSCertificate { + TLSCertificate(StringRef private_key_file, StringRef cert_file, + std::vector sct_data) + : private_key_file(std::move(private_key_file)), + cert_file(std::move(cert_file)), + sct_data(std::move(sct_data)) {} + + StringRef private_key_file; + StringRef cert_file; + std::vector sct_data; +}; + +#ifdef ENABLE_HTTP3 +struct QUICKeyingMaterial { + std::array reserved; + std::array secret; + std::array salt; + std::array cid_encryption_key; + // Identifier of this keying material. Only the first 2 bits are + // used. + uint8_t id; +}; + +struct QUICKeyingMaterials { + std::vector keying_materials; +}; +#endif // ENABLE_HTTP3 + +struct HttpProxy { + Address addr; + // host in http proxy URI + StringRef host; + // userinfo in http proxy URI, not percent-encoded form + StringRef userinfo; + // port in http proxy URI + uint16_t port; +}; + +struct TLSConfig { + // RFC 5077 Session ticket related configurations + struct { + struct { + Address addr; + uint16_t port; + // Hostname of memcached server. This is also used as SNI field + // if TLS is enabled. + StringRef host; + // Client private key and certificate for authentication + StringRef private_key_file; + StringRef cert_file; + ev_tstamp interval; + // Maximum number of retries when getting TLS ticket key from + // mamcached, due to network error. + size_t max_retry; + // Maximum number of consecutive error from memcached, when this + // limit reached, TLS ticket is disabled. + size_t max_fail; + // Address family of memcached connection. One of either + // AF_INET, AF_INET6 or AF_UNSPEC. + int family; + bool tls; + } memcached; + std::vector files; + const EVP_CIPHER *cipher; + // true if --tls-ticket-key-cipher is used + bool cipher_given; + } ticket; + + // Session cache related configurations + struct { + struct { + Address addr; + uint16_t port; + // Hostname of memcached server. This is also used as SNI field + // if TLS is enabled. + StringRef host; + // Client private key and certificate for authentication + StringRef private_key_file; + StringRef cert_file; + // Address family of memcached connection. One of either + // AF_INET, AF_INET6 or AF_UNSPEC. + int family; + bool tls; + } memcached; + } session_cache; + + // Dynamic record sizing configurations + struct { + size_t warmup_threshold; + ev_tstamp idle_timeout; + } dyn_rec; + + // OCSP related configurations + struct { + ev_tstamp update_interval; + StringRef fetch_ocsp_response_file; + bool disabled; + bool startup; + bool no_verify; + } ocsp; + + // Client verification configurations + struct { + // Path to file containing CA certificate solely used for client + // certificate validation + StringRef cacert; + bool enabled; + // true if we accept an expired client certificate. + bool tolerate_expired; + } client_verify; + + // Client (backend connection) TLS configuration. + struct { + // Client PSK configuration + struct { + // identity must be NULL terminated string. + StringRef identity; + StringRef secret; + } psk; + StringRef private_key_file; + StringRef cert_file; + StringRef ciphers; + StringRef tls13_ciphers; + bool no_http2_cipher_block_list; + } client; + + // PSK secrets. The key is identity, and the associated value is + // its secret. + std::map psk_secrets; + // The list of additional TLS certificate pair + std::vector subcerts; + std::vector alpn_prefs; + // list of supported NPN/ALPN protocol strings in the order of + // preference. + std::vector npn_list; + // list of supported SSL/TLS protocol strings. + std::vector tls_proto_list; + std::vector sct_data; + BIO_METHOD *bio_method; + // Bit mask to disable SSL/TLS protocol versions. This will be + // passed to SSL_CTX_set_options(). + long int tls_proto_mask; + StringRef backend_sni_name; + std::chrono::seconds session_timeout; + StringRef private_key_file; + StringRef private_key_passwd; + StringRef cert_file; + StringRef dh_param_file; + StringRef ciphers; + StringRef tls13_ciphers; + StringRef ecdh_curves; + StringRef cacert; + // The maximum amount of 0-RTT data that server accepts. + uint32_t max_early_data; + // The minimum and maximum TLS version. These values are defined in + // OpenSSL header file. + int min_proto_version; + int max_proto_version; + bool insecure; + bool no_http2_cipher_block_list; + // true if forwarding requests included in TLS early data should not + // be postponed until TLS handshake finishes. + bool no_postpone_early_data; + bool ktls; +}; + +#ifdef ENABLE_HTTP3 +struct QUICConfig { + struct { + struct { + ev_tstamp idle; + } timeout; + struct { + bool log; + } debug; + struct { + StringRef dir; + } qlog; + ngtcp2_cc_algo congestion_controller; + bool early_data; + bool require_token; + StringRef secret_file; + ev_tstamp initial_rtt; + } upstream; + struct { + StringRef prog_file; + bool disabled; + } bpf; + std::array server_id; +}; + +struct Http3Config { + struct { + size_t max_concurrent_streams; + int32_t window_size; + int32_t connection_window_size; + int32_t max_window_size; + int32_t max_connection_window_size; + } upstream; +}; +#endif // ENABLE_HTTP3 + +// custom error page +struct ErrorPage { + // not NULL-terminated + std::vector content; + // 0 is special value, and it matches all HTTP status code. + unsigned int http_status; +}; + +struct HttpConfig { + struct { + // obfuscated value used in "by" parameter of Forwarded header + // field. This is only used when user defined static obfuscated + // string is provided. + StringRef by_obfuscated; + // bitwise-OR of one or more of shrpx_forwarded_param values. + uint32_t params; + // type of value recorded in "by" parameter of Forwarded header + // field. + ForwardedNode by_node_type; + // type of value recorded in "for" parameter of Forwarded header + // field. + ForwardedNode for_node_type; + bool strip_incoming; + } forwarded; + struct { + bool add; + bool strip_incoming; + } xff; + struct { + bool add; + bool strip_incoming; + } xfp; + struct { + bool strip_incoming; + } early_data; + std::vector altsvcs; + // altsvcs serialized in a wire format. + StringRef altsvc_header_value; + std::vector http2_altsvcs; + // http2_altsvcs serialized in a wire format. + StringRef http2_altsvc_header_value; + std::vector error_pages; + HeaderRefs add_request_headers; + HeaderRefs add_response_headers; + StringRef server_name; + // Port number which appears in Location header field when https + // redirect is made. + StringRef redirect_https_port; + size_t request_header_field_buffer; + size_t max_request_header_fields; + size_t response_header_field_buffer; + size_t max_response_header_fields; + size_t max_requests; + bool no_via; + bool no_location_rewrite; + bool no_host_rewrite; + bool no_server_rewrite; + bool require_http_scheme; +}; + +struct Http2Config { + struct { + struct { + struct { + StringRef request_header_file; + StringRef response_header_file; + FILE *request_header; + FILE *response_header; + } dump; + bool frame_debug; + } debug; + struct { + ev_tstamp settings; + } timeout; + nghttp2_option *option; + nghttp2_option *alt_mode_option; + nghttp2_session_callbacks *callbacks; + size_t max_concurrent_streams; + size_t encoder_dynamic_table_size; + size_t decoder_dynamic_table_size; + int32_t window_size; + int32_t connection_window_size; + bool optimize_write_buffer_size; + bool optimize_window_size; + } upstream; + struct { + struct { + ev_tstamp settings; + } timeout; + nghttp2_option *option; + nghttp2_session_callbacks *callbacks; + size_t encoder_dynamic_table_size; + size_t decoder_dynamic_table_size; + int32_t window_size; + int32_t connection_window_size; + size_t max_concurrent_streams; + } downstream; + struct { + ev_tstamp stream_read; + ev_tstamp stream_write; + } timeout; + bool no_cookie_crumbling; + bool no_server_push; +}; + +struct LoggingConfig { + struct { + std::vector format; + StringRef file; + // Send accesslog to syslog, ignoring accesslog_file. + bool syslog; + // Write accesslog when response headers are received from + // backend, rather than response body is received and sent. + bool write_early; + } access; + struct { + StringRef file; + // Send errorlog to syslog, ignoring errorlog_file. + bool syslog; + } error; + int syslog_facility; + int severity; +}; + +struct RateLimitConfig { + size_t rate; + size_t burst; +}; + +// Wildcard host pattern routing. We strips left most '*' from host +// field. router includes all path patterns sharing the same wildcard +// host. +struct WildcardPattern { + WildcardPattern(const StringRef &host) : host(host) {} + + // This might not be NULL terminated. Currently it is only used for + // comparison. + StringRef host; + Router router; +}; + +// Configuration to select backend to forward request +struct RouterConfig { + Router router; + // Router for reversed wildcard hosts. Since this router has + // wildcard hosts reversed without '*', one should call match() + // function with reversed host stripping last character. This is + // because we require at least one character must match for '*'. + // The index stored in this router is index of wildcard_patterns. + Router rev_wildcard_router; + std::vector wildcard_patterns; +}; + +struct DownstreamConfig { + DownstreamConfig() + : balloc(1024, 1024), + timeout{}, + addr_group_catch_all{0}, + connections_per_host{0}, + connections_per_frontend{0}, + request_buffer_size{0}, + response_buffer_size{0}, + family{0} {} + + DownstreamConfig(const DownstreamConfig &) = delete; + DownstreamConfig(DownstreamConfig &&) = delete; + DownstreamConfig &operator=(const DownstreamConfig &) = delete; + DownstreamConfig &operator=(DownstreamConfig &&) = delete; + + // Allocator to allocate memory for Downstream configuration. Since + // we may swap around DownstreamConfig in arbitrary times with API + // calls, we should use their own allocator instead of per Config + // allocator. + BlockAllocator balloc; + struct { + ev_tstamp read; + ev_tstamp write; + ev_tstamp idle_read; + ev_tstamp connect; + // The maximum backoff while checking health check for offline + // backend or while detaching failed backend from load balancing + // group temporarily. + ev_tstamp max_backoff; + } timeout; + RouterConfig router; + std::vector addr_groups; + // The index of catch-all group in downstream_addr_groups. + size_t addr_group_catch_all; + size_t connections_per_host; + size_t connections_per_frontend; + size_t request_buffer_size; + size_t response_buffer_size; + // Address family of backend connection. One of either AF_INET, + // AF_INET6 or AF_UNSPEC. This is ignored if backend connection + // is made via Unix domain socket. + int family; +}; + +struct ConnectionConfig { + struct { + struct { + ev_tstamp sleep; + } timeout; + // address of frontend acceptors + std::vector addrs; + int backlog; + // TCP fastopen. If this is positive, it is passed to + // setsockopt() along with TCP_FASTOPEN. + int fastopen; + } listener; + +#ifdef ENABLE_HTTP3 + struct { + std::vector addrs; + } quic_listener; +#endif // ENABLE_HTTP3 + + struct { + struct { + ev_tstamp http2_read; + ev_tstamp http3_read; + ev_tstamp read; + ev_tstamp write; + ev_tstamp idle_read; + } timeout; + struct { + RateLimitConfig read; + RateLimitConfig write; + } ratelimit; + size_t worker_connections; + // Deprecated. See UpstreamAddr.accept_proxy_protocol. + bool accept_proxy_protocol; + } upstream; + + std::shared_ptr downstream; +}; + +struct APIConfig { + // Maximum request body size for one API request + size_t max_request_body; + // true if at least one of UpstreamAddr has api enabled + bool enabled; +}; + +struct DNSConfig { + struct { + ev_tstamp cache; + ev_tstamp lookup; + } timeout; + // The number of tries name resolver makes before abandoning + // request. + size_t max_try; +}; + +struct Config { + Config() + : balloc(4096, 4096), + downstream_http_proxy{}, + http{}, + http2{}, + tls{}, +#ifdef ENABLE_HTTP3 + quic{}, +#endif // ENABLE_HTTP3 + logging{}, + conn{}, + api{}, + dns{}, + config_revision{0}, + num_worker{0}, + padding{0}, + rlimit_nofile{0}, + rlimit_memlock{0}, + uid{0}, + gid{0}, + pid{0}, + verbose{false}, + daemon{false}, + http2_proxy{false}, + single_process{false}, + single_thread{false}, + ignore_per_pattern_mruby_error{false}, + ev_loop_flags{0}, + max_worker_processes{0}, + worker_process_grace_shutdown_period{0.} { + } + ~Config(); + + Config(Config &&) = delete; + Config(const Config &&) = delete; + Config &operator=(Config &&) = delete; + Config &operator=(const Config &&) = delete; + + // Allocator to allocate memory for this object except for + // DownstreamConfig. Currently, it is used to allocate memory for + // strings. + BlockAllocator balloc; + HttpProxy downstream_http_proxy; + HttpConfig http; + Http2Config http2; + TLSConfig tls; +#ifdef ENABLE_HTTP3 + QUICConfig quic; + Http3Config http3; +#endif // ENABLE_HTTP3 + LoggingConfig logging; + ConnectionConfig conn; + APIConfig api; + DNSConfig dns; + StringRef pid_file; + StringRef conf_path; + StringRef user; + StringRef mruby_file; + // The revision of configuration which is opaque string, and changes + // on each configuration reloading. This does not change on + // backendconfig API call. This value is returned in health check + // as "nghttpx-conf-rev" response header field. The external + // program can check this value to know whether reloading has + // completed or not. + uint64_t config_revision; + size_t num_worker; + size_t padding; + size_t rlimit_nofile; + size_t rlimit_memlock; + uid_t uid; + gid_t gid; + pid_t pid; + bool verbose; + bool daemon; + bool http2_proxy; + // Run nghttpx in single process mode. With this mode, signal + // handling is omitted. + bool single_process; + bool single_thread; + // Ignore mruby compile error for per-pattern mruby script. + bool ignore_per_pattern_mruby_error; + // flags passed to ev_default_loop() and ev_loop_new() + int ev_loop_flags; + size_t max_worker_processes; + ev_tstamp worker_process_grace_shutdown_period; +}; + +const Config *get_config(); +Config *mod_config(); +// Replaces the current config with given |new_config|. The old config is +// returned. +std::unique_ptr replace_config(std::unique_ptr new_config); +void create_config(); + +// generated by gennghttpxfun.py +enum { + SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL, + SHRPX_OPTID_ACCESSLOG_FILE, + SHRPX_OPTID_ACCESSLOG_FORMAT, + SHRPX_OPTID_ACCESSLOG_SYSLOG, + SHRPX_OPTID_ACCESSLOG_WRITE_EARLY, + SHRPX_OPTID_ADD_FORWARDED, + SHRPX_OPTID_ADD_REQUEST_HEADER, + SHRPX_OPTID_ADD_RESPONSE_HEADER, + SHRPX_OPTID_ADD_X_FORWARDED_FOR, + SHRPX_OPTID_ALTSVC, + SHRPX_OPTID_API_MAX_REQUEST_BODY, + SHRPX_OPTID_BACKEND, + SHRPX_OPTID_BACKEND_ADDRESS_FAMILY, + SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT, + SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND, + SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST, + SHRPX_OPTID_BACKEND_HTTP_PROXY_URI, + SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND, + SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST, + SHRPX_OPTID_BACKEND_HTTP1_TLS, + SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS, + SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER, + SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS, + SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT, + SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS, + SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE, + SHRPX_OPTID_BACKEND_IPV4, + SHRPX_OPTID_BACKEND_IPV6, + SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT, + SHRPX_OPTID_BACKEND_MAX_BACKOFF, + SHRPX_OPTID_BACKEND_NO_TLS, + SHRPX_OPTID_BACKEND_READ_TIMEOUT, + SHRPX_OPTID_BACKEND_REQUEST_BUFFER, + SHRPX_OPTID_BACKEND_RESPONSE_BUFFER, + SHRPX_OPTID_BACKEND_TLS, + SHRPX_OPTID_BACKEND_TLS_SNI_FIELD, + SHRPX_OPTID_BACKEND_WRITE_TIMEOUT, + SHRPX_OPTID_BACKLOG, + SHRPX_OPTID_CACERT, + SHRPX_OPTID_CERTIFICATE_FILE, + SHRPX_OPTID_CIPHERS, + SHRPX_OPTID_CLIENT, + SHRPX_OPTID_CLIENT_CERT_FILE, + SHRPX_OPTID_CLIENT_CIPHERS, + SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST, + SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST, + SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE, + SHRPX_OPTID_CLIENT_PROXY, + SHRPX_OPTID_CLIENT_PSK_SECRETS, + SHRPX_OPTID_CONF, + SHRPX_OPTID_DAEMON, + SHRPX_OPTID_DH_PARAM_FILE, + SHRPX_OPTID_DNS_CACHE_TIMEOUT, + SHRPX_OPTID_DNS_LOOKUP_TIMEOUT, + SHRPX_OPTID_DNS_MAX_TRY, + SHRPX_OPTID_ECDH_CURVES, + SHRPX_OPTID_ERROR_PAGE, + SHRPX_OPTID_ERRORLOG_FILE, + SHRPX_OPTID_ERRORLOG_SYSLOG, + SHRPX_OPTID_FASTOPEN, + SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE, + SHRPX_OPTID_FORWARDED_BY, + SHRPX_OPTID_FORWARDED_FOR, + SHRPX_OPTID_FRONTEND, + SHRPX_OPTID_FRONTEND_FRAME_DEBUG, + SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS, + SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER, + SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER, + SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS, + SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE, + SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT, + SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT, + SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS, + SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS, + SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT, + SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE, + SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT, + SHRPX_OPTID_FRONTEND_MAX_REQUESTS, + SHRPX_OPTID_FRONTEND_NO_TLS, + SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER, + SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG, + SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA, + SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT, + SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT, + SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR, + SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN, + SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE, + SHRPX_OPTID_FRONTEND_READ_TIMEOUT, + SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT, + SHRPX_OPTID_HEADER_FIELD_BUFFER, + SHRPX_OPTID_HOST_REWRITE, + SHRPX_OPTID_HTTP2_ALTSVC, + SHRPX_OPTID_HTTP2_BRIDGE, + SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS, + SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING, + SHRPX_OPTID_HTTP2_PROXY, + SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR, + SHRPX_OPTID_INCLUDE, + SHRPX_OPTID_INSECURE, + SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT, + SHRPX_OPTID_LOG_LEVEL, + SHRPX_OPTID_MAX_HEADER_FIELDS, + SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS, + SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS, + SHRPX_OPTID_MAX_WORKER_PROCESSES, + SHRPX_OPTID_MRUBY_FILE, + SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO, + SHRPX_OPTID_NO_HOST_REWRITE, + SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST, + SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST, + SHRPX_OPTID_NO_KQUEUE, + SHRPX_OPTID_NO_LOCATION_REWRITE, + SHRPX_OPTID_NO_OCSP, + SHRPX_OPTID_NO_QUIC_BPF, + SHRPX_OPTID_NO_SERVER_PUSH, + SHRPX_OPTID_NO_SERVER_REWRITE, + SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA, + SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO, + SHRPX_OPTID_NO_VERIFY_OCSP, + SHRPX_OPTID_NO_VIA, + SHRPX_OPTID_NPN_LIST, + SHRPX_OPTID_OCSP_STARTUP, + SHRPX_OPTID_OCSP_UPDATE_INTERVAL, + SHRPX_OPTID_PADDING, + SHRPX_OPTID_PID_FILE, + SHRPX_OPTID_PRIVATE_KEY_FILE, + SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE, + SHRPX_OPTID_PSK_SECRETS, + SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE, + SHRPX_OPTID_QUIC_SERVER_ID, + SHRPX_OPTID_READ_BURST, + SHRPX_OPTID_READ_RATE, + SHRPX_OPTID_REDIRECT_HTTPS_PORT, + SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER, + SHRPX_OPTID_REQUIRE_HTTP_SCHEME, + SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER, + SHRPX_OPTID_RLIMIT_MEMLOCK, + SHRPX_OPTID_RLIMIT_NOFILE, + SHRPX_OPTID_SERVER_NAME, + SHRPX_OPTID_SINGLE_PROCESS, + SHRPX_OPTID_SINGLE_THREAD, + SHRPX_OPTID_STREAM_READ_TIMEOUT, + SHRPX_OPTID_STREAM_WRITE_TIMEOUT, + SHRPX_OPTID_STRIP_INCOMING_FORWARDED, + SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR, + SHRPX_OPTID_SUBCERT, + SHRPX_OPTID_SYSLOG_FACILITY, + SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT, + SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD, + SHRPX_OPTID_TLS_KTLS, + SHRPX_OPTID_TLS_MAX_EARLY_DATA, + SHRPX_OPTID_TLS_MAX_PROTO_VERSION, + SHRPX_OPTID_TLS_MIN_PROTO_VERSION, + SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA, + SHRPX_OPTID_TLS_PROTO_LIST, + SHRPX_OPTID_TLS_SCT_DIR, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE, + SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS, + SHRPX_OPTID_TLS_TICKET_KEY_CIPHER, + SHRPX_OPTID_TLS_TICKET_KEY_FILE, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE, + SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS, + SHRPX_OPTID_TLS13_CIPHERS, + SHRPX_OPTID_TLS13_CLIENT_CIPHERS, + SHRPX_OPTID_USER, + SHRPX_OPTID_VERIFY_CLIENT, + SHRPX_OPTID_VERIFY_CLIENT_CACERT, + SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED, + SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS, + SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD, + SHRPX_OPTID_WORKER_READ_BURST, + SHRPX_OPTID_WORKER_READ_RATE, + SHRPX_OPTID_WORKER_WRITE_BURST, + SHRPX_OPTID_WORKER_WRITE_RATE, + SHRPX_OPTID_WORKERS, + SHRPX_OPTID_WRITE_BURST, + SHRPX_OPTID_WRITE_RATE, + SHRPX_OPTID_MAXIDX, +}; + +// Looks up token for given option name |name| of length |namelen|. +int option_lookup_token(const char *name, size_t namelen); + +// Parses option name |opt| and value |optarg|. The results are +// stored into the object pointed by |config|. This function returns 0 +// if it succeeds, or -1. The |included_set| contains the all paths +// already included while processing this configuration, to avoid loop +// in --include option. The |pattern_addr_indexer| contains a pair of +// pattern of backend, and its index in DownstreamConfig::addr_groups. +// It is introduced to speed up loading configuration file with lots +// of backends. +int parse_config(Config *config, const StringRef &opt, const StringRef &optarg, + std::set &included_set, + std::map &pattern_addr_indexer); + +// Similar to parse_config() above, but additional |optid| which +// should be the return value of option_lookup_token(opt). +int parse_config(Config *config, int optid, const StringRef &opt, + const StringRef &optarg, std::set &included_set, + std::map &pattern_addr_indexer); + +// Loads configurations from |filename| and stores them in |config|. +// This function returns 0 if it succeeds, or -1. See parse_config() +// for |include_set|. +int load_config(Config *config, const char *filename, + std::set &include_set, + std::map &pattern_addr_indexer); + +// Parses header field in |optarg|. We expect header field is formed +// like "NAME: VALUE". We require that NAME is non empty string. ":" +// is allowed at the start of the NAME, but NAME == ":" is not +// allowed. This function returns pair of NAME and VALUE. +HeaderRefs::value_type parse_header(BlockAllocator &balloc, + const StringRef &optarg); + +std::vector parse_log_format(BlockAllocator &balloc, + const StringRef &optarg); + +// Returns string for syslog |facility|. +StringRef str_syslog_facility(int facility); + +// Returns integer value of syslog |facility| string. +int int_syslog_facility(const StringRef &strfacility); + +FILE *open_file_for_write(const char *filename); + +// Reads TLS ticket key file in |files| and returns TicketKey which +// stores read key data. The given |cipher| and |hmac| determine the +// expected file size. This function returns TicketKey if it +// succeeds, or nullptr. +std::unique_ptr +read_tls_ticket_key_file(const std::vector &files, + const EVP_CIPHER *cipher, const EVP_MD *hmac); + +#ifdef ENABLE_HTTP3 +std::shared_ptr +read_quic_secret_file(const StringRef &path); +#endif // ENABLE_HTTP3 + +// Returns string representation of |proto|. +StringRef strproto(Proto proto); + +int configure_downstream_group(Config *config, bool http2_proxy, + bool numeric_addr_only, + const TLSConfig &tlsconf); + +int resolve_hostname(Address *addr, const char *hostname, uint16_t port, + int family, int additional_flags = 0); + +} // namespace shrpx + +#endif // SHRPX_CONFIG_H diff --git a/lib/nghttp2/src/shrpx_config_test.cc b/lib/nghttp2/src/shrpx_config_test.cc new file mode 100644 index 00000000000..a8f09628d71 --- /dev/null +++ b/lib/nghttp2/src/shrpx_config_test.cc @@ -0,0 +1,249 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "shrpx_config_test.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H + +#include + +#include + +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +void test_shrpx_config_parse_header(void) { + BlockAllocator balloc(4096, 4096); + + auto p = parse_header(balloc, StringRef::from_lit("a: b")); + CU_ASSERT("a" == p.name); + CU_ASSERT("b" == p.value); + + p = parse_header(balloc, StringRef::from_lit("a: b")); + CU_ASSERT("a" == p.name); + CU_ASSERT("b" == p.value); + + p = parse_header(balloc, StringRef::from_lit(":a: b")); + CU_ASSERT(p.name.empty()); + + p = parse_header(balloc, StringRef::from_lit("a: :b")); + CU_ASSERT("a" == p.name); + CU_ASSERT(":b" == p.value); + + p = parse_header(balloc, StringRef::from_lit(": b")); + CU_ASSERT(p.name.empty()); + + p = parse_header(balloc, StringRef::from_lit("alpha: bravo charlie")); + CU_ASSERT("alpha" == p.name); + CU_ASSERT("bravo charlie" == p.value); + + p = parse_header(balloc, StringRef::from_lit("a,: b")); + CU_ASSERT(p.name.empty()); + + p = parse_header(balloc, StringRef::from_lit("a: b\x0a")); + CU_ASSERT(p.name.empty()); +} + +void test_shrpx_config_parse_log_format(void) { + BlockAllocator balloc(4096, 4096); + + auto res = parse_log_format( + balloc, StringRef::from_lit( + R"($remote_addr - $remote_user [$time_local] )" + R"("$request" $status $body_bytes_sent )" + R"("${http_referer}" $http_host "$http_user_agent")")); + CU_ASSERT(16 == res.size()); + + CU_ASSERT(LogFragmentType::REMOTE_ADDR == res[0].type); + + CU_ASSERT(LogFragmentType::LITERAL == res[1].type); + CU_ASSERT(" - $remote_user [" == res[1].value); + + CU_ASSERT(LogFragmentType::TIME_LOCAL == res[2].type); + + CU_ASSERT(LogFragmentType::LITERAL == res[3].type); + CU_ASSERT("] \"" == res[3].value); + + CU_ASSERT(LogFragmentType::REQUEST == res[4].type); + + CU_ASSERT(LogFragmentType::LITERAL == res[5].type); + CU_ASSERT("\" " == res[5].value); + + CU_ASSERT(LogFragmentType::STATUS == res[6].type); + + CU_ASSERT(LogFragmentType::LITERAL == res[7].type); + CU_ASSERT(" " == res[7].value); + + CU_ASSERT(LogFragmentType::BODY_BYTES_SENT == res[8].type); + + CU_ASSERT(LogFragmentType::LITERAL == res[9].type); + CU_ASSERT(" \"" == res[9].value); + + CU_ASSERT(LogFragmentType::HTTP == res[10].type); + CU_ASSERT("referer" == res[10].value); + + CU_ASSERT(LogFragmentType::LITERAL == res[11].type); + CU_ASSERT("\" " == res[11].value); + + CU_ASSERT(LogFragmentType::AUTHORITY == res[12].type); + + CU_ASSERT(LogFragmentType::LITERAL == res[13].type); + CU_ASSERT(" \"" == res[13].value); + + CU_ASSERT(LogFragmentType::HTTP == res[14].type); + CU_ASSERT("user-agent" == res[14].value); + + CU_ASSERT(LogFragmentType::LITERAL == res[15].type); + CU_ASSERT("\"" == res[15].value); + + res = parse_log_format(balloc, StringRef::from_lit("$")); + + CU_ASSERT(1 == res.size()); + + CU_ASSERT(LogFragmentType::LITERAL == res[0].type); + CU_ASSERT("$" == res[0].value); + + res = parse_log_format(balloc, StringRef::from_lit("${")); + + CU_ASSERT(1 == res.size()); + + CU_ASSERT(LogFragmentType::LITERAL == res[0].type); + CU_ASSERT("${" == res[0].value); + + res = parse_log_format(balloc, StringRef::from_lit("${a")); + + CU_ASSERT(1 == res.size()); + + CU_ASSERT(LogFragmentType::LITERAL == res[0].type); + CU_ASSERT("${a" == res[0].value); + + res = parse_log_format(balloc, StringRef::from_lit("${a ")); + + CU_ASSERT(1 == res.size()); + + CU_ASSERT(LogFragmentType::LITERAL == res[0].type); + CU_ASSERT("${a " == res[0].value); + + res = parse_log_format(balloc, StringRef::from_lit("$$remote_addr")); + + CU_ASSERT(2 == res.size()); + + CU_ASSERT(LogFragmentType::LITERAL == res[0].type); + CU_ASSERT("$" == res[0].value); + + CU_ASSERT(LogFragmentType::REMOTE_ADDR == res[1].type); + CU_ASSERT("" == res[1].value); +} + +void test_shrpx_config_read_tls_ticket_key_file(void) { + char file1[] = "/tmp/nghttpx-unittest.XXXXXX"; + auto fd1 = mkstemp(file1); + CU_ASSERT(fd1 != -1); + CU_ASSERT(48 == + write(fd1, "0..............12..............34..............5", 48)); + char file2[] = "/tmp/nghttpx-unittest.XXXXXX"; + auto fd2 = mkstemp(file2); + CU_ASSERT(fd2 != -1); + CU_ASSERT(48 == + write(fd2, "6..............78..............9a..............b", 48)); + + close(fd1); + close(fd2); + auto ticket_keys = read_tls_ticket_key_file( + {StringRef{file1}, StringRef{file2}}, EVP_aes_128_cbc(), EVP_sha256()); + unlink(file1); + unlink(file2); + CU_ASSERT(ticket_keys.get() != nullptr); + CU_ASSERT(2 == ticket_keys->keys.size()); + auto key = &ticket_keys->keys[0]; + CU_ASSERT(std::equal(std::begin(key->data.name), std::end(key->data.name), + "0..............1")); + CU_ASSERT(std::equal(std::begin(key->data.enc_key), + std::begin(key->data.enc_key) + 16, "2..............3")); + CU_ASSERT(std::equal(std::begin(key->data.hmac_key), + std::begin(key->data.hmac_key) + 16, + "4..............5")); + CU_ASSERT(16 == key->hmac_keylen); + + key = &ticket_keys->keys[1]; + CU_ASSERT(std::equal(std::begin(key->data.name), std::end(key->data.name), + "6..............7")); + CU_ASSERT(std::equal(std::begin(key->data.enc_key), + std::begin(key->data.enc_key) + 16, "8..............9")); + CU_ASSERT(std::equal(std::begin(key->data.hmac_key), + std::begin(key->data.hmac_key) + 16, + "a..............b")); + CU_ASSERT(16 == key->hmac_keylen); +} + +void test_shrpx_config_read_tls_ticket_key_file_aes_256(void) { + char file1[] = "/tmp/nghttpx-unittest.XXXXXX"; + auto fd1 = mkstemp(file1); + CU_ASSERT(fd1 != -1); + CU_ASSERT(80 == write(fd1, + "0..............12..............................34..." + "...........................5", + 80)); + char file2[] = "/tmp/nghttpx-unittest.XXXXXX"; + auto fd2 = mkstemp(file2); + CU_ASSERT(fd2 != -1); + CU_ASSERT(80 == write(fd2, + "6..............78..............................9a..." + "...........................b", + 80)); + + close(fd1); + close(fd2); + auto ticket_keys = read_tls_ticket_key_file( + {StringRef{file1}, StringRef{file2}}, EVP_aes_256_cbc(), EVP_sha256()); + unlink(file1); + unlink(file2); + CU_ASSERT(ticket_keys.get() != nullptr); + CU_ASSERT(2 == ticket_keys->keys.size()); + auto key = &ticket_keys->keys[0]; + CU_ASSERT(std::equal(std::begin(key->data.name), std::end(key->data.name), + "0..............1")); + CU_ASSERT(std::equal(std::begin(key->data.enc_key), + std::end(key->data.enc_key), + "2..............................3")); + CU_ASSERT(std::equal(std::begin(key->data.hmac_key), + std::end(key->data.hmac_key), + "4..............................5")); + + key = &ticket_keys->keys[1]; + CU_ASSERT(std::equal(std::begin(key->data.name), std::end(key->data.name), + "6..............7")); + CU_ASSERT(std::equal(std::begin(key->data.enc_key), + std::end(key->data.enc_key), + "8..............................9")); + CU_ASSERT(std::equal(std::begin(key->data.hmac_key), + std::end(key->data.hmac_key), + "a..............................b")); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_config_test.h b/lib/nghttp2/src/shrpx_config_test.h new file mode 100644 index 00000000000..a30de41aa5f --- /dev/null +++ b/lib/nghttp2/src/shrpx_config_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 SHRPX_CONFIG_TEST_H +#define SHRPX_CONFIG_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_config_parse_header(void); +void test_shrpx_config_parse_log_format(void); +void test_shrpx_config_read_tls_ticket_key_file(void); +void test_shrpx_config_read_tls_ticket_key_file_aes_256(void); +void test_shrpx_config_match_downstream_addr_group(void); + +} // namespace shrpx + +#endif // SHRPX_CONFIG_TEST_H diff --git a/lib/nghttp2/src/shrpx_connect_blocker.cc b/lib/nghttp2/src/shrpx_connect_blocker.cc new file mode 100644 index 00000000000..ff767b078ea --- /dev/null +++ b/lib/nghttp2/src/shrpx_connect_blocker.cc @@ -0,0 +1,143 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "shrpx_connect_blocker.h" +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +void connect_blocker_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto connect_blocker = static_cast(w->data); + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Unblock"; + } + + connect_blocker->call_unblock_func(); +} +} // namespace + +ConnectBlocker::ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop, + std::function block_func, + std::function unblock_func) + : gen_(gen), + block_func_(std::move(block_func)), + unblock_func_(std::move(unblock_func)), + loop_(loop), + fail_count_(0), + offline_(false) { + ev_timer_init(&timer_, connect_blocker_cb, 0., 0.); + timer_.data = this; +} + +ConnectBlocker::~ConnectBlocker() { ev_timer_stop(loop_, &timer_); } + +bool ConnectBlocker::blocked() const { return ev_is_active(&timer_); } + +void ConnectBlocker::on_success() { + if (ev_is_active(&timer_)) { + return; + } + + fail_count_ = 0; +} + +// Use the similar backoff algorithm described in +// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md +namespace { +constexpr size_t MAX_BACKOFF_EXP = 10; +constexpr auto MULTIPLIER = 1.6; +constexpr auto JITTER = 0.2; +} // namespace + +void ConnectBlocker::on_failure() { + if (ev_is_active(&timer_)) { + return; + } + + call_block_func(); + + ++fail_count_; + + auto base_backoff = + util::int_pow(MULTIPLIER, std::min(MAX_BACKOFF_EXP, fail_count_)); + auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff, + JITTER * base_backoff); + + auto &downstreamconf = *get_config()->conn.downstream; + + auto backoff = + std::min(downstreamconf.timeout.max_backoff, base_backoff + dist(gen_)); + + LOG(WARN) << "Could not connect " << fail_count_ + << " times in a row; sleep for " << backoff << " seconds"; + + ev_timer_set(&timer_, backoff, 0.); + ev_timer_start(loop_, &timer_); +} + +size_t ConnectBlocker::get_fail_count() const { return fail_count_; } + +void ConnectBlocker::offline() { + if (offline_) { + return; + } + + if (!ev_is_active(&timer_)) { + call_block_func(); + } + + offline_ = true; + + ev_timer_stop(loop_, &timer_); + ev_timer_set(&timer_, std::numeric_limits::max(), 0.); + ev_timer_start(loop_, &timer_); +} + +void ConnectBlocker::online() { + ev_timer_stop(loop_, &timer_); + + call_unblock_func(); + + fail_count_ = 0; + + offline_ = false; +} + +bool ConnectBlocker::in_offline() const { return offline_; } + +void ConnectBlocker::call_block_func() { + if (block_func_) { + block_func_(); + } +} + +void ConnectBlocker::call_unblock_func() { + if (unblock_func_) { + unblock_func_(); + } +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_connect_blocker.h b/lib/nghttp2/src/shrpx_connect_blocker.h new file mode 100644 index 00000000000..1ebe78903cc --- /dev/null +++ b/lib/nghttp2/src/shrpx_connect_blocker.h @@ -0,0 +1,86 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 SHRPX_CONNECT_BLOCKER_H +#define SHRPX_CONNECT_BLOCKER_H + +#include "shrpx.h" + +#include +#include + +#include + +namespace shrpx { + +class ConnectBlocker { +public: + ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop, + std::function block_func, + std::function unblock_func); + ~ConnectBlocker(); + + // Returns true if making connection is not allowed. + bool blocked() const; + // Call this function if connect operation succeeded. This will + // reset sleep_ to minimum value. + void on_success(); + // Call this function if connect operations failed. This will start + // timer and blocks connection establishment with exponential + // backoff. + void on_failure(); + + size_t get_fail_count() const; + + // Peer is now considered offline. This effectively means that the + // connection is blocked until online() is called. + void offline(); + + // Peer is now considered online + void online(); + + // Returns true if peer is considered offline. + bool in_offline() const; + + void call_block_func(); + void call_unblock_func(); + +private: + std::mt19937 &gen_; + // Called when blocking is started + std::function block_func_; + // Called when unblocked + std::function unblock_func_; + ev_timer timer_; + struct ev_loop *loop_; + // The number of consecutive connection failure. Reset to 0 on + // success. + size_t fail_count_; + // true if peer is considered offline. + bool offline_; +}; + +} // namespace shrpx + +#endif // SHRPX_CONNECT_BLOCKER_H diff --git a/lib/nghttp2/src/shrpx_connection.cc b/lib/nghttp2/src/shrpx_connection.cc new file mode 100644 index 00000000000..28c25b18218 --- /dev/null +++ b/lib/nghttp2/src/shrpx_connection.cc @@ -0,0 +1,1318 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_connection.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include + +#include + +#include + +#include "shrpx_tls.h" +#include "shrpx_memcached_request.h" +#include "shrpx_log.h" +#include "memchunk.h" +#include "util.h" +#include "ssl_compat.h" + +using namespace nghttp2; +using namespace std::chrono_literals; + +namespace shrpx { + +#if !LIBRESSL_3_5_API && !LIBRESSL_2_7_API && !OPENSSL_1_1_API + +void *BIO_get_data(BIO *bio) { return bio->ptr; } +void BIO_set_data(BIO *bio, void *ptr) { bio->ptr = ptr; } +void BIO_set_init(BIO *bio, int init) { bio->init = init; } + +#endif // !LIBRESSL_3_5_API && !LIBRESSL_2_7_API && !OPENSSL_1_1_API + +Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl, + MemchunkPool *mcpool, ev_tstamp write_timeout, + ev_tstamp read_timeout, + const RateLimitConfig &write_limit, + const RateLimitConfig &read_limit, IOCb writecb, + IOCb readcb, TimerCb timeoutcb, void *data, + size_t tls_dyn_rec_warmup_threshold, + ev_tstamp tls_dyn_rec_idle_timeout, Proto proto) + : +#ifdef ENABLE_HTTP3 + conn_ref{nullptr, this}, +#endif // ENABLE_HTTP3 + tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool), + DefaultMemchunks(mcpool)}, + wlimit(loop, &wev, write_limit.rate, write_limit.burst), + rlimit(loop, &rev, read_limit.rate, read_limit.burst, this), + loop(loop), + data(data), + fd(fd), + tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold), + tls_dyn_rec_idle_timeout(util::duration_from(tls_dyn_rec_idle_timeout)), + proto(proto), + read_timeout(read_timeout) { + + ev_io_init(&wev, writecb, fd, EV_WRITE); + ev_io_init(&rev, readcb, proto == Proto::HTTP3 ? 0 : fd, EV_READ); + + wev.data = this; + rev.data = this; + + ev_timer_init(&wt, timeoutcb, 0., write_timeout); + ev_timer_init(&rt, timeoutcb, 0., read_timeout); + + wt.data = this; + rt.data = this; + + if (ssl) { + set_ssl(ssl); + } +} + +Connection::~Connection() { disconnect(); } + +void Connection::disconnect() { + if (tls.ssl) { + if (proto != Proto::HTTP3) { + SSL_set_shutdown(tls.ssl, + SSL_get_shutdown(tls.ssl) | SSL_RECEIVED_SHUTDOWN); + ERR_clear_error(); + + if (tls.cached_session) { + SSL_SESSION_free(tls.cached_session); + tls.cached_session = nullptr; + } + + if (tls.cached_session_lookup_req) { + tls.cached_session_lookup_req->canceled = true; + tls.cached_session_lookup_req = nullptr; + } + + SSL_shutdown(tls.ssl); + } + + SSL_free(tls.ssl); + tls.ssl = nullptr; + + tls.wbuf.reset(); + tls.rbuf.reset(); + tls.last_write_idle = {}; + tls.warmup_writelen = 0; + tls.last_writelen = 0; + tls.last_readlen = 0; + tls.handshake_state = TLSHandshakeState::NORMAL; + tls.initial_handshake_done = false; + tls.reneg_started = false; + tls.sct_requested = false; + tls.early_data_finish = false; + } + + if (proto != Proto::HTTP3 && fd != -1) { + shutdown(fd, SHUT_WR); + close(fd); + fd = -1; + } + + // Stop watchers here because they could be activated in + // SSL_shutdown(). + ev_timer_stop(loop, &rt); + ev_timer_stop(loop, &wt); + + rlimit.stopw(); + wlimit.stopw(); +} + +void Connection::prepare_client_handshake() { + SSL_set_connect_state(tls.ssl); + // This prevents SSL_read_early_data from being called. + tls.early_data_finish = true; +} + +void Connection::prepare_server_handshake() { + auto &tlsconf = get_config()->tls; + if (proto != Proto::HTTP3 && !tlsconf.session_cache.memcached.host.empty()) { + auto bio = BIO_new(tlsconf.bio_method); + BIO_set_data(bio, this); + SSL_set_bio(tls.ssl, bio, bio); + } + + SSL_set_accept_state(tls.ssl); + tls.server_handshake = true; +} + +// BIO implementation is inspired by openldap implementation: +// http://www.openldap.org/devel/cvsweb.cgi/~checkout~/libraries/libldap/tls_o.c +namespace { +int shrpx_bio_write(BIO *b, const char *buf, int len) { + if (buf == nullptr || len <= 0) { + return 0; + } + + auto conn = static_cast(BIO_get_data(b)); + auto &wbuf = conn->tls.wbuf; + + BIO_clear_retry_flags(b); + + if (conn->tls.initial_handshake_done) { + // After handshake finished, send |buf| of length |len| to the + // socket directly. + + // Only when TLS session was prematurely ended before server sent + // all handshake message, this condition is true. This could be + // alert from SSL_shutdown(). Since connection is already down, + // just return error. + if (wbuf.rleft()) { + return -1; + } + auto nwrite = conn->write_clear(buf, len); + if (nwrite < 0) { + return -1; + } + + if (nwrite == 0) { + BIO_set_retry_write(b); + return -1; + } + + return nwrite; + } + + wbuf.append(buf, len); + + return len; +} +} // namespace + +namespace { +int shrpx_bio_read(BIO *b, char *buf, int len) { + if (buf == nullptr || len <= 0) { + return 0; + } + + auto conn = static_cast(BIO_get_data(b)); + auto &rbuf = conn->tls.rbuf; + + BIO_clear_retry_flags(b); + + if (conn->tls.initial_handshake_done && rbuf.rleft() == 0) { + auto nread = conn->read_clear(buf, len); + if (nread < 0) { + return -1; + } + if (nread == 0) { + BIO_set_retry_read(b); + return -1; + } + return nread; + } + + if (rbuf.rleft() == 0) { + BIO_set_retry_read(b); + return -1; + } + + return rbuf.remove(buf, len); +} +} // namespace + +namespace { +int shrpx_bio_puts(BIO *b, const char *str) { + return shrpx_bio_write(b, str, strlen(str)); +} +} // namespace + +namespace { +int shrpx_bio_gets(BIO *b, char *buf, int len) { return -1; } +} // namespace + +namespace { +long shrpx_bio_ctrl(BIO *b, int cmd, long num, void *ptr) { + switch (cmd) { + case BIO_CTRL_FLUSH: + return 1; + } + + return 0; +} +} // namespace + +namespace { +int shrpx_bio_create(BIO *b) { +#if OPENSSL_1_1_API || LIBRESSL_3_5_API + BIO_set_init(b, 1); +#else // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + b->init = 1; + b->num = 0; + b->ptr = nullptr; + b->flags = 0; +#endif // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + return 1; +} +} // namespace + +namespace { +int shrpx_bio_destroy(BIO *b) { + if (b == nullptr) { + return 0; + } + +#if !OPENSSL_1_1_API && !LIBRESSL_3_5_API + b->ptr = nullptr; + b->init = 0; + b->flags = 0; +#endif // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + + return 1; +} +} // namespace + +#if OPENSSL_1_1_API || LIBRESSL_3_5_API + +BIO_METHOD *create_bio_method() { + auto meth = BIO_meth_new(BIO_TYPE_FD, "nghttpx-bio"); + BIO_meth_set_write(meth, shrpx_bio_write); + BIO_meth_set_read(meth, shrpx_bio_read); + BIO_meth_set_puts(meth, shrpx_bio_puts); + BIO_meth_set_gets(meth, shrpx_bio_gets); + BIO_meth_set_ctrl(meth, shrpx_bio_ctrl); + BIO_meth_set_create(meth, shrpx_bio_create); + BIO_meth_set_destroy(meth, shrpx_bio_destroy); + + return meth; +} + +#else // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + +BIO_METHOD *create_bio_method() { + static auto meth = new BIO_METHOD{ + BIO_TYPE_FD, "nghttpx-bio", shrpx_bio_write, + shrpx_bio_read, shrpx_bio_puts, shrpx_bio_gets, + shrpx_bio_ctrl, shrpx_bio_create, shrpx_bio_destroy, + }; + + return meth; +} + +#endif // !OPENSSL_1_1_API && !LIBRESSL_3_5_API + +void Connection::set_ssl(SSL *ssl) { + tls.ssl = ssl; + + SSL_set_app_data(tls.ssl, this); +} + +namespace { +// We should buffer at least full encrypted TLS record here. +// Theoretically, peer can send client hello in several TLS records, +// which could exceed this limit, but it is not portable, and we don't +// have to handle such exotic behaviour. +bool read_buffer_full(DefaultPeekMemchunks &rbuf) { + return rbuf.rleft_buffered() >= 20_k; +} +} // namespace + +int Connection::tls_handshake() { + wlimit.stopw(); + ev_timer_stop(loop, &wt); + + auto &tlsconf = get_config()->tls; + + if (!tls.server_handshake || tlsconf.session_cache.memcached.host.empty()) { + return tls_handshake_simple(); + } + + std::array buf; + + if (ev_is_active(&rev)) { + auto nread = read_clear(buf.data(), buf.size()); + if (nread < 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake read error"; + } + return -1; + } + tls.rbuf.append(buf.data(), nread); + if (read_buffer_full(tls.rbuf)) { + rlimit.stopw(); + } + } + + if (tls.initial_handshake_done) { + return write_tls_pending_handshake(); + } + + switch (tls.handshake_state) { + case TLSHandshakeState::WAIT_FOR_SESSION_CACHE: + return SHRPX_ERR_INPROGRESS; + case TLSHandshakeState::GOT_SESSION_CACHE: { + // Use the same trick invented by @kazuho in h2o project. + + // Discard all outgoing data. + tls.wbuf.reset(); + // Rewind buffered incoming data to replay client hello. + tls.rbuf.disable_peek(false); + + auto ssl_ctx = SSL_get_SSL_CTX(tls.ssl); + auto ssl_opts = SSL_get_options(tls.ssl); + SSL_free(tls.ssl); + + auto ssl = tls::create_ssl(ssl_ctx); + if (!ssl) { + return -1; + } + if (ssl_opts & SSL_OP_NO_TICKET) { + SSL_set_options(ssl, SSL_OP_NO_TICKET); + } + + set_ssl(ssl); + + prepare_server_handshake(); + + tls.handshake_state = TLSHandshakeState::NORMAL; + break; + } + case TLSHandshakeState::CANCEL_SESSION_CACHE: + tls.handshake_state = TLSHandshakeState::NORMAL; + break; + default: + break; + } + + int rv; + + ERR_clear_error(); + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (!tls.server_handshake || tls.early_data_finish) { + rv = SSL_do_handshake(tls.ssl); + } else { + for (;;) { + size_t nread; + + rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread); + if (rv == SSL_READ_EARLY_DATA_ERROR) { + // If we have early data, and server sends ServerHello, assume + // that handshake is completed in server side, and start + // processing request. If we don't exit handshake code here, + // server waits for EndOfEarlyData and Finished message from + // client, which voids the purpose of 0-RTT data. The left + // over of handshake is done through write_tls or read_tls. + if (tlsconf.no_postpone_early_data && + (tls.handshake_state == TLSHandshakeState::WRITE_STARTED || + tls.wbuf.rleft()) && + tls.earlybuf.rleft()) { + rv = 1; + } + + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read early data " << nread << " bytes"; + } + + tls.earlybuf.append(buf.data(), nread); + + if (rv == SSL_READ_EARLY_DATA_FINISH) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read all early data; total " + << tls.earlybuf.rleft() << " bytes"; + } + tls.early_data_finish = true; + // The same reason stated above. + if (tlsconf.no_postpone_early_data && + (tls.handshake_state == TLSHandshakeState::WRITE_STARTED || + tls.wbuf.rleft()) && + tls.earlybuf.rleft()) { + rv = 1; + } else { + ERR_clear_error(); + rv = SSL_do_handshake(tls.ssl); + } + break; + } + } + } +#else // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + rv = SSL_do_handshake(tls.ssl); +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + + if (rv <= 0) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + if (read_buffer_full(tls.rbuf)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake message is too large"; + } + return -1; + } + break; + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_SSL: { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake libssl error: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + struct iovec iov[1]; + auto iovcnt = tls.wbuf.riovec(iov, 1); + auto nwrite = writev_clear(iov, iovcnt); + if (nwrite > 0) { + tls.wbuf.drain(nwrite); + } + + return SHRPX_ERR_NETWORK; + } + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake libssl error " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (tls.handshake_state == TLSHandshakeState::WAIT_FOR_SESSION_CACHE) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake is still in progress"; + } + return SHRPX_ERR_INPROGRESS; + } + + // Don't send handshake data if handshake was completed in OpenSSL + // routine. We have to check HTTP/2 requirement if HTTP/2 was + // negotiated before sending finished message to the peer. + if ((rv != 1 +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + || SSL_in_init(tls.ssl) +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + ) && + tls.wbuf.rleft()) { + // First write indicates that resumption stuff has done. + if (tls.handshake_state != TLSHandshakeState::WRITE_STARTED) { + tls.handshake_state = TLSHandshakeState::WRITE_STARTED; + // If peek has already disabled, this is noop. + tls.rbuf.disable_peek(true); + } + std::array iov; + auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size()); + auto nwrite = writev_clear(iov.data(), iovcnt); + if (nwrite < 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake write error"; + } + return -1; + } + tls.wbuf.drain(nwrite); + + if (tls.wbuf.rleft()) { + wlimit.startw(); + ev_timer_again(loop, &wt); + } + } + + if (!read_buffer_full(tls.rbuf)) { + // We may have stopped reading + rlimit.startw(); + } + + if (rv != 1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake is still in progress"; + } + return SHRPX_ERR_INPROGRESS; + } + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) && + SSL_in_init(tls.ssl)) { + auto nread = SSL_read(tls.ssl, buf.data(), buf.size()); + if (nread <= 0) { + auto err = SSL_get_error(tls.ssl, nread); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_ZERO_RETURN: + return SHRPX_ERR_EOF; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } else { + tls.earlybuf.append(buf.data(), nread); + } + + if (SSL_in_init(tls.ssl)) { + return SHRPX_ERR_INPROGRESS; + } + } +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // Handshake was done + + rv = check_http2_requirement(); + if (rv != 0) { + return -1; + } + + // Just in case + tls.rbuf.disable_peek(true); + + tls.initial_handshake_done = true; + + return write_tls_pending_handshake(); +} + +int Connection::tls_handshake_simple() { + wlimit.stopw(); + ev_timer_stop(loop, &wt); + + if (tls.initial_handshake_done) { + return write_tls_pending_handshake(); + } + + if (SSL_get_fd(tls.ssl) == -1) { + SSL_set_fd(tls.ssl, fd); + } + + int rv; +#if OPENSSL_1_1_1_API || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto &tlsconf = get_config()->tls; + std::array buf; +#endif // OPENSSL_1_1_1_API || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + ERR_clear_error(); + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (!tls.server_handshake || tls.early_data_finish) { + rv = SSL_do_handshake(tls.ssl); + } else { + for (;;) { + size_t nread; + + rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread); + if (rv == SSL_READ_EARLY_DATA_ERROR) { + // If we have early data, and server sends ServerHello, assume + // that handshake is completed in server side, and start + // processing request. If we don't exit handshake code here, + // server waits for EndOfEarlyData and Finished message from + // client, which voids the purpose of 0-RTT data. The left + // over of handshake is done through write_tls or read_tls. + if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) { + rv = 1; + } + + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read early data " << nread << " bytes"; + } + + tls.earlybuf.append(buf.data(), nread); + + if (rv == SSL_READ_EARLY_DATA_FINISH) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read all early data; total " + << tls.earlybuf.rleft() << " bytes"; + } + tls.early_data_finish = true; + // The same reason stated above. + if (tlsconf.no_postpone_early_data && tls.earlybuf.rleft()) { + rv = 1; + } else { + ERR_clear_error(); + rv = SSL_do_handshake(tls.ssl); + } + break; + } + } + } +#else // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + rv = SSL_do_handshake(tls.ssl); +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + + if (rv <= 0) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + if (read_buffer_full(tls.rbuf)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake message is too large"; + } + return -1; + } + break; + case SSL_ERROR_WANT_WRITE: + wlimit.startw(); + ev_timer_again(loop, &wt); + break; + case SSL_ERROR_SSL: { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake libssl error: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + } + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake libssl error " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (rv != 1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake is still in progress"; + } + return SHRPX_ERR_INPROGRESS; + } + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + if (!tlsconf.no_postpone_early_data && SSL_in_early_data(tls.ssl) && + SSL_in_init(tls.ssl)) { + auto nread = SSL_read(tls.ssl, buf.data(), buf.size()); + if (nread <= 0) { + auto err = SSL_get_error(tls.ssl, nread); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_ZERO_RETURN: + return SHRPX_ERR_EOF; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } else { + tls.earlybuf.append(buf.data(), nread); + } + + if (SSL_in_init(tls.ssl)) { + return SHRPX_ERR_INPROGRESS; + } + } +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // Handshake was done + + rv = check_http2_requirement(); + if (rv != 0) { + return -1; + } + + tls.initial_handshake_done = true; + + return write_tls_pending_handshake(); +} + +int Connection::write_tls_pending_handshake() { + // Send handshake data left in the buffer + while (tls.wbuf.rleft()) { + std::array iov; + auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size()); + auto nwrite = writev_clear(iov.data(), iovcnt); + if (nwrite < 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake write error"; + } + return -1; + } + if (nwrite == 0) { + wlimit.startw(); + ev_timer_again(loop, &wt); + + return SHRPX_ERR_INPROGRESS; + } + tls.wbuf.drain(nwrite); + } + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + if (!SSL_in_init(tls.ssl)) { + // This will send a session ticket. + auto nwrite = SSL_write(tls.ssl, "", 0); + if (nwrite < 0) { + auto err = SSL_get_error(tls.ssl, nwrite); + switch (err) { + case SSL_ERROR_WANT_READ: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Close connection due to TLS renegotiation"; + } + return SHRPX_ERR_NETWORK; + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_write: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_write: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } + } +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // We have to start read watcher, since later stage of code expects + // this. + rlimit.startw(); + + // We may have whole request in tls.rbuf. This means that we don't + // get notified further read event. This is especially true for + // HTTP/1.1. + handle_tls_pending_read(); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL/TLS handshake completed"; + nghttp2::tls::TLSSessionInfo tls_info{}; + if (nghttp2::tls::get_tls_session_info(&tls_info, tls.ssl)) { + LOG(INFO) << "cipher=" << tls_info.cipher + << " protocol=" << tls_info.protocol + << " resumption=" << (tls_info.session_reused ? "yes" : "no") + << " session_id=" + << util::format_hex(tls_info.session_id, + tls_info.session_id_length); + } + } + + return 0; +} + +int Connection::check_http2_requirement() { + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(tls.ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr || + !util::check_h2_is_selected(StringRef{next_proto, next_proto_len})) { + return 0; + } + if (!nghttp2::tls::check_http2_tls_version(tls.ssl)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be used."; + } + return -1; + } + + auto check_block_list = false; + if (tls.server_handshake) { + check_block_list = !get_config()->tls.no_http2_cipher_block_list; + } else { + check_block_list = !get_config()->tls.client.no_http2_cipher_block_list; + } + + if (check_block_list && + nghttp2::tls::check_http2_cipher_block_list(tls.ssl)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "The negotiated cipher suite is in HTTP/2 cipher suite " + "block list. HTTP/2 must not be used."; + } + return -1; + } + + return 0; +} + +namespace { +constexpr size_t SHRPX_SMALL_WRITE_LIMIT = 1300; +} // namespace + +size_t Connection::get_tls_write_limit() { + + if (tls_dyn_rec_warmup_threshold == 0) { + return std::numeric_limits::max(); + } + + auto t = std::chrono::steady_clock::now(); + + if (tls.last_write_idle.time_since_epoch().count() >= 0 && + t - tls.last_write_idle > tls_dyn_rec_idle_timeout) { + // Time out, use small record size + tls.warmup_writelen = 0; + return SHRPX_SMALL_WRITE_LIMIT; + } + + if (tls.warmup_writelen >= tls_dyn_rec_warmup_threshold) { + return std::numeric_limits::max(); + } + + return SHRPX_SMALL_WRITE_LIMIT; +} + +void Connection::update_tls_warmup_writelen(size_t n) { + if (tls.warmup_writelen < tls_dyn_rec_warmup_threshold) { + tls.warmup_writelen += n; + } +} + +void Connection::start_tls_write_idle() { + if (tls.last_write_idle.time_since_epoch().count() < 0) { + tls.last_write_idle = std::chrono::steady_clock::now(); + } +} + +ssize_t Connection::write_tls(const void *data, size_t len) { + // SSL_write requires the same arguments (buf pointer and its + // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. + // get_write_limit() may return smaller length than previously + // passed to SSL_write, which violates OpenSSL assumption. To avoid + // this, we keep last length passed to SSL_write to + // tls.last_writelen if SSL_write indicated I/O blocking. + if (tls.last_writelen == 0) { + len = std::min(len, wlimit.avail()); + len = std::min(len, get_tls_write_limit()); + if (len == 0) { + return 0; + } + } else { + len = tls.last_writelen; + tls.last_writelen = 0; + } + + tls.last_write_idle = std::chrono::steady_clock::time_point(-1s); + + auto &tlsconf = get_config()->tls; + auto via_bio = + tls.server_handshake && !tlsconf.session_cache.memcached.host.empty(); + + ERR_clear_error(); + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + int rv; + if (SSL_is_init_finished(tls.ssl)) { + rv = SSL_write(tls.ssl, data, len); + } else { + size_t nwrite; + rv = SSL_write_early_data(tls.ssl, data, len, &nwrite); + // Use the same semantics with SSL_write. + if (rv == 1) { + rv = nwrite; + } + } +#else // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + auto rv = SSL_write(tls.ssl, data, len); +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + + if (rv <= 0) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Close connection due to TLS renegotiation"; + } + return SHRPX_ERR_NETWORK; + case SSL_ERROR_WANT_WRITE: + tls.last_writelen = len; + // starting write watcher and timer is done in write_clear via + // bio otherwise. + if (!via_bio) { + wlimit.startw(); + ev_timer_again(loop, &wt); + } + + return 0; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_write: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_write: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (!via_bio) { + wlimit.drain(rv); + + if (ev_is_active(&wt)) { + ev_timer_again(loop, &wt); + } + } + + update_tls_warmup_writelen(rv); + + return rv; +} + +ssize_t Connection::read_tls(void *data, size_t len) { + ERR_clear_error(); + +#if OPENSSL_1_1_1_API + if (tls.earlybuf.rleft()) { + return tls.earlybuf.remove(data, len); + } +#endif // OPENSSL_1_1_1_API + + // SSL_read requires the same arguments (buf pointer and its + // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. + // rlimit_.avail() or rlimit_.avail() may return different length + // than the length previously passed to SSL_read, which violates + // OpenSSL assumption. To avoid this, we keep last length passed + // to SSL_read to tls_last_readlen_ if SSL_read indicated I/O + // blocking. + if (tls.last_readlen == 0) { + len = std::min(len, rlimit.avail()); + if (len == 0) { + return 0; + } + } else { + len = tls.last_readlen; + tls.last_readlen = 0; + } + + auto &tlsconf = get_config()->tls; + auto via_bio = + tls.server_handshake && !tlsconf.session_cache.memcached.host.empty(); + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (!tls.early_data_finish) { + // TLSv1.3 handshake is still going on. + size_t nread; + auto rv = SSL_read_early_data(tls.ssl, data, len, &nread); + if (rv == SSL_READ_EARLY_DATA_ERROR) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + tls.last_readlen = len; + return 0; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: " + << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read early data " << nread << " bytes"; + } + + if (rv == SSL_READ_EARLY_DATA_FINISH) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: read all early data"; + } + tls.early_data_finish = true; + // We may have stopped write watcher in write_tls. + wlimit.startw(); + } + + if (!via_bio) { + rlimit.drain(nread); + } + + return nread; + } +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + auto rv = SSL_read(tls.ssl, data, len); + + if (rv <= 0) { + auto err = SSL_get_error(tls.ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + tls.last_readlen = len; + return 0; + case SSL_ERROR_WANT_WRITE: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Close connection due to TLS renegotiation"; + } + return SHRPX_ERR_NETWORK; + case SSL_ERROR_ZERO_RETURN: + return SHRPX_ERR_EOF; + case SSL_ERROR_SSL: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: " << ERR_error_string(ERR_get_error(), nullptr); + } + return SHRPX_ERR_NETWORK; + default: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL_read: SSL_get_error returned " << err; + } + return SHRPX_ERR_NETWORK; + } + } + + if (!via_bio) { + rlimit.drain(rv); + } + + return rv; +} + +ssize_t Connection::write_clear(const void *data, size_t len) { + len = std::min(len, wlimit.avail()); + if (len == 0) { + return 0; + } + + ssize_t nwrite; + while ((nwrite = write(fd, data, len)) == -1 && errno == EINTR) + ; + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + wlimit.startw(); + ev_timer_again(loop, &wt); + return 0; + } + return SHRPX_ERR_NETWORK; + } + + wlimit.drain(nwrite); + + if (ev_is_active(&wt)) { + ev_timer_again(loop, &wt); + } + + return nwrite; +} + +ssize_t Connection::writev_clear(struct iovec *iov, int iovcnt) { + iovcnt = limit_iovec(iov, iovcnt, wlimit.avail()); + if (iovcnt == 0) { + return 0; + } + + ssize_t nwrite; + while ((nwrite = writev(fd, iov, iovcnt)) == -1 && errno == EINTR) + ; + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + wlimit.startw(); + ev_timer_again(loop, &wt); + return 0; + } + return SHRPX_ERR_NETWORK; + } + + wlimit.drain(nwrite); + + if (ev_is_active(&wt)) { + ev_timer_again(loop, &wt); + } + + return nwrite; +} + +ssize_t Connection::read_clear(void *data, size_t len) { + len = std::min(len, rlimit.avail()); + if (len == 0) { + return 0; + } + + ssize_t nread; + while ((nread = read(fd, data, len)) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return SHRPX_ERR_NETWORK; + } + + if (nread == 0) { + return SHRPX_ERR_EOF; + } + + rlimit.drain(nread); + + return nread; +} + +ssize_t Connection::read_nolim_clear(void *data, size_t len) { + ssize_t nread; + while ((nread = read(fd, data, len)) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return SHRPX_ERR_NETWORK; + } + + if (nread == 0) { + return SHRPX_ERR_EOF; + } + + return nread; +} + +ssize_t Connection::peek_clear(void *data, size_t len) { + ssize_t nread; + while ((nread = recv(fd, data, len, MSG_PEEK)) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return SHRPX_ERR_NETWORK; + } + + if (nread == 0) { + return SHRPX_ERR_EOF; + } + + return nread; +} + +void Connection::handle_tls_pending_read() { + if (!ev_is_active(&rev)) { + return; + } + rlimit.handle_tls_pending_read(); +} + +int Connection::get_tcp_hint(TCPHint *hint) const { +#if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT) + struct tcp_info tcp_info; + socklen_t tcp_info_len = sizeof(tcp_info); + int rv; + + rv = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len); + + if (rv != 0) { + return -1; + } + + auto avail_packets = tcp_info.tcpi_snd_cwnd > tcp_info.tcpi_unacked + ? tcp_info.tcpi_snd_cwnd - tcp_info.tcpi_unacked + : 0; + + // http://www.slideshare.net/kazuho/programming-tcp-for-responsiveness + + // TODO 29 (5 (header) + 8 (explicit nonce) + 16 (tag)) is TLS + // overhead for AES-GCM. For CHACHA20_POLY1305, it is 21 since it + // does not need 8 bytes explicit nonce. + // + // For TLSv1.3, AES-GCM and CHACHA20_POLY1305 overhead are now 22 + // bytes (5 (header) + 1 (ContentType) + 16 (tag)). + size_t tls_overhead; +# ifdef TLS1_3_VERSION + if (SSL_version(tls.ssl) == TLS1_3_VERSION) { + tls_overhead = 22; + } else +# endif // TLS1_3_VERSION + { + tls_overhead = 29; + } + + auto writable_size = + (avail_packets + 2) * (tcp_info.tcpi_snd_mss - tls_overhead); + if (writable_size > 16_k) { + writable_size = writable_size & ~(16_k - 1); + } else { + if (writable_size < 536) { + LOG(INFO) << "writable_size is too small: " << writable_size; + } + // TODO is this required? + writable_size = std::max(writable_size, static_cast(536 * 2)); + } + + // if (LOG_ENABLED(INFO)) { + // LOG(INFO) << "snd_cwnd=" << tcp_info.tcpi_snd_cwnd + // << ", unacked=" << tcp_info.tcpi_unacked + // << ", snd_mss=" << tcp_info.tcpi_snd_mss + // << ", rtt=" << tcp_info.tcpi_rtt << "us" + // << ", rcv_space=" << tcp_info.tcpi_rcv_space + // << ", writable=" << writable_size; + // } + + hint->write_buffer_size = writable_size; + // TODO tcpi_rcv_space is considered as rwin, is that correct? + hint->rwin = tcp_info.tcpi_rcv_space; + + return 0; +#else // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT) + return -1; +#endif // !defined(TCP_INFO) || !defined(TCP_NOTSENT_LOWAT) +} + +void Connection::again_rt(ev_tstamp t) { + read_timeout = t; + rt.repeat = t; + ev_timer_again(loop, &rt); + last_read = std::chrono::steady_clock::now(); +} + +void Connection::again_rt() { + rt.repeat = read_timeout; + ev_timer_again(loop, &rt); + last_read = std::chrono::steady_clock::now(); +} + +bool Connection::expired_rt() { + auto delta = read_timeout - util::ev_tstamp_from( + std::chrono::steady_clock::now() - last_read); + if (delta < 1e-9) { + return true; + } + rt.repeat = delta; + ev_timer_again(loop, &rt); + return false; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_connection.h b/lib/nghttp2/src/shrpx_connection.h new file mode 100644 index 00000000000..3a458488bb5 --- /dev/null +++ b/lib/nghttp2/src/shrpx_connection.h @@ -0,0 +1,203 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_CONNECTION_H +#define SHRPX_CONNECTION_H + +#include "shrpx_config.h" + +#include + +#include + +#include + +#ifdef ENABLE_HTTP3 +# include +#endif // ENABLE_HTTP3 + +#include "shrpx_rate_limit.h" +#include "shrpx_error.h" +#include "memchunk.h" + +namespace shrpx { + +struct MemcachedRequest; + +namespace tls { +struct TLSSessionCache; +} // namespace tls + +enum class TLSHandshakeState { + NORMAL, + WAIT_FOR_SESSION_CACHE, + GOT_SESSION_CACHE, + CANCEL_SESSION_CACHE, + WRITE_STARTED, +}; + +struct TLSConnection { + DefaultMemchunks wbuf; + DefaultPeekMemchunks rbuf; + // Stores TLSv1.3 early data. + DefaultMemchunks earlybuf; + SSL *ssl; + SSL_SESSION *cached_session; + MemcachedRequest *cached_session_lookup_req; + tls::TLSSessionCache *client_session_cache; + std::chrono::steady_clock::time_point last_write_idle; + size_t warmup_writelen; + // length passed to SSL_write and SSL_read last time. This is + // required since these functions require the exact same parameters + // on non-blocking I/O. + size_t last_writelen, last_readlen; + TLSHandshakeState handshake_state; + bool initial_handshake_done; + bool reneg_started; + // true if ssl is prepared to do handshake as server. + bool server_handshake; + // true if ssl is initialized as server, and client requested + // signed_certificate_timestamp extension. + bool sct_requested; + // true if TLSv1.3 early data has been completely received. Since + // SSL_read_early_data acts like SSL_do_handshake, this field may be + // true even if the negotiated TLS version is TLSv1.2 or earlier. + // This value is also true if this is client side connection for + // convenience. + bool early_data_finish; +}; + +struct TCPHint { + size_t write_buffer_size; + uint32_t rwin; +}; + +template using EVCb = void (*)(struct ev_loop *, T *, int); + +using IOCb = EVCb; +using TimerCb = EVCb; + +struct Connection { + Connection(struct ev_loop *loop, int fd, SSL *ssl, MemchunkPool *mcpool, + ev_tstamp write_timeout, ev_tstamp read_timeout, + const RateLimitConfig &write_limit, + const RateLimitConfig &read_limit, IOCb writecb, IOCb readcb, + TimerCb timeoutcb, void *data, size_t tls_dyn_rec_warmup_threshold, + ev_tstamp tls_dyn_rec_idle_timeout, Proto proto); + ~Connection(); + + void disconnect(); + + void prepare_client_handshake(); + void prepare_server_handshake(); + + int tls_handshake(); + int tls_handshake_simple(); + int write_tls_pending_handshake(); + + int check_http2_requirement(); + + // All write_* and writev_clear functions return number of bytes + // written. If nothing cannot be written (e.g., there is no + // allowance in RateLimit or underlying connection blocks), return + // 0. SHRPX_ERR_NETWORK is returned in case of error. + // + // All read_* functions return number of bytes read. If nothing + // cannot be read (e.g., there is no allowance in Ratelimit or + // underlying connection blocks), return 0. SHRPX_ERR_EOF is + // returned in case of EOF and no data was read. Otherwise + // SHRPX_ERR_NETWORK is return in case of error. + ssize_t write_tls(const void *data, size_t len); + ssize_t read_tls(void *data, size_t len); + + size_t get_tls_write_limit(); + // Updates the number of bytes written in warm up period. + void update_tls_warmup_writelen(size_t n); + // Tells there is no immediate write now. This triggers timer to + // determine fallback to short record size mode. + void start_tls_write_idle(); + + ssize_t write_clear(const void *data, size_t len); + ssize_t writev_clear(struct iovec *iov, int iovcnt); + ssize_t read_clear(void *data, size_t len); + // Read at most |len| bytes of data from socket without rate limit. + ssize_t read_nolim_clear(void *data, size_t len); + // Peek at most |len| bytes of data from socket without rate limit. + ssize_t peek_clear(void *data, size_t len); + + void handle_tls_pending_read(); + + void set_ssl(SSL *ssl); + + int get_tcp_hint(TCPHint *hint) const; + + // These functions are provided for read timer which is frequently + // restarted. We do a trick to make a bit more efficient than just + // calling ev_timer_again(). + + // Restarts read timer with timeout value |t|. + void again_rt(ev_tstamp t); + // Restarts read timer without chainging timeout. + void again_rt(); + // Returns true if read timer expired. + bool expired_rt(); + +#ifdef ENABLE_HTTP3 + // This must be the first member of Connection. + ngtcp2_crypto_conn_ref conn_ref; +#endif // ENABLE_HTTP3 + TLSConnection tls; + ev_io wev; + ev_io rev; + ev_timer wt; + ev_timer rt; + RateLimit wlimit; + RateLimit rlimit; + struct ev_loop *loop; + void *data; + int fd; + size_t tls_dyn_rec_warmup_threshold; + std::chrono::steady_clock::duration tls_dyn_rec_idle_timeout; + // Application protocol used over the connection. This field is not + // used in this object at the moment. The rest of the program may + // use this value when it is useful. + Proto proto; + // The point of time when last read is observed. Note: since we use + // |rt| as idle timer, the activity is not limited to read. + std::chrono::steady_clock::time_point last_read; + // Timeout for read timer |rt|. + ev_tstamp read_timeout; +}; + +#ifdef ENABLE_HTTP3 +static_assert(std::is_standard_layout::value, + "Conneciton is not standard layout"); +#endif // ENABLE_HTTP3 + +// Creates BIO_method shared by all SSL objects. +BIO_METHOD *create_bio_method(); + +} // namespace shrpx + +#endif // SHRPX_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_connection_handler.cc b/lib/nghttp2/src/shrpx_connection_handler.cc new file mode 100644 index 00000000000..9d78e80e9e1 --- /dev/null +++ b/lib/nghttp2/src/shrpx_connection_handler.cc @@ -0,0 +1,1320 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_connection_handler.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include +#include + +#include +#include +#include + +#include "shrpx_client_handler.h" +#include "shrpx_tls.h" +#include "shrpx_worker.h" +#include "shrpx_config.h" +#include "shrpx_http2_session.h" +#include "shrpx_connect_blocker.h" +#include "shrpx_downstream_connection.h" +#include "shrpx_accept_handler.h" +#include "shrpx_memcached_dispatcher.h" +#include "shrpx_signal.h" +#include "shrpx_log.h" +#include "xsi_strerror.h" +#include "util.h" +#include "template.h" +#include "ssl_compat.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) { + auto h = static_cast(w->data); + + // If we are in graceful shutdown period, we must not enable + // acceptors again. + if (h->get_graceful_shutdown()) { + return; + } + + h->enable_acceptor(); +} +} // namespace + +namespace { +void ocsp_cb(struct ev_loop *loop, ev_timer *w, int revent) { + auto h = static_cast(w->data); + + // If we are in graceful shutdown period, we won't do ocsp query. + if (h->get_graceful_shutdown()) { + return; + } + + LOG(NOTICE) << "Start ocsp update"; + + h->proceed_next_cert_ocsp(); +} +} // namespace + +namespace { +void ocsp_read_cb(struct ev_loop *loop, ev_io *w, int revent) { + auto h = static_cast(w->data); + + h->read_ocsp_chunk(); +} +} // namespace + +namespace { +void ocsp_chld_cb(struct ev_loop *loop, ev_child *w, int revent) { + auto h = static_cast(w->data); + + h->handle_ocsp_complete(); +} +} // namespace + +namespace { +void thread_join_async_cb(struct ev_loop *loop, ev_async *w, int revent) { + ev_break(loop); +} +} // namespace + +namespace { +void serial_event_async_cb(struct ev_loop *loop, ev_async *w, int revent) { + auto h = static_cast(w->data); + + h->handle_serial_event(); +} +} // namespace + +ConnectionHandler::ConnectionHandler(struct ev_loop *loop, std::mt19937 &gen) + : +#ifdef ENABLE_HTTP3 + quic_ipc_fd_(-1), +#endif // ENABLE_HTTP3 + gen_(gen), + single_worker_(nullptr), + loop_(loop), +#ifdef HAVE_NEVERBLEED + nb_(nullptr), +#endif // HAVE_NEVERBLEED + tls_ticket_key_memcached_get_retry_count_(0), + tls_ticket_key_memcached_fail_count_(0), + worker_round_robin_cnt_(get_config()->api.enabled ? 1 : 0), + graceful_shutdown_(false), + enable_acceptor_on_ocsp_completion_(false) { + ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.); + disable_acceptor_timer_.data = this; + + ev_timer_init(&ocsp_timer_, ocsp_cb, 0., 0.); + ocsp_timer_.data = this; + + ev_io_init(&ocsp_.rev, ocsp_read_cb, -1, EV_READ); + ocsp_.rev.data = this; + + ev_async_init(&thread_join_asyncev_, thread_join_async_cb); + + ev_async_init(&serial_event_asyncev_, serial_event_async_cb); + serial_event_asyncev_.data = this; + + ev_async_start(loop_, &serial_event_asyncev_); + + ev_child_init(&ocsp_.chldev, ocsp_chld_cb, 0, 0); + ocsp_.chldev.data = this; + + ocsp_.next = 0; + ocsp_.proc.rfd = -1; + + reset_ocsp(); +} + +ConnectionHandler::~ConnectionHandler() { + ev_child_stop(loop_, &ocsp_.chldev); + ev_async_stop(loop_, &serial_event_asyncev_); + ev_async_stop(loop_, &thread_join_asyncev_); + ev_io_stop(loop_, &ocsp_.rev); + ev_timer_stop(loop_, &ocsp_timer_); + ev_timer_stop(loop_, &disable_acceptor_timer_); + +#ifdef ENABLE_HTTP3 + for (auto ssl_ctx : quic_all_ssl_ctx_) { + if (ssl_ctx == nullptr) { + continue; + } + + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + delete tls_ctx_data; + SSL_CTX_free(ssl_ctx); + } +#endif // ENABLE_HTTP3 + + for (auto ssl_ctx : all_ssl_ctx_) { + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + delete tls_ctx_data; + SSL_CTX_free(ssl_ctx); + } + + // Free workers before destroying ev_loop + workers_.clear(); + + for (auto loop : worker_loops_) { + ev_loop_destroy(loop); + } +} + +void ConnectionHandler::set_ticket_keys_to_worker( + const std::shared_ptr &ticket_keys) { + for (auto &worker : workers_) { + worker->set_ticket_keys(ticket_keys); + } +} + +void ConnectionHandler::worker_reopen_log_files() { + for (auto &worker : workers_) { + WorkerEvent wev{}; + + wev.type = WorkerEventType::REOPEN_LOG; + + worker->send(std::move(wev)); + } +} + +void ConnectionHandler::worker_replace_downstream( + std::shared_ptr downstreamconf) { + for (auto &worker : workers_) { + WorkerEvent wev{}; + + wev.type = WorkerEventType::REPLACE_DOWNSTREAM; + wev.downstreamconf = downstreamconf; + + worker->send(std::move(wev)); + } +} + +int ConnectionHandler::create_single_worker() { + cert_tree_ = tls::create_cert_lookup_tree(); + auto sv_ssl_ctx = tls::setup_server_ssl_context( + all_ssl_ctx_, indexed_ssl_ctx_, cert_tree_.get() +#ifdef HAVE_NEVERBLEED + , + nb_ +#endif // HAVE_NEVERBLEED + ); + +#ifdef ENABLE_HTTP3 + quic_cert_tree_ = tls::create_cert_lookup_tree(); + auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context( + quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get() +# ifdef HAVE_NEVERBLEED + , + nb_ +# endif // HAVE_NEVERBLEED + ); +#endif // ENABLE_HTTP3 + + auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context( +#ifdef HAVE_NEVERBLEED + nb_ +#endif // HAVE_NEVERBLEED + ); + + if (cl_ssl_ctx) { + all_ssl_ctx_.push_back(cl_ssl_ctx); +#ifdef ENABLE_HTTP3 + quic_all_ssl_ctx_.push_back(nullptr); +#endif // ENABLE_HTTP3 + } + + auto config = get_config(); + auto &tlsconf = config->tls; + + SSL_CTX *session_cache_ssl_ctx = nullptr; + { + auto &memcachedconf = config->tls.session_cache.memcached; + if (memcachedconf.tls) { + session_cache_ssl_ctx = tls::create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + nb_, +#endif // HAVE_NEVERBLEED + tlsconf.cacert, memcachedconf.cert_file, + memcachedconf.private_key_file, nullptr); + all_ssl_ctx_.push_back(session_cache_ssl_ctx); +#ifdef ENABLE_HTTP3 + quic_all_ssl_ctx_.push_back(nullptr); +#endif // ENABLE_HTTP3 + } + } + +#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF) + quic_bpf_refs_.resize(config->conn.quic_listener.addrs.size()); +#endif // ENABLE_HTTP3 && HAVE_LIBBPF + +#ifdef ENABLE_HTTP3 + assert(cid_prefixes_.size() == 1); + const auto &cid_prefix = cid_prefixes_[0]; +#endif // ENABLE_HTTP3 + + single_worker_ = std::make_unique( + loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(), +#ifdef ENABLE_HTTP3 + quic_sv_ssl_ctx, quic_cert_tree_.get(), cid_prefix.data(), + cid_prefix.size(), +# ifdef HAVE_LIBBPF + /* index = */ 0, +# endif // HAVE_LIBBPF +#endif // ENABLE_HTTP3 + ticket_keys_, this, config->conn.downstream); +#ifdef HAVE_MRUBY + if (single_worker_->create_mruby_context() != 0) { + return -1; + } +#endif // HAVE_MRUBY + +#ifdef ENABLE_HTTP3 + if (single_worker_->setup_quic_server_socket() != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + return 0; +} + +int ConnectionHandler::create_worker_thread(size_t num) { +#ifndef NOTHREADS + assert(workers_.size() == 0); + + cert_tree_ = tls::create_cert_lookup_tree(); + auto sv_ssl_ctx = tls::setup_server_ssl_context( + all_ssl_ctx_, indexed_ssl_ctx_, cert_tree_.get() +# ifdef HAVE_NEVERBLEED + , + nb_ +# endif // HAVE_NEVERBLEED + ); + +# ifdef ENABLE_HTTP3 + quic_cert_tree_ = tls::create_cert_lookup_tree(); + auto quic_sv_ssl_ctx = tls::setup_quic_server_ssl_context( + quic_all_ssl_ctx_, quic_indexed_ssl_ctx_, quic_cert_tree_.get() +# ifdef HAVE_NEVERBLEED + , + nb_ +# endif // HAVE_NEVERBLEED + ); +# endif // ENABLE_HTTP3 + + auto cl_ssl_ctx = tls::setup_downstream_client_ssl_context( +# ifdef HAVE_NEVERBLEED + nb_ +# endif // HAVE_NEVERBLEED + ); + + if (cl_ssl_ctx) { + all_ssl_ctx_.push_back(cl_ssl_ctx); +# ifdef ENABLE_HTTP3 + quic_all_ssl_ctx_.push_back(nullptr); +# endif // ENABLE_HTTP3 + } + + auto config = get_config(); + auto &tlsconf = config->tls; + auto &apiconf = config->api; + +# if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF) + quic_bpf_refs_.resize(config->conn.quic_listener.addrs.size()); +# endif // ENABLE_HTTP3 && HAVE_LIBBPF + + // We have dedicated worker for API request processing. + if (apiconf.enabled) { + ++num; + } + + SSL_CTX *session_cache_ssl_ctx = nullptr; + { + auto &memcachedconf = config->tls.session_cache.memcached; + + if (memcachedconf.tls) { + session_cache_ssl_ctx = tls::create_ssl_client_context( +# ifdef HAVE_NEVERBLEED + nb_, +# endif // HAVE_NEVERBLEED + tlsconf.cacert, memcachedconf.cert_file, + memcachedconf.private_key_file, nullptr); + all_ssl_ctx_.push_back(session_cache_ssl_ctx); +# ifdef ENABLE_HTTP3 + quic_all_ssl_ctx_.push_back(nullptr); +# endif // ENABLE_HTTP3 + } + } + +# ifdef ENABLE_HTTP3 + assert(cid_prefixes_.size() == num); +# endif // ENABLE_HTTP3 + + for (size_t i = 0; i < num; ++i) { + auto loop = ev_loop_new(config->ev_loop_flags); + +# ifdef ENABLE_HTTP3 + const auto &cid_prefix = cid_prefixes_[i]; +# endif // ENABLE_HTTP3 + + auto worker = std::make_unique( + loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(), +# ifdef ENABLE_HTTP3 + quic_sv_ssl_ctx, quic_cert_tree_.get(), cid_prefix.data(), + cid_prefix.size(), +# ifdef HAVE_LIBBPF + i, +# endif // HAVE_LIBBPF +# endif // ENABLE_HTTP3 + ticket_keys_, this, config->conn.downstream); +# ifdef HAVE_MRUBY + if (worker->create_mruby_context() != 0) { + return -1; + } +# endif // HAVE_MRUBY + +# ifdef ENABLE_HTTP3 + if ((!apiconf.enabled || i != 0) && + worker->setup_quic_server_socket() != 0) { + return -1; + } +# endif // ENABLE_HTTP3 + + workers_.push_back(std::move(worker)); + worker_loops_.push_back(loop); + + LLOG(NOTICE, this) << "Created worker thread #" << workers_.size() - 1; + } + + for (auto &worker : workers_) { + worker->run_async(); + } + +#endif // NOTHREADS + + return 0; +} + +void ConnectionHandler::join_worker() { +#ifndef NOTHREADS + int n = 0; + + if (LOG_ENABLED(INFO)) { + LLOG(INFO, this) << "Waiting for worker thread to join: n=" + << workers_.size(); + } + + for (auto &worker : workers_) { + worker->wait(); + if (LOG_ENABLED(INFO)) { + LLOG(INFO, this) << "Thread #" << n << " joined"; + } + ++n; + } +#endif // NOTHREADS +} + +void ConnectionHandler::graceful_shutdown_worker() { + if (single_worker_) { + return; + } + + if (LOG_ENABLED(INFO)) { + LLOG(INFO, this) << "Sending graceful shutdown signal to worker"; + } + + for (auto &worker : workers_) { + WorkerEvent wev{}; + wev.type = WorkerEventType::GRACEFUL_SHUTDOWN; + + worker->send(std::move(wev)); + } + +#ifndef NOTHREADS + ev_async_start(loop_, &thread_join_asyncev_); + + thread_join_fut_ = std::async(std::launch::async, [this]() { + (void)reopen_log_files(get_config()->logging); + join_worker(); + ev_async_send(get_loop(), &thread_join_asyncev_); + delete_log_config(); + }); +#endif // NOTHREADS +} + +int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen, + const UpstreamAddr *faddr) { + if (LOG_ENABLED(INFO)) { + LLOG(INFO, this) << "Accepted connection from " + << util::numeric_name(addr, addrlen) << ", fd=" << fd; + } + + auto config = get_config(); + + if (single_worker_) { + auto &upstreamconf = config->conn.upstream; + if (single_worker_->get_worker_stat()->num_connections >= + upstreamconf.worker_connections) { + + if (LOG_ENABLED(INFO)) { + LLOG(INFO, this) << "Too many connections >=" + << upstreamconf.worker_connections; + } + + close(fd); + return -1; + } + + auto client = + tls::accept_connection(single_worker_.get(), fd, addr, addrlen, faddr); + if (!client) { + LLOG(ERROR, this) << "ClientHandler creation failed"; + + close(fd); + return -1; + } + + return 0; + } + + Worker *worker; + + if (faddr->alt_mode == UpstreamAltMode::API) { + worker = workers_[0].get(); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Dispatch connection to API worker #0"; + } + } else { + worker = workers_[worker_round_robin_cnt_].get(); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Dispatch connection to worker #" << worker_round_robin_cnt_; + } + + if (++worker_round_robin_cnt_ == workers_.size()) { + auto &apiconf = config->api; + + if (apiconf.enabled) { + worker_round_robin_cnt_ = 1; + } else { + worker_round_robin_cnt_ = 0; + } + } + } + + WorkerEvent wev{}; + wev.type = WorkerEventType::NEW_CONNECTION; + wev.client_fd = fd; + memcpy(&wev.client_addr, addr, addrlen); + wev.client_addrlen = addrlen; + wev.faddr = faddr; + + worker->send(std::move(wev)); + + return 0; +} + +struct ev_loop *ConnectionHandler::get_loop() const { return loop_; } + +Worker *ConnectionHandler::get_single_worker() const { + return single_worker_.get(); +} + +void ConnectionHandler::add_acceptor(std::unique_ptr h) { + acceptors_.push_back(std::move(h)); +} + +void ConnectionHandler::delete_acceptor() { acceptors_.clear(); } + +void ConnectionHandler::enable_acceptor() { + for (auto &a : acceptors_) { + a->enable(); + } +} + +void ConnectionHandler::disable_acceptor() { + for (auto &a : acceptors_) { + a->disable(); + } +} + +void ConnectionHandler::sleep_acceptor(ev_tstamp t) { + if (t == 0. || ev_is_active(&disable_acceptor_timer_)) { + return; + } + + disable_acceptor(); + + ev_timer_set(&disable_acceptor_timer_, t, 0.); + ev_timer_start(loop_, &disable_acceptor_timer_); +} + +void ConnectionHandler::accept_pending_connection() { + for (auto &a : acceptors_) { + a->accept_connection(); + } +} + +void ConnectionHandler::set_ticket_keys( + std::shared_ptr ticket_keys) { + ticket_keys_ = std::move(ticket_keys); + if (single_worker_) { + single_worker_->set_ticket_keys(ticket_keys_); + } +} + +const std::shared_ptr &ConnectionHandler::get_ticket_keys() const { + return ticket_keys_; +} + +void ConnectionHandler::set_graceful_shutdown(bool f) { + graceful_shutdown_ = f; + if (single_worker_) { + single_worker_->set_graceful_shutdown(f); + } +} + +bool ConnectionHandler::get_graceful_shutdown() const { + return graceful_shutdown_; +} + +void ConnectionHandler::cancel_ocsp_update() { + enable_acceptor_on_ocsp_completion_ = false; + ev_timer_stop(loop_, &ocsp_timer_); + + if (ocsp_.proc.pid == 0) { + return; + } + + int rv; + + rv = kill(ocsp_.proc.pid, SIGTERM); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Could not send signal to OCSP query process: errno=" + << error; + } + + while ((rv = waitpid(ocsp_.proc.pid, nullptr, 0)) == -1 && errno == EINTR) + ; + if (rv == -1) { + auto error = errno; + LOG(ERROR) << "Error occurred while we were waiting for the completion of " + "OCSP query process: errno=" + << error; + } +} + +// inspired by h2o_read_command function from h2o project: +// https://github.com/h2o/h2o +int ConnectionHandler::start_ocsp_update(const char *cert_file) { + int rv; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Start ocsp update for " << cert_file; + } + + assert(!ev_is_active(&ocsp_.rev)); + assert(!ev_is_active(&ocsp_.chldev)); + + char *const argv[] = { + const_cast( + get_config()->tls.ocsp.fetch_ocsp_response_file.c_str()), + const_cast(cert_file), nullptr}; + + Process proc; + rv = exec_read_command(proc, argv); + if (rv != 0) { + return -1; + } + + ocsp_.proc = proc; + + ev_io_set(&ocsp_.rev, ocsp_.proc.rfd, EV_READ); + ev_io_start(loop_, &ocsp_.rev); + + ev_child_set(&ocsp_.chldev, ocsp_.proc.pid, 0); + ev_child_start(loop_, &ocsp_.chldev); + + return 0; +} + +void ConnectionHandler::read_ocsp_chunk() { + std::array buf; + for (;;) { + ssize_t n; + while ((n = read(ocsp_.proc.rfd, buf.data(), buf.size())) == -1 && + errno == EINTR) + ; + + if (n == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return; + } + auto error = errno; + LOG(WARN) << "Reading from ocsp query command failed: errno=" << error; + ocsp_.error = error; + + break; + } + + if (n == 0) { + break; + } + + std::copy_n(std::begin(buf), n, std::back_inserter(ocsp_.resp)); + } + + ev_io_stop(loop_, &ocsp_.rev); +} + +void ConnectionHandler::handle_ocsp_complete() { + ev_io_stop(loop_, &ocsp_.rev); + ev_child_stop(loop_, &ocsp_.chldev); + + assert(ocsp_.next < all_ssl_ctx_.size()); +#ifdef ENABLE_HTTP3 + assert(all_ssl_ctx_.size() == quic_all_ssl_ctx_.size()); +#endif // ENABLE_HTTP3 + + auto ssl_ctx = all_ssl_ctx_[ocsp_.next]; + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + + auto rstatus = ocsp_.chldev.rstatus; + auto status = WEXITSTATUS(rstatus); + if (ocsp_.error || !WIFEXITED(rstatus) || status != 0) { + LOG(WARN) << "ocsp query command for " << tls_ctx_data->cert_file + << " failed: error=" << ocsp_.error << ", rstatus=" << log::hex + << rstatus << log::dec << ", status=" << status; + ++ocsp_.next; + proceed_next_cert_ocsp(); + return; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "ocsp update for " << tls_ctx_data->cert_file + << " finished successfully"; + } + + auto config = get_config(); + auto &tlsconf = config->tls; + + if (tlsconf.ocsp.no_verify || + tls::verify_ocsp_response(ssl_ctx, ocsp_.resp.data(), + ocsp_.resp.size()) == 0) { +#ifdef ENABLE_HTTP3 + // We have list of SSL_CTX with the same certificate in + // quic_all_ssl_ctx_ as well. Some SSL_CTXs are missing there in + // that case we get nullptr. + auto quic_ssl_ctx = quic_all_ssl_ctx_[ocsp_.next]; + if (quic_ssl_ctx) { +# ifndef NGHTTP2_OPENSSL_IS_BORINGSSL + auto quic_tls_ctx_data = static_cast( + SSL_CTX_get_app_data(quic_ssl_ctx)); +# ifdef HAVE_ATOMIC_STD_SHARED_PTR + std::atomic_store_explicit( + &quic_tls_ctx_data->ocsp_data, + std::make_shared>(ocsp_.resp), + std::memory_order_release); +# else // !HAVE_ATOMIC_STD_SHARED_PTR + std::lock_guard g(quic_tls_ctx_data->mu); + quic_tls_ctx_data->ocsp_data = + std::make_shared>(ocsp_.resp); +# endif // !HAVE_ATOMIC_STD_SHARED_PTR +# else // NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_ocsp_response(quic_ssl_ctx, ocsp_.resp.data(), + ocsp_.resp.size()); +# endif // NGHTTP2_OPENSSL_IS_BORINGSSL + } +#endif // ENABLE_HTTP3 + +#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL +# ifdef HAVE_ATOMIC_STD_SHARED_PTR + std::atomic_store_explicit( + &tls_ctx_data->ocsp_data, + std::make_shared>(std::move(ocsp_.resp)), + std::memory_order_release); +# else // !HAVE_ATOMIC_STD_SHARED_PTR + std::lock_guard g(tls_ctx_data->mu); + tls_ctx_data->ocsp_data = + std::make_shared>(std::move(ocsp_.resp)); +# endif // !HAVE_ATOMIC_STD_SHARED_PTR +#else // NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_ocsp_response(ssl_ctx, ocsp_.resp.data(), ocsp_.resp.size()); +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + } + + ++ocsp_.next; + proceed_next_cert_ocsp(); +} + +void ConnectionHandler::reset_ocsp() { + if (ocsp_.proc.rfd != -1) { + close(ocsp_.proc.rfd); + } + + ocsp_.proc.rfd = -1; + ocsp_.proc.pid = 0; + ocsp_.error = 0; + ocsp_.resp = std::vector(); +} + +void ConnectionHandler::proceed_next_cert_ocsp() { + for (;;) { + reset_ocsp(); + if (ocsp_.next == all_ssl_ctx_.size()) { + ocsp_.next = 0; + // We have updated all ocsp response, and schedule next update. + ev_timer_set(&ocsp_timer_, get_config()->tls.ocsp.update_interval, 0.); + ev_timer_start(loop_, &ocsp_timer_); + + if (enable_acceptor_on_ocsp_completion_) { + enable_acceptor_on_ocsp_completion_ = false; + enable_acceptor(); + } + + return; + } + + auto ssl_ctx = all_ssl_ctx_[ocsp_.next]; + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + + // client SSL_CTX is also included in all_ssl_ctx_, but has no + // tls_ctx_data. + if (!tls_ctx_data) { + ++ocsp_.next; + continue; + } + + auto cert_file = tls_ctx_data->cert_file; + + if (start_ocsp_update(cert_file) != 0) { + ++ocsp_.next; + continue; + } + + break; + } +} + +void ConnectionHandler::set_tls_ticket_key_memcached_dispatcher( + std::unique_ptr dispatcher) { + tls_ticket_key_memcached_dispatcher_ = std::move(dispatcher); +} + +MemcachedDispatcher * +ConnectionHandler::get_tls_ticket_key_memcached_dispatcher() const { + return tls_ticket_key_memcached_dispatcher_.get(); +} + +// Use the similar backoff algorithm described in +// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md +namespace { +constexpr size_t MAX_BACKOFF_EXP = 10; +constexpr auto MULTIPLIER = 3.2; +constexpr auto JITTER = 0.2; +} // namespace + +void ConnectionHandler::on_tls_ticket_key_network_error(ev_timer *w) { + if (++tls_ticket_key_memcached_get_retry_count_ >= + get_config()->tls.ticket.memcached.max_retry) { + LOG(WARN) << "Memcached: tls ticket get retry all failed " + << tls_ticket_key_memcached_get_retry_count_ << " times."; + + on_tls_ticket_key_not_found(w); + return; + } + + auto base_backoff = util::int_pow( + MULTIPLIER, + std::min(MAX_BACKOFF_EXP, tls_ticket_key_memcached_get_retry_count_)); + auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff, + JITTER * base_backoff); + + auto backoff = base_backoff + dist(gen_); + + LOG(WARN) + << "Memcached: tls ticket get failed due to network error, retrying in " + << backoff << " seconds"; + + ev_timer_set(w, backoff, 0.); + ev_timer_start(loop_, w); +} + +void ConnectionHandler::on_tls_ticket_key_not_found(ev_timer *w) { + tls_ticket_key_memcached_get_retry_count_ = 0; + + if (++tls_ticket_key_memcached_fail_count_ >= + get_config()->tls.ticket.memcached.max_fail) { + LOG(WARN) << "Memcached: could not get tls ticket; disable tls ticket"; + + tls_ticket_key_memcached_fail_count_ = 0; + + set_ticket_keys(nullptr); + set_ticket_keys_to_worker(nullptr); + } + + LOG(WARN) << "Memcached: tls ticket get failed, schedule next"; + schedule_next_tls_ticket_key_memcached_get(w); +} + +void ConnectionHandler::on_tls_ticket_key_get_success( + const std::shared_ptr &ticket_keys, ev_timer *w) { + LOG(NOTICE) << "Memcached: tls ticket get success"; + + tls_ticket_key_memcached_get_retry_count_ = 0; + tls_ticket_key_memcached_fail_count_ = 0; + + schedule_next_tls_ticket_key_memcached_get(w); + + if (!ticket_keys || ticket_keys->keys.empty()) { + LOG(WARN) << "Memcached: tls ticket keys are empty; tls ticket disabled"; + set_ticket_keys(nullptr); + set_ticket_keys_to_worker(nullptr); + return; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "ticket keys get done"; + LOG(INFO) << 0 << " enc+dec: " + << util::format_hex(ticket_keys->keys[0].data.name); + for (size_t i = 1; i < ticket_keys->keys.size(); ++i) { + auto &key = ticket_keys->keys[i]; + LOG(INFO) << i << " dec: " << util::format_hex(key.data.name); + } + } + + set_ticket_keys(ticket_keys); + set_ticket_keys_to_worker(ticket_keys); +} + +void ConnectionHandler::schedule_next_tls_ticket_key_memcached_get( + ev_timer *w) { + ev_timer_set(w, get_config()->tls.ticket.memcached.interval, 0.); + ev_timer_start(loop_, w); +} + +SSL_CTX *ConnectionHandler::create_tls_ticket_key_memcached_ssl_ctx() { + auto config = get_config(); + auto &tlsconf = config->tls; + auto &memcachedconf = config->tls.ticket.memcached; + + auto ssl_ctx = tls::create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + nb_, +#endif // HAVE_NEVERBLEED + tlsconf.cacert, memcachedconf.cert_file, memcachedconf.private_key_file, + nullptr); + + all_ssl_ctx_.push_back(ssl_ctx); +#ifdef ENABLE_HTTP3 + quic_all_ssl_ctx_.push_back(nullptr); +#endif // ENABLE_HTTP3 + + return ssl_ctx; +} + +#ifdef HAVE_NEVERBLEED +void ConnectionHandler::set_neverbleed(neverbleed_t *nb) { nb_ = nb; } +#endif // HAVE_NEVERBLEED + +void ConnectionHandler::handle_serial_event() { + std::vector q; + { + std::lock_guard g(serial_event_mu_); + q.swap(serial_events_); + } + + for (auto &sev : q) { + switch (sev.type) { + case SerialEventType::REPLACE_DOWNSTREAM: + // Mmake sure that none of worker uses + // get_config()->conn.downstream + mod_config()->conn.downstream = sev.downstreamconf; + + if (single_worker_) { + single_worker_->replace_downstream_config(sev.downstreamconf); + + break; + } + + worker_replace_downstream(sev.downstreamconf); + + break; + default: + break; + } + } +} + +void ConnectionHandler::send_replace_downstream( + const std::shared_ptr &downstreamconf) { + send_serial_event( + SerialEvent(SerialEventType::REPLACE_DOWNSTREAM, downstreamconf)); +} + +void ConnectionHandler::send_serial_event(SerialEvent ev) { + { + std::lock_guard g(serial_event_mu_); + + serial_events_.push_back(std::move(ev)); + } + + ev_async_send(loop_, &serial_event_asyncev_); +} + +SSL_CTX *ConnectionHandler::get_ssl_ctx(size_t idx) const { + return all_ssl_ctx_[idx]; +} + +const std::vector & +ConnectionHandler::get_indexed_ssl_ctx(size_t idx) const { + return indexed_ssl_ctx_[idx]; +} + +#ifdef ENABLE_HTTP3 +const std::vector & +ConnectionHandler::get_quic_indexed_ssl_ctx(size_t idx) const { + return quic_indexed_ssl_ctx_[idx]; +} +#endif // ENABLE_HTTP3 + +void ConnectionHandler::set_enable_acceptor_on_ocsp_completion(bool f) { + enable_acceptor_on_ocsp_completion_ = f; +} + +#ifdef ENABLE_HTTP3 +int ConnectionHandler::forward_quic_packet( + const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *cid_prefix, const uint8_t *data, size_t datalen) { + assert(!get_config()->single_thread); + + for (auto &worker : workers_) { + if (!std::equal(cid_prefix, cid_prefix + SHRPX_QUIC_CID_PREFIXLEN, + worker->get_cid_prefix())) { + continue; + } + + WorkerEvent wev{}; + wev.type = WorkerEventType::QUIC_PKT_FORWARD; + wev.quic_pkt = std::make_unique(faddr->index, remote_addr, + local_addr, pi, data, datalen); + + worker->send(std::move(wev)); + + return 0; + } + + return -1; +} + +void ConnectionHandler::set_quic_keying_materials( + std::shared_ptr qkms) { + quic_keying_materials_ = std::move(qkms); +} + +const std::shared_ptr & +ConnectionHandler::get_quic_keying_materials() const { + return quic_keying_materials_; +} + +void ConnectionHandler::set_cid_prefixes( + const std::vector> + &cid_prefixes) { + cid_prefixes_ = cid_prefixes; +} + +QUICLingeringWorkerProcess * +ConnectionHandler::match_quic_lingering_worker_process_cid_prefix( + const uint8_t *dcid, size_t dcidlen) { + assert(dcidlen >= SHRPX_QUIC_CID_PREFIXLEN); + + for (auto &lwps : quic_lingering_worker_processes_) { + for (auto &cid_prefix : lwps.cid_prefixes) { + if (std::equal(std::begin(cid_prefix), std::end(cid_prefix), dcid)) { + return &lwps; + } + } + } + + return nullptr; +} + +# ifdef HAVE_LIBBPF +std::vector &ConnectionHandler::get_quic_bpf_refs() { + return quic_bpf_refs_; +} + +void ConnectionHandler::unload_bpf_objects() { + LOG(NOTICE) << "Unloading BPF objects"; + + for (auto &ref : quic_bpf_refs_) { + if (ref.obj == nullptr) { + continue; + } + + bpf_object__close(ref.obj); + + ref.obj = nullptr; + } +} +# endif // HAVE_LIBBPF + +void ConnectionHandler::set_quic_ipc_fd(int fd) { quic_ipc_fd_ = fd; } + +void ConnectionHandler::set_quic_lingering_worker_processes( + const std::vector &quic_lwps) { + quic_lingering_worker_processes_ = quic_lwps; +} + +int ConnectionHandler::forward_quic_packet_to_lingering_worker_process( + QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, const uint8_t *data, + size_t datalen) { + std::array header; + + assert(header.size() >= 1 + 1 + 1 + 1 + sizeof(sockaddr_storage) * 2); + assert(remote_addr.len > 0); + assert(local_addr.len > 0); + + auto p = header.data(); + + *p++ = static_cast(QUICIPCType::DGRAM_FORWARD); + *p++ = static_cast(remote_addr.len - 1); + p = std::copy_n(reinterpret_cast(&remote_addr.su), + remote_addr.len, p); + *p++ = static_cast(local_addr.len - 1); + p = std::copy_n(reinterpret_cast(&local_addr.su), + local_addr.len, p); + *p++ = pi.ecn; + + iovec msg_iov[] = { + { + .iov_base = header.data(), + .iov_len = static_cast(p - header.data()), + }, + { + .iov_base = const_cast(data), + .iov_len = datalen, + }, + }; + + msghdr msg{}; + msg.msg_iov = msg_iov; + msg.msg_iovlen = array_size(msg_iov); + + ssize_t nwrite; + + while ((nwrite = sendmsg(quic_lwp->quic_ipc_fd, &msg, 0)) == -1 && + errno == EINTR) + ; + + if (nwrite == -1) { + std::array errbuf; + + auto error = errno; + LOG(ERROR) << "Failed to send QUIC IPC message: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + return -1; + } + + return 0; +} + +int ConnectionHandler::quic_ipc_read() { + std::array buf; + + ssize_t nread; + + while ((nread = recv(quic_ipc_fd_, buf.data(), buf.size(), 0)) == -1 && + errno == EINTR) + ; + + if (nread == -1) { + std::array errbuf; + + auto error = errno; + LOG(ERROR) << "Failed to read data from QUIC IPC channel: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + return -1; + } + + if (nread == 0) { + return 0; + } + + size_t len = 1 + 1 + 1 + 1; + + // Wire format: + // TYPE(1) REMOTE_ADDRLEN(1) REMOTE_ADDR(N) LOCAL_ADDRLEN(1) LOCAL_ADDR(N) + // ECN(1) DGRAM_PAYLOAD(N) + // + // When encoding, REMOTE_ADDRLEN and LOCAL_ADDRLEN are decremented + // by 1. + if (static_cast(nread) < len) { + return 0; + } + + auto p = buf.data(); + if (*p != static_cast(QUICIPCType::DGRAM_FORWARD)) { + LOG(ERROR) << "Unknown QUICIPCType: " << static_cast(*p); + + return -1; + } + + ++p; + + auto pkt = std::make_unique(); + + auto remote_addrlen = static_cast(*p++) + 1; + if (remote_addrlen > sizeof(sockaddr_storage)) { + LOG(ERROR) << "The length of remote address is too large: " + << remote_addrlen; + + return -1; + } + + len += remote_addrlen; + + if (static_cast(nread) < len) { + LOG(ERROR) << "Insufficient QUIC IPC message length"; + + return -1; + } + + pkt->remote_addr.len = remote_addrlen; + memcpy(&pkt->remote_addr.su, p, remote_addrlen); + + p += remote_addrlen; + + auto local_addrlen = static_cast(*p++) + 1; + if (local_addrlen > sizeof(sockaddr_storage)) { + LOG(ERROR) << "The length of local address is too large: " << local_addrlen; + + return -1; + } + + len += local_addrlen; + + if (static_cast(nread) < len) { + LOG(ERROR) << "Insufficient QUIC IPC message length"; + + return -1; + } + + pkt->local_addr.len = local_addrlen; + memcpy(&pkt->local_addr.su, p, local_addrlen); + + p += local_addrlen; + + pkt->pi.ecn = *p++; + + auto datalen = nread - (p - buf.data()); + + pkt->data.assign(p, p + datalen); + + // At the moment, UpstreamAddr index is unknown. + pkt->upstream_addr_index = static_cast(-1); + + ngtcp2_version_cid vc; + + auto rv = ngtcp2_pkt_decode_version_cid(&vc, p, datalen, SHRPX_QUIC_SCIDLEN); + if (rv < 0) { + LOG(ERROR) << "ngtcp2_pkt_decode_version_cid: " << ngtcp2_strerror(rv); + + return -1; + } + + if (vc.dcidlen != SHRPX_QUIC_SCIDLEN) { + LOG(ERROR) << "DCID length is invalid"; + return -1; + } + + if (single_worker_) { + auto faddr = single_worker_->find_quic_upstream_addr(pkt->local_addr); + if (faddr == nullptr) { + LOG(ERROR) << "No suitable upstream address found"; + + return 0; + } + + auto quic_conn_handler = single_worker_->get_quic_connection_handler(); + + // Ignore return value + quic_conn_handler->handle_packet(faddr, pkt->remote_addr, pkt->local_addr, + pkt->pi, pkt->data.data(), + pkt->data.size()); + + return 0; + } + + auto &qkm = quic_keying_materials_->keying_materials.front(); + + std::array decrypted_dcid; + + if (decrypt_quic_connection_id(decrypted_dcid.data(), + vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET, + qkm.cid_encryption_key.data()) != 0) { + return -1; + } + + for (auto &worker : workers_) { + if (!std::equal(std::begin(decrypted_dcid), + std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN, + worker->get_cid_prefix())) { + continue; + } + + WorkerEvent wev{ + .type = WorkerEventType::QUIC_PKT_FORWARD, + .quic_pkt = std::move(pkt), + }; + worker->send(std::move(wev)); + + return 0; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "No worker to match CID prefix"; + } + + return 0; +} +#endif // ENABLE_HTTP3 + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_connection_handler.h b/lib/nghttp2/src/shrpx_connection_handler.h new file mode 100644 index 00000000000..f3748ab678e --- /dev/null +++ b/lib/nghttp2/src/shrpx_connection_handler.h @@ -0,0 +1,322 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_CONNECTION_HANDLER_H +#define SHRPX_CONNECTION_HANDLER_H + +#include "shrpx.h" + +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H + +#include +#include +#include +#include +#ifndef NOTHREADS +# include +#endif // NOTHREADS + +#ifdef HAVE_LIBBPF +# include +#endif // HAVE_LIBBPF + +#include + +#include + +#ifdef HAVE_NEVERBLEED +# include +#endif // HAVE_NEVERBLEED + +#include "shrpx_downstream_connection_pool.h" +#include "shrpx_config.h" +#include "shrpx_exec.h" + +namespace shrpx { + +class Http2Session; +class ConnectBlocker; +class AcceptHandler; +class Worker; +struct WorkerStat; +struct TicketKeys; +class MemcachedDispatcher; +struct UpstreamAddr; + +namespace tls { + +class CertLookupTree; + +} // namespace tls + +struct OCSPUpdateContext { + // ocsp response buffer + std::vector resp; + // Process running fetch-ocsp-response script + Process proc; + // index to ConnectionHandler::all_ssl_ctx_, which points to next + // SSL_CTX to update ocsp response cache. + size_t next; + ev_child chldev; + ev_io rev; + // errno encountered while processing response + int error; +}; + +// SerialEvent is an event sent from Worker thread. +enum class SerialEventType { + NONE, + REPLACE_DOWNSTREAM, +}; + +struct SerialEvent { + // ctor for event uses DownstreamConfig + SerialEvent(SerialEventType type, + const std::shared_ptr &downstreamconf) + : type(type), downstreamconf(downstreamconf) {} + + SerialEventType type; + std::shared_ptr downstreamconf; +}; + +#ifdef ENABLE_HTTP3 +# ifdef HAVE_LIBBPF +struct BPFRef { + bpf_object *obj; + bpf_map *reuseport_array; + bpf_map *cid_prefix_map; +}; +# endif // HAVE_LIBBPF + +// QUIC IPC message type. +enum class QUICIPCType { + NONE, + // Send forwarded QUIC UDP datagram and its metadata. + DGRAM_FORWARD, +}; + +// WorkerProcesses which are in graceful shutdown period. +struct QUICLingeringWorkerProcess { + QUICLingeringWorkerProcess( + std::vector> cid_prefixes, + int quic_ipc_fd) + : cid_prefixes{std::move(cid_prefixes)}, quic_ipc_fd{quic_ipc_fd} {} + + std::vector> cid_prefixes; + // Socket to send QUIC IPC message to this worker process. + int quic_ipc_fd; +}; +#endif // ENABLE_HTTP3 + +class ConnectionHandler { +public: + ConnectionHandler(struct ev_loop *loop, std::mt19937 &gen); + ~ConnectionHandler(); + int handle_connection(int fd, sockaddr *addr, int addrlen, + const UpstreamAddr *faddr); + // Creates Worker object for single threaded configuration. + int create_single_worker(); + // Creates |num| Worker objects for multi threaded configuration. + // The |num| must be strictly more than 1. + int create_worker_thread(size_t num); + void + set_ticket_keys_to_worker(const std::shared_ptr &ticket_keys); + void worker_reopen_log_files(); + void set_ticket_keys(std::shared_ptr ticket_keys); + const std::shared_ptr &get_ticket_keys() const; + struct ev_loop *get_loop() const; + Worker *get_single_worker() const; + void add_acceptor(std::unique_ptr h); + void delete_acceptor(); + void enable_acceptor(); + void disable_acceptor(); + void sleep_acceptor(ev_tstamp t); + void accept_pending_connection(); + void graceful_shutdown_worker(); + void set_graceful_shutdown(bool f); + bool get_graceful_shutdown() const; + void join_worker(); + + // Cancels ocsp update process + void cancel_ocsp_update(); + // Starts ocsp update for certificate |cert_file|. + int start_ocsp_update(const char *cert_file); + // Reads incoming data from ocsp update process + void read_ocsp_chunk(); + // Handles the completion of one ocsp update + void handle_ocsp_complete(); + // Resets ocsp_; + void reset_ocsp(); + // Proceeds to the next certificate's ocsp update. If all + // certificates' ocsp update has been done, schedule next ocsp + // update. + void proceed_next_cert_ocsp(); + + void set_tls_ticket_key_memcached_dispatcher( + std::unique_ptr dispatcher); + + MemcachedDispatcher *get_tls_ticket_key_memcached_dispatcher() const; + void on_tls_ticket_key_network_error(ev_timer *w); + void on_tls_ticket_key_not_found(ev_timer *w); + void + on_tls_ticket_key_get_success(const std::shared_ptr &ticket_keys, + ev_timer *w); + void schedule_next_tls_ticket_key_memcached_get(ev_timer *w); + SSL_CTX *create_tls_ticket_key_memcached_ssl_ctx(); + // Returns the SSL_CTX at all_ssl_ctx_[idx]. This does not perform + // array bound checking. + SSL_CTX *get_ssl_ctx(size_t idx) const; + + const std::vector &get_indexed_ssl_ctx(size_t idx) const; +#ifdef ENABLE_HTTP3 + const std::vector &get_quic_indexed_ssl_ctx(size_t idx) const; + + int forward_quic_packet(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *cid_prefix, const uint8_t *data, + size_t datalen); + + void set_quic_keying_materials(std::shared_ptr qkms); + const std::shared_ptr &get_quic_keying_materials() const; + + void set_cid_prefixes( + const std::vector> + &cid_prefixes); + + void set_quic_lingering_worker_processes( + const std::vector &quic_lwps); + + // Return matching QUICLingeringWorkerProcess which has a CID prefix + // such that |dcid| starts with it. If no such + // QUICLingeringWorkerProcess, it returns nullptr. + QUICLingeringWorkerProcess * + match_quic_lingering_worker_process_cid_prefix(const uint8_t *dcid, + size_t dcidlen); + + int forward_quic_packet_to_lingering_worker_process( + QUICLingeringWorkerProcess *quic_lwp, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, const uint8_t *data, + size_t datalen); + + void set_quic_ipc_fd(int fd); + + int quic_ipc_read(); + +# ifdef HAVE_LIBBPF + std::vector &get_quic_bpf_refs(); + void unload_bpf_objects(); +# endif // HAVE_LIBBPF +#endif // ENABLE_HTTP3 + +#ifdef HAVE_NEVERBLEED + void set_neverbleed(neverbleed_t *nb); +#endif // HAVE_NEVERBLEED + + // Send SerialEvent SerialEventType::REPLACE_DOWNSTREAM to this + // object. + void send_replace_downstream( + const std::shared_ptr &downstreamconf); + // Internal function to send |ev| to this object. + void send_serial_event(SerialEvent ev); + // Handles SerialEvents received. + void handle_serial_event(); + // Sends WorkerEvent to make them replace downstream. + void + worker_replace_downstream(std::shared_ptr downstreamconf); + + void set_enable_acceptor_on_ocsp_completion(bool f); + +private: + // Stores all SSL_CTX objects. + std::vector all_ssl_ctx_; + // Stores all SSL_CTX objects in a way that its index is stored in + // cert_tree. The SSL_CTXs stored in the same index share the same + // hostname, but could have different signature algorithm. The + // selection among them are performed by hostname presented by SNI, + // and signature algorithm presented by client. + std::vector> indexed_ssl_ctx_; +#ifdef ENABLE_HTTP3 + std::vector> cid_prefixes_; + std::vector> + lingering_cid_prefixes_; + int quic_ipc_fd_; + std::vector quic_lingering_worker_processes_; +# ifdef HAVE_LIBBPF + std::vector quic_bpf_refs_; +# endif // HAVE_LIBBPF + std::shared_ptr quic_keying_materials_; + std::vector quic_all_ssl_ctx_; + std::vector> quic_indexed_ssl_ctx_; +#endif // ENABLE_HTTP3 + OCSPUpdateContext ocsp_; + std::mt19937 &gen_; + // ev_loop for each worker + std::vector worker_loops_; + // Worker instances when multi threaded mode (-nN, N >= 2) is used. + // If at least one frontend enables API request, we allocate 1 + // additional worker dedicated to API request . + std::vector> workers_; + // mutex for serial event resive buffer handling + std::mutex serial_event_mu_; + // SerialEvent receive buffer + std::vector serial_events_; + // Worker instance used when single threaded mode (-n1) is used. + // Otherwise, nullptr and workers_ has instances of Worker instead. + std::unique_ptr single_worker_; + std::unique_ptr cert_tree_; +#ifdef ENABLE_HTTP3 + std::unique_ptr quic_cert_tree_; +#endif // ENABLE_HTTP3 + std::unique_ptr tls_ticket_key_memcached_dispatcher_; + // Current TLS session ticket keys. Note that TLS connection does + // not refer to this field directly. They use TicketKeys object in + // Worker object. + std::shared_ptr ticket_keys_; + struct ev_loop *loop_; + std::vector> acceptors_; +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb_; +#endif // HAVE_NEVERBLEED + ev_timer disable_acceptor_timer_; + ev_timer ocsp_timer_; + ev_async thread_join_asyncev_; + ev_async serial_event_asyncev_; +#ifndef NOTHREADS + std::future thread_join_fut_; +#endif // NOTHREADS + size_t tls_ticket_key_memcached_get_retry_count_; + size_t tls_ticket_key_memcached_fail_count_; + unsigned int worker_round_robin_cnt_; + bool graceful_shutdown_; + // true if acceptors should be enabled after the initial ocsp update + // has finished. + bool enable_acceptor_on_ocsp_completion_; +}; + +} // namespace shrpx + +#endif // SHRPX_CONNECTION_HANDLER_H diff --git a/lib/nghttp2/src/shrpx_dns_resolver.cc b/lib/nghttp2/src/shrpx_dns_resolver.cc new file mode 100644 index 00000000000..f83ecb786a9 --- /dev/null +++ b/lib/nghttp2/src/shrpx_dns_resolver.cc @@ -0,0 +1,353 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_dns_resolver.h" + +#include +#include + +#include "shrpx_log.h" +#include "shrpx_connection.h" +#include "shrpx_config.h" + +namespace shrpx { + +namespace { +void sock_state_cb(void *data, int s, int read, int write) { + auto resolv = static_cast(data); + + if (resolv->get_status(nullptr) != DNSResolverStatus::RUNNING) { + return; + } + + if (read) { + resolv->start_rev(s); + } else { + resolv->stop_rev(s); + } + if (write) { + resolv->start_wev(s); + } else { + resolv->stop_wev(s); + } +} +} // namespace + +namespace { +void host_cb(void *arg, int status, int timeouts, hostent *hostent) { + auto resolv = static_cast(arg); + resolv->on_result(status, hostent); +} +} // namespace + +namespace { +void process_result(DNSResolver *resolv) { + auto cb = resolv->get_complete_cb(); + if (!cb) { + return; + } + Address result; + auto status = resolv->get_status(&result); + switch (status) { + case DNSResolverStatus::OK: + case DNSResolverStatus::ERROR: + cb(status, &result); + break; + default: + break; + } + // resolv may be deleted here. +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto resolv = static_cast(w->data); + resolv->on_read(w->fd); + process_result(resolv); +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto resolv = static_cast(w->data); + resolv->on_write(w->fd); + process_result(resolv); +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto resolv = static_cast(w->data); + resolv->on_timeout(); + process_result(resolv); +} +} // namespace + +namespace { +void stop_ev(struct ev_loop *loop, + const std::vector> &evs) { + for (auto &w : evs) { + ev_io_stop(loop, w.get()); + } +} +} // namespace + +DNSResolver::DNSResolver(struct ev_loop *loop) + : result_{}, + loop_(loop), + channel_(nullptr), + family_(AF_UNSPEC), + status_(DNSResolverStatus::IDLE) { + ev_timer_init(&timer_, timeoutcb, 0., 0.); + timer_.data = this; +} + +DNSResolver::~DNSResolver() { + if (channel_) { + ares_destroy(channel_); + } + + stop_ev(loop_, revs_); + stop_ev(loop_, wevs_); + + ev_timer_stop(loop_, &timer_); +} + +int DNSResolver::resolve(const StringRef &name, int family) { + if (status_ != DNSResolverStatus::IDLE) { + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Start resolving host " << name << " in IPv" + << (family == AF_INET ? "4" : "6"); + } + + name_ = name; + family_ = family; + + int rv; + + auto &dnsconf = get_config()->dns; + + ares_options opts{}; + opts.sock_state_cb = sock_state_cb; + opts.sock_state_cb_data = this; + opts.timeout = static_cast(dnsconf.timeout.lookup * 1000); + opts.tries = dnsconf.max_try; + + auto optmask = ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES; + + ares_channel chan; + rv = ares_init_options(&chan, &opts, optmask); + if (rv != ARES_SUCCESS) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "ares_init_options failed: " << ares_strerror(rv); + } + status_ = DNSResolverStatus::ERROR; + return -1; + } + + channel_ = chan; + status_ = DNSResolverStatus::RUNNING; + + ares_gethostbyname(channel_, name_.c_str(), family_, host_cb, this); + reset_timeout(); + + return 0; +} + +int DNSResolver::on_read(int fd) { return handle_event(fd, ARES_SOCKET_BAD); } + +int DNSResolver::on_write(int fd) { return handle_event(ARES_SOCKET_BAD, fd); } + +int DNSResolver::on_timeout() { + return handle_event(ARES_SOCKET_BAD, ARES_SOCKET_BAD); +} + +int DNSResolver::handle_event(int rfd, int wfd) { + if (status_ == DNSResolverStatus::IDLE) { + return -1; + } + + ares_process_fd(channel_, rfd, wfd); + + switch (status_) { + case DNSResolverStatus::RUNNING: + reset_timeout(); + return 0; + case DNSResolverStatus::OK: + return 0; + case DNSResolverStatus::ERROR: + return -1; + default: + // Unreachable + assert(0); + abort(); + } +} + +void DNSResolver::reset_timeout() { + if (status_ != DNSResolverStatus::RUNNING) { + return; + } + timeval tvout; + auto tv = ares_timeout(channel_, nullptr, &tvout); + if (tv == nullptr) { + return; + } + // To avoid that timer_.repeat becomes 0, which makes ev_timer_again + // useless, add tiny fraction of time. + timer_.repeat = tv->tv_sec + tv->tv_usec / 1000000. + 1e-9; + ev_timer_again(loop_, &timer_); +} + +DNSResolverStatus DNSResolver::get_status(Address *result) const { + if (status_ != DNSResolverStatus::OK) { + return status_; + } + + if (result) { + memcpy(result, &result_, sizeof(result_)); + } + + return status_; +} + +namespace { +void start_ev(std::vector> &evs, struct ev_loop *loop, + int fd, int event, IOCb cb, void *data) { + for (auto &w : evs) { + if (w->fd == fd) { + return; + } + } + for (auto &w : evs) { + if (w->fd == -1) { + ev_io_set(w.get(), fd, event); + ev_io_start(loop, w.get()); + return; + } + } + + auto w = std::make_unique(); + ev_io_init(w.get(), cb, fd, event); + w->data = data; + ev_io_start(loop, w.get()); + evs.emplace_back(std::move(w)); +} +} // namespace + +namespace { +void stop_ev(std::vector> &evs, struct ev_loop *loop, + int fd, int event) { + for (auto &w : evs) { + if (w->fd == fd) { + ev_io_stop(loop, w.get()); + ev_io_set(w.get(), -1, event); + return; + } + } +} +} // namespace + +void DNSResolver::start_rev(int fd) { + start_ev(revs_, loop_, fd, EV_READ, readcb, this); +} + +void DNSResolver::stop_rev(int fd) { stop_ev(revs_, loop_, fd, EV_READ); } + +void DNSResolver::start_wev(int fd) { + start_ev(wevs_, loop_, fd, EV_WRITE, writecb, this); +} + +void DNSResolver::stop_wev(int fd) { stop_ev(wevs_, loop_, fd, EV_WRITE); } + +void DNSResolver::on_result(int status, hostent *hostent) { + stop_ev(loop_, revs_); + stop_ev(loop_, wevs_); + ev_timer_stop(loop_, &timer_); + + if (status != ARES_SUCCESS) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup for " << name_ + << " failed: " << ares_strerror(status); + } + status_ = DNSResolverStatus::ERROR; + return; + } + + auto ap = *hostent->h_addr_list; + if (!ap) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup for " << name_ << "failed: no address returned"; + } + status_ = DNSResolverStatus::ERROR; + return; + } + + switch (hostent->h_addrtype) { + case AF_INET: + status_ = DNSResolverStatus::OK; + result_.len = sizeof(result_.su.in); + result_.su.in = {}; + result_.su.in.sin_family = AF_INET; +#ifdef HAVE_SOCKADDR_IN_SIN_LEN + result_.su.in.sin_len = sizeof(result_.su.in); +#endif // HAVE_SOCKADDR_IN_SIN_LEN + memcpy(&result_.su.in.sin_addr, ap, sizeof(result_.su.in.sin_addr)); + break; + case AF_INET6: + status_ = DNSResolverStatus::OK; + result_.len = sizeof(result_.su.in6); + result_.su.in6 = {}; + result_.su.in6.sin6_family = AF_INET6; +#ifdef HAVE_SOCKADDR_IN6_SIN6_LEN + result_.su.in6.sin6_len = sizeof(result_.su.in6); +#endif // HAVE_SOCKADDR_IN6_SIN6_LEN + memcpy(&result_.su.in6.sin6_addr, ap, sizeof(result_.su.in6.sin6_addr)); + break; + default: + assert(0); + } + + if (status_ == DNSResolverStatus::OK) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup succeeded: " << name_ << " -> " + << util::numeric_name(&result_.su.sa, result_.len); + } + return; + } + + status_ = DNSResolverStatus::ERROR; +} + +void DNSResolver::set_complete_cb(CompleteCb cb) { + completeCb_ = std::move(cb); +} + +CompleteCb DNSResolver::get_complete_cb() const { return completeCb_; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_dns_resolver.h b/lib/nghttp2/src/shrpx_dns_resolver.h new file mode 100644 index 00000000000..e622f99625a --- /dev/null +++ b/lib/nghttp2/src/shrpx_dns_resolver.h @@ -0,0 +1,118 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_DNS_RESOLVER_H +#define SHRPX_DNS_RESOLVER_H + +#include "shrpx.h" + +#include +#include + +#include + +#include +#include + +#include "template.h" +#include "network.h" + +using namespace nghttp2; + +namespace shrpx { + +enum class DNSResolverStatus { + // Resolver is in initial status + IDLE, + // Resolver is currently resolving host name + RUNNING, + // Resolver successfully resolved host name + OK, + // Resolver failed to resolve host name + ERROR, +}; + +// Callback function called when host name lookup is finished. +// |status| is either DNSResolverStatus::OK, or +// DNSResolverStatus::ERROR. If |status| is DNSResolverStatus::OK, +// |result| points to the resolved address. Note that port portion of +// |result| is undefined, and must be initialized by application. +// This callback function is not called if name lookup finishes in +// DNSResolver::resolve() completely. In this case, application +// should call DNSResolver::get_status() to get current status and +// result. In other words, callback is called if get_status() returns +// DNSResolverStatus::RUNNING. +using CompleteCb = + std::function; + +// DNSResolver is asynchronous name resolver, backed by c-ares +// library. +class DNSResolver { +public: + DNSResolver(struct ev_loop *loop); + ~DNSResolver(); + + // Starts resolving hostname |name|. + int resolve(const StringRef &name, int family); + // Returns status. If status_ is DNSResolverStatus::SUCCESS && + // |result| is not nullptr, |*result| is filled. + DNSResolverStatus get_status(Address *result) const; + // Sets callback function when name lookup finishes. The callback + // function is called in a way that it can destroy this DNSResolver. + void set_complete_cb(CompleteCb cb); + CompleteCb get_complete_cb() const; + + // Calls these functions when read/write event occurred respectively. + int on_read(int fd); + int on_write(int fd); + int on_timeout(); + // Calls this function when DNS query finished. + void on_result(int status, hostent *hostent); + void reset_timeout(); + + void start_rev(int fd); + void stop_rev(int fd); + void start_wev(int fd); + void stop_wev(int fd); + +private: + int handle_event(int rfd, int wfd); + + std::vector> revs_, wevs_; + Address result_; + CompleteCb completeCb_; + ev_timer timer_; + StringRef name_; + struct ev_loop *loop_; + // ares_channel is pointer type + ares_channel channel_; + // AF_INET or AF_INET6. AF_INET for A record lookup, and AF_INET6 + // for AAAA record lookup. + int family_; + DNSResolverStatus status_; +}; + +} // namespace shrpx + +#endif // SHRPX_DNS_RESOLVER_H diff --git a/lib/nghttp2/src/shrpx_dns_tracker.cc b/lib/nghttp2/src/shrpx_dns_tracker.cc new file mode 100644 index 00000000000..57387ce72b2 --- /dev/null +++ b/lib/nghttp2/src/shrpx_dns_tracker.cc @@ -0,0 +1,328 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_dns_tracker.h" +#include "shrpx_config.h" +#include "shrpx_log.h" +#include "util.h" + +namespace shrpx { + +namespace { +void gccb(struct ev_loop *loop, ev_timer *w, int revents) { + auto dns_tracker = static_cast(w->data); + dns_tracker->gc(); +} +} // namespace + +DNSTracker::DNSTracker(struct ev_loop *loop, int family) + : loop_(loop), family_(family) { + ev_timer_init(&gc_timer_, gccb, 0., 12_h); + gc_timer_.data = this; +} + +DNSTracker::~DNSTracker() { + ev_timer_stop(loop_, &gc_timer_); + + for (auto &p : ents_) { + auto &qlist = p.second.qlist; + while (!qlist.empty()) { + auto head = qlist.head; + qlist.remove(head); + head->status = DNSResolverStatus::ERROR; + head->in_qlist = false; + // TODO Not sure we should call callback here, or it is even be + // safe to do that. + } + } +} + +ResolverEntry DNSTracker::make_entry(std::unique_ptr resolv, + ImmutableString host, + DNSResolverStatus status, + const Address *result) { + auto &dnsconf = get_config()->dns; + + auto ent = ResolverEntry{}; + ent.resolv = std::move(resolv); + ent.host = std::move(host); + ent.status = status; + switch (status) { + case DNSResolverStatus::ERROR: + case DNSResolverStatus::OK: + ent.expiry = std::chrono::steady_clock::now() + + util::duration_from(dnsconf.timeout.cache); + break; + default: + break; + } + if (result) { + ent.result = *result; + } + return ent; +} + +void DNSTracker::update_entry(ResolverEntry &ent, + std::unique_ptr resolv, + DNSResolverStatus status, const Address *result) { + auto &dnsconf = get_config()->dns; + + ent.resolv = std::move(resolv); + ent.status = status; + switch (status) { + case DNSResolverStatus::ERROR: + case DNSResolverStatus::OK: + ent.expiry = std::chrono::steady_clock::now() + + util::duration_from(dnsconf.timeout.cache); + break; + default: + break; + } + if (result) { + ent.result = *result; + } +} + +DNSResolverStatus DNSTracker::resolve(Address *result, DNSQuery *dnsq) { + int rv; + + auto it = ents_.find(dnsq->host); + + if (it == std::end(ents_)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "DNS entry not found for " << dnsq->host; + } + + auto resolv = std::make_unique(loop_, family_); + auto host_copy = + ImmutableString{std::begin(dnsq->host), std::end(dnsq->host)}; + auto host = StringRef{host_copy}; + + rv = resolv->resolve(host); + if (rv != 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup failed for " << host; + } + + ents_.emplace(host, make_entry(nullptr, std::move(host_copy), + DNSResolverStatus::ERROR, nullptr)); + + start_gc_timer(); + + return DNSResolverStatus::ERROR; + } + + switch (resolv->get_status(result)) { + case DNSResolverStatus::ERROR: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup failed for " << host; + } + + ents_.emplace(host, make_entry(nullptr, std::move(host_copy), + DNSResolverStatus::ERROR, nullptr)); + + start_gc_timer(); + + return DNSResolverStatus::ERROR; + case DNSResolverStatus::OK: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup succeeded: " << host << " -> " + << util::numeric_name(&result->su.sa, result->len); + } + + ents_.emplace(host, make_entry(nullptr, std::move(host_copy), + DNSResolverStatus::OK, result)); + + start_gc_timer(); + + return DNSResolverStatus::OK; + case DNSResolverStatus::RUNNING: { + auto p = ents_.emplace(host, + make_entry(std::move(resolv), std::move(host_copy), + DNSResolverStatus::RUNNING, nullptr)); + + start_gc_timer(); + + auto &ent = (*p.first).second; + + add_to_qlist(ent, dnsq); + + return DNSResolverStatus::RUNNING; + } + default: + assert(0); + } + } + + auto &ent = (*it).second; + + if (ent.status != DNSResolverStatus::RUNNING && + ent.expiry < std::chrono::steady_clock::now()) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "DNS entry found for " << dnsq->host + << ", but it has been expired"; + } + + auto resolv = std::make_unique(loop_, family_); + auto host = StringRef{ent.host}; + + rv = resolv->resolve(host); + if (rv != 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup failed for " << host; + } + + update_entry(ent, nullptr, DNSResolverStatus::ERROR, nullptr); + + return DNSResolverStatus::ERROR; + } + + switch (resolv->get_status(result)) { + case DNSResolverStatus::ERROR: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup failed for " << host; + } + + update_entry(ent, nullptr, DNSResolverStatus::ERROR, nullptr); + + return DNSResolverStatus::ERROR; + case DNSResolverStatus::OK: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup succeeded: " << host << " -> " + << util::numeric_name(&result->su.sa, result->len); + } + + update_entry(ent, nullptr, DNSResolverStatus::OK, result); + + return DNSResolverStatus::OK; + case DNSResolverStatus::RUNNING: + update_entry(ent, std::move(resolv), DNSResolverStatus::RUNNING, nullptr); + add_to_qlist(ent, dnsq); + + return DNSResolverStatus::RUNNING; + default: + assert(0); + } + } + + switch (ent.status) { + case DNSResolverStatus::RUNNING: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Waiting for name lookup complete for " << dnsq->host; + } + ent.qlist.append(dnsq); + dnsq->in_qlist = true; + return DNSResolverStatus::RUNNING; + case DNSResolverStatus::ERROR: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup failed for " << dnsq->host << " (cached)"; + } + return DNSResolverStatus::ERROR; + case DNSResolverStatus::OK: + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Name lookup succeeded (cached): " << dnsq->host << " -> " + << util::numeric_name(&ent.result.su.sa, ent.result.len); + } + if (result) { + memcpy(result, &ent.result, sizeof(*result)); + } + return DNSResolverStatus::OK; + default: + assert(0); + abort(); + } +} + +void DNSTracker::add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq) { + ent.resolv->set_complete_cb( + [&ent](DNSResolverStatus status, const Address *result) { + auto &qlist = ent.qlist; + while (!qlist.empty()) { + auto head = qlist.head; + qlist.remove(head); + head->status = status; + head->in_qlist = false; + auto cb = head->cb; + cb(status, result); + } + + auto &dnsconf = get_config()->dns; + + ent.resolv.reset(); + ent.status = status; + ent.expiry = std::chrono::steady_clock::now() + + util::duration_from(dnsconf.timeout.cache); + if (ent.status == DNSResolverStatus::OK) { + ent.result = *result; + } + }); + ent.qlist.append(dnsq); + dnsq->in_qlist = true; +} + +void DNSTracker::cancel(DNSQuery *dnsq) { + if (!dnsq->in_qlist) { + return; + } + + auto it = ents_.find(dnsq->host); + if (it == std::end(ents_)) { + return; + } + + auto &ent = (*it).second; + ent.qlist.remove(dnsq); + dnsq->in_qlist = false; +} + +void DNSTracker::start_gc_timer() { + if (ev_is_active(&gc_timer_)) { + return; + } + + ev_timer_again(loop_, &gc_timer_); +} + +void DNSTracker::gc() { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Starting removing expired DNS cache entries"; + } + + auto now = std::chrono::steady_clock::now(); + for (auto it = std::begin(ents_); it != std::end(ents_);) { + auto &ent = (*it).second; + if (ent.expiry >= now) { + ++it; + continue; + } + + it = ents_.erase(it); + } + + if (ents_.empty()) { + ev_timer_stop(loop_, &gc_timer_); + } +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_dns_tracker.h b/lib/nghttp2/src/shrpx_dns_tracker.h new file mode 100644 index 00000000000..c7caac0c2e1 --- /dev/null +++ b/lib/nghttp2/src/shrpx_dns_tracker.h @@ -0,0 +1,121 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_DNS_TRACKER_H +#define SHRPX_DNS_TRACKER_H + +#include "shrpx.h" + +#include +#include + +#include "shrpx_dual_dns_resolver.h" + +using namespace nghttp2; + +namespace shrpx { + +struct DNSQuery { + DNSQuery(StringRef host, CompleteCb cb) + : host(std::move(host)), + cb(std::move(cb)), + dlnext(nullptr), + dlprev(nullptr), + status(DNSResolverStatus::IDLE), + in_qlist(false) {} + + // Host name we lookup for. + StringRef host; + // Callback function called when name lookup finished. This + // callback is not called if name lookup finishes within + // DNSTracker::resolve(). + CompleteCb cb; + DNSQuery *dlnext, *dlprev; + DNSResolverStatus status; + // true if this object is in linked list ResolverEntry::qlist. + bool in_qlist; +}; + +struct ResolverEntry { + // Host name this entry lookups for. + ImmutableString host; + // DNS resolver. Only non-nullptr if status is + // DNSResolverStatus::RUNNING. + std::unique_ptr resolv; + // DNSQuery interested in this name lookup result. The result is + // notified to them all. + DList qlist; + // Use the same enum with DNSResolverStatus + DNSResolverStatus status; + // result and its expiry time + Address result; + // time point when cached result expires. + std::chrono::steady_clock::time_point expiry; +}; + +class DNSTracker { +public: + DNSTracker(struct ev_loop *loop, int family); + ~DNSTracker(); + + // Lookups host name described in |dnsq|. If name lookup finishes + // within this function (either it came from /etc/hosts, host name + // is numeric, lookup result is cached, etc), it returns + // DNSResolverStatus::OK or DNSResolverStatus::ERROR. If lookup is + // successful, DNSResolverStatus::OK is returned, and |result| is + // filled. If lookup failed, DNSResolverStatus::ERROR is returned. + // If name lookup is being done background, it returns + // DNSResolverStatus::RUNNING. Its completion is notified by + // calling dnsq->cb. + DNSResolverStatus resolve(Address *result, DNSQuery *dnsq); + // Cancels name lookup requested by |dnsq|. + void cancel(DNSQuery *dnsq); + // Removes expired entries from ents_. + void gc(); + // Starts GC timer. + void start_gc_timer(); + +private: + ResolverEntry make_entry(std::unique_ptr resolv, + ImmutableString host, DNSResolverStatus status, + const Address *result); + + void update_entry(ResolverEntry &ent, std::unique_ptr resolv, + DNSResolverStatus status, const Address *result); + + void add_to_qlist(ResolverEntry &ent, DNSQuery *dnsq); + + std::map ents_; + // Periodically iterates ents_, and removes expired entries to avoid + // excessive use of memory. Since only backend API can potentially + // increase memory consumption, interval could be very long. + ev_timer gc_timer_; + struct ev_loop *loop_; + // IP version preference. + int family_; +}; + +} // namespace shrpx + +#endif // SHRPX_DNS_TRACKER_H diff --git a/lib/nghttp2/src/shrpx_downstream.cc b/lib/nghttp2/src/shrpx_downstream.cc new file mode 100644 index 00000000000..9ea52b4ac05 --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream.cc @@ -0,0 +1,1189 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_downstream.h" + +#include + +#include "url-parser/url_parser.h" + +#include "shrpx_upstream.h" +#include "shrpx_client_handler.h" +#include "shrpx_config.h" +#include "shrpx_error.h" +#include "shrpx_downstream_connection.h" +#include "shrpx_downstream_queue.h" +#include "shrpx_worker.h" +#include "shrpx_http2_session.h" +#include "shrpx_log.h" +#ifdef HAVE_MRUBY +# include "shrpx_mruby.h" +#endif // HAVE_MRUBY +#include "util.h" +#include "http2.h" + +namespace shrpx { + +namespace { +void upstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto downstream = static_cast(w->data); + auto upstream = downstream->get_upstream(); + + auto which = revents == EV_READ ? "read" : "write"; + + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "upstream timeout stream_id=" + << downstream->get_stream_id() << " event=" << which; + } + + downstream->disable_upstream_rtimer(); + downstream->disable_upstream_wtimer(); + + upstream->on_timeout(downstream); +} +} // namespace + +namespace { +void upstream_rtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + upstream_timeoutcb(loop, w, EV_READ); +} +} // namespace + +namespace { +void upstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + upstream_timeoutcb(loop, w, EV_WRITE); +} +} // namespace + +namespace { +void downstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto downstream = static_cast(w->data); + + auto which = revents == EV_READ ? "read" : "write"; + + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "downstream timeout stream_id=" + << downstream->get_downstream_stream_id() + << " event=" << which; + } + + downstream->disable_downstream_rtimer(); + downstream->disable_downstream_wtimer(); + + auto dconn = downstream->get_downstream_connection(); + + if (dconn) { + dconn->on_timeout(); + } +} +} // namespace + +namespace { +void downstream_rtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + downstream_timeoutcb(loop, w, EV_READ); +} +} // namespace + +namespace { +void downstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + downstream_timeoutcb(loop, w, EV_WRITE); +} +} // namespace + +// upstream could be nullptr for unittests +Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool, + int64_t stream_id) + : dlnext(nullptr), + dlprev(nullptr), + response_sent_body_length(0), + balloc_(1024, 1024), + req_(balloc_), + resp_(balloc_), + request_start_time_(std::chrono::high_resolution_clock::now()), + blocked_request_buf_(mcpool), + request_buf_(mcpool), + response_buf_(mcpool), + upstream_(upstream), + blocked_link_(nullptr), + addr_(nullptr), + num_retry_(0), + stream_id_(stream_id), + assoc_stream_id_(-1), + downstream_stream_id_(-1), + response_rst_stream_error_code_(NGHTTP2_NO_ERROR), + affinity_cookie_(0), + request_state_(DownstreamState::INITIAL), + response_state_(DownstreamState::INITIAL), + dispatch_state_(DispatchState::NONE), + upgraded_(false), + chunked_request_(false), + chunked_response_(false), + expect_final_response_(false), + request_pending_(false), + request_header_sent_(false), + accesslog_written_(false), + new_affinity_cookie_(false), + blocked_request_data_eof_(false), + expect_100_continue_(false), + stop_reading_(false) { + + auto &timeoutconf = get_config()->http2.timeout; + + ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0., + timeoutconf.stream_read); + ev_timer_init(&upstream_wtimer_, &upstream_wtimeoutcb, 0., + timeoutconf.stream_write); + ev_timer_init(&downstream_rtimer_, &downstream_rtimeoutcb, 0., + timeoutconf.stream_read); + ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0., + timeoutconf.stream_write); + + upstream_rtimer_.data = this; + upstream_wtimer_.data = this; + downstream_rtimer_.data = this; + downstream_wtimer_.data = this; + + rcbufs_.reserve(32); +#ifdef ENABLE_HTTP3 + rcbufs3_.reserve(32); +#endif // ENABLE_HTTP3 +} + +Downstream::~Downstream() { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, this) << "Deleting"; + } + + // check nullptr for unittest + if (upstream_) { + auto loop = upstream_->get_client_handler()->get_loop(); + + ev_timer_stop(loop, &upstream_rtimer_); + ev_timer_stop(loop, &upstream_wtimer_); + ev_timer_stop(loop, &downstream_rtimer_); + ev_timer_stop(loop, &downstream_wtimer_); + +#ifdef HAVE_MRUBY + auto handler = upstream_->get_client_handler(); + auto worker = handler->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + mruby_ctx->delete_downstream(this); +#endif // HAVE_MRUBY + } + +#ifdef HAVE_MRUBY + if (dconn_) { + const auto &group = dconn_->get_downstream_addr_group(); + if (group) { + const auto &mruby_ctx = group->shared_addr->mruby_ctx; + mruby_ctx->delete_downstream(this); + } + } +#endif // HAVE_MRUBY + + // DownstreamConnection may refer to this object. Delete it now + // explicitly. + dconn_.reset(); + +#ifdef ENABLE_HTTP3 + for (auto rcbuf : rcbufs3_) { + nghttp3_rcbuf_decref(rcbuf); + } +#endif // ENABLE_HTTP3 + + for (auto rcbuf : rcbufs_) { + nghttp2_rcbuf_decref(rcbuf); + } + + if (LOG_ENABLED(INFO)) { + DLOG(INFO, this) << "Deleted"; + } +} + +int Downstream::attach_downstream_connection( + std::unique_ptr dconn) { + if (dconn->attach_downstream(this) != 0) { + return -1; + } + + dconn_ = std::move(dconn); + + return 0; +} + +void Downstream::detach_downstream_connection() { + if (!dconn_) { + return; + } + +#ifdef HAVE_MRUBY + const auto &group = dconn_->get_downstream_addr_group(); + if (group) { + const auto &mruby_ctx = group->shared_addr->mruby_ctx; + mruby_ctx->delete_downstream(this); + } +#endif // HAVE_MRUBY + + dconn_->detach_downstream(this); + + auto handler = dconn_->get_client_handler(); + + handler->pool_downstream_connection( + std::unique_ptr(dconn_.release())); +} + +DownstreamConnection *Downstream::get_downstream_connection() { + return dconn_.get(); +} + +std::unique_ptr Downstream::pop_downstream_connection() { +#ifdef HAVE_MRUBY + if (!dconn_) { + return nullptr; + } + + const auto &group = dconn_->get_downstream_addr_group(); + if (group) { + const auto &mruby_ctx = group->shared_addr->mruby_ctx; + mruby_ctx->delete_downstream(this); + } +#endif // HAVE_MRUBY + + return std::unique_ptr(dconn_.release()); +} + +void Downstream::pause_read(IOCtrlReason reason) { + if (dconn_) { + dconn_->pause_read(reason); + } +} + +int Downstream::resume_read(IOCtrlReason reason, size_t consumed) { + if (dconn_) { + return dconn_->resume_read(reason, consumed); + } + + return 0; +} + +void Downstream::force_resume_read() { + if (dconn_) { + dconn_->force_resume_read(); + } +} + +namespace { +const HeaderRefs::value_type * +search_header_linear_backwards(const HeaderRefs &headers, + const StringRef &name) { + for (auto it = headers.rbegin(); it != headers.rend(); ++it) { + auto &kv = *it; + if (kv.name == name) { + return &kv; + } + } + return nullptr; +} +} // namespace + +StringRef Downstream::assemble_request_cookie() { + size_t len = 0; + + for (auto &kv : req_.fs.headers()) { + if (kv.token != http2::HD_COOKIE || kv.value.empty()) { + continue; + } + + len += kv.value.size() + str_size("; "); + } + + auto iov = make_byte_ref(balloc_, len + 1); + auto p = iov.base; + + for (auto &kv : req_.fs.headers()) { + if (kv.token != http2::HD_COOKIE || kv.value.empty()) { + continue; + } + + auto end = std::end(kv.value); + for (auto it = std::begin(kv.value) + kv.value.size(); + it != std::begin(kv.value); --it) { + auto c = *(it - 1); + if (c == ' ' || c == ';') { + continue; + } + end = it; + break; + } + + p = std::copy(std::begin(kv.value), end, p); + p = util::copy_lit(p, "; "); + } + + // cut trailing "; " + if (p - iov.base >= 2) { + p -= 2; + } + + return StringRef{iov.base, p}; +} + +uint32_t Downstream::find_affinity_cookie(const StringRef &name) { + for (auto &kv : req_.fs.headers()) { + if (kv.token != http2::HD_COOKIE) { + continue; + } + + for (auto it = std::begin(kv.value); it != std::end(kv.value);) { + if (*it == '\t' || *it == ' ' || *it == ';') { + ++it; + continue; + } + + auto end = std::find(it, std::end(kv.value), '='); + if (end == std::end(kv.value)) { + return 0; + } + + if (!util::streq(name, StringRef{it, end})) { + it = std::find(it, std::end(kv.value), ';'); + continue; + } + + it = std::find(end + 1, std::end(kv.value), ';'); + auto val = StringRef{end + 1, it}; + if (val.size() != 8) { + return 0; + } + uint32_t h = 0; + for (auto c : val) { + auto n = util::hex_to_uint(c); + if (n == 256) { + return 0; + } + h <<= 4; + h += n; + } + affinity_cookie_ = h; + return h; + } + } + return 0; +} + +size_t Downstream::count_crumble_request_cookie() { + size_t n = 0; + for (auto &kv : req_.fs.headers()) { + if (kv.token != http2::HD_COOKIE) { + continue; + } + + for (auto it = std::begin(kv.value); it != std::end(kv.value);) { + if (*it == '\t' || *it == ' ' || *it == ';') { + ++it; + continue; + } + + it = std::find(it, std::end(kv.value), ';'); + + ++n; + } + } + return n; +} + +void Downstream::crumble_request_cookie(std::vector &nva) { + for (auto &kv : req_.fs.headers()) { + if (kv.token != http2::HD_COOKIE) { + continue; + } + + for (auto it = std::begin(kv.value); it != std::end(kv.value);) { + if (*it == '\t' || *it == ' ' || *it == ';') { + ++it; + continue; + } + + auto first = it; + + it = std::find(it, std::end(kv.value), ';'); + + nva.push_back({(uint8_t *)"cookie", (uint8_t *)first, str_size("cookie"), + (size_t)(it - first), + (uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME | + NGHTTP2_NV_FLAG_NO_COPY_VALUE | + (kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))}); + } + } +} + +namespace { +void add_header(size_t &sum, HeaderRefs &headers, const StringRef &name, + const StringRef &value, bool no_index, int32_t token) { + sum += name.size() + value.size(); + headers.emplace_back(name, value, no_index, token); +} +} // namespace + +namespace { +StringRef alloc_header_name(BlockAllocator &balloc, const StringRef &name) { + auto iov = make_byte_ref(balloc, name.size() + 1); + auto p = iov.base; + p = std::copy(std::begin(name), std::end(name), p); + util::inp_strlower(iov.base, p); + *p = '\0'; + + return StringRef{iov.base, p}; +} +} // namespace + +namespace { +void append_last_header_key(BlockAllocator &balloc, bool &key_prev, size_t &sum, + HeaderRefs &headers, const char *data, size_t len) { + assert(key_prev); + sum += len; + auto &item = headers.back(); + auto name = + realloc_concat_string_ref(balloc, item.name, StringRef{data, len}); + + auto p = const_cast(name.byte()); + util::inp_strlower(p + name.size() - len, p + name.size()); + + item.name = name; + item.token = http2::lookup_token(item.name); +} +} // namespace + +namespace { +void append_last_header_value(BlockAllocator &balloc, bool &key_prev, + size_t &sum, HeaderRefs &headers, + const char *data, size_t len) { + key_prev = false; + sum += len; + auto &item = headers.back(); + item.value = + realloc_concat_string_ref(balloc, item.value, StringRef{data, len}); +} +} // namespace + +int FieldStore::parse_content_length() { + content_length = -1; + + for (auto &kv : headers_) { + if (kv.token != http2::HD_CONTENT_LENGTH) { + continue; + } + + auto len = util::parse_uint(kv.value); + if (len == -1) { + return -1; + } + if (content_length != -1) { + return -1; + } + content_length = len; + } + return 0; +} + +const HeaderRefs::value_type *FieldStore::header(int32_t token) const { + for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) { + auto &kv = *it; + if (kv.token == token) { + return &kv; + } + } + return nullptr; +} + +HeaderRefs::value_type *FieldStore::header(int32_t token) { + for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) { + auto &kv = *it; + if (kv.token == token) { + return &kv; + } + } + return nullptr; +} + +const HeaderRefs::value_type *FieldStore::header(const StringRef &name) const { + return search_header_linear_backwards(headers_, name); +} + +void FieldStore::add_header_token(const StringRef &name, const StringRef &value, + bool no_index, int32_t token) { + shrpx::add_header(buffer_size_, headers_, name, value, no_index, token); +} + +void FieldStore::alloc_add_header_name(const StringRef &name) { + auto name_ref = alloc_header_name(balloc_, name); + auto token = http2::lookup_token(name_ref); + add_header_token(name_ref, StringRef{}, false, token); + header_key_prev_ = true; +} + +void FieldStore::append_last_header_key(const char *data, size_t len) { + shrpx::append_last_header_key(balloc_, header_key_prev_, buffer_size_, + headers_, data, len); +} + +void FieldStore::append_last_header_value(const char *data, size_t len) { + shrpx::append_last_header_value(balloc_, header_key_prev_, buffer_size_, + headers_, data, len); +} + +void FieldStore::clear_headers() { + headers_.clear(); + header_key_prev_ = false; +} + +void FieldStore::add_trailer_token(const StringRef &name, + const StringRef &value, bool no_index, + int32_t token) { + // Header size limit should be applied to all header and trailer + // fields combined. + shrpx::add_header(buffer_size_, trailers_, name, value, no_index, token); +} + +void FieldStore::alloc_add_trailer_name(const StringRef &name) { + auto name_ref = alloc_header_name(balloc_, name); + auto token = http2::lookup_token(name_ref); + add_trailer_token(name_ref, StringRef{}, false, token); + trailer_key_prev_ = true; +} + +void FieldStore::append_last_trailer_key(const char *data, size_t len) { + shrpx::append_last_header_key(balloc_, trailer_key_prev_, buffer_size_, + trailers_, data, len); +} + +void FieldStore::append_last_trailer_value(const char *data, size_t len) { + shrpx::append_last_header_value(balloc_, trailer_key_prev_, buffer_size_, + trailers_, data, len); +} + +void FieldStore::erase_content_length_and_transfer_encoding() { + for (auto &kv : headers_) { + switch (kv.token) { + case http2::HD_CONTENT_LENGTH: + case http2::HD_TRANSFER_ENCODING: + kv.name = StringRef{}; + kv.token = -1; + break; + } + } +} + +void Downstream::set_request_start_time( + std::chrono::high_resolution_clock::time_point time) { + request_start_time_ = std::move(time); +} + +const std::chrono::high_resolution_clock::time_point & +Downstream::get_request_start_time() const { + return request_start_time_; +} + +void Downstream::reset_upstream(Upstream *upstream) { + upstream_ = upstream; + if (dconn_) { + dconn_->on_upstream_change(upstream); + } +} + +Upstream *Downstream::get_upstream() const { return upstream_; } + +void Downstream::set_stream_id(int64_t stream_id) { stream_id_ = stream_id; } + +int64_t Downstream::get_stream_id() const { return stream_id_; } + +void Downstream::set_request_state(DownstreamState state) { + request_state_ = state; +} + +DownstreamState Downstream::get_request_state() const { return request_state_; } + +bool Downstream::get_chunked_request() const { return chunked_request_; } + +void Downstream::set_chunked_request(bool f) { chunked_request_ = f; } + +bool Downstream::request_buf_full() { + auto handler = upstream_->get_client_handler(); + auto faddr = handler->get_upstream_addr(); + auto worker = handler->get_worker(); + + // We don't check buffer size here for API endpoint. + if (faddr->alt_mode == UpstreamAltMode::API) { + return false; + } + + if (dconn_) { + auto &downstreamconf = *worker->get_downstream_config(); + return blocked_request_buf_.rleft() + request_buf_.rleft() >= + downstreamconf.request_buffer_size; + } + + return false; +} + +DefaultMemchunks *Downstream::get_request_buf() { return &request_buf_; } + +// Call this function after this object is attached to +// Downstream. Otherwise, the program will crash. +int Downstream::push_request_headers() { + if (!dconn_) { + DLOG(INFO, this) << "dconn_ is NULL"; + return -1; + } + return dconn_->push_request_headers(); +} + +int Downstream::push_upload_data_chunk(const uint8_t *data, size_t datalen) { + req_.recv_body_length += datalen; + + if (!dconn_ && !request_header_sent_) { + blocked_request_buf_.append(data, datalen); + req_.unconsumed_body_length += datalen; + return 0; + } + + // Assumes that request headers have already been pushed to output + // buffer using push_request_headers(). + if (!dconn_) { + DLOG(INFO, this) << "dconn_ is NULL"; + return -1; + } + if (dconn_->push_upload_data_chunk(data, datalen) != 0) { + return -1; + } + + req_.unconsumed_body_length += datalen; + + return 0; +} + +int Downstream::end_upload_data() { + if (!dconn_ && !request_header_sent_) { + blocked_request_data_eof_ = true; + return 0; + } + if (!dconn_) { + DLOG(INFO, this) << "dconn_ is NULL"; + return -1; + } + return dconn_->end_upload_data(); +} + +void Downstream::rewrite_location_response_header( + const StringRef &upstream_scheme) { + auto hd = resp_.fs.header(http2::HD_LOCATION); + if (!hd) { + return; + } + + if (request_downstream_host_.empty() || req_.authority.empty()) { + return; + } + + http_parser_url u{}; + auto rv = http_parser_parse_url(hd->value.c_str(), hd->value.size(), 0, &u); + if (rv != 0) { + return; + } + + auto new_uri = http2::rewrite_location_uri(balloc_, hd->value, u, + request_downstream_host_, + req_.authority, upstream_scheme); + + if (new_uri.empty()) { + return; + } + + hd->value = new_uri; +} + +bool Downstream::get_chunked_response() const { return chunked_response_; } + +void Downstream::set_chunked_response(bool f) { chunked_response_ = f; } + +int Downstream::on_read() { + if (!dconn_) { + DLOG(INFO, this) << "dconn_ is NULL"; + return -1; + } + return dconn_->on_read(); +} + +void Downstream::set_response_state(DownstreamState state) { + response_state_ = state; +} + +DownstreamState Downstream::get_response_state() const { + return response_state_; +} + +DefaultMemchunks *Downstream::get_response_buf() { return &response_buf_; } + +bool Downstream::response_buf_full() { + if (dconn_) { + auto handler = upstream_->get_client_handler(); + auto worker = handler->get_worker(); + auto &downstreamconf = *worker->get_downstream_config(); + + return response_buf_.rleft() >= downstreamconf.response_buffer_size; + } + + return false; +} + +bool Downstream::validate_request_recv_body_length() const { + if (req_.fs.content_length == -1) { + return true; + } + + if (req_.fs.content_length != req_.recv_body_length) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, this) << "request invalid bodylen: content-length=" + << req_.fs.content_length + << ", received=" << req_.recv_body_length; + } + return false; + } + + return true; +} + +bool Downstream::validate_response_recv_body_length() const { + if (!expect_response_body() || resp_.fs.content_length == -1) { + return true; + } + + if (resp_.fs.content_length != resp_.recv_body_length) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, this) << "response invalid bodylen: content-length=" + << resp_.fs.content_length + << ", received=" << resp_.recv_body_length; + } + return false; + } + + return true; +} + +void Downstream::check_upgrade_fulfilled_http2() { + // This handles nonzero req_.connect_proto and h1 frontend requests + // WebSocket upgrade. + upgraded_ = (req_.method == HTTP_CONNECT || + req_.connect_proto == ConnectProto::WEBSOCKET) && + resp_.http_status / 100 == 2; +} + +void Downstream::check_upgrade_fulfilled_http1() { + if (req_.method == HTTP_CONNECT) { + if (req_.connect_proto == ConnectProto::WEBSOCKET) { + if (resp_.http_status != 101) { + return; + } + + // This is done for HTTP/2 frontend only. + auto accept = resp_.fs.header(http2::HD_SEC_WEBSOCKET_ACCEPT); + if (!accept) { + return; + } + + std::array accept_buf; + auto expected = + http2::make_websocket_accept_token(accept_buf.data(), ws_key_); + + upgraded_ = expected != "" && expected == accept->value; + } else { + upgraded_ = resp_.http_status / 100 == 2; + } + + return; + } + + if (resp_.http_status == 101) { + // TODO Do more strict checking for upgrade headers + upgraded_ = req_.upgrade_request; + + return; + } +} + +void Downstream::inspect_http2_request() { + if (req_.method == HTTP_CONNECT) { + req_.upgrade_request = true; + } +} + +void Downstream::inspect_http1_request() { + if (req_.method == HTTP_CONNECT) { + req_.upgrade_request = true; + } else if (req_.http_minor > 0) { + auto upgrade = req_.fs.header(http2::HD_UPGRADE); + if (upgrade) { + const auto &val = upgrade->value; + // TODO Perform more strict checking for upgrade headers + if (util::streq_l(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, val.c_str(), + val.size())) { + req_.http2_upgrade_seen = true; + } else { + req_.upgrade_request = true; + + // TODO Should we check Sec-WebSocket-Key, and + // Sec-WebSocket-Version as well? + if (util::strieq_l("websocket", val)) { + req_.connect_proto = ConnectProto::WEBSOCKET; + } + } + } + } + auto transfer_encoding = req_.fs.header(http2::HD_TRANSFER_ENCODING); + if (transfer_encoding) { + req_.fs.content_length = -1; + } + + auto expect = req_.fs.header(http2::HD_EXPECT); + expect_100_continue_ = + expect && + util::strieq(expect->value, StringRef::from_lit("100-continue")); +} + +void Downstream::inspect_http1_response() { + auto transfer_encoding = resp_.fs.header(http2::HD_TRANSFER_ENCODING); + if (transfer_encoding) { + resp_.fs.content_length = -1; + } +} + +void Downstream::reset_response() { + resp_.http_status = 0; + resp_.http_major = 1; + resp_.http_minor = 1; +} + +bool Downstream::get_non_final_response() const { + return !upgraded_ && resp_.http_status / 100 == 1; +} + +bool Downstream::supports_non_final_response() const { + return req_.http_major == 3 || req_.http_major == 2 || + (req_.http_major == 1 && req_.http_minor == 1); +} + +bool Downstream::get_upgraded() const { return upgraded_; } + +bool Downstream::get_http2_upgrade_request() const { + return req_.http2_upgrade_seen && req_.fs.header(http2::HD_HTTP2_SETTINGS) && + response_state_ == DownstreamState::INITIAL; +} + +StringRef Downstream::get_http2_settings() const { + auto http2_settings = req_.fs.header(http2::HD_HTTP2_SETTINGS); + if (!http2_settings) { + return StringRef{}; + } + return http2_settings->value; +} + +void Downstream::set_downstream_stream_id(int64_t stream_id) { + downstream_stream_id_ = stream_id; +} + +int64_t Downstream::get_downstream_stream_id() const { + return downstream_stream_id_; +} + +uint32_t Downstream::get_response_rst_stream_error_code() const { + return response_rst_stream_error_code_; +} + +void Downstream::set_response_rst_stream_error_code(uint32_t error_code) { + response_rst_stream_error_code_ = error_code; +} + +void Downstream::set_expect_final_response(bool f) { + expect_final_response_ = f; +} + +bool Downstream::get_expect_final_response() const { + return expect_final_response_; +} + +bool Downstream::expect_response_body() const { + return !resp_.headers_only && + http2::expect_response_body(req_.method, resp_.http_status); +} + +bool Downstream::expect_response_trailer() const { + // In HTTP/2, if final response HEADERS does not bear END_STREAM it + // is possible trailer fields might come, regardless of request + // method or status code. + return !resp_.headers_only && + (resp_.http_major == 3 || resp_.http_major == 2); +} + +namespace { +void reset_timer(struct ev_loop *loop, ev_timer *w) { ev_timer_again(loop, w); } +} // namespace + +namespace { +void try_reset_timer(struct ev_loop *loop, ev_timer *w) { + if (!ev_is_active(w)) { + return; + } + ev_timer_again(loop, w); +} +} // namespace + +namespace { +void ensure_timer(struct ev_loop *loop, ev_timer *w) { + if (ev_is_active(w)) { + return; + } + ev_timer_again(loop, w); +} +} // namespace + +namespace { +void disable_timer(struct ev_loop *loop, ev_timer *w) { + ev_timer_stop(loop, w); +} +} // namespace + +void Downstream::reset_upstream_rtimer() { + if (get_config()->http2.timeout.stream_read == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + reset_timer(loop, &upstream_rtimer_); +} + +void Downstream::reset_upstream_wtimer() { + auto loop = upstream_->get_client_handler()->get_loop(); + auto &timeoutconf = get_config()->http2.timeout; + + if (timeoutconf.stream_write != 0.) { + reset_timer(loop, &upstream_wtimer_); + } + if (timeoutconf.stream_read != 0.) { + try_reset_timer(loop, &upstream_rtimer_); + } +} + +void Downstream::ensure_upstream_wtimer() { + if (get_config()->http2.timeout.stream_write == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + ensure_timer(loop, &upstream_wtimer_); +} + +void Downstream::disable_upstream_rtimer() { + if (get_config()->http2.timeout.stream_read == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + disable_timer(loop, &upstream_rtimer_); +} + +void Downstream::disable_upstream_wtimer() { + if (get_config()->http2.timeout.stream_write == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + disable_timer(loop, &upstream_wtimer_); +} + +void Downstream::reset_downstream_rtimer() { + if (get_config()->http2.timeout.stream_read == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + reset_timer(loop, &downstream_rtimer_); +} + +void Downstream::reset_downstream_wtimer() { + auto loop = upstream_->get_client_handler()->get_loop(); + auto &timeoutconf = get_config()->http2.timeout; + + if (timeoutconf.stream_write != 0.) { + reset_timer(loop, &downstream_wtimer_); + } + if (timeoutconf.stream_read != 0.) { + try_reset_timer(loop, &downstream_rtimer_); + } +} + +void Downstream::ensure_downstream_wtimer() { + if (get_config()->http2.timeout.stream_write == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + ensure_timer(loop, &downstream_wtimer_); +} + +void Downstream::disable_downstream_rtimer() { + if (get_config()->http2.timeout.stream_read == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + disable_timer(loop, &downstream_rtimer_); +} + +void Downstream::disable_downstream_wtimer() { + if (get_config()->http2.timeout.stream_write == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + disable_timer(loop, &downstream_wtimer_); +} + +bool Downstream::accesslog_ready() const { + return !accesslog_written_ && resp_.http_status > 0; +} + +void Downstream::add_retry() { ++num_retry_; } + +bool Downstream::no_more_retry() const { return num_retry_ > 50; } + +void Downstream::set_request_downstream_host(const StringRef &host) { + request_downstream_host_ = host; +} + +void Downstream::set_request_pending(bool f) { request_pending_ = f; } + +bool Downstream::get_request_pending() const { return request_pending_; } + +void Downstream::set_request_header_sent(bool f) { request_header_sent_ = f; } + +bool Downstream::get_request_header_sent() const { + return request_header_sent_; +} + +bool Downstream::request_submission_ready() const { + return (request_state_ == DownstreamState::HEADER_COMPLETE || + request_state_ == DownstreamState::MSG_COMPLETE) && + (request_pending_ || !request_header_sent_) && + response_state_ == DownstreamState::INITIAL; +} + +DispatchState Downstream::get_dispatch_state() const { return dispatch_state_; } + +void Downstream::set_dispatch_state(DispatchState s) { dispatch_state_ = s; } + +void Downstream::attach_blocked_link(BlockedLink *l) { + assert(!blocked_link_); + + l->downstream = this; + blocked_link_ = l; +} + +BlockedLink *Downstream::detach_blocked_link() { + auto link = blocked_link_; + blocked_link_ = nullptr; + return link; +} + +bool Downstream::can_detach_downstream_connection() const { + // We should check request and response buffer. If request buffer + // is not empty, then we might leave downstream connection in weird + // state, especially for HTTP/1.1 + return dconn_ && response_state_ == DownstreamState::MSG_COMPLETE && + request_state_ == DownstreamState::MSG_COMPLETE && !upgraded_ && + !resp_.connection_close && request_buf_.rleft() == 0; +} + +DefaultMemchunks Downstream::pop_response_buf() { + return std::move(response_buf_); +} + +void Downstream::set_assoc_stream_id(int64_t stream_id) { + assoc_stream_id_ = stream_id; +} + +int64_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; } + +BlockAllocator &Downstream::get_block_allocator() { return balloc_; } + +void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) { + nghttp2_rcbuf_incref(rcbuf); + rcbufs_.push_back(rcbuf); +} + +#ifdef ENABLE_HTTP3 +void Downstream::add_rcbuf(nghttp3_rcbuf *rcbuf) { + nghttp3_rcbuf_incref(rcbuf); + rcbufs3_.push_back(rcbuf); +} +#endif // ENABLE_HTTP3 + +void Downstream::set_downstream_addr_group( + const std::shared_ptr &group) { + group_ = group; +} + +void Downstream::set_addr(const DownstreamAddr *addr) { addr_ = addr; } + +const DownstreamAddr *Downstream::get_addr() const { return addr_; } + +void Downstream::set_accesslog_written(bool f) { accesslog_written_ = f; } + +void Downstream::renew_affinity_cookie(uint32_t h) { + affinity_cookie_ = h; + new_affinity_cookie_ = true; +} + +uint32_t Downstream::get_affinity_cookie_to_send() const { + if (new_affinity_cookie_) { + return affinity_cookie_; + } + return 0; +} + +DefaultMemchunks *Downstream::get_blocked_request_buf() { + return &blocked_request_buf_; +} + +bool Downstream::get_blocked_request_data_eof() const { + return blocked_request_data_eof_; +} + +void Downstream::set_blocked_request_data_eof(bool f) { + blocked_request_data_eof_ = f; +} + +void Downstream::set_ws_key(const StringRef &key) { ws_key_ = key; } + +bool Downstream::get_expect_100_continue() const { + return expect_100_continue_; +} + +bool Downstream::get_stop_reading() const { return stop_reading_; } + +void Downstream::set_stop_reading(bool f) { stop_reading_ = f; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_downstream.h b/lib/nghttp2/src/shrpx_downstream.h new file mode 100644 index 00000000000..146cae583b7 --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream.h @@ -0,0 +1,628 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_DOWNSTREAM_H +#define SHRPX_DOWNSTREAM_H + +#include "shrpx.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#ifdef ENABLE_HTTP3 +# include +#endif // ENABLE_HTTP3 + +#include "llhttp.h" + +#include "shrpx_io_control.h" +#include "shrpx_log_config.h" +#include "http2.h" +#include "memchunk.h" +#include "allocator.h" + +using namespace nghttp2; + +namespace shrpx { + +class Upstream; +class DownstreamConnection; +struct BlockedLink; +struct DownstreamAddrGroup; +struct DownstreamAddr; + +class FieldStore { +public: + FieldStore(BlockAllocator &balloc, size_t headers_initial_capacity) + : content_length(-1), + balloc_(balloc), + buffer_size_(0), + header_key_prev_(false), + trailer_key_prev_(false) { + headers_.reserve(headers_initial_capacity); + } + + const HeaderRefs &headers() const { return headers_; } + const HeaderRefs &trailers() const { return trailers_; } + + HeaderRefs &headers() { return headers_; } + HeaderRefs &trailers() { return trailers_; } + + const void add_extra_buffer_size(size_t n) { buffer_size_ += n; } + size_t buffer_size() const { return buffer_size_; } + + size_t num_fields() const { return headers_.size() + trailers_.size(); } + + // Returns pointer to the header field with the name |name|. If + // multiple header have |name| as name, return last occurrence from + // the beginning. If no such header is found, returns nullptr. + const HeaderRefs::value_type *header(int32_t token) const; + HeaderRefs::value_type *header(int32_t token); + // Returns pointer to the header field with the name |name|. If no + // such header is found, returns nullptr. + const HeaderRefs::value_type *header(const StringRef &name) const; + + void add_header_token(const StringRef &name, const StringRef &value, + bool no_index, int32_t token); + + // Adds header field name |name|. First, the copy of header field + // name pointed by name.c_str() of length name.size() is made, and + // stored. + void alloc_add_header_name(const StringRef &name); + + void append_last_header_key(const char *data, size_t len); + void append_last_header_value(const char *data, size_t len); + + bool header_key_prev() const { return header_key_prev_; } + + // Parses content-length, and records it in the field. If there are + // multiple Content-Length, returns -1. + int parse_content_length(); + + // Empties headers. + void clear_headers(); + + void add_trailer_token(const StringRef &name, const StringRef &value, + bool no_index, int32_t token); + + // Adds trailer field name |name|. First, the copy of trailer field + // name pointed by name.c_str() of length name.size() is made, and + // stored. + void alloc_add_trailer_name(const StringRef &name); + + void append_last_trailer_key(const char *data, size_t len); + void append_last_trailer_value(const char *data, size_t len); + + bool trailer_key_prev() const { return trailer_key_prev_; } + + // erase_content_length_and_transfer_encoding erases content-length + // and transfer-encoding header fields. + void erase_content_length_and_transfer_encoding(); + + // content-length, -1 if it is unknown. + int64_t content_length; + +private: + BlockAllocator &balloc_; + HeaderRefs headers_; + // trailer fields. For HTTP/1.1, trailer fields are only included + // with chunked encoding. For HTTP/2, there is no such limit. + HeaderRefs trailers_; + // Sum of the length of name and value in headers_ and trailers_. + // This could also be increased by add_extra_buffer_size() to take + // into account for request URI in case of HTTP/1.x request. + size_t buffer_size_; + bool header_key_prev_; + bool trailer_key_prev_; +}; + +// Protocols allowed in HTTP/2 :protocol header field. +enum class ConnectProto { + NONE, + WEBSOCKET, +}; + +struct Request { + Request(BlockAllocator &balloc) + : fs(balloc, 16), + recv_body_length(0), + unconsumed_body_length(0), + method(-1), + http_major(1), + http_minor(1), + connect_proto(ConnectProto::NONE), + upgrade_request(false), + http2_upgrade_seen(false), + connection_close(false), + http2_expect_body(false), + no_authority(false), + forwarded_once(false) {} + + void consume(size_t len) { + assert(unconsumed_body_length >= len); + unconsumed_body_length -= len; + } + + bool regular_connect_method() const { + return method == HTTP_CONNECT && connect_proto == ConnectProto::NONE; + } + + bool extended_connect_method() const { + return connect_proto != ConnectProto::NONE; + } + + FieldStore fs; + // Timestamp when all request header fields are received. + std::shared_ptr tstamp; + // Request scheme. For HTTP/2, this is :scheme header field value. + // For HTTP/1.1, this is deduced from URI or connection. + StringRef scheme; + // Request authority. This is HTTP/2 :authority header field value + // or host header field value. We may deduce it from absolute-form + // HTTP/1 request. We also store authority-form HTTP/1 request. + // This could be empty if request comes from HTTP/1.0 without Host + // header field and origin-form. + StringRef authority; + // Request path, including query component. For HTTP/1.1, this is + // request-target. For HTTP/2, this is :path header field value. + // For CONNECT request, this is empty. + StringRef path; + // This is original authority which cannot be changed by per-pattern + // mruby script. + StringRef orig_authority; + // This is original path which cannot be changed by per-pattern + // mruby script. + StringRef orig_path; + // the length of request body received so far + int64_t recv_body_length; + // The number of bytes not consumed by the application yet. + size_t unconsumed_body_length; + int method; + // HTTP major and minor version + int http_major, http_minor; + // connect_proto specified in HTTP/2 :protocol pseudo header field + // which enables extended CONNECT method. This field is also set if + // WebSocket upgrade is requested in h1 frontend for convenience. + ConnectProto connect_proto; + // Returns true if the request is HTTP upgrade (HTTP Upgrade or + // CONNECT method). Upgrade to HTTP/2 is excluded. For HTTP/2 + // Upgrade, check get_http2_upgrade_request(). + bool upgrade_request; + // true if h2c is seen in Upgrade header field. + bool http2_upgrade_seen; + bool connection_close; + // true if this is HTTP/2, and request body is expected. Note that + // we don't take into account HTTP method here. + bool http2_expect_body; + // true if request does not have any information about authority. + // This happens when: For HTTP/2 request, :authority is missing. + // For HTTP/1 request, origin or asterisk form is used. + bool no_authority; + // true if backend selection is done for request once. + // orig_authority and orig_path have the authority and path which + // are used for the first backend selection. + bool forwarded_once; +}; + +struct Response { + Response(BlockAllocator &balloc) + : fs(balloc, 32), + recv_body_length(0), + unconsumed_body_length(0), + http_status(0), + http_major(1), + http_minor(1), + connection_close(false), + headers_only(false) {} + + void consume(size_t len) { + assert(unconsumed_body_length >= len); + unconsumed_body_length -= len; + } + + // returns true if a resource denoted by scheme, authority, and path + // has already been pushed. + bool is_resource_pushed(const StringRef &scheme, const StringRef &authority, + const StringRef &path) const { + if (!pushed_resources) { + return false; + } + return std::find(std::begin(*pushed_resources), std::end(*pushed_resources), + std::make_tuple(scheme, authority, path)) != + std::end(*pushed_resources); + } + + // remember that a resource denoted by scheme, authority, and path + // is pushed. + void resource_pushed(const StringRef &scheme, const StringRef &authority, + const StringRef &path) { + if (!pushed_resources) { + pushed_resources = std::make_unique< + std::vector>>(); + } + pushed_resources->emplace_back(scheme, authority, path); + } + + FieldStore fs; + // array of the tuple of scheme, authority, and path of pushed + // resource. This is required because RFC 8297 says that server + // typically includes header fields appeared in non-final response + // header fields in final response header fields. Without checking + // that a particular resource has already been pushed, or not, we + // end up pushing the same resource at least twice. It is unknown + // that we should use more complex data structure (e.g., std::set) + // to find the resources faster. + std::unique_ptr>> + pushed_resources; + // the length of response body received so far + int64_t recv_body_length; + // The number of bytes not consumed by the application yet. This is + // mainly for HTTP/2 backend. + size_t unconsumed_body_length; + // HTTP status code + unsigned int http_status; + int http_major, http_minor; + bool connection_close; + // true if response only consists of HEADERS, and it bears + // END_STREAM. This is used to tell Http2Upstream that it can send + // response with single HEADERS with END_STREAM flag only. + bool headers_only; +}; + +enum class DownstreamState { + INITIAL, + HEADER_COMPLETE, + MSG_COMPLETE, + STREAM_CLOSED, + CONNECT_FAIL, + MSG_RESET, + // header contains invalid header field. We can safely send error + // response (502) to a client. + MSG_BAD_HEADER, + // header fields in HTTP/1 request exceed the configuration limit. + // This state is only transitioned from INITIAL state, and solely + // used to signal 431 status code to the client. + HTTP1_REQUEST_HEADER_TOO_LARGE, +}; + +enum class DispatchState { + NONE, + PENDING, + BLOCKED, + ACTIVE, + FAILURE, +}; + +class Downstream { +public: + Downstream(Upstream *upstream, MemchunkPool *mcpool, int64_t stream_id); + ~Downstream(); + void reset_upstream(Upstream *upstream); + Upstream *get_upstream() const; + void set_stream_id(int64_t stream_id); + int64_t get_stream_id() const; + void set_assoc_stream_id(int64_t stream_id); + int64_t get_assoc_stream_id() const; + void pause_read(IOCtrlReason reason); + int resume_read(IOCtrlReason reason, size_t consumed); + void force_resume_read(); + // Set stream ID for downstream HTTP2 connection. + void set_downstream_stream_id(int64_t stream_id); + int64_t get_downstream_stream_id() const; + + int attach_downstream_connection(std::unique_ptr dconn); + void detach_downstream_connection(); + DownstreamConnection *get_downstream_connection(); + // Returns dconn_ and nullifies dconn_. + std::unique_ptr pop_downstream_connection(); + + // Returns true if output buffer is full. If underlying dconn_ is + // NULL, this function always returns false. + bool request_buf_full(); + // Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in + // h1 backend. This should not depend on inspect_http1_response(). + void check_upgrade_fulfilled_http1(); + // Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in + // h2 backend. + void check_upgrade_fulfilled_http2(); + // Returns true if the upgrade is succeeded as a result of the call + // check_upgrade_fulfilled_http*(). HTTP/2 Upgrade is excluded. + bool get_upgraded() const; + // Inspects HTTP/2 request. + void inspect_http2_request(); + // Inspects HTTP/1 request. This checks whether the request is + // upgrade request and tranfer-encoding etc. + void inspect_http1_request(); + // Returns true if the request is HTTP Upgrade for HTTP/2 + bool get_http2_upgrade_request() const; + // Returns the value of HTTP2-Settings request header field. + StringRef get_http2_settings() const; + + // downstream request API + const Request &request() const { return req_; } + Request &request() { return req_; } + + // Count number of crumbled cookies + size_t count_crumble_request_cookie(); + // Crumbles (split cookie by ";") in request_headers_ and adds them + // in |nva|. Headers::no_index is inherited. + void crumble_request_cookie(std::vector &nva); + // Assembles request cookies. The opposite operation against + // crumble_request_cookie(). + StringRef assemble_request_cookie(); + + void + set_request_start_time(std::chrono::high_resolution_clock::time_point time); + const std::chrono::high_resolution_clock::time_point & + get_request_start_time() const; + int push_request_headers(); + bool get_chunked_request() const; + void set_chunked_request(bool f); + int push_upload_data_chunk(const uint8_t *data, size_t datalen); + int end_upload_data(); + // Validates that received request body length and content-length + // matches. + bool validate_request_recv_body_length() const; + void set_request_downstream_host(const StringRef &host); + bool expect_response_body() const; + bool expect_response_trailer() const; + void set_request_state(DownstreamState state); + DownstreamState get_request_state() const; + DefaultMemchunks *get_request_buf(); + void set_request_pending(bool f); + bool get_request_pending() const; + void set_request_header_sent(bool f); + bool get_request_header_sent() const; + // Returns true if request is ready to be submitted to downstream. + // When sending pending request, get_request_pending() should be + // checked too because this function may return true when + // get_request_pending() returns false. + bool request_submission_ready() const; + + DefaultMemchunks *get_blocked_request_buf(); + bool get_blocked_request_data_eof() const; + void set_blocked_request_data_eof(bool f); + + // downstream response API + const Response &response() const { return resp_; } + Response &response() { return resp_; } + + // Rewrites the location response header field. + void rewrite_location_response_header(const StringRef &upstream_scheme); + + bool get_chunked_response() const; + void set_chunked_response(bool f); + + void set_response_state(DownstreamState state); + DownstreamState get_response_state() const; + DefaultMemchunks *get_response_buf(); + bool response_buf_full(); + // Validates that received response body length and content-length + // matches. + bool validate_response_recv_body_length() const; + uint32_t get_response_rst_stream_error_code() const; + void set_response_rst_stream_error_code(uint32_t error_code); + // Inspects HTTP/1 response. This checks tranfer-encoding etc. + void inspect_http1_response(); + // Clears some of member variables for response. + void reset_response(); + // True if the response is non-final (1xx status code). Note that + // if connection was upgraded, 101 status code is treated as final. + bool get_non_final_response() const; + // True if protocol version used by client supports non final + // response. Only HTTP/1.1 and HTTP/2 clients support it. + bool supports_non_final_response() const; + void set_expect_final_response(bool f); + bool get_expect_final_response() const; + + // Call this method when there is incoming data in downstream + // connection. + int on_read(); + + // Resets upstream read timer. If it is active, timeout value is + // reset. If it is not active, timer will be started. + void reset_upstream_rtimer(); + // Resets upstream write timer. If it is active, timeout value is + // reset. If it is not active, timer will be started. This + // function also resets read timer if it has been started. + void reset_upstream_wtimer(); + // Makes sure that upstream write timer is started. If it has been + // started, do nothing. Otherwise, write timer will be started. + void ensure_upstream_wtimer(); + // Disables upstream read timer. + void disable_upstream_rtimer(); + // Disables upstream write timer. + void disable_upstream_wtimer(); + + // Downstream timer functions. They works in a similar way just + // like the upstream timer function. + void reset_downstream_rtimer(); + void reset_downstream_wtimer(); + void ensure_downstream_wtimer(); + void disable_downstream_rtimer(); + void disable_downstream_wtimer(); + + // Returns true if accesslog can be written for this downstream. + bool accesslog_ready() const; + + // Increment retry count + void add_retry(); + // true if retry attempt should not be done. + bool no_more_retry() const; + + DispatchState get_dispatch_state() const; + void set_dispatch_state(DispatchState s); + + void attach_blocked_link(BlockedLink *l); + BlockedLink *detach_blocked_link(); + + // Returns true if downstream_connection can be detached and reused. + bool can_detach_downstream_connection() const; + + DefaultMemchunks pop_response_buf(); + + BlockAllocator &get_block_allocator(); + + void add_rcbuf(nghttp2_rcbuf *rcbuf); +#ifdef ENABLE_HTTP3 + void add_rcbuf(nghttp3_rcbuf *rcbuf); +#endif // ENABLE_HTTP3 + + void + set_downstream_addr_group(const std::shared_ptr &group); + void set_addr(const DownstreamAddr *addr); + + const DownstreamAddr *get_addr() const; + + void set_accesslog_written(bool f); + + // Finds affinity cookie from request header fields. The name of + // cookie is given in |name|. If an affinity cookie is found, it is + // assigned to a member function, and is returned. If it is not + // found, or is malformed, returns 0. + uint32_t find_affinity_cookie(const StringRef &name); + // Set |h| as affinity cookie. + void renew_affinity_cookie(uint32_t h); + // Returns affinity cookie to send. If it does not need to be sent, + // for example, because the value is retrieved from a request header + // field, returns 0. + uint32_t get_affinity_cookie_to_send() const; + + void set_ws_key(const StringRef &key); + + bool get_expect_100_continue() const; + + bool get_stop_reading() const; + void set_stop_reading(bool f); + + enum { + EVENT_ERROR = 0x1, + EVENT_TIMEOUT = 0x2, + }; + + Downstream *dlnext, *dlprev; + + // the length of response body sent to upstream client + int64_t response_sent_body_length; + +private: + BlockAllocator balloc_; + + std::vector rcbufs_; +#ifdef ENABLE_HTTP3 + std::vector rcbufs3_; +#endif // ENABLE_HTTP3 + + Request req_; + Response resp_; + + std::chrono::high_resolution_clock::time_point request_start_time_; + + // host we requested to downstream. This is used to rewrite + // location header field to decide the location should be rewritten + // or not. + StringRef request_downstream_host_; + + // Data arrived in frontend before sending header fields to backend + // are stored in this buffer. + DefaultMemchunks blocked_request_buf_; + DefaultMemchunks request_buf_; + DefaultMemchunks response_buf_; + + // The Sec-WebSocket-Key field sent to the peer. This field is used + // if frontend uses RFC 8441 WebSocket bootstrapping via HTTP/2. + StringRef ws_key_; + + ev_timer upstream_rtimer_; + ev_timer upstream_wtimer_; + + ev_timer downstream_rtimer_; + ev_timer downstream_wtimer_; + + Upstream *upstream_; + std::unique_ptr dconn_; + + // only used by HTTP/2 upstream + BlockedLink *blocked_link_; + // The backend address used to fulfill this request. These are for + // logging purpose. + std::shared_ptr group_; + const DownstreamAddr *addr_; + // How many times we tried in backend connection + size_t num_retry_; + // The stream ID in frontend connection + int64_t stream_id_; + // The associated stream ID in frontend connection if this is pushed + // stream. + int64_t assoc_stream_id_; + // stream ID in backend connection + int64_t downstream_stream_id_; + // RST_STREAM error_code from downstream HTTP2 connection + uint32_t response_rst_stream_error_code_; + // An affinity cookie value. + uint32_t affinity_cookie_; + // request state + DownstreamState request_state_; + // response state + DownstreamState response_state_; + // only used by HTTP/2 upstream + DispatchState dispatch_state_; + // true if the connection is upgraded (HTTP Upgrade or CONNECT), + // excluding upgrade to HTTP/2. + bool upgraded_; + // true if backend request uses chunked transfer-encoding + bool chunked_request_; + // true if response to client uses chunked transfer-encoding + bool chunked_response_; + // true if we have not got final response code + bool expect_final_response_; + // true if downstream request is pending because backend connection + // has not been established or should be checked before use; + // currently used only with HTTP/2 connection. + bool request_pending_; + // true if downstream request header is considered to be sent. + bool request_header_sent_; + // true if access.log has been written. + bool accesslog_written_; + // true if affinity cookie is generated for this request. + bool new_affinity_cookie_; + // true if eof is received from client before sending header fields + // to backend. + bool blocked_request_data_eof_; + // true if request contains "expect: 100-continue" header field. + bool expect_100_continue_; + bool stop_reading_; +}; + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_H diff --git a/lib/nghttp2/src/shrpx_downstream_connection.cc b/lib/nghttp2/src/shrpx_downstream_connection.cc new file mode 100644 index 00000000000..16a2d6fd188 --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream_connection.cc @@ -0,0 +1,48 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_downstream_connection.h" + +#include "shrpx_client_handler.h" +#include "shrpx_downstream.h" +#include "shrpx_log.h" + +namespace shrpx { + +DownstreamConnection::DownstreamConnection() + : client_handler_(nullptr), downstream_(nullptr) {} + +DownstreamConnection::~DownstreamConnection() {} + +void DownstreamConnection::set_client_handler(ClientHandler *handler) { + client_handler_ = handler; +} + +ClientHandler *DownstreamConnection::get_client_handler() { + return client_handler_; +} + +Downstream *DownstreamConnection::get_downstream() { return downstream_; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_downstream_connection.h b/lib/nghttp2/src/shrpx_downstream_connection.h new file mode 100644 index 00000000000..8efdcbef5a2 --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream_connection.h @@ -0,0 +1,81 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_DOWNSTREAM_CONNECTION_H +#define SHRPX_DOWNSTREAM_CONNECTION_H + +#include "shrpx.h" + +#include + +#include "shrpx_io_control.h" + +namespace shrpx { + +class ClientHandler; +class Upstream; +class Downstream; +struct DownstreamAddrGroup; +struct DownstreamAddr; + +class DownstreamConnection { +public: + DownstreamConnection(); + virtual ~DownstreamConnection(); + virtual int attach_downstream(Downstream *downstream) = 0; + virtual void detach_downstream(Downstream *downstream) = 0; + + virtual int push_request_headers() = 0; + virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen) = 0; + virtual int end_upload_data() = 0; + + virtual void pause_read(IOCtrlReason reason) = 0; + virtual int resume_read(IOCtrlReason reason, size_t consumed) = 0; + virtual void force_resume_read() = 0; + + virtual int on_read() = 0; + virtual int on_write() = 0; + virtual int on_timeout() { return 0; } + + virtual void on_upstream_change(Upstream *upstream) = 0; + + // true if this object is poolable. + virtual bool poolable() const = 0; + + virtual const std::shared_ptr & + get_downstream_addr_group() const = 0; + virtual DownstreamAddr *get_addr() const = 0; + + void set_client_handler(ClientHandler *client_handler); + ClientHandler *get_client_handler(); + Downstream *get_downstream(); + +protected: + ClientHandler *client_handler_; + Downstream *downstream_; +}; + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_downstream_connection_pool.cc b/lib/nghttp2/src/shrpx_downstream_connection_pool.cc new file mode 100644 index 00000000000..0ee66b60f1e --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream_connection_pool.cc @@ -0,0 +1,66 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "shrpx_downstream_connection_pool.h" +#include "shrpx_downstream_connection.h" + +namespace shrpx { + +DownstreamConnectionPool::DownstreamConnectionPool() {} + +DownstreamConnectionPool::~DownstreamConnectionPool() { remove_all(); } + +void DownstreamConnectionPool::remove_all() { + for (auto dconn : pool_) { + delete dconn; + } + + pool_.clear(); +} + +void DownstreamConnectionPool::add_downstream_connection( + std::unique_ptr dconn) { + pool_.insert(dconn.release()); +} + +std::unique_ptr +DownstreamConnectionPool::pop_downstream_connection() { + if (pool_.empty()) { + return nullptr; + } + + auto it = std::begin(pool_); + auto dconn = std::unique_ptr(*it); + pool_.erase(it); + + return dconn; +} + +void DownstreamConnectionPool::remove_downstream_connection( + DownstreamConnection *dconn) { + pool_.erase(dconn); + delete dconn; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_downstream_connection_pool.h b/lib/nghttp2/src/shrpx_downstream_connection_pool.h new file mode 100644 index 00000000000..34dc30d8754 --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream_connection_pool.h @@ -0,0 +1,53 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 SHRPX_DOWNSTREAM_CONNECTION_POOL_H +#define SHRPX_DOWNSTREAM_CONNECTION_POOL_H + +#include "shrpx.h" + +#include +#include + +namespace shrpx { + +class DownstreamConnection; + +class DownstreamConnectionPool { +public: + DownstreamConnectionPool(); + ~DownstreamConnectionPool(); + + void add_downstream_connection(std::unique_ptr dconn); + std::unique_ptr pop_downstream_connection(); + void remove_downstream_connection(DownstreamConnection *dconn); + void remove_all(); + +private: + std::set pool_; +}; + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_CONNECTION_POOL_H diff --git a/lib/nghttp2/src/shrpx_downstream_queue.cc b/lib/nghttp2/src/shrpx_downstream_queue.cc new file mode 100644 index 00000000000..f8906e8e2e5 --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream_queue.cc @@ -0,0 +1,175 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_downstream_queue.h" + +#include +#include + +#include "shrpx_downstream.h" + +namespace shrpx { + +DownstreamQueue::HostEntry::HostEntry(ImmutableString &&key) + : key(std::move(key)), num_active(0) {} + +DownstreamQueue::DownstreamQueue(size_t conn_max_per_host, bool unified_host) + : conn_max_per_host_(conn_max_per_host == 0 + ? std::numeric_limits::max() + : conn_max_per_host), + unified_host_(unified_host) {} + +DownstreamQueue::~DownstreamQueue() { + dlist_delete_all(downstreams_); + for (auto &p : host_entries_) { + auto &ent = p.second; + dlist_delete_all(ent.blocked); + } +} + +void DownstreamQueue::add_pending(std::unique_ptr downstream) { + downstream->set_dispatch_state(DispatchState::PENDING); + downstreams_.append(downstream.release()); +} + +void DownstreamQueue::mark_failure(Downstream *downstream) { + downstream->set_dispatch_state(DispatchState::FAILURE); +} + +DownstreamQueue::HostEntry & +DownstreamQueue::find_host_entry(const StringRef &host) { + auto itr = host_entries_.find(host); + if (itr == std::end(host_entries_)) { + auto key = ImmutableString{std::begin(host), std::end(host)}; + auto key_ref = StringRef{key}; +#ifdef HAVE_STD_MAP_EMPLACE + std::tie(itr, std::ignore) = + host_entries_.emplace(key_ref, HostEntry(std::move(key))); +#else // !HAVE_STD_MAP_EMPLACE + // for g++-4.7 + std::tie(itr, std::ignore) = host_entries_.insert( + std::make_pair(key_ref, HostEntry(std::move(key)))); +#endif // !HAVE_STD_MAP_EMPLACE + } + return (*itr).second; +} + +StringRef DownstreamQueue::make_host_key(const StringRef &host) const { + return unified_host_ ? StringRef{} : host; +} + +StringRef DownstreamQueue::make_host_key(Downstream *downstream) const { + return make_host_key(downstream->request().authority); +} + +void DownstreamQueue::mark_active(Downstream *downstream) { + auto &ent = find_host_entry(make_host_key(downstream)); + ++ent.num_active; + + downstream->set_dispatch_state(DispatchState::ACTIVE); +} + +void DownstreamQueue::mark_blocked(Downstream *downstream) { + auto &ent = find_host_entry(make_host_key(downstream)); + + downstream->set_dispatch_state(DispatchState::BLOCKED); + + auto link = new BlockedLink{}; + downstream->attach_blocked_link(link); + ent.blocked.append(link); +} + +bool DownstreamQueue::can_activate(const StringRef &host) const { + auto itr = host_entries_.find(make_host_key(host)); + if (itr == std::end(host_entries_)) { + return true; + } + auto &ent = (*itr).second; + return ent.num_active < conn_max_per_host_; +} + +namespace { +bool remove_host_entry_if_empty(const DownstreamQueue::HostEntry &ent, + DownstreamQueue::HostEntryMap &host_entries, + const StringRef &host) { + if (ent.blocked.empty() && ent.num_active == 0) { + host_entries.erase(host); + return true; + } + return false; +} +} // namespace + +Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream, + bool next_blocked) { + // Delete downstream when this function returns. + auto delptr = std::unique_ptr(downstream); + + downstreams_.remove(downstream); + + auto host = make_host_key(downstream); + auto &ent = find_host_entry(host); + + if (downstream->get_dispatch_state() == DispatchState::ACTIVE) { + --ent.num_active; + } else { + // For those downstreams deleted while in blocked state + auto link = downstream->detach_blocked_link(); + if (link) { + ent.blocked.remove(link); + delete link; + } + } + + if (remove_host_entry_if_empty(ent, host_entries_, host)) { + return nullptr; + } + + if (!next_blocked || ent.num_active >= conn_max_per_host_) { + return nullptr; + } + + auto link = ent.blocked.head; + + if (!link) { + return nullptr; + } + + auto next_downstream = link->downstream; + auto link2 = next_downstream->detach_blocked_link(); + // This is required with --disable-assert. + (void)link2; + assert(link2 == link); + ent.blocked.remove(link); + delete link; + remove_host_entry_if_empty(ent, host_entries_, host); + + return next_downstream; +} + +Downstream *DownstreamQueue::get_downstreams() const { + return downstreams_.head; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_downstream_queue.h b/lib/nghttp2/src/shrpx_downstream_queue.h new file mode 100644 index 00000000000..a5b980fbb45 --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream_queue.h @@ -0,0 +1,116 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_DOWNSTREAM_QUEUE_H +#define SHRPX_DOWNSTREAM_QUEUE_H + +#include "shrpx.h" + +#include +#include +#include +#include + +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +class Downstream; + +// Link entry in HostEntry.blocked and downstream because downstream +// could be deleted in anytime and we'd like to find Downstream in +// O(1). Downstream has field to link back to this object. +struct BlockedLink { + Downstream *downstream; + BlockedLink *dlnext, *dlprev; +}; + +class DownstreamQueue { +public: + struct HostEntry { + HostEntry(ImmutableString &&key); + + HostEntry(HostEntry &&) = default; + HostEntry &operator=(HostEntry &&) = default; + + HostEntry(const HostEntry &) = delete; + HostEntry &operator=(const HostEntry &) = delete; + + // Key that associates this object + ImmutableString key; + // Set of stream ID that blocked by conn_max_per_host_. + DList blocked; + // The number of connections currently made to this host. + size_t num_active; + }; + + using HostEntryMap = std::map; + + // conn_max_per_host == 0 means no limit for downstream connection. + DownstreamQueue(size_t conn_max_per_host = 0, bool unified_host = true); + ~DownstreamQueue(); + // Add |downstream| to this queue. This is entry point for + // Downstream object. + void add_pending(std::unique_ptr downstream); + // Set |downstream| to failure state, which means that downstream + // failed to connect to backend. + void mark_failure(Downstream *downstream); + // Set |downstream| to active state, which means that downstream + // connection has started. + void mark_active(Downstream *downstream); + // Set |downstream| to blocked state, which means that download + // connection was blocked because conn_max_per_host_ limit. + void mark_blocked(Downstream *downstream); + // Returns true if we can make downstream connection to given + // |host|. + bool can_activate(const StringRef &host) const; + // Removes and frees |downstream| object. If |downstream| is in + // DispatchState::ACTIVE, and |next_blocked| is true, this function + // may return Downstream object with the same target host in + // DispatchState::BLOCKED if its connection is now not blocked by + // conn_max_per_host_ limit. + Downstream *remove_and_get_blocked(Downstream *downstream, + bool next_blocked = true); + Downstream *get_downstreams() const; + HostEntry &find_host_entry(const StringRef &host); + StringRef make_host_key(const StringRef &host) const; + StringRef make_host_key(Downstream *downstream) const; + +private: + // Per target host structure to keep track of the number of + // connections to the same host. + HostEntryMap host_entries_; + DList downstreams_; + // Maximum number of concurrent connections to the same host. + size_t conn_max_per_host_; + // true if downstream host is treated as the same. Used for reverse + // proxying. + bool unified_host_; +}; + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_QUEUE_H diff --git a/lib/nghttp2/src/shrpx_downstream_test.cc b/lib/nghttp2/src/shrpx_downstream_test.cc new file mode 100644 index 00000000000..6100b18050e --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream_test.cc @@ -0,0 +1,231 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "shrpx_downstream_test.h" + +#include + +#include + +#include "shrpx_downstream.h" + +namespace shrpx { + +void test_downstream_field_store_append_last_header(void) { + BlockAllocator balloc(16, 16); + FieldStore fs(balloc, 0); + fs.alloc_add_header_name(StringRef::from_lit("alpha")); + auto bravo = StringRef::from_lit("BRAVO"); + fs.append_last_header_key(bravo.c_str(), bravo.size()); + // Add more characters so that relloc occurs + auto golf = StringRef::from_lit("golF0123456789"); + fs.append_last_header_key(golf.c_str(), golf.size()); + + auto charlie = StringRef::from_lit("Charlie"); + fs.append_last_header_value(charlie.c_str(), charlie.size()); + auto delta = StringRef::from_lit("deltA"); + fs.append_last_header_value(delta.c_str(), delta.size()); + // Add more characters so that relloc occurs + auto echo = StringRef::from_lit("echo0123456789"); + fs.append_last_header_value(echo.c_str(), echo.size()); + + fs.add_header_token(StringRef::from_lit("echo"), + StringRef::from_lit("foxtrot"), false, -1); + + auto ans = + HeaderRefs{{StringRef::from_lit("alphabravogolf0123456789"), + StringRef::from_lit("CharliedeltAecho0123456789")}, + {StringRef::from_lit("echo"), StringRef::from_lit("foxtrot")}}; + CU_ASSERT(ans == fs.headers()); +} + +void test_downstream_field_store_header(void) { + BlockAllocator balloc(16, 16); + FieldStore fs(balloc, 0); + fs.add_header_token(StringRef::from_lit("alpha"), StringRef::from_lit("0"), + false, -1); + fs.add_header_token(StringRef::from_lit(":authority"), + StringRef::from_lit("1"), false, http2::HD__AUTHORITY); + fs.add_header_token(StringRef::from_lit("content-length"), + StringRef::from_lit("2"), false, + http2::HD_CONTENT_LENGTH); + + // By token + CU_ASSERT(HeaderRef(StringRef{":authority"}, StringRef{"1"}) == + *fs.header(http2::HD__AUTHORITY)); + CU_ASSERT(nullptr == fs.header(http2::HD__METHOD)); + + // By name + CU_ASSERT(HeaderRef(StringRef{"alpha"}, StringRef{"0"}) == + *fs.header(StringRef::from_lit("alpha"))); + CU_ASSERT(nullptr == fs.header(StringRef::from_lit("bravo"))); +} + +void test_downstream_crumble_request_cookie(void) { + Downstream d(nullptr, nullptr, 0); + auto &req = d.request(); + req.fs.add_header_token(StringRef::from_lit(":method"), + StringRef::from_lit("get"), false, -1); + req.fs.add_header_token(StringRef::from_lit(":path"), + StringRef::from_lit("/"), false, -1); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("alpha; bravo; ; ;; charlie;;"), + true, http2::HD_COOKIE); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit(";delta"), false, + http2::HD_COOKIE); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("echo"), false, http2::HD_COOKIE); + + std::vector nva; + d.crumble_request_cookie(nva); + + auto num_cookies = d.count_crumble_request_cookie(); + + CU_ASSERT(5 == nva.size()); + CU_ASSERT(5 == num_cookies); + + HeaderRefs cookies; + std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies), + [](const nghttp2_nv &nv) { + return HeaderRef(StringRef{nv.name, nv.namelen}, + StringRef{nv.value, nv.valuelen}, + nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); + }); + + HeaderRefs ans = { + {StringRef::from_lit("cookie"), StringRef::from_lit("alpha")}, + {StringRef::from_lit("cookie"), StringRef::from_lit("bravo")}, + {StringRef::from_lit("cookie"), StringRef::from_lit("charlie")}, + {StringRef::from_lit("cookie"), StringRef::from_lit("delta")}, + {StringRef::from_lit("cookie"), StringRef::from_lit("echo")}}; + + CU_ASSERT(ans == cookies); + CU_ASSERT(cookies[0].no_index); + CU_ASSERT(cookies[1].no_index); + CU_ASSERT(cookies[2].no_index); +} + +void test_downstream_assemble_request_cookie(void) { + Downstream d(nullptr, nullptr, 0); + auto &req = d.request(); + + req.fs.add_header_token(StringRef::from_lit(":method"), + StringRef::from_lit("get"), false, -1); + req.fs.add_header_token(StringRef::from_lit(":path"), + StringRef::from_lit("/"), false, -1); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("alpha"), false, + http2::HD_COOKIE); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("bravo;"), false, + http2::HD_COOKIE); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("charlie; "), false, + http2::HD_COOKIE); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("delta;;"), false, + http2::HD_COOKIE); + CU_ASSERT("alpha; bravo; charlie; delta" == d.assemble_request_cookie()); +} + +void test_downstream_rewrite_location_response_header(void) { + Downstream d(nullptr, nullptr, 0); + auto &req = d.request(); + auto &resp = d.response(); + d.set_request_downstream_host(StringRef::from_lit("localhost2")); + req.authority = StringRef::from_lit("localhost:8443"); + resp.fs.add_header_token(StringRef::from_lit("location"), + StringRef::from_lit("http://localhost2:3000/"), + false, http2::HD_LOCATION); + d.rewrite_location_response_header(StringRef::from_lit("https")); + auto location = resp.fs.header(http2::HD_LOCATION); + CU_ASSERT("https://localhost:8443/" == (*location).value); +} + +void test_downstream_supports_non_final_response(void) { + Downstream d(nullptr, nullptr, 0); + auto &req = d.request(); + + req.http_major = 3; + req.http_minor = 0; + + CU_ASSERT(d.supports_non_final_response()); + + req.http_major = 2; + req.http_minor = 0; + + CU_ASSERT(d.supports_non_final_response()); + + req.http_major = 1; + req.http_minor = 1; + + CU_ASSERT(d.supports_non_final_response()); + + req.http_major = 1; + req.http_minor = 0; + + CU_ASSERT(!d.supports_non_final_response()); + + req.http_major = 0; + req.http_minor = 9; + + CU_ASSERT(!d.supports_non_final_response()); +} + +void test_downstream_find_affinity_cookie(void) { + Downstream d(nullptr, nullptr, 0); + + auto &req = d.request(); + req.fs.add_header_token(StringRef::from_lit("cookie"), StringRef{}, false, + http2::HD_COOKIE); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("a=b;;c=d"), false, + http2::HD_COOKIE); + req.fs.add_header_token(StringRef::from_lit("content-length"), + StringRef::from_lit("599"), false, + http2::HD_CONTENT_LENGTH); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("lb=deadbeef;LB=f1f2f3f4"), false, + http2::HD_COOKIE); + req.fs.add_header_token(StringRef::from_lit("cookie"), + StringRef::from_lit("short=e1e2e3e"), false, + http2::HD_COOKIE); + + uint32_t aff; + + aff = d.find_affinity_cookie(StringRef::from_lit("lb")); + + CU_ASSERT(0xdeadbeef == aff); + + aff = d.find_affinity_cookie(StringRef::from_lit("LB")); + + CU_ASSERT(0xf1f2f3f4 == aff); + + aff = d.find_affinity_cookie(StringRef::from_lit("short")); + + CU_ASSERT(0 == aff); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_downstream_test.h b/lib/nghttp2/src/shrpx_downstream_test.h new file mode 100644 index 00000000000..ef06ea301a9 --- /dev/null +++ b/lib/nghttp2/src/shrpx_downstream_test.h @@ -0,0 +1,44 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 SHRPX_DOWNSTREAM_TEST_H +#define SHRPX_DOWNSTREAM_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_downstream_field_store_append_last_header(void); +void test_downstream_field_store_header(void); +void test_downstream_crumble_request_cookie(void); +void test_downstream_assemble_request_cookie(void); +void test_downstream_rewrite_location_response_header(void); +void test_downstream_supports_non_final_response(void); +void test_downstream_find_affinity_cookie(void); + +} // namespace shrpx + +#endif // SHRPX_DOWNSTREAM_TEST_H diff --git a/lib/nghttp2/src/shrpx_dual_dns_resolver.cc b/lib/nghttp2/src/shrpx_dual_dns_resolver.cc new file mode 100644 index 00000000000..8c6c5c9b308 --- /dev/null +++ b/lib/nghttp2/src/shrpx_dual_dns_resolver.cc @@ -0,0 +1,93 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_dual_dns_resolver.h" + +namespace shrpx { + +DualDNSResolver::DualDNSResolver(struct ev_loop *loop, int family) + : family_(family), resolv4_(loop), resolv6_(loop) { + auto cb = [this](DNSResolverStatus, const Address *) { + Address result; + + auto status = this->get_status(&result); + switch (status) { + case DNSResolverStatus::ERROR: + case DNSResolverStatus::OK: + break; + default: + return; + } + + auto cb = this->get_complete_cb(); + cb(status, &result); + }; + + if (family_ == AF_UNSPEC || family_ == AF_INET) { + resolv4_.set_complete_cb(cb); + } + if (family_ == AF_UNSPEC || family_ == AF_INET6) { + resolv6_.set_complete_cb(cb); + } +} + +int DualDNSResolver::resolve(const StringRef &host) { + int rv4 = 0, rv6 = 0; + if (family_ == AF_UNSPEC || family_ == AF_INET) { + rv4 = resolv4_.resolve(host, AF_INET); + } + if (family_ == AF_UNSPEC || family_ == AF_INET6) { + rv6 = resolv6_.resolve(host, AF_INET6); + } + + if (rv4 != 0 && rv6 != 0) { + return -1; + } + + return 0; +} + +CompleteCb DualDNSResolver::get_complete_cb() const { return complete_cb_; } + +void DualDNSResolver::set_complete_cb(CompleteCb cb) { complete_cb_ = cb; } + +DNSResolverStatus DualDNSResolver::get_status(Address *result) const { + auto rv6 = resolv6_.get_status(result); + if (rv6 == DNSResolverStatus::OK) { + return DNSResolverStatus::OK; + } + auto rv4 = resolv4_.get_status(result); + if (rv4 == DNSResolverStatus::OK) { + return DNSResolverStatus::OK; + } + if (rv4 == DNSResolverStatus::RUNNING || rv6 == DNSResolverStatus::RUNNING) { + return DNSResolverStatus::RUNNING; + } + if (rv4 == DNSResolverStatus::ERROR || rv6 == DNSResolverStatus::ERROR) { + return DNSResolverStatus::ERROR; + } + return DNSResolverStatus::IDLE; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_dual_dns_resolver.h b/lib/nghttp2/src/shrpx_dual_dns_resolver.h new file mode 100644 index 00000000000..98065dab160 --- /dev/null +++ b/lib/nghttp2/src/shrpx_dual_dns_resolver.h @@ -0,0 +1,69 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_DUAL_DNS_RESOLVER_H +#define SHRPX_DUAL_DNS_RESOLVER_H + +#include "shrpx.h" + +#include + +#include "shrpx_dns_resolver.h" + +using namespace nghttp2; + +namespace shrpx { + +// DualDNSResolver performs name resolution for both A and AAAA +// records at the same time. The first successful return (or if we +// have both successful results, prefer to AAAA) is chosen. This is +// wrapper around 2 DNSResolver inside. resolve(), get_status(), and +// how CompleteCb is called have the same semantics with DNSResolver. +class DualDNSResolver { +public: + // |family| controls IP version preference. If |family| == + // AF_UNSPEC, bot A and AAAA lookups are performed. If |family| == + // AF_INET, only A lookup is performed. If |family| == AF_INET6, + // only AAAA lookup is performed. + DualDNSResolver(struct ev_loop *loop, int family); + + // Resolves |host|. |host| must be NULL-terminated string. + int resolve(const StringRef &host); + CompleteCb get_complete_cb() const; + void set_complete_cb(CompleteCb cb); + DNSResolverStatus get_status(Address *result) const; + +private: + // IP version preference. + int family_; + // For A record + DNSResolver resolv4_; + // For AAAA record + DNSResolver resolv6_; + CompleteCb complete_cb_; +}; + +} // namespace shrpx + +#endif // SHRPX_DUAL_DNS_RESOLVER_H diff --git a/lib/nghttp2/src/shrpx_error.h b/lib/nghttp2/src/shrpx_error.h new file mode 100644 index 00000000000..c89611f1ca7 --- /dev/null +++ b/lib/nghttp2/src/shrpx_error.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_ERROR_H +#define SHRPX_ERROR_H + +#include "shrpx.h" + +namespace shrpx { + +// Deprecated, do not use. +enum ErrorCode { + SHRPX_ERR_SUCCESS = 0, + SHRPX_ERR_ERROR = -1, + SHRPX_ERR_NETWORK = -100, + SHRPX_ERR_EOF = -101, + SHRPX_ERR_INPROGRESS = -102, + SHRPX_ERR_DCONN_CANCELED = -103, + SHRPX_ERR_RETRY = -104, + SHRPX_ERR_TLS_REQUIRED = -105, + SHRPX_ERR_SEND_BLOCKED = -106, +}; + +} // namespace shrpx + +#endif // SHRPX_ERROR_H diff --git a/lib/nghttp2/src/shrpx_exec.cc b/lib/nghttp2/src/shrpx_exec.cc new file mode 100644 index 00000000000..d5cd0916b2b --- /dev/null +++ b/lib/nghttp2/src/shrpx_exec.cc @@ -0,0 +1,138 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_exec.h" + +#include + +#include "shrpx_signal.h" +#include "shrpx_log.h" +#include "util.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +// inspired by h2o_read_command function from h2o project: +// https://github.com/h2o/h2o +int exec_read_command(Process &proc, char *const argv[]) { + int rv; + int pfd[2]; + +#ifdef O_CLOEXEC + if (pipe2(pfd, O_CLOEXEC) == -1) { + return -1; + } +#else // !O_CLOEXEC + if (pipe(pfd) == -1) { + return -1; + } + util::make_socket_closeonexec(pfd[0]); + util::make_socket_closeonexec(pfd[1]); +#endif // !O_CLOEXEC + + auto closer = defer([&pfd]() { + if (pfd[0] != -1) { + close(pfd[0]); + } + + if (pfd[1] != -1) { + close(pfd[1]); + } + }); + + sigset_t oldset; + + rv = shrpx_signal_block_all(&oldset); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Blocking all signals failed: errno=" << error; + + return -1; + } + + auto pid = fork(); + + if (pid == 0) { + // This is multithreaded program, and we are allowed to use only + // async-signal-safe functions here. + + // child process + shrpx_signal_unset_worker_proc_ign_handler(); + + rv = shrpx_signal_unblock_all(); + if (rv != 0) { + static constexpr char msg[] = "Unblocking all signals failed\n"; + while (write(STDERR_FILENO, msg, str_size(msg)) == -1 && errno == EINTR) + ; + nghttp2_Exit(EXIT_FAILURE); + } + + dup2(pfd[1], 1); + close(pfd[0]); + + rv = execv(argv[0], argv); + if (rv == -1) { + static constexpr char msg[] = "Could not execute command\n"; + while (write(STDERR_FILENO, msg, str_size(msg)) == -1 && errno == EINTR) + ; + nghttp2_Exit(EXIT_FAILURE); + } + // unreachable + } + + // parent process + if (pid == -1) { + auto error = errno; + LOG(ERROR) << "Could not execute command: " << argv[0] + << ", fork() failed, errno=" << error; + } + + rv = shrpx_signal_set(&oldset); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Restoring all signals failed: errno=" << error; + + nghttp2_Exit(EXIT_FAILURE); + } + + if (pid == -1) { + return -1; + } + + close(pfd[1]); + pfd[1] = -1; + + util::make_socket_nonblocking(pfd[0]); + + proc.pid = pid; + proc.rfd = pfd[0]; + + pfd[0] = -1; + + return 0; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_exec.h b/lib/nghttp2/src/shrpx_exec.h new file mode 100644 index 00000000000..2d8a7701755 --- /dev/null +++ b/lib/nghttp2/src/shrpx_exec.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_EXEC_H +#define SHRPX_EXEC_H + +#include "unistd.h" + +namespace shrpx { + +struct Process { + pid_t pid; + // fd to read from process + int rfd; +}; + +// Executes command |argv| after forking current process. The command +// should not expect to read from stdin. Parent process can read the +// stdout from command using proc.rfd. On success, this function +// returns 0, and process information is stored in |proc|. Otherwise, +// returns -1. +int exec_read_command(Process &proc, char *const argv[]); + +} // namespace shrpx + +#endif // SHRPX_EXEC_H diff --git a/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.cc b/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.cc new file mode 100644 index 00000000000..89e539630ad --- /dev/null +++ b/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.cc @@ -0,0 +1,116 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_health_monitor_downstream_connection.h" + +#include "shrpx_client_handler.h" +#include "shrpx_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_log.h" + +namespace shrpx { + +HealthMonitorDownstreamConnection::HealthMonitorDownstreamConnection() {} + +HealthMonitorDownstreamConnection::~HealthMonitorDownstreamConnection() {} + +int HealthMonitorDownstreamConnection::attach_downstream( + Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; + } + + downstream_ = downstream; + + return 0; +} + +void HealthMonitorDownstreamConnection::detach_downstream( + Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream; + } + downstream_ = nullptr; +} + +int HealthMonitorDownstreamConnection::push_request_headers() { + downstream_->set_request_header_sent(true); + auto src = downstream_->get_blocked_request_buf(); + auto dest = downstream_->get_request_buf(); + src->remove(*dest); + + return 0; +} + +int HealthMonitorDownstreamConnection::push_upload_data_chunk( + const uint8_t *data, size_t datalen) { + return 0; +} + +int HealthMonitorDownstreamConnection::end_upload_data() { + auto upstream = downstream_->get_upstream(); + auto &resp = downstream_->response(); + + resp.http_status = 200; + + resp.fs.add_header_token(StringRef::from_lit("content-length"), + StringRef::from_lit("0"), false, + http2::HD_CONTENT_LENGTH); + + if (upstream->send_reply(downstream_, nullptr, 0) != 0) { + return -1; + } + + return 0; +} + +void HealthMonitorDownstreamConnection::pause_read(IOCtrlReason reason) {} + +int HealthMonitorDownstreamConnection::resume_read(IOCtrlReason reason, + size_t consumed) { + return 0; +} + +void HealthMonitorDownstreamConnection::force_resume_read() {} + +int HealthMonitorDownstreamConnection::on_read() { return 0; } + +int HealthMonitorDownstreamConnection::on_write() { return 0; } + +void HealthMonitorDownstreamConnection::on_upstream_change(Upstream *upstream) { +} + +bool HealthMonitorDownstreamConnection::poolable() const { return false; } + +const std::shared_ptr & +HealthMonitorDownstreamConnection::get_downstream_addr_group() const { + static std::shared_ptr s; + return s; +} + +DownstreamAddr *HealthMonitorDownstreamConnection::get_addr() const { + return nullptr; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.h b/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.h new file mode 100644 index 00000000000..c0eb633459d --- /dev/null +++ b/lib/nghttp2/src/shrpx_health_monitor_downstream_connection.h @@ -0,0 +1,64 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H +#define SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H + +#include "shrpx_downstream_connection.h" + +namespace shrpx { + +class Worker; + +class HealthMonitorDownstreamConnection : public DownstreamConnection { +public: + HealthMonitorDownstreamConnection(); + virtual ~HealthMonitorDownstreamConnection(); + virtual int attach_downstream(Downstream *downstream); + virtual void detach_downstream(Downstream *downstream); + + virtual int push_request_headers(); + virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen); + virtual int end_upload_data(); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, size_t consumed); + virtual void force_resume_read(); + + virtual int on_read(); + virtual int on_write(); + + virtual void on_upstream_change(Upstream *upstream); + + // true if this object is poolable. + virtual bool poolable() const; + + virtual const std::shared_ptr & + get_downstream_addr_group() const; + virtual DownstreamAddr *get_addr() const; +}; + +} // namespace shrpx + +#endif // SHRPX_HEALTH_MONITOR_DOWNSTREAM_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_http.cc b/lib/nghttp2/src/shrpx_http.cc new file mode 100644 index 00000000000..ad32dc9e191 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http.cc @@ -0,0 +1,280 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_http.h" + +#include "shrpx_config.h" +#include "shrpx_log.h" +#include "http2.h" +#include "util.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace http { + +StringRef create_error_html(BlockAllocator &balloc, unsigned int http_status) { + auto &httpconf = get_config()->http; + + const auto &error_pages = httpconf.error_pages; + for (const auto &page : error_pages) { + if (page.http_status == 0 || page.http_status == http_status) { + return StringRef{std::begin(page.content), std::end(page.content)}; + } + } + + auto status_string = http2::stringify_status(balloc, http_status); + auto reason_phrase = http2::get_reason_phrase(http_status); + + return concat_string_ref( + balloc, StringRef::from_lit(R"()"), + status_string, StringRef::from_lit(" "), reason_phrase, + StringRef::from_lit("

"), status_string, + StringRef::from_lit(" "), reason_phrase, + StringRef::from_lit("

"), httpconf.server_name, + StringRef::from_lit("
")); +} + +StringRef create_forwarded(BlockAllocator &balloc, int params, + const StringRef &node_by, const StringRef &node_for, + const StringRef &host, const StringRef &proto) { + size_t len = 0; + if ((params & FORWARDED_BY) && !node_by.empty()) { + len += str_size("by=\"") + node_by.size() + str_size("\";"); + } + if ((params & FORWARDED_FOR) && !node_for.empty()) { + len += str_size("for=\"") + node_for.size() + str_size("\";"); + } + if ((params & FORWARDED_HOST) && !host.empty()) { + len += str_size("host=\"") + host.size() + str_size("\";"); + } + if ((params & FORWARDED_PROTO) && !proto.empty()) { + len += str_size("proto=") + proto.size() + str_size(";"); + } + + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + + if ((params & FORWARDED_BY) && !node_by.empty()) { + // This must be quoted-string unless it is obfuscated version + // (which starts with "_") or some special value (e.g., + // "localhost" for UNIX domain socket), since ':' is not allowed + // in token. ':' is used to separate host and port. + if (node_by[0] == '_' || node_by[0] == 'l') { + p = util::copy_lit(p, "by="); + p = std::copy(std::begin(node_by), std::end(node_by), p); + p = util::copy_lit(p, ";"); + } else { + p = util::copy_lit(p, "by=\""); + p = std::copy(std::begin(node_by), std::end(node_by), p); + p = util::copy_lit(p, "\";"); + } + } + if ((params & FORWARDED_FOR) && !node_for.empty()) { + // We only quote IPv6 literal address only, which starts with '['. + if (node_for[0] == '[') { + p = util::copy_lit(p, "for=\""); + p = std::copy(std::begin(node_for), std::end(node_for), p); + p = util::copy_lit(p, "\";"); + } else { + p = util::copy_lit(p, "for="); + p = std::copy(std::begin(node_for), std::end(node_for), p); + p = util::copy_lit(p, ";"); + } + } + if ((params & FORWARDED_HOST) && !host.empty()) { + // Just be quoted to skip checking characters. + p = util::copy_lit(p, "host=\""); + p = std::copy(std::begin(host), std::end(host), p); + p = util::copy_lit(p, "\";"); + } + if ((params & FORWARDED_PROTO) && !proto.empty()) { + // Scheme production rule only allow characters which are all in + // token. + p = util::copy_lit(p, "proto="); + p = std::copy(std::begin(proto), std::end(proto), p); + *p++ = ';'; + } + + if (iov.base == p) { + return StringRef{}; + } + + --p; + *p = '\0'; + + return StringRef{iov.base, p}; +} + +std::string colorizeHeaders(const char *hdrs) { + std::string nhdrs; + const char *p = strchr(hdrs, '\n'); + if (!p) { + // Not valid HTTP header + return hdrs; + } + nhdrs.append(hdrs, p + 1); + ++p; + while (1) { + const char *np = strchr(p, ':'); + if (!np) { + nhdrs.append(p); + break; + } + nhdrs += TTY_HTTP_HD; + nhdrs.append(p, np); + nhdrs += TTY_RST; + auto redact = util::strieq_l("authorization", StringRef{p, np}); + p = np; + np = strchr(p, '\n'); + if (!np) { + if (redact) { + nhdrs.append(": "); + } else { + nhdrs.append(p); + } + break; + } + if (redact) { + nhdrs.append(": \n"); + } else { + nhdrs.append(p, np + 1); + } + p = np + 1; + } + return nhdrs; +} + +ssize_t select_padding_callback(nghttp2_session *session, + const nghttp2_frame *frame, size_t max_payload, + void *user_data) { + return std::min(max_payload, frame->hd.length + get_config()->padding); +} + +StringRef create_affinity_cookie(BlockAllocator &balloc, const StringRef &name, + uint32_t affinity_cookie, + const StringRef &path, bool secure) { + static constexpr auto PATH_PREFIX = StringRef::from_lit("; Path="); + static constexpr auto SECURE = StringRef::from_lit("; Secure"); + // =[; Path=][; Secure] + size_t len = name.size() + 1 + 8; + + if (!path.empty()) { + len += PATH_PREFIX.size() + path.size(); + } + if (secure) { + len += SECURE.size(); + } + + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + p = std::copy(std::begin(name), std::end(name), p); + *p++ = '='; + affinity_cookie = htonl(affinity_cookie); + p = util::format_hex(p, + StringRef{reinterpret_cast(&affinity_cookie), + reinterpret_cast(&affinity_cookie) + + sizeof(affinity_cookie)}); + if (!path.empty()) { + p = std::copy(std::begin(PATH_PREFIX), std::end(PATH_PREFIX), p); + p = std::copy(std::begin(path), std::end(path), p); + } + if (secure) { + p = std::copy(std::begin(SECURE), std::end(SECURE), p); + } + *p = '\0'; + return StringRef{iov.base, p}; +} + +bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure, + const StringRef &scheme) { + switch (secure) { + case SessionAffinityCookieSecure::AUTO: + return scheme == "https"; + case SessionAffinityCookieSecure::YES: + return true; + default: + return false; + } +} + +StringRef create_altsvc_header_value(BlockAllocator &balloc, + const std::vector &altsvcs) { + // =":"; + size_t len = 0; + + if (altsvcs.empty()) { + return StringRef{}; + } + + for (auto &altsvc : altsvcs) { + len += util::percent_encode_tokenlen(altsvc.protocol_id); + len += str_size("=\""); + len += util::quote_stringlen(altsvc.host); + len += str_size(":"); + len += altsvc.service.size(); + len += str_size("\""); + if (!altsvc.params.empty()) { + len += str_size("; "); + len += altsvc.params.size(); + } + } + + // ", " between items. + len += (altsvcs.size() - 1) * 2; + + // We will write additional ", " at the end, and cut it later. + auto iov = make_byte_ref(balloc, len + 2); + auto p = iov.base; + + for (auto &altsvc : altsvcs) { + p = util::percent_encode_token(p, altsvc.protocol_id); + p = util::copy_lit(p, "=\""); + p = util::quote_string(p, altsvc.host); + *p++ = ':'; + p = std::copy(std::begin(altsvc.service), std::end(altsvc.service), p); + *p++ = '"'; + if (!altsvc.params.empty()) { + p = util::copy_lit(p, "; "); + p = std::copy(std::begin(altsvc.params), std::end(altsvc.params), p); + } + p = util::copy_lit(p, ", "); + } + + p -= 2; + *p = '\0'; + + assert(static_cast(p - iov.base) == len); + + return StringRef{iov.base, p}; +} + +bool check_http_scheme(const StringRef &scheme, bool encrypted) { + return encrypted ? scheme == "https" : scheme == "http"; +} + +} // namespace http + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_http.h b/lib/nghttp2/src/shrpx_http.h new file mode 100644 index 00000000000..18935d82796 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http.h @@ -0,0 +1,96 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTP_H +#define SHRPX_HTTP_H + +#include "shrpx.h" + +#include + +#include + +#include "shrpx_config.h" +#include "util.h" +#include "allocator.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace http { + +StringRef create_error_html(BlockAllocator &balloc, unsigned int status_code); + +template +OutputIt create_via_header_value(OutputIt dst, int major, int minor) { + *dst++ = static_cast(major + '0'); + if (major < 2) { + *dst++ = '.'; + *dst++ = static_cast(minor + '0'); + } + return util::copy_lit(dst, " nghttpx"); +} + +// Returns generated RFC 7239 Forwarded header field value. The +// |params| is bitwise-OR of zero or more of shrpx_forwarded_param +// defined in shrpx_config.h. +StringRef create_forwarded(BlockAllocator &balloc, int params, + const StringRef &node_by, const StringRef &node_for, + const StringRef &host, const StringRef &proto); + +// Adds ANSI color codes to HTTP headers |hdrs|. +std::string colorizeHeaders(const char *hdrs); + +ssize_t select_padding_callback(nghttp2_session *session, + const nghttp2_frame *frame, size_t max_payload, + void *user_data); + +// Creates set-cookie-string for cookie based affinity. If |path| is +// not empty, "; " is added. If |secure| is true, "; Secure" is +// added. +StringRef create_affinity_cookie(BlockAllocator &balloc, const StringRef &name, + uint32_t affinity_cookie, + const StringRef &path, bool secure); + +// Returns true if |secure| indicates that Secure attribute should be +// set. +bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure, + const StringRef &scheme); + +// Returns RFC 7838 alt-svc header field value. +StringRef create_altsvc_header_value(BlockAllocator &balloc, + const std::vector &altsvcs); + +// Returns true if either of the following conditions holds: +// - scheme is https and encrypted is true +// - scheme is http and encrypted is false +// Otherwise returns false. +bool check_http_scheme(const StringRef &scheme, bool encrypted); + +} // namespace http + +} // namespace shrpx + +#endif // SHRPX_HTTP_H diff --git a/lib/nghttp2/src/shrpx_http2_downstream_connection.cc b/lib/nghttp2/src/shrpx_http2_downstream_connection.cc new file mode 100644 index 00000000000..d27dcc114d5 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http2_downstream_connection.cc @@ -0,0 +1,621 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_http2_downstream_connection.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H + +#include "llhttp.h" + +#include "shrpx_client_handler.h" +#include "shrpx_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_config.h" +#include "shrpx_error.h" +#include "shrpx_http.h" +#include "shrpx_http2_session.h" +#include "shrpx_worker.h" +#include "shrpx_log.h" +#include "http2.h" +#include "util.h" +#include "ssl_compat.h" + +using namespace nghttp2; + +namespace shrpx { + +Http2DownstreamConnection::Http2DownstreamConnection(Http2Session *http2session) + : dlnext(nullptr), + dlprev(nullptr), + http2session_(http2session), + sd_(nullptr) {} + +Http2DownstreamConnection::~Http2DownstreamConnection() { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Deleting"; + } + if (downstream_) { + downstream_->disable_downstream_rtimer(); + downstream_->disable_downstream_wtimer(); + + uint32_t error_code; + if (downstream_->get_request_state() == DownstreamState::STREAM_CLOSED && + downstream_->get_upgraded()) { + // For upgraded connection, send NO_ERROR. Should we consider + // request states other than DownstreamState::STREAM_CLOSED ? + error_code = NGHTTP2_NO_ERROR; + } else { + error_code = NGHTTP2_INTERNAL_ERROR; + } + + if (http2session_->get_state() == Http2SessionState::CONNECTED && + downstream_->get_downstream_stream_id() != -1) { + submit_rst_stream(downstream_, error_code); + + auto &resp = downstream_->response(); + + http2session_->consume(downstream_->get_downstream_stream_id(), + resp.unconsumed_body_length); + + resp.unconsumed_body_length = 0; + + http2session_->signal_write(); + } + } + http2session_->remove_downstream_connection(this); + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Deleted"; + } +} + +int Http2DownstreamConnection::attach_downstream(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; + } + http2session_->add_downstream_connection(this); + http2session_->signal_write(); + + downstream_ = downstream; + downstream_->reset_downstream_rtimer(); + + auto &req = downstream_->request(); + + // HTTP/2 disables HTTP Upgrade. + if (req.method != HTTP_CONNECT && req.connect_proto == ConnectProto::NONE) { + req.upgrade_request = false; + } + + return 0; +} + +void Http2DownstreamConnection::detach_downstream(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream; + } + + auto &resp = downstream_->response(); + + if (downstream_->get_downstream_stream_id() != -1) { + if (submit_rst_stream(downstream) == 0) { + http2session_->signal_write(); + } + + http2session_->consume(downstream_->get_downstream_stream_id(), + resp.unconsumed_body_length); + + resp.unconsumed_body_length = 0; + + http2session_->signal_write(); + } + + downstream->disable_downstream_rtimer(); + downstream->disable_downstream_wtimer(); + downstream_ = nullptr; +} + +int Http2DownstreamConnection::submit_rst_stream(Downstream *downstream, + uint32_t error_code) { + int rv = -1; + if (http2session_->get_state() == Http2SessionState::CONNECTED && + downstream->get_downstream_stream_id() != -1) { + switch (downstream->get_response_state()) { + case DownstreamState::MSG_RESET: + case DownstreamState::MSG_BAD_HEADER: + case DownstreamState::MSG_COMPLETE: + break; + default: + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream + << ", stream_id=" + << downstream->get_downstream_stream_id() + << ", error_code=" << error_code; + } + rv = http2session_->submit_rst_stream( + downstream->get_downstream_stream_id(), error_code); + } + } + return rv; +} + +namespace { +ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id, + uint8_t *buf, size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) { + int rv; + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + if (!sd || !sd->dconn) { + return NGHTTP2_ERR_DEFERRED; + } + auto dconn = sd->dconn; + auto downstream = dconn->get_downstream(); + if (!downstream) { + // In this case, RST_STREAM should have been issued. But depending + // on the priority, DATA frame may come first. + return NGHTTP2_ERR_DEFERRED; + } + const auto &req = downstream->request(); + auto input = downstream->get_request_buf(); + + auto nread = std::min(input->rleft(), length); + auto input_empty = input->rleft() == nread; + + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + + if (input_empty && + downstream->get_request_state() == DownstreamState::MSG_COMPLETE && + // If connection is upgraded, don't set EOF flag, since HTTP/1 + // will set MSG_COMPLETE to request state after upgrade response + // header is seen. + (!req.upgrade_request || + (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE && + !downstream->get_upgraded()))) { + + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + const auto &trailers = req.fs.trailers(); + if (!trailers.empty()) { + std::vector nva; + nva.reserve(trailers.size()); + http2::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL); + if (!nva.empty()) { + rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size()); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } else { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + } + } + } + + if (nread == 0 && (*data_flags & NGHTTP2_DATA_FLAG_EOF) == 0) { + downstream->disable_downstream_wtimer(); + + return NGHTTP2_ERR_DEFERRED; + } + + return nread; +} +} // namespace + +int Http2DownstreamConnection::push_request_headers() { + int rv; + if (!downstream_) { + return 0; + } + if (!http2session_->can_push_request(downstream_)) { + // The HTTP2 session to the backend has not been established or + // connection is now being checked. This function will be called + // again just after it is established. + downstream_->set_request_pending(true); + http2session_->start_checking_connection(); + return 0; + } + + downstream_->set_request_pending(false); + + const auto &req = downstream_->request(); + + if (req.connect_proto != ConnectProto::NONE && + !http2session_->get_allow_connect_proto()) { + return -1; + } + + auto &balloc = downstream_->get_block_allocator(); + + auto config = get_config(); + auto &httpconf = config->http; + auto &http2conf = config->http2; + + auto no_host_rewrite = httpconf.no_host_rewrite || config->http2_proxy || + req.regular_connect_method(); + + // http2session_ has already in CONNECTED state, so we can get + // addr_idx here. + const auto &downstream_hostport = http2session_->get_addr()->hostport; + + // For HTTP/1.0 request, there is no authority in request. In that + // case, we use backend server's host nonetheless. + auto authority = StringRef(downstream_hostport); + + if (no_host_rewrite && !req.authority.empty()) { + authority = req.authority; + } + + downstream_->set_request_downstream_host(authority); + + size_t num_cookies = 0; + if (!http2conf.no_cookie_crumbling) { + num_cookies = downstream_->count_crumble_request_cookie(); + } + + // 11 means: + // 1. :method + // 2. :scheme + // 3. :path + // 4. :authority (or host) + // 5. :protocol (optional) + // 6. via (optional) + // 7. x-forwarded-for (optional) + // 8. x-forwarded-proto (optional) + // 9. te (optional) + // 10. forwarded (optional) + // 11. early-data (optional) + auto nva = std::vector(); + nva.reserve(req.fs.headers().size() + 11 + num_cookies + + httpconf.add_request_headers.size()); + + if (req.connect_proto == ConnectProto::WEBSOCKET) { + nva.push_back(http2::make_nv_ll(":method", "CONNECT")); + nva.push_back(http2::make_nv_ll(":protocol", "websocket")); + } else { + nva.push_back(http2::make_nv_ls_nocopy( + ":method", http2::to_method_string(req.method))); + } + + if (!req.regular_connect_method()) { + assert(!req.scheme.empty()); + + auto addr = http2session_->get_addr(); + assert(addr); + // We will handle more protocol scheme upgrade in the future. + if (addr->tls && addr->upgrade_scheme && req.scheme == "http") { + nva.push_back(http2::make_nv_ll(":scheme", "https")); + } else { + nva.push_back(http2::make_nv_ls_nocopy(":scheme", req.scheme)); + } + + if (req.method == HTTP_OPTIONS && req.path.empty()) { + nva.push_back(http2::make_nv_ll(":path", "*")); + } else { + nva.push_back(http2::make_nv_ls_nocopy(":path", req.path)); + } + + if (!req.no_authority || req.connect_proto != ConnectProto::NONE) { + nva.push_back(http2::make_nv_ls_nocopy(":authority", authority)); + } else { + nva.push_back(http2::make_nv_ls_nocopy("host", authority)); + } + } else { + nva.push_back(http2::make_nv_ls_nocopy(":authority", authority)); + } + + auto &fwdconf = httpconf.forwarded; + auto &xffconf = httpconf.xff; + auto &xfpconf = httpconf.xfp; + auto &earlydataconf = httpconf.early_data; + + uint32_t build_flags = + (fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) | + (xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) | + (xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) | + (earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0) | + http2::HDOP_STRIP_SEC_WEBSOCKET_KEY; + + http2::copy_headers_to_nva_nocopy(nva, req.fs.headers(), build_flags); + + if (!http2conf.no_cookie_crumbling) { + downstream_->crumble_request_cookie(nva); + } + + auto upstream = downstream_->get_upstream(); + auto handler = upstream->get_client_handler(); + +#if OPENSSL_1_1_1_API + auto conn = handler->get_connection(); + + if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) { + nva.push_back(http2::make_nv_ll("early-data", "1")); + } +#endif // OPENSSL_1_1_1_API + + auto fwd = + fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED); + + if (fwdconf.params) { + auto params = fwdconf.params; + + if (config->http2_proxy || req.regular_connect_method()) { + params &= ~FORWARDED_PROTO; + } + + auto value = http::create_forwarded( + balloc, params, handler->get_forwarded_by(), + handler->get_forwarded_for(), req.authority, req.scheme); + + if (fwd || !value.empty()) { + if (fwd) { + if (value.empty()) { + value = fwd->value; + } else { + value = concat_string_ref(balloc, fwd->value, + StringRef::from_lit(", "), value); + } + } + + nva.push_back(http2::make_nv_ls_nocopy("forwarded", value)); + } + } else if (fwd) { + nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value)); + } + + auto xff = xffconf.strip_incoming ? nullptr + : req.fs.header(http2::HD_X_FORWARDED_FOR); + + if (xffconf.add) { + StringRef xff_value; + const auto &addr = upstream->get_client_handler()->get_ipaddr(); + if (xff) { + xff_value = concat_string_ref(balloc, xff->value, + StringRef::from_lit(", "), addr); + } else { + xff_value = addr; + } + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff_value)); + } else if (xff) { + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value)); + } + + if (!config->http2_proxy && !req.regular_connect_method()) { + auto xfp = xfpconf.strip_incoming + ? nullptr + : req.fs.header(http2::HD_X_FORWARDED_PROTO); + + if (xfpconf.add) { + StringRef xfp_value; + // We use same protocol with :scheme header field + if (xfp) { + xfp_value = concat_string_ref(balloc, xfp->value, + StringRef::from_lit(", "), req.scheme); + } else { + xfp_value = req.scheme; + } + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", xfp_value)); + } else if (xfp) { + nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", xfp->value)); + } + } + + auto via = req.fs.header(http2::HD_VIA); + if (httpconf.no_via) { + if (via) { + nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value)); + } + } else { + size_t vialen = 16; + if (via) { + vialen += via->value.size() + 2; + } + + auto iov = make_byte_ref(balloc, vialen + 1); + auto p = iov.base; + + if (via) { + p = std::copy(std::begin(via->value), std::end(via->value), p); + p = util::copy_lit(p, ", "); + } + p = http::create_via_header_value(p, req.http_major, req.http_minor); + *p = '\0'; + + nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p})); + } + + auto te = req.fs.header(http2::HD_TE); + // HTTP/1 upstream request can contain keyword other than + // "trailers". We just forward "trailers". + // TODO more strict handling required here. + if (te && http2::contains_trailers(te->value)) { + nva.push_back(http2::make_nv_ll("te", "trailers")); + } + + for (auto &p : httpconf.add_request_headers) { + nva.push_back(http2::make_nv_nocopy(p.name, p.value)); + } + + if (LOG_ENABLED(INFO)) { + std::stringstream ss; + for (auto &nv : nva) { + if (util::streq_l("authorization", nv.name, nv.namelen)) { + ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST + << ": \n"; + continue; + } + ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": " + << StringRef{nv.value, nv.valuelen} << "\n"; + } + DCLOG(INFO, this) << "HTTP request headers\n" << ss.str(); + } + + auto transfer_encoding = req.fs.header(http2::HD_TRANSFER_ENCODING); + + nghttp2_data_provider *data_prdptr = nullptr; + nghttp2_data_provider data_prd; + + // Add body as long as transfer-encoding is given even if + // req.fs.content_length == 0 to forward trailer fields. + if (req.method == HTTP_CONNECT || req.connect_proto != ConnectProto::NONE || + transfer_encoding || req.fs.content_length > 0 || req.http2_expect_body) { + // Request-body is expected. + data_prd = {{}, http2_data_read_callback}; + data_prdptr = &data_prd; + } + + rv = http2session_->submit_request(this, nva.data(), nva.size(), data_prdptr); + if (rv != 0) { + DCLOG(FATAL, this) << "nghttp2_submit_request() failed"; + return -1; + } + + if (data_prdptr) { + downstream_->reset_downstream_wtimer(); + } + + http2session_->signal_write(); + return 0; +} + +int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data, + size_t datalen) { + if (!downstream_->get_request_header_sent()) { + auto output = downstream_->get_blocked_request_buf(); + auto &req = downstream_->request(); + output->append(data, datalen); + req.unconsumed_body_length += datalen; + return 0; + } + + int rv; + auto output = downstream_->get_request_buf(); + output->append(data, datalen); + if (downstream_->get_downstream_stream_id() != -1) { + rv = http2session_->resume_data(this); + if (rv != 0) { + return -1; + } + + downstream_->ensure_downstream_wtimer(); + + http2session_->signal_write(); + } + return 0; +} + +int Http2DownstreamConnection::end_upload_data() { + if (!downstream_->get_request_header_sent()) { + downstream_->set_blocked_request_data_eof(true); + return 0; + } + + int rv; + if (downstream_->get_downstream_stream_id() != -1) { + rv = http2session_->resume_data(this); + if (rv != 0) { + return -1; + } + + downstream_->ensure_downstream_wtimer(); + + http2session_->signal_write(); + } + return 0; +} + +int Http2DownstreamConnection::resume_read(IOCtrlReason reason, + size_t consumed) { + int rv; + + if (http2session_->get_state() != Http2SessionState::CONNECTED) { + return 0; + } + + if (!downstream_ || downstream_->get_downstream_stream_id() == -1) { + return 0; + } + + if (consumed > 0) { + rv = http2session_->consume(downstream_->get_downstream_stream_id(), + consumed); + + if (rv != 0) { + return -1; + } + + auto &resp = downstream_->response(); + + resp.unconsumed_body_length -= consumed; + + http2session_->signal_write(); + } + + return 0; +} + +int Http2DownstreamConnection::on_read() { return 0; } + +int Http2DownstreamConnection::on_write() { return 0; } + +void Http2DownstreamConnection::attach_stream_data(StreamData *sd) { + // It is possible sd->dconn is not NULL. sd is detached when + // on_stream_close_callback. Before that, after MSG_COMPLETE is set + // to Downstream::set_response_state(), upstream's readcb is called + // and execution path eventually could reach here. Since the + // response was already handled, we just detach sd. + detach_stream_data(); + sd_ = sd; + sd_->dconn = this; +} + +StreamData *Http2DownstreamConnection::detach_stream_data() { + if (sd_) { + auto sd = sd_; + sd_ = nullptr; + sd->dconn = nullptr; + return sd; + } + return nullptr; +} + +int Http2DownstreamConnection::on_timeout() { + if (!downstream_) { + return 0; + } + + return submit_rst_stream(downstream_, NGHTTP2_NO_ERROR); +} + +const std::shared_ptr & +Http2DownstreamConnection::get_downstream_addr_group() const { + return http2session_->get_downstream_addr_group(); +} + +DownstreamAddr *Http2DownstreamConnection::get_addr() const { return nullptr; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_http2_downstream_connection.h b/lib/nghttp2/src/shrpx_http2_downstream_connection.h new file mode 100644 index 00000000000..0fc7d91cf24 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http2_downstream_connection.h @@ -0,0 +1,88 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H +#define SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H + +#include "shrpx.h" + +#include + +#include + +#include "shrpx_downstream_connection.h" + +namespace shrpx { + +struct StreamData; +class Http2Session; +class DownstreamConnectionPool; + +class Http2DownstreamConnection : public DownstreamConnection { +public: + Http2DownstreamConnection(Http2Session *http2session); + virtual ~Http2DownstreamConnection(); + virtual int attach_downstream(Downstream *downstream); + virtual void detach_downstream(Downstream *downstream); + + virtual int push_request_headers(); + virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen); + virtual int end_upload_data(); + + virtual void pause_read(IOCtrlReason reason) {} + virtual int resume_read(IOCtrlReason reason, size_t consumed); + virtual void force_resume_read() {} + + virtual int on_read(); + virtual int on_write(); + virtual int on_timeout(); + + virtual void on_upstream_change(Upstream *upstream) {} + + // This object is not poolable because we don't have facility to + // migrate to another Http2Session object. + virtual bool poolable() const { return false; } + + virtual const std::shared_ptr & + get_downstream_addr_group() const; + virtual DownstreamAddr *get_addr() const; + + int send(); + + void attach_stream_data(StreamData *sd); + StreamData *detach_stream_data(); + + int submit_rst_stream(Downstream *downstream, + uint32_t error_code = NGHTTP2_INTERNAL_ERROR); + + Http2DownstreamConnection *dlnext, *dlprev; + +private: + Http2Session *http2session_; + StreamData *sd_; +}; + +} // namespace shrpx + +#endif // SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_http2_session.cc b/lib/nghttp2/src/shrpx_http2_session.cc new file mode 100644 index 00000000000..18c934bed41 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http2_session.cc @@ -0,0 +1,2433 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_http2_session.h" + +#include +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H + +#include + +#include + +#include "shrpx_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_config.h" +#include "shrpx_error.h" +#include "shrpx_http2_downstream_connection.h" +#include "shrpx_client_handler.h" +#include "shrpx_tls.h" +#include "shrpx_http.h" +#include "shrpx_worker.h" +#include "shrpx_connect_blocker.h" +#include "shrpx_log.h" +#include "http2.h" +#include "util.h" +#include "base64.h" +#include "tls.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +constexpr ev_tstamp CONNCHK_TIMEOUT = 5.; +constexpr ev_tstamp CONNCHK_PING_TIMEOUT = 1.; +} // namespace + +namespace { +constexpr size_t MAX_BUFFER_SIZE = 32_k; +} // namespace + +namespace { +void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto http2session = static_cast(w->data); + + ev_timer_stop(loop, w); + + switch (http2session->get_connection_check_state()) { + case ConnectionCheck::STARTED: + // ping timeout; disconnect + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "ping timeout"; + } + + delete http2session; + + return; + default: + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "connection check required"; + } + http2session->set_connection_check_state(ConnectionCheck::REQUIRED); + } +} +} // namespace + +namespace { +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto http2session = static_cast(w->data); + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "SETTINGS timeout"; + } + + downstream_failure(http2session->get_addr(), http2session->get_raddr()); + + if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) { + delete http2session; + + return; + } + http2session->signal_write(); +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto conn = static_cast(w->data); + auto http2session = static_cast(conn->data); + + if (w == &conn->rt && !conn->expired_rt()) { + return; + } + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "Timeout"; + } + + http2session->on_timeout(); + + delete http2session; +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto conn = static_cast(w->data); + auto http2session = static_cast(conn->data); + rv = http2session->do_read(); + if (rv != 0) { + delete http2session; + + return; + } + http2session->connection_alive(); +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto conn = static_cast(w->data); + auto http2session = static_cast(conn->data); + rv = http2session->do_write(); + if (rv != 0) { + delete http2session; + + return; + } + http2session->reset_connection_check_timer_if_not_checking(); +} +} // namespace + +namespace { +void initiate_connection_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto http2session = static_cast(w->data); + ev_timer_stop(loop, w); + if (http2session->initiate_connection() != 0) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "Could not initiate backend connection"; + } + + delete http2session; + + return; + } +} +} // namespace + +namespace { +void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revents) { + auto http2session = static_cast(w->data); + http2session->check_retire(); +} +} // namespace + +Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx, + Worker *worker, + const std::shared_ptr &group, + DownstreamAddr *addr) + : dlnext(nullptr), + dlprev(nullptr), + conn_(loop, -1, nullptr, worker->get_mcpool(), + group->shared_addr->timeout.write, group->shared_addr->timeout.read, + {}, {}, writecb, readcb, timeoutcb, this, + get_config()->tls.dyn_rec.warmup_threshold, + get_config()->tls.dyn_rec.idle_timeout, Proto::HTTP2), + wb_(worker->get_mcpool()), + worker_(worker), + ssl_ctx_(ssl_ctx), + group_(group), + addr_(addr), + session_(nullptr), + raddr_(nullptr), + state_(Http2SessionState::DISCONNECTED), + connection_check_state_(ConnectionCheck::NONE), + freelist_zone_(FreelistZone::NONE), + settings_recved_(false), + allow_connect_proto_(false) { + read_ = write_ = &Http2Session::noop; + + on_read_ = &Http2Session::read_noop; + on_write_ = &Http2Session::write_noop; + + // We will reuse this many times, so use repeat timeout value. The + // timeout value is set later. + ev_timer_init(&connchk_timer_, connchk_timeout_cb, 0., 0.); + + connchk_timer_.data = this; + + // SETTINGS ACK timeout is 10 seconds for now. We will reuse this + // many times, so use repeat timeout value. + ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 0.); + + settings_timer_.data = this; + + ev_timer_init(&initiate_connection_timer_, initiate_connection_cb, 0., 0.); + initiate_connection_timer_.data = this; + + ev_prepare_init(&prep_, prepare_cb); + prep_.data = this; + ev_prepare_start(loop, &prep_); +} + +Http2Session::~Http2Session() { + exclude_from_scheduling(); + disconnect(should_hard_fail()); +} + +int Http2Session::disconnect(bool hard) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Disconnecting"; + } + nghttp2_session_del(session_); + session_ = nullptr; + + wb_.reset(); + + if (dns_query_) { + auto dns_tracker = worker_->get_dns_tracker(); + dns_tracker->cancel(dns_query_.get()); + } + + conn_.rlimit.stopw(); + conn_.wlimit.stopw(); + + ev_prepare_stop(conn_.loop, &prep_); + + ev_timer_stop(conn_.loop, &initiate_connection_timer_); + ev_timer_stop(conn_.loop, &settings_timer_); + ev_timer_stop(conn_.loop, &connchk_timer_); + + read_ = write_ = &Http2Session::noop; + + on_read_ = &Http2Session::read_noop; + on_write_ = &Http2Session::write_noop; + + conn_.disconnect(); + + if (proxy_htp_) { + proxy_htp_.reset(); + } + + connection_check_state_ = ConnectionCheck::NONE; + state_ = Http2SessionState::DISCONNECTED; + + // When deleting Http2DownstreamConnection, it calls this object's + // remove_downstream_connection(). The multiple + // Http2DownstreamConnection objects belong to the same + // ClientHandler object if upstream is h2. So be careful when you + // delete ClientHandler here. + // + // We allow creating new pending Http2DownstreamConnection with this + // object. Upstream::on_downstream_reset() may add + // Http2DownstreamConnection to another Http2Session. + + for (auto dc = dconns_.head; dc;) { + auto next = dc->dlnext; + auto downstream = dc->get_downstream(); + auto upstream = downstream->get_upstream(); + + // Failure is allowed only for HTTP/1 upstream where upstream is + // not shared by multiple Downstreams. + if (upstream->on_downstream_reset(downstream, hard) != 0) { + delete upstream->get_client_handler(); + } + + // dc was deleted + dc = next; + } + + auto streams = std::move(streams_); + for (auto s = streams.head; s;) { + auto next = s->dlnext; + delete s; + s = next; + } + + return 0; +} + +int Http2Session::resolve_name() { + auto dns_query = std::make_unique( + addr_->host, [this](DNSResolverStatus status, const Address *result) { + int rv; + + if (status == DNSResolverStatus::OK) { + *resolved_addr_ = *result; + util::set_port(*this->resolved_addr_, this->addr_->port); + } + + rv = this->initiate_connection(); + if (rv != 0) { + delete this; + } + }); + resolved_addr_ = std::make_unique
(); + auto dns_tracker = worker_->get_dns_tracker(); + switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) { + case DNSResolverStatus::ERROR: + return -1; + case DNSResolverStatus::RUNNING: + dns_query_ = std::move(dns_query); + state_ = Http2SessionState::RESOLVING_NAME; + return 0; + case DNSResolverStatus::OK: + util::set_port(*resolved_addr_, addr_->port); + return 0; + default: + assert(0); + abort(); + } +} + +namespace { +int htp_hdrs_completecb(llhttp_t *htp); +} // namespace + +namespace { +constexpr llhttp_settings_t htp_hooks = { + nullptr, // llhttp_cb on_message_begin; + nullptr, // llhttp_data_cb on_url; + nullptr, // llhttp_data_cb on_status; + nullptr, // llhttp_data_cb on_method; + nullptr, // llhttp_data_cb on_version; + nullptr, // llhttp_data_cb on_header_field; + nullptr, // llhttp_data_cb on_header_value; + nullptr, // llhttp_data_cb on_chunk_extension_name; + nullptr, // llhttp_data_cb on_chunk_extension_value; + htp_hdrs_completecb, // llhttp_cb on_headers_complete; + nullptr, // llhttp_data_cb on_body; + nullptr, // llhttp_cb on_message_complete; + nullptr, // llhttp_cb on_url_complete; + nullptr, // llhttp_cb on_status_complete; + nullptr, // llhttp_cb on_method_complete; + nullptr, // llhttp_cb on_version_complete; + nullptr, // llhttp_cb on_header_field_complete; + nullptr, // llhttp_cb on_header_value_complete; + nullptr, // llhttp_cb on_chunk_extension_name_complete; + nullptr, // llhttp_cb on_chunk_extension_value_complete; + nullptr, // llhttp_cb on_chunk_header; + nullptr, // llhttp_cb on_chunk_complete; + nullptr, // llhttp_cb on_reset; +}; +} // namespace + +int Http2Session::initiate_connection() { + int rv = 0; + + auto worker_blocker = worker_->get_connect_blocker(); + + if (state_ == Http2SessionState::DISCONNECTED || + state_ == Http2SessionState::RESOLVING_NAME) { + if (worker_blocker->blocked()) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) + << "Worker wide backend connection was blocked temporarily"; + } + return -1; + } + } + + auto &downstreamconf = *get_config()->conn.downstream; + + const auto &proxy = get_config()->downstream_http_proxy; + if (!proxy.host.empty() && state_ == Http2SessionState::DISCONNECTED) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Connecting to the proxy " << proxy.host << ":" + << proxy.port; + } + + conn_.fd = util::create_nonblock_socket(proxy.addr.su.storage.ss_family); + + if (conn_.fd == -1) { + auto error = errno; + SSLOG(WARN, this) << "Backend proxy socket() failed; addr=" + << util::to_numeric_addr(&proxy.addr) + << ", errno=" << error; + + worker_blocker->on_failure(); + return -1; + } + + rv = connect(conn_.fd, &proxy.addr.su.sa, proxy.addr.len); + if (rv != 0 && errno != EINPROGRESS) { + auto error = errno; + SSLOG(WARN, this) << "Backend proxy connect() failed; addr=" + << util::to_numeric_addr(&proxy.addr) + << ", errno=" << error; + + worker_blocker->on_failure(); + + return -1; + } + + raddr_ = &proxy.addr; + + worker_blocker->on_success(); + + ev_io_set(&conn_.rev, conn_.fd, EV_READ); + ev_io_set(&conn_.wev, conn_.fd, EV_WRITE); + + conn_.wlimit.startw(); + + conn_.wt.repeat = downstreamconf.timeout.connect; + ev_timer_again(conn_.loop, &conn_.wt); + + write_ = &Http2Session::connected; + + on_read_ = &Http2Session::downstream_read_proxy; + on_write_ = &Http2Session::downstream_connect_proxy; + + proxy_htp_ = std::make_unique(); + llhttp_init(proxy_htp_.get(), HTTP_RESPONSE, &htp_hooks); + proxy_htp_->data = this; + + state_ = Http2SessionState::PROXY_CONNECTING; + + return 0; + } + + if (state_ == Http2SessionState::DISCONNECTED || + state_ == Http2SessionState::PROXY_CONNECTED || + state_ == Http2SessionState::RESOLVING_NAME) { + if (LOG_ENABLED(INFO)) { + if (state_ != Http2SessionState::RESOLVING_NAME) { + SSLOG(INFO, this) << "Connecting to downstream server"; + } + } + if (addr_->tls) { + assert(ssl_ctx_); + + if (state_ != Http2SessionState::RESOLVING_NAME) { + auto ssl = tls::create_ssl(ssl_ctx_); + if (!ssl) { + return -1; + } + + tls::setup_downstream_http2_alpn(ssl); + + conn_.set_ssl(ssl); + conn_.tls.client_session_cache = &addr_->tls_session_cache; + + auto sni_name = + addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni}; + + if (!util::numeric_host(sni_name.c_str())) { + // TLS extensions: SNI. There is no documentation about the return + // code for this function (actually this is macro wrapping SSL_ctrl + // at the time of this writing). + SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str()); + } + + auto tls_session = tls::reuse_tls_session(addr_->tls_session_cache); + if (tls_session) { + SSL_set_session(conn_.tls.ssl, tls_session); + SSL_SESSION_free(tls_session); + } + } + + if (state_ == Http2SessionState::DISCONNECTED) { + if (addr_->dns) { + rv = resolve_name(); + if (rv != 0) { + downstream_failure(addr_, nullptr); + return -1; + } + if (state_ == Http2SessionState::RESOLVING_NAME) { + return 0; + } + raddr_ = resolved_addr_.get(); + } else { + raddr_ = &addr_->addr; + } + } + + if (state_ == Http2SessionState::RESOLVING_NAME) { + if (dns_query_->status == DNSResolverStatus::ERROR) { + downstream_failure(addr_, nullptr); + return -1; + } + assert(dns_query_->status == DNSResolverStatus::OK); + state_ = Http2SessionState::DISCONNECTED; + dns_query_.reset(); + raddr_ = resolved_addr_.get(); + } + + // If state_ == Http2SessionState::PROXY_CONNECTED, we have + // connected to the proxy using conn_.fd and tunnel has been + // established. + if (state_ == Http2SessionState::DISCONNECTED) { + assert(conn_.fd == -1); + + conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family); + if (conn_.fd == -1) { + auto error = errno; + SSLOG(WARN, this) + << "socket() failed; addr=" << util::to_numeric_addr(raddr_) + << ", errno=" << error; + + worker_blocker->on_failure(); + return -1; + } + + worker_blocker->on_success(); + + rv = connect(conn_.fd, + // TODO maybe not thread-safe? + const_cast(&raddr_->su.sa), raddr_->len); + if (rv != 0 && errno != EINPROGRESS) { + auto error = errno; + SSLOG(WARN, this) + << "connect() failed; addr=" << util::to_numeric_addr(raddr_) + << ", errno=" << error; + + downstream_failure(addr_, raddr_); + return -1; + } + + ev_io_set(&conn_.rev, conn_.fd, EV_READ); + ev_io_set(&conn_.wev, conn_.fd, EV_WRITE); + } + + conn_.prepare_client_handshake(); + } else { + if (state_ == Http2SessionState::DISCONNECTED) { + // Without TLS and proxy. + if (addr_->dns) { + rv = resolve_name(); + if (rv != 0) { + downstream_failure(addr_, nullptr); + return -1; + } + if (state_ == Http2SessionState::RESOLVING_NAME) { + return 0; + } + raddr_ = resolved_addr_.get(); + } else { + raddr_ = &addr_->addr; + } + } + + if (state_ == Http2SessionState::RESOLVING_NAME) { + if (dns_query_->status == DNSResolverStatus::ERROR) { + downstream_failure(addr_, nullptr); + return -1; + } + assert(dns_query_->status == DNSResolverStatus::OK); + state_ = Http2SessionState::DISCONNECTED; + dns_query_.reset(); + raddr_ = resolved_addr_.get(); + } + + if (state_ == Http2SessionState::DISCONNECTED) { + // Without TLS and proxy. + assert(conn_.fd == -1); + + conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family); + + if (conn_.fd == -1) { + auto error = errno; + SSLOG(WARN, this) + << "socket() failed; addr=" << util::to_numeric_addr(raddr_) + << ", errno=" << error; + + worker_blocker->on_failure(); + return -1; + } + + worker_blocker->on_success(); + + rv = connect(conn_.fd, const_cast(&raddr_->su.sa), + raddr_->len); + if (rv != 0 && errno != EINPROGRESS) { + auto error = errno; + SSLOG(WARN, this) + << "connect() failed; addr=" << util::to_numeric_addr(raddr_) + << ", errno=" << error; + + downstream_failure(addr_, raddr_); + return -1; + } + + ev_io_set(&conn_.rev, conn_.fd, EV_READ); + ev_io_set(&conn_.wev, conn_.fd, EV_WRITE); + } + } + + // We have been already connected when no TLS and proxy is used. + if (state_ == Http2SessionState::PROXY_CONNECTED) { + on_read_ = &Http2Session::read_noop; + on_write_ = &Http2Session::write_noop; + + return connected(); + } + + write_ = &Http2Session::connected; + + state_ = Http2SessionState::CONNECTING; + conn_.wlimit.startw(); + + conn_.wt.repeat = downstreamconf.timeout.connect; + ev_timer_again(conn_.loop, &conn_.wt); + + return 0; + } + + // Unreachable + assert(0); + + return 0; +} + +namespace { +int htp_hdrs_completecb(llhttp_t *htp) { + auto http2session = static_cast(htp->data); + + // We only read HTTP header part. If tunneling succeeds, response + // body is a different protocol (HTTP/2 in this case), we don't read + // them here. + + // We just check status code here + if (htp->status_code / 100 == 2) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "Tunneling success"; + } + http2session->set_state(Http2SessionState::PROXY_CONNECTED); + + return HPE_PAUSED; + } + + SSLOG(WARN, http2session) << "Tunneling failed: " << htp->status_code; + http2session->set_state(Http2SessionState::PROXY_FAILED); + + return HPE_PAUSED; +} +} // namespace + +int Http2Session::downstream_read_proxy(const uint8_t *data, size_t datalen) { + auto htperr = llhttp_execute(proxy_htp_.get(), + reinterpret_cast(data), datalen); + if (htperr == HPE_PAUSED) { + switch (state_) { + case Http2SessionState::PROXY_CONNECTED: + // Initiate SSL/TLS handshake through established tunnel. + if (initiate_connection() != 0) { + return -1; + } + return 0; + case Http2SessionState::PROXY_FAILED: + return -1; + default: + break; + } + // should not be here + assert(0); + } + + if (htperr != HPE_OK) { + return -1; + } + + return 0; +} + +int Http2Session::downstream_connect_proxy() { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Connected to the proxy"; + } + + std::string req = "CONNECT "; + req.append(addr_->hostport.c_str(), addr_->hostport.size()); + if (addr_->port == 80 || addr_->port == 443) { + req += ':'; + req += util::utos(addr_->port); + } + req += " HTTP/1.1\r\nHost: "; + req += addr_->host; + req += "\r\n"; + const auto &proxy = get_config()->downstream_http_proxy; + if (!proxy.userinfo.empty()) { + req += "Proxy-Authorization: Basic "; + req += base64::encode(std::begin(proxy.userinfo), std::end(proxy.userinfo)); + req += "\r\n"; + } + req += "\r\n"; + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "HTTP proxy request headers\n" << req; + } + wb_.append(req); + + on_write_ = &Http2Session::write_noop; + + signal_write(); + return 0; +} + +void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) { + dconns_.append(dconn); + ++addr_->num_dconn; +} + +void Http2Session::remove_downstream_connection( + Http2DownstreamConnection *dconn) { + --addr_->num_dconn; + dconns_.remove(dconn); + dconn->detach_stream_data(); + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Remove downstream"; + } + + if (freelist_zone_ == FreelistZone::NONE && !max_concurrency_reached()) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Append to http2_extra_freelist, addr=" << addr_ + << ", freelist.size=" + << addr_->http2_extra_freelist.size(); + } + + add_to_extra_freelist(); + } +} + +void Http2Session::remove_stream_data(StreamData *sd) { + streams_.remove(sd); + if (sd->dconn) { + sd->dconn->detach_stream_data(); + } + delete sd; +} + +int Http2Session::submit_request(Http2DownstreamConnection *dconn, + const nghttp2_nv *nva, size_t nvlen, + const nghttp2_data_provider *data_prd) { + assert(state_ == Http2SessionState::CONNECTED); + auto sd = std::make_unique(); + sd->dlnext = sd->dlprev = nullptr; + // TODO Specify nullptr to pri_spec for now + auto stream_id = + nghttp2_submit_request(session_, nullptr, nva, nvlen, data_prd, sd.get()); + if (stream_id < 0) { + SSLOG(FATAL, this) << "nghttp2_submit_request() failed: " + << nghttp2_strerror(stream_id); + return -1; + } + + dconn->attach_stream_data(sd.get()); + dconn->get_downstream()->set_downstream_stream_id(stream_id); + streams_.append(sd.release()); + + return 0; +} + +int Http2Session::submit_rst_stream(int32_t stream_id, uint32_t error_code) { + assert(state_ == Http2SessionState::CONNECTED); + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "RST_STREAM stream_id=" << stream_id + << " with error_code=" << error_code; + } + int rv = nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, stream_id, + error_code); + if (rv != 0) { + SSLOG(FATAL, this) << "nghttp2_submit_rst_stream() failed: " + << nghttp2_strerror(rv); + return -1; + } + return 0; +} + +nghttp2_session *Http2Session::get_session() const { return session_; } + +int Http2Session::resume_data(Http2DownstreamConnection *dconn) { + assert(state_ == Http2SessionState::CONNECTED); + auto downstream = dconn->get_downstream(); + int rv = nghttp2_session_resume_data(session_, + downstream->get_downstream_stream_id()); + switch (rv) { + case 0: + case NGHTTP2_ERR_INVALID_ARGUMENT: + return 0; + default: + SSLOG(FATAL, this) << "nghttp2_resume_session() failed: " + << nghttp2_strerror(rv); + return -1; + } +} + +namespace { +void call_downstream_readcb(Http2Session *http2session, + Downstream *downstream) { + auto upstream = downstream->get_upstream(); + if (!upstream) { + return; + } + if (upstream->downstream_read(downstream->get_downstream_connection()) != 0) { + delete upstream->get_client_handler(); + } +} +} // namespace + +namespace { +int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + auto http2session = static_cast(user_data); + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) + << "Stream stream_id=" << stream_id + << " is being closed with error code " << error_code; + } + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + if (sd == 0) { + // We might get this close callback when pushed streams are + // closed. + return 0; + } + auto dconn = sd->dconn; + if (dconn) { + auto downstream = dconn->get_downstream(); + auto upstream = downstream->get_upstream(); + + if (downstream->get_downstream_stream_id() % 2 == 0 && + downstream->get_request_state() == DownstreamState::INITIAL) { + // Downstream is canceled in backend before it is submitted in + // frontend session. + + // This will avoid to send RST_STREAM to backend + downstream->set_response_state(DownstreamState::MSG_RESET); + upstream->cancel_premature_downstream(downstream); + } else { + if (downstream->get_upgraded() && downstream->get_response_state() == + DownstreamState::HEADER_COMPLETE) { + // For tunneled connection, we have to submit RST_STREAM to + // upstream *after* whole response body is sent. We just set + // MSG_COMPLETE here. Upstream will take care of that. + downstream->get_upstream()->on_downstream_body_complete(downstream); + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + } else if (error_code == NGHTTP2_NO_ERROR) { + switch (downstream->get_response_state()) { + case DownstreamState::MSG_COMPLETE: + case DownstreamState::MSG_BAD_HEADER: + break; + default: + downstream->set_response_state(DownstreamState::MSG_RESET); + } + } else if (downstream->get_response_state() != + DownstreamState::MSG_BAD_HEADER) { + downstream->set_response_state(DownstreamState::MSG_RESET); + } + if (downstream->get_response_state() == DownstreamState::MSG_RESET && + downstream->get_response_rst_stream_error_code() == + NGHTTP2_NO_ERROR) { + downstream->set_response_rst_stream_error_code(error_code); + } + call_downstream_readcb(http2session, downstream); + } + // dconn may be deleted + } + // The life time of StreamData ends here + http2session->remove_stream_data(sd); + return 0; +} +} // namespace + +void Http2Session::start_settings_timer() { + auto &downstreamconf = get_config()->http2.downstream; + + ev_timer_set(&settings_timer_, downstreamconf.timeout.settings, 0.); + ev_timer_start(conn_.loop, &settings_timer_); +} + +void Http2Session::stop_settings_timer() { + ev_timer_stop(conn_.loop, &settings_timer_); +} + +namespace { +int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame, + nghttp2_rcbuf *name, nghttp2_rcbuf *value, + uint8_t flags, void *user_data) { + auto http2session = static_cast(user_data); + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!sd || !sd->dconn) { + return 0; + } + auto downstream = sd->dconn->get_downstream(); + + auto namebuf = nghttp2_rcbuf_get_buf(name); + auto valuebuf = nghttp2_rcbuf_get_buf(value); + + auto &resp = downstream->response(); + auto &httpconf = get_config()->http; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: { + auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS && + !downstream->get_expect_final_response(); + + if (resp.fs.buffer_size() + namebuf.len + valuebuf.len > + httpconf.response_header_field_buffer || + resp.fs.num_fields() >= httpconf.max_response_header_fields) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) + << "Too large or many header field size=" + << resp.fs.buffer_size() + namebuf.len + valuebuf.len + << ", num=" << resp.fs.num_fields() + 1; + } + + if (trailer) { + // We don't care trailer part exceeds header size limit; just + // discard it. + return 0; + } + + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + auto token = http2::lookup_token(namebuf.base, namebuf.len); + auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX; + + downstream->add_rcbuf(name); + downstream->add_rcbuf(value); + + if (trailer) { + // just store header fields for trailer part + resp.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len}, + StringRef{valuebuf.base, valuebuf.len}, + no_index, token); + return 0; + } + + resp.fs.add_header_token(StringRef{namebuf.base, namebuf.len}, + StringRef{valuebuf.base, valuebuf.len}, no_index, + token); + return 0; + } + case NGHTTP2_PUSH_PROMISE: { + auto promised_stream_id = frame->push_promise.promised_stream_id; + auto promised_sd = static_cast( + nghttp2_session_get_stream_user_data(session, promised_stream_id)); + if (!promised_sd || !promised_sd->dconn) { + http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + auto promised_downstream = promised_sd->dconn->get_downstream(); + + auto namebuf = nghttp2_rcbuf_get_buf(name); + auto valuebuf = nghttp2_rcbuf_get_buf(value); + + assert(promised_downstream); + + auto &promised_req = promised_downstream->request(); + + // We use request header limit for PUSH_PROMISE + if (promised_req.fs.buffer_size() + namebuf.len + valuebuf.len > + httpconf.request_header_field_buffer || + promised_req.fs.num_fields() >= httpconf.max_request_header_fields) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) + << "Too large or many header field size=" + << promised_req.fs.buffer_size() + namebuf.len + valuebuf.len + << ", num=" << promised_req.fs.num_fields() + 1; + } + + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + promised_downstream->add_rcbuf(name); + promised_downstream->add_rcbuf(value); + + auto token = http2::lookup_token(namebuf.base, namebuf.len); + promised_req.fs.add_header_token(StringRef{namebuf.base, namebuf.len}, + StringRef{valuebuf.base, valuebuf.len}, + flags & NGHTTP2_NV_FLAG_NO_INDEX, token); + + return 0; + } + } + + return 0; +} +} // namespace + +namespace { +int on_invalid_header_callback2(nghttp2_session *session, + const nghttp2_frame *frame, nghttp2_rcbuf *name, + nghttp2_rcbuf *value, uint8_t flags, + void *user_data) { + auto http2session = static_cast(user_data); + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!sd || !sd->dconn) { + return 0; + } + + int32_t stream_id; + + if (frame->hd.type == NGHTTP2_PUSH_PROMISE) { + stream_id = frame->push_promise.promised_stream_id; + } else { + stream_id = frame->hd.stream_id; + } + + if (LOG_ENABLED(INFO)) { + auto namebuf = nghttp2_rcbuf_get_buf(name); + auto valuebuf = nghttp2_rcbuf_get_buf(value); + + SSLOG(INFO, http2session) + << "Invalid header field for stream_id=" << stream_id + << " in frame type=" << static_cast(frame->hd.type) + << ": name=[" << StringRef{namebuf.base, namebuf.len} << "], value=[" + << StringRef{valuebuf.base, valuebuf.len} << "]"; + } + + http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR); + + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} +} // namespace + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + auto http2session = static_cast(user_data); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: { + if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE && + frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE) { + return 0; + } + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!sd || !sd->dconn) { + http2session->submit_rst_stream(frame->hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + return 0; + } + return 0; + } + case NGHTTP2_PUSH_PROMISE: { + auto promised_stream_id = frame->push_promise.promised_stream_id; + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!sd || !sd->dconn) { + http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + auto downstream = sd->dconn->get_downstream(); + + assert(downstream); + assert(downstream->get_downstream_stream_id() == frame->hd.stream_id); + + if (http2session->handle_downstream_push_promise(downstream, + promised_stream_id) != 0) { + http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + return 0; + } + } + + return 0; +} +} // namespace + +namespace { +int on_response_headers(Http2Session *http2session, Downstream *downstream, + nghttp2_session *session, const nghttp2_frame *frame) { + int rv; + + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + const auto &req = downstream->request(); + auto &resp = downstream->response(); + + auto &nva = resp.fs.headers(); + + auto config = get_config(); + auto &loggingconf = config->logging; + + downstream->set_expect_final_response(false); + + auto status = resp.fs.header(http2::HD__STATUS); + // libnghttp2 guarantees this exists and can be parsed + assert(status); + auto status_code = http2::parse_http_status_code(status->value); + + resp.http_status = status_code; + resp.http_major = 2; + resp.http_minor = 0; + + downstream->set_downstream_addr_group( + http2session->get_downstream_addr_group()); + downstream->set_addr(http2session->get_addr()); + + if (LOG_ENABLED(INFO)) { + std::stringstream ss; + for (auto &nv : nva) { + ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n"; + } + SSLOG(INFO, http2session) + << "HTTP response headers. stream_id=" << frame->hd.stream_id << "\n" + << ss.str(); + } + + if (downstream->get_non_final_response()) { + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "This is non-final response."; + } + + downstream->set_expect_final_response(true); + rv = upstream->on_downstream_header_complete(downstream); + + // Now Dowstream's response headers are erased. + + if (rv != 0) { + http2session->submit_rst_stream(frame->hd.stream_id, + NGHTTP2_PROTOCOL_ERROR); + downstream->set_response_state(DownstreamState::MSG_RESET); + } + + return 0; + } + + downstream->set_response_state(DownstreamState::HEADER_COMPLETE); + downstream->check_upgrade_fulfilled_http2(); + + if (downstream->get_upgraded()) { + resp.connection_close = true; + // On upgrade success, both ends can send data + if (upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0) != 0) { + // If resume_read fails, just drop connection. Not ideal. + delete handler; + return -1; + } + downstream->set_request_state(DownstreamState::HEADER_COMPLETE); + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) + << "HTTP upgrade success. stream_id=" << frame->hd.stream_id; + } + } else { + auto content_length = resp.fs.header(http2::HD_CONTENT_LENGTH); + if (content_length) { + // libnghttp2 guarantees this can be parsed + resp.fs.content_length = util::parse_uint(content_length->value); + } + + if (resp.fs.content_length == -1 && downstream->expect_response_body()) { + // Here we have response body but Content-Length is not known in + // advance. + if (req.http_major <= 0 || (req.http_major == 1 && req.http_minor == 0)) { + // We simply close connection for pre-HTTP/1.1 in this case. + resp.connection_close = true; + } else { + // Otherwise, use chunked encoding to keep upstream connection + // open. In HTTP2, we are supposed not to receive + // transfer-encoding. + resp.fs.add_header_token(StringRef::from_lit("transfer-encoding"), + StringRef::from_lit("chunked"), false, + http2::HD_TRANSFER_ENCODING); + downstream->set_chunked_response(true); + } + } + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + resp.headers_only = true; + } + + if (loggingconf.access.write_early && downstream->accesslog_ready()) { + handler->write_accesslog(downstream); + downstream->set_accesslog_written(true); + } + + rv = upstream->on_downstream_header_complete(downstream); + if (rv != 0) { + // Handling early return (in other words, response was hijacked by + // mruby scripting). + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_CANCEL); + } else { + http2session->submit_rst_stream(frame->hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + downstream->set_response_state(DownstreamState::MSG_RESET); + } + } + + return 0; +} +} // namespace + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + int rv; + auto http2session = static_cast(user_data); + + switch (frame->hd.type) { + case NGHTTP2_DATA: { + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!sd || !sd->dconn) { + return 0; + } + auto downstream = sd->dconn->get_downstream(); + auto upstream = downstream->get_upstream(); + rv = upstream->on_downstream_body(downstream, nullptr, 0, true); + if (rv != 0) { + http2session->submit_rst_stream(frame->hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + downstream->set_response_state(DownstreamState::MSG_RESET); + + } else if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + + downstream->disable_downstream_rtimer(); + + if (downstream->get_response_state() == + DownstreamState::HEADER_COMPLETE) { + + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + rv = upstream->on_downstream_body_complete(downstream); + + if (rv != 0) { + downstream->set_response_state(DownstreamState::MSG_RESET); + } + } + } + + call_downstream_readcb(http2session, downstream); + return 0; + } + case NGHTTP2_HEADERS: { + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!sd || !sd->dconn) { + return 0; + } + auto downstream = sd->dconn->get_downstream(); + + if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE || + frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) { + rv = on_response_headers(http2session, downstream, session, frame); + + if (rv != 0) { + return 0; + } + } else if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { + if (downstream->get_expect_final_response()) { + rv = on_response_headers(http2session, downstream, session, frame); + + if (rv != 0) { + return 0; + } + } + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + downstream->disable_downstream_rtimer(); + + if (downstream->get_response_state() == + DownstreamState::HEADER_COMPLETE) { + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + auto upstream = downstream->get_upstream(); + + rv = upstream->on_downstream_body_complete(downstream); + + if (rv != 0) { + downstream->set_response_state(DownstreamState::MSG_RESET); + } + } + } else { + downstream->reset_downstream_rtimer(); + } + + // This may delete downstream + call_downstream_readcb(http2session, downstream); + + return 0; + } + case NGHTTP2_RST_STREAM: { + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (sd && sd->dconn) { + auto downstream = sd->dconn->get_downstream(); + downstream->set_response_rst_stream_error_code( + frame->rst_stream.error_code); + call_downstream_readcb(http2session, downstream); + } + return 0; + } + case NGHTTP2_SETTINGS: { + if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { + http2session->on_settings_received(frame); + return 0; + } + + http2session->stop_settings_timer(); + + auto addr = http2session->get_addr(); + auto &connect_blocker = addr->connect_blocker; + + connect_blocker->on_success(); + + return 0; + } + case NGHTTP2_PING: + if (frame->hd.flags & NGHTTP2_FLAG_ACK) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "PING ACK received"; + } + http2session->connection_alive(); + } + return 0; + case NGHTTP2_PUSH_PROMISE: { + auto promised_stream_id = frame->push_promise.promised_stream_id; + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) + << "Received downstream PUSH_PROMISE stream_id=" + << frame->hd.stream_id + << ", promised_stream_id=" << promised_stream_id; + } + + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!sd || !sd->dconn) { + http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL); + return 0; + } + + auto downstream = sd->dconn->get_downstream(); + + assert(downstream); + assert(downstream->get_downstream_stream_id() == frame->hd.stream_id); + + auto promised_sd = static_cast( + nghttp2_session_get_stream_user_data(session, promised_stream_id)); + if (!promised_sd || !promised_sd->dconn) { + http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL); + return 0; + } + + auto promised_downstream = promised_sd->dconn->get_downstream(); + + assert(promised_downstream); + + if (http2session->handle_downstream_push_promise_complete( + downstream, promised_downstream) != 0) { + http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL); + return 0; + } + + return 0; + } + case NGHTTP2_GOAWAY: + if (LOG_ENABLED(INFO)) { + auto debug_data = util::ascii_dump(frame->goaway.opaque_data, + frame->goaway.opaque_data_len); + + SSLOG(INFO, http2session) + << "GOAWAY received: last-stream-id=" << frame->goaway.last_stream_id + << ", error_code=" << frame->goaway.error_code + << ", debug_data=" << debug_data; + } + return 0; + default: + return 0; + } +} +} // namespace + +namespace { +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + int rv; + auto http2session = static_cast(user_data); + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + if (!sd || !sd->dconn) { + http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR); + + if (http2session->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; + } + auto downstream = sd->dconn->get_downstream(); + if (!downstream->expect_response_body()) { + http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR); + + if (http2session->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; + } + + // We don't want DATA after non-final response, which is illegal in + // HTTP. + if (downstream->get_non_final_response()) { + http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR); + + if (http2session->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; + } + + downstream->reset_downstream_rtimer(); + + auto &resp = downstream->response(); + + resp.recv_body_length += len; + resp.unconsumed_body_length += len; + + auto upstream = downstream->get_upstream(); + rv = upstream->on_downstream_body(downstream, data, len, false); + if (rv != 0) { + http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR); + + if (http2session->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + downstream->set_response_state(DownstreamState::MSG_RESET); + } + + call_downstream_readcb(http2session, downstream); + return 0; +} +} // namespace + +namespace { +int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + auto http2session = static_cast(user_data); + + if (frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) { + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + + if (!sd || !sd->dconn) { + return 0; + } + + auto downstream = sd->dconn->get_downstream(); + + if (frame->hd.type == NGHTTP2_HEADERS && + frame->headers.cat == NGHTTP2_HCAT_REQUEST) { + downstream->set_request_header_sent(true); + auto src = downstream->get_blocked_request_buf(); + if (src->rleft()) { + auto dest = downstream->get_request_buf(); + src->remove(*dest); + if (http2session->resume_data(sd->dconn) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + downstream->ensure_downstream_wtimer(); + } + } + + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + return 0; + } + + downstream->reset_downstream_rtimer(); + + return 0; + } + + if (frame->hd.type == NGHTTP2_SETTINGS && + (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { + http2session->start_settings_timer(); + } + return 0; +} +} // namespace + +namespace { +int on_frame_not_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, int lib_error_code, + void *user_data) { + auto http2session = static_cast(user_data); + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "Failed to send control frame type=" + << static_cast(frame->hd.type) + << ", lib_error_code=" << lib_error_code << ": " + << nghttp2_strerror(lib_error_code); + } + if (frame->hd.type != NGHTTP2_HEADERS || + lib_error_code == NGHTTP2_ERR_STREAM_CLOSED || + lib_error_code == NGHTTP2_ERR_STREAM_CLOSING) { + return 0; + } + + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!sd) { + return 0; + } + if (!sd->dconn) { + return 0; + } + auto downstream = sd->dconn->get_downstream(); + + if (lib_error_code == NGHTTP2_ERR_START_STREAM_NOT_ALLOWED) { + // Migrate to another downstream connection. + auto upstream = downstream->get_upstream(); + + if (upstream->on_downstream_reset(downstream, false)) { + // This should be done for h1 upstream only. Deleting + // ClientHandler for h2 upstream may lead to crash. + delete upstream->get_client_handler(); + } + + return 0; + } + + // To avoid stream hanging around, flag DownstreamState::MSG_RESET. + downstream->set_response_state(DownstreamState::MSG_RESET); + call_downstream_readcb(http2session, downstream); + + return 0; +} +} // namespace + +namespace { +constexpr auto PADDING = std::array{}; +} // namespace + +namespace { +int send_data_callback(nghttp2_session *session, nghttp2_frame *frame, + const uint8_t *framehd, size_t length, + nghttp2_data_source *source, void *user_data) { + auto http2session = static_cast(user_data); + auto sd = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + + if (sd == nullptr) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + auto dconn = sd->dconn; + auto downstream = dconn->get_downstream(); + auto input = downstream->get_request_buf(); + auto wb = http2session->get_request_buf(); + + size_t padlen = 0; + + wb->append(framehd, 9); + if (frame->data.padlen > 0) { + padlen = frame->data.padlen - 1; + wb->append(static_cast(padlen)); + } + + input->remove(*wb, length); + + wb->append(PADDING.data(), padlen); + + if (input->rleft() == 0) { + downstream->disable_downstream_wtimer(); + } else { + downstream->reset_downstream_wtimer(); + } + + if (length > 0) { + // This is important because it will handle flow control + // stuff. + if (downstream->get_upstream()->resume_read(SHRPX_NO_BUFFER, downstream, + length) != 0) { + // In this case, downstream may be deleted. + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + // Here sd->dconn could be nullptr, because + // Upstream::resume_read() may delete downstream which will delete + // dconn. Is this still really true? + } + + return 0; +} +} // namespace + +nghttp2_session_callbacks *create_http2_downstream_callbacks() { + int rv; + nghttp2_session_callbacks *callbacks; + + rv = nghttp2_session_callbacks_new(&callbacks); + + if (rv != 0) { + return nullptr; + } + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + + nghttp2_session_callbacks_set_on_frame_not_send_callback( + callbacks, on_frame_not_send_callback); + + nghttp2_session_callbacks_set_on_header_callback2(callbacks, + on_header_callback2); + + nghttp2_session_callbacks_set_on_invalid_header_callback2( + callbacks, on_invalid_header_callback2); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_callbacks_set_send_data_callback(callbacks, + send_data_callback); + + if (get_config()->padding) { + nghttp2_session_callbacks_set_select_padding_callback( + callbacks, http::select_padding_callback); + } + + return callbacks; +} + +int Http2Session::connection_made() { + int rv; + + state_ = Http2SessionState::CONNECTED; + + on_write_ = &Http2Session::downstream_write; + on_read_ = &Http2Session::downstream_read; + + if (addr_->tls) { + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len = 0; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (!next_proto) { + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + if (!next_proto) { + downstream_failure(addr_, raddr_); + return -1; + } + + auto proto = StringRef{next_proto, next_proto_len}; + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Negotiated next protocol: " << proto; + } + if (!util::check_h2_is_selected(proto)) { + downstream_failure(addr_, raddr_); + return -1; + } + } + + auto config = get_config(); + auto &http2conf = config->http2; + + rv = nghttp2_session_client_new2(&session_, http2conf.downstream.callbacks, + this, http2conf.downstream.option); + + if (rv != 0) { + return -1; + } + + std::array entry; + size_t nentry = 3; + entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + entry[0].value = http2conf.downstream.max_concurrent_streams; + + entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + entry[1].value = http2conf.downstream.window_size; + + entry[2].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + entry[2].value = 1; + + if (http2conf.no_server_push || config->http2_proxy) { + entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + entry[nentry].value = 0; + ++nentry; + } + + if (http2conf.downstream.decoder_dynamic_table_size != + NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { + entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + entry[nentry].value = http2conf.downstream.decoder_dynamic_table_size; + ++nentry; + } + + rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), + nentry); + if (rv != 0) { + return -1; + } + + rv = nghttp2_session_set_local_window_size( + session_, NGHTTP2_FLAG_NONE, 0, + http2conf.downstream.connection_window_size); + if (rv != 0) { + return -1; + } + + reset_connection_check_timer(CONNCHK_TIMEOUT); + + submit_pending_requests(); + + signal_write(); + return 0; +} + +int Http2Session::do_read() { return read_(*this); } +int Http2Session::do_write() { return write_(*this); } + +int Http2Session::on_read(const uint8_t *data, size_t datalen) { + return on_read_(*this, data, datalen); +} + +int Http2Session::on_write() { return on_write_(*this); } + +int Http2Session::downstream_read(const uint8_t *data, size_t datalen) { + ssize_t rv; + + rv = nghttp2_session_mem_recv(session_, data, datalen); + if (rv < 0) { + SSLOG(ERROR, this) << "nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv); + return -1; + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "No more read/write for this HTTP2 session"; + } + return -1; + } + + signal_write(); + return 0; +} + +int Http2Session::downstream_write() { + for (;;) { + const uint8_t *data; + auto datalen = nghttp2_session_mem_send(session_, &data); + if (datalen < 0) { + SSLOG(ERROR, this) << "nghttp2_session_mem_send() returned error: " + << nghttp2_strerror(datalen); + return -1; + } + if (datalen == 0) { + break; + } + wb_.append(data, datalen); + + if (wb_.rleft() >= MAX_BUFFER_SIZE) { + break; + } + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "No more read/write for this session"; + } + return -1; + } + + return 0; +} + +void Http2Session::signal_write() { + switch (state_) { + case Http2SessionState::DISCONNECTED: + if (!ev_is_active(&initiate_connection_timer_)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Start connecting to backend server"; + } + // Since the timer is set to 0., these will feed 2 events. We + // will stop the timer in the initiate_connection_timer_ to void + // 2nd event. + ev_timer_start(conn_.loop, &initiate_connection_timer_); + ev_feed_event(conn_.loop, &initiate_connection_timer_, 0); + } + break; + case Http2SessionState::CONNECTED: + conn_.wlimit.startw(); + break; + default: + break; + } +} + +struct ev_loop *Http2Session::get_loop() const { return conn_.loop; } + +ev_io *Http2Session::get_wev() { return &conn_.wev; } + +Http2SessionState Http2Session::get_state() const { return state_; } + +void Http2Session::set_state(Http2SessionState state) { state_ = state; } + +int Http2Session::terminate_session(uint32_t error_code) { + int rv; + rv = nghttp2_session_terminate_session(session_, error_code); + if (rv != 0) { + return -1; + } + return 0; +} + +SSL *Http2Session::get_ssl() const { return conn_.tls.ssl; } + +int Http2Session::consume(int32_t stream_id, size_t len) { + int rv; + + if (!session_) { + return 0; + } + + rv = nghttp2_session_consume(session_, stream_id, len); + + if (rv != 0) { + SSLOG(WARN, this) << "nghttp2_session_consume() returned error: " + << nghttp2_strerror(rv); + + return -1; + } + + return 0; +} + +bool Http2Session::can_push_request(const Downstream *downstream) const { + auto &req = downstream->request(); + return state_ == Http2SessionState::CONNECTED && + connection_check_state_ == ConnectionCheck::NONE && + (req.connect_proto == ConnectProto::NONE || settings_recved_); +} + +void Http2Session::start_checking_connection() { + if (state_ != Http2SessionState::CONNECTED || + connection_check_state_ != ConnectionCheck::REQUIRED) { + return; + } + connection_check_state_ = ConnectionCheck::STARTED; + + SSLOG(INFO, this) << "Start checking connection"; + // If connection is down, we may get error when writing data. Issue + // ping frame to see whether connection is alive. + nghttp2_submit_ping(session_, NGHTTP2_FLAG_NONE, nullptr); + + // set ping timeout and start timer again + reset_connection_check_timer(CONNCHK_PING_TIMEOUT); + + signal_write(); +} + +void Http2Session::reset_connection_check_timer(ev_tstamp t) { + connchk_timer_.repeat = t; + ev_timer_again(conn_.loop, &connchk_timer_); +} + +void Http2Session::reset_connection_check_timer_if_not_checking() { + if (connection_check_state_ != ConnectionCheck::NONE) { + return; + } + + reset_connection_check_timer(CONNCHK_TIMEOUT); +} + +void Http2Session::connection_alive() { + reset_connection_check_timer(CONNCHK_TIMEOUT); + + if (connection_check_state_ == ConnectionCheck::NONE) { + return; + } + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Connection alive"; + } + + connection_check_state_ = ConnectionCheck::NONE; + + submit_pending_requests(); +} + +void Http2Session::submit_pending_requests() { + for (auto dconn = dconns_.head; dconn; dconn = dconn->dlnext) { + auto downstream = dconn->get_downstream(); + + if (!downstream->get_request_pending() || + !downstream->request_submission_ready()) { + continue; + } + + auto &req = downstream->request(); + if (req.connect_proto != ConnectProto::NONE && !settings_recved_) { + continue; + } + + auto upstream = downstream->get_upstream(); + + if (dconn->push_request_headers() != 0) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "backend request failed"; + } + + upstream->on_downstream_abort_request(downstream, 400); + + continue; + } + + upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0); + } +} + +void Http2Session::set_connection_check_state(ConnectionCheck state) { + connection_check_state_ = state; +} + +ConnectionCheck Http2Session::get_connection_check_state() const { + return connection_check_state_; +} + +int Http2Session::noop() { return 0; } + +int Http2Session::read_noop(const uint8_t *data, size_t datalen) { return 0; } + +int Http2Session::write_noop() { return 0; } + +int Http2Session::connected() { + auto sock_error = util::get_socket_error(conn_.fd); + if (sock_error != 0) { + SSLOG(WARN, this) << "Backend connect failed; addr=" + << util::to_numeric_addr(raddr_) + << ": errno=" << sock_error; + + downstream_failure(addr_, raddr_); + + return -1; + } + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Connection established"; + } + + // Reset timeout for write. Previously, we set timeout for connect. + conn_.wt.repeat = group_->shared_addr->timeout.write; + ev_timer_again(conn_.loop, &conn_.wt); + + conn_.rlimit.startw(); + conn_.again_rt(); + + read_ = &Http2Session::read_clear; + write_ = &Http2Session::write_clear; + + if (state_ == Http2SessionState::PROXY_CONNECTING) { + return do_write(); + } + + if (conn_.tls.ssl) { + read_ = &Http2Session::tls_handshake; + write_ = &Http2Session::tls_handshake; + + return do_write(); + } + + if (connection_made() != 0) { + state_ = Http2SessionState::CONNECT_FAILING; + return -1; + } + + return 0; +} + +int Http2Session::read_clear() { + conn_.last_read = std::chrono::steady_clock::now(); + + std::array buf; + + for (;;) { + auto nread = conn_.read_clear(buf.data(), buf.size()); + + if (nread == 0) { + return write_clear(); + } + + if (nread < 0) { + return nread; + } + + if (on_read(buf.data(), nread) != 0) { + return -1; + } + } +} + +int Http2Session::write_clear() { + conn_.last_read = std::chrono::steady_clock::now(); + + std::array iov; + + for (;;) { + if (wb_.rleft() > 0) { + auto iovcnt = wb_.riovec(iov.data(), iov.size()); + auto nwrite = conn_.writev_clear(iov.data(), iovcnt); + + if (nwrite == 0) { + return 0; + } + + if (nwrite < 0) { + // We may have pending data in receive buffer which may + // contain part of response body. So keep reading. Invoke + // read event to get read(2) error just in case. + ev_feed_event(conn_.loop, &conn_.rev, EV_READ); + write_ = &Http2Session::write_void; + break; + } + + wb_.drain(nwrite); + continue; + } + + if (on_write() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + break; + } + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + return 0; +} + +int Http2Session::tls_handshake() { + conn_.last_read = std::chrono::steady_clock::now(); + + ERR_clear_error(); + + auto rv = conn_.tls_handshake(); + + if (rv == SHRPX_ERR_INPROGRESS) { + return 0; + } + + if (rv < 0) { + downstream_failure(addr_, raddr_); + + return rv; + } + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "SSL/TLS handshake completed"; + } + + if (!get_config()->tls.insecure && + tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) { + downstream_failure(addr_, raddr_); + + return -1; + } + + read_ = &Http2Session::read_tls; + write_ = &Http2Session::write_tls; + + if (connection_made() != 0) { + state_ = Http2SessionState::CONNECT_FAILING; + return -1; + } + + return 0; +} + +int Http2Session::read_tls() { + conn_.last_read = std::chrono::steady_clock::now(); + + std::array buf; + + ERR_clear_error(); + + for (;;) { + auto nread = conn_.read_tls(buf.data(), buf.size()); + + if (nread == 0) { + return write_tls(); + } + + if (nread < 0) { + return nread; + } + + if (on_read(buf.data(), nread) != 0) { + return -1; + } + } +} + +int Http2Session::write_tls() { + conn_.last_read = std::chrono::steady_clock::now(); + + ERR_clear_error(); + + struct iovec iov; + + for (;;) { + if (wb_.rleft() > 0) { + auto iovcnt = wb_.riovec(&iov, 1); + if (iovcnt != 1) { + assert(0); + return -1; + } + auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len); + + if (nwrite == 0) { + return 0; + } + + if (nwrite < 0) { + // We may have pending data in receive buffer which may + // contain part of response body. So keep reading. Invoke + // read event to get read(2) error just in case. + ev_feed_event(conn_.loop, &conn_.rev, EV_READ); + write_ = &Http2Session::write_void; + break; + } + + wb_.drain(nwrite); + + continue; + } + + if (on_write() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + conn_.start_tls_write_idle(); + break; + } + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + return 0; +} + +int Http2Session::write_void() { + conn_.wlimit.stopw(); + return 0; +} + +bool Http2Session::should_hard_fail() const { + switch (state_) { + case Http2SessionState::PROXY_CONNECTING: + case Http2SessionState::PROXY_FAILED: + return true; + case Http2SessionState::DISCONNECTED: { + const auto &proxy = get_config()->downstream_http_proxy; + return !proxy.host.empty(); + } + default: + return false; + } +} + +DownstreamAddr *Http2Session::get_addr() const { return addr_; } + +int Http2Session::handle_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id) { + auto upstream = downstream->get_upstream(); + if (!upstream->push_enabled()) { + return -1; + } + + auto promised_downstream = + upstream->on_downstream_push_promise(downstream, promised_stream_id); + if (!promised_downstream) { + return -1; + } + + // Now we have Downstream object for pushed stream. + // promised_downstream->get_stream() still returns 0. + + auto handler = upstream->get_client_handler(); + + auto promised_dconn = std::make_unique(this); + promised_dconn->set_client_handler(handler); + + auto ptr = promised_dconn.get(); + + if (promised_downstream->attach_downstream_connection( + std::move(promised_dconn)) != 0) { + return -1; + } + + auto promised_sd = std::make_unique(); + + nghttp2_session_set_stream_user_data(session_, promised_stream_id, + promised_sd.get()); + + ptr->attach_stream_data(promised_sd.get()); + streams_.append(promised_sd.release()); + + return 0; +} + +int Http2Session::handle_downstream_push_promise_complete( + Downstream *downstream, Downstream *promised_downstream) { + auto &promised_req = promised_downstream->request(); + + auto &promised_balloc = promised_downstream->get_block_allocator(); + + auto authority = promised_req.fs.header(http2::HD__AUTHORITY); + auto path = promised_req.fs.header(http2::HD__PATH); + auto method = promised_req.fs.header(http2::HD__METHOD); + auto scheme = promised_req.fs.header(http2::HD__SCHEME); + + if (!authority) { + authority = promised_req.fs.header(http2::HD_HOST); + } + + auto method_token = http2::lookup_method_token(method->value); + if (method_token == -1) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Unrecognized method: " << method->value; + } + + return -1; + } + + // TODO Rewrite authority if we enabled rewrite host. But we + // really don't know how to rewrite host. Should we use the same + // host in associated stream? + if (authority) { + promised_req.authority = authority->value; + } + promised_req.method = method_token; + // libnghttp2 ensures that we don't have CONNECT method in + // PUSH_PROMISE, and guarantees that :scheme exists. + if (scheme) { + promised_req.scheme = scheme->value; + } + + // For server-wide OPTIONS request, path is empty. + if (method_token != HTTP_OPTIONS || path->value != "*") { + promised_req.path = http2::rewrite_clean_path(promised_balloc, path->value); + } + + promised_downstream->inspect_http2_request(); + + auto upstream = promised_downstream->get_upstream(); + + promised_downstream->set_request_state(DownstreamState::MSG_COMPLETE); + promised_downstream->set_request_header_sent(true); + + if (upstream->on_downstream_push_promise_complete(downstream, + promised_downstream) != 0) { + return -1; + } + + return 0; +} + +size_t Http2Session::get_num_dconns() const { return dconns_.size(); } + +bool Http2Session::max_concurrency_reached(size_t extra) const { + if (!session_) { + return dconns_.size() + extra >= 100; + } + + // If session does not allow further requests, it effectively means + // that maximum concurrency is reached. + return !nghttp2_session_check_request_allowed(session_) || + dconns_.size() + extra >= + nghttp2_session_get_remote_settings( + session_, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); +} + +const std::shared_ptr & +Http2Session::get_downstream_addr_group() const { + return group_; +} + +void Http2Session::add_to_extra_freelist() { + if (freelist_zone_ != FreelistZone::NONE) { + return; + } + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Append to http2_extra_freelist, addr=" << addr_ + << ", freelist.size=" + << addr_->http2_extra_freelist.size(); + } + + freelist_zone_ = FreelistZone::EXTRA; + addr_->http2_extra_freelist.append(this); +} + +void Http2Session::remove_from_freelist() { + switch (freelist_zone_) { + case FreelistZone::NONE: + return; + case FreelistZone::EXTRA: + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Remove from http2_extra_freelist, addr=" << addr_ + << ", freelist.size=" + << addr_->http2_extra_freelist.size(); + } + addr_->http2_extra_freelist.remove(this); + break; + case FreelistZone::GONE: + return; + } + + freelist_zone_ = FreelistZone::NONE; +} + +void Http2Session::exclude_from_scheduling() { + remove_from_freelist(); + freelist_zone_ = FreelistZone::GONE; +} + +DefaultMemchunks *Http2Session::get_request_buf() { return &wb_; } + +void Http2Session::on_timeout() { + switch (state_) { + case Http2SessionState::PROXY_CONNECTING: { + auto worker_blocker = worker_->get_connect_blocker(); + worker_blocker->on_failure(); + break; + } + case Http2SessionState::CONNECTING: + SSLOG(WARN, this) << "Connect time out; addr=" + << util::to_numeric_addr(raddr_); + + downstream_failure(addr_, raddr_); + break; + default: + break; + } +} + +void Http2Session::check_retire() { + if (!group_->retired) { + return; + } + + ev_prepare_stop(conn_.loop, &prep_); + + if (!session_) { + return; + } + + auto last_stream_id = nghttp2_session_get_last_proc_stream_id(session_); + nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE, last_stream_id, + NGHTTP2_NO_ERROR, nullptr, 0); + + signal_write(); +} + +const Address *Http2Session::get_raddr() const { return raddr_; } + +void Http2Session::on_settings_received(const nghttp2_frame *frame) { + // TODO This effectively disallows nghttpx to change its behaviour + // based on the 2nd SETTINGS. + if (settings_recved_) { + return; + } + + settings_recved_ = true; + + for (size_t i = 0; i < frame->settings.niv; ++i) { + auto &ent = frame->settings.iv[i]; + if (ent.settings_id == NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) { + allow_connect_proto_ = true; + break; + } + } + + submit_pending_requests(); +} + +bool Http2Session::get_allow_connect_proto() const { + return allow_connect_proto_; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_http2_session.h b/lib/nghttp2/src/shrpx_http2_session.h new file mode 100644 index 00000000000..31b25458bf2 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http2_session.h @@ -0,0 +1,296 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTP2_SESSION_H +#define SHRPX_HTTP2_SESSION_H + +#include "shrpx.h" + +#include +#include + +#include + +#include + +#include + +#include "llhttp.h" + +#include "shrpx_connection.h" +#include "buffer.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +class Http2DownstreamConnection; +class Worker; +class Downstream; +struct DownstreamAddrGroup; +struct DownstreamAddr; +struct DNSQuery; + +struct StreamData { + StreamData *dlnext, *dlprev; + Http2DownstreamConnection *dconn; +}; + +enum class FreelistZone { + // Http2Session object is not linked in any freelist. + NONE, + // Http2Session object is linked in address scope + // http2_extra_freelist. + EXTRA, + // Http2Session object is about to be deleted, and it does not + // belong to any linked list. + GONE +}; + +enum class Http2SessionState { + // Disconnected + DISCONNECTED, + // Connecting proxy and making CONNECT request + PROXY_CONNECTING, + // Tunnel is established with proxy + PROXY_CONNECTED, + // Establishing tunnel is failed + PROXY_FAILED, + // Connecting to downstream and/or performing SSL/TLS handshake + CONNECTING, + // Connected to downstream + CONNECTED, + // Connection is started to fail + CONNECT_FAILING, + // Resolving host name + RESOLVING_NAME, +}; + +enum class ConnectionCheck { + // Connection checking is not required + NONE, + // Connection checking is required + REQUIRED, + // Connection checking has been started + STARTED, +}; + +class Http2Session { +public: + Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker, + const std::shared_ptr &group, + DownstreamAddr *addr); + ~Http2Session(); + + // If hard is true, all pending requests are abandoned and + // associated ClientHandlers will be deleted. + int disconnect(bool hard = false); + int initiate_connection(); + int resolve_name(); + + void add_downstream_connection(Http2DownstreamConnection *dconn); + void remove_downstream_connection(Http2DownstreamConnection *dconn); + + void remove_stream_data(StreamData *sd); + + int submit_request(Http2DownstreamConnection *dconn, const nghttp2_nv *nva, + size_t nvlen, const nghttp2_data_provider *data_prd); + + int submit_rst_stream(int32_t stream_id, uint32_t error_code); + + int terminate_session(uint32_t error_code); + + nghttp2_session *get_session() const; + + int resume_data(Http2DownstreamConnection *dconn); + + int connection_made(); + + int do_read(); + int do_write(); + + int on_read(const uint8_t *data, size_t datalen); + int on_write(); + + int connected(); + int read_clear(); + int write_clear(); + int tls_handshake(); + int read_tls(); + int write_tls(); + // This is a special write function which just stop write event + // watcher. + int write_void(); + + int downstream_read_proxy(const uint8_t *data, size_t datalen); + int downstream_connect_proxy(); + + int downstream_read(const uint8_t *data, size_t datalen); + int downstream_write(); + + int noop(); + int read_noop(const uint8_t *data, size_t datalen); + int write_noop(); + + void signal_write(); + + struct ev_loop *get_loop() const; + + ev_io *get_wev(); + + Http2SessionState get_state() const; + void set_state(Http2SessionState state); + + void start_settings_timer(); + void stop_settings_timer(); + + SSL *get_ssl() const; + + int consume(int32_t stream_id, size_t len); + + // Returns true if request can be issued on downstream connection. + bool can_push_request(const Downstream *downstream) const; + // Initiates the connection checking if downstream connection has + // been established and connection checking is required. + void start_checking_connection(); + // Resets connection check timer to timeout |t|. After timeout, we + // require connection checking. If connection checking is already + // enabled, this timeout is for PING ACK timeout. + void reset_connection_check_timer(ev_tstamp t); + void reset_connection_check_timer_if_not_checking(); + // Signals that connection is alive. Internally + // reset_connection_check_timer() is called. + void connection_alive(); + // Change connection check state. + void set_connection_check_state(ConnectionCheck state); + ConnectionCheck get_connection_check_state() const; + + bool should_hard_fail() const; + + void submit_pending_requests(); + + DownstreamAddr *get_addr() const; + + const std::shared_ptr &get_downstream_addr_group() const; + + int handle_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id); + int handle_downstream_push_promise_complete(Downstream *downstream, + Downstream *promised_downstream); + + // Returns number of downstream connections, including pushed + // streams. + size_t get_num_dconns() const; + + // Adds to group scope http2_avail_freelist. + void add_to_avail_freelist(); + // Adds to address scope http2_extra_freelist. + void add_to_extra_freelist(); + + // Removes this object from any freelist. If this object is not + // linked from any freelist, this function does nothing. + void remove_from_freelist(); + + // Removes this object form any freelist, and marks this object as + // not schedulable. + void exclude_from_scheduling(); + + // Returns true if the maximum concurrency is reached. In other + // words, the number of currently participated streams in this + // session is equal or greater than the max concurrent streams limit + // advertised by server. If |extra| is nonzero, it is added to the + // number of current concurrent streams when comparing against + // server initiated concurrency limit. + bool max_concurrency_reached(size_t extra = 0) const; + + DefaultMemchunks *get_request_buf(); + + void on_timeout(); + + // This is called periodically using ev_prepare watcher, and if + // group_ is retired (backend has been replaced), send GOAWAY to + // shutdown the connection. + void check_retire(); + + // Returns address used to connect to backend. Could be nullptr. + const Address *get_raddr() const; + + // This is called when SETTINGS frame without ACK flag set is + // received. + void on_settings_received(const nghttp2_frame *frame); + + bool get_allow_connect_proto() const; + + using ReadBuf = Buffer<8_k>; + + Http2Session *dlnext, *dlprev; + +private: + Connection conn_; + DefaultMemchunks wb_; + ev_timer settings_timer_; + // This timer has 2 purpose: when it first timeout, set + // connection_check_state_ = ConnectionCheck::REQUIRED. After + // connection check has started, this timer is started again and + // traps PING ACK timeout. + ev_timer connchk_timer_; + // timer to initiate connection. usually, this fires immediately. + ev_timer initiate_connection_timer_; + ev_prepare prep_; + DList dconns_; + DList streams_; + std::function read_, write_; + std::function on_read_; + std::function on_write_; + // Used to parse the response from HTTP proxy + std::unique_ptr proxy_htp_; + Worker *worker_; + // NULL if no TLS is configured + SSL_CTX *ssl_ctx_; + std::shared_ptr group_; + // Address of remote endpoint + DownstreamAddr *addr_; + nghttp2_session *session_; + // Actual remote address used to contact backend. This is initially + // nullptr, and may point to either &addr_->addr, + // resolved_addr_.get(), or HTTP proxy's address structure. + const Address *raddr_; + // Resolved IP address if dns parameter is used + std::unique_ptr
resolved_addr_; + std::unique_ptr dns_query_; + Http2SessionState state_; + ConnectionCheck connection_check_state_; + FreelistZone freelist_zone_; + // true if SETTINGS without ACK is received from peer. + bool settings_recved_; + // true if peer enables RFC 8441 CONNECT protocol. + bool allow_connect_proto_; +}; + +nghttp2_session_callbacks *create_http2_downstream_callbacks(); + +} // namespace shrpx + +#endif // SHRPX_HTTP2_SESSION_H diff --git a/lib/nghttp2/src/shrpx_http2_upstream.cc b/lib/nghttp2/src/shrpx_http2_upstream.cc new file mode 100644 index 00000000000..c9f8a8caca2 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http2_upstream.cc @@ -0,0 +1,2404 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_http2_upstream.h" + +#include +#include +#include +#include + +#include "shrpx_client_handler.h" +#include "shrpx_https_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_downstream_connection.h" +#include "shrpx_config.h" +#include "shrpx_http.h" +#include "shrpx_worker.h" +#include "shrpx_http2_session.h" +#include "shrpx_log.h" +#ifdef HAVE_MRUBY +# include "shrpx_mruby.h" +#endif // HAVE_MRUBY +#include "http2.h" +#include "util.h" +#include "base64.h" +#include "app_helper.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +constexpr size_t MAX_BUFFER_SIZE = 32_k; +} // namespace + +namespace { +int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + auto upstream = static_cast(user_data); + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Stream stream_id=" << stream_id + << " is being closed"; + } + + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + + if (!downstream) { + return 0; + } + + auto &req = downstream->request(); + + upstream->consume(stream_id, req.unconsumed_body_length); + + req.unconsumed_body_length = 0; + + if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) { + upstream->remove_downstream(downstream); + // downstream was deleted + + return 0; + } + + if (downstream->can_detach_downstream_connection()) { + // Keep-alive + downstream->detach_downstream_connection(); + } + + downstream->set_request_state(DownstreamState::STREAM_CLOSED); + + // At this point, downstream read may be paused. + + // If shrpx_downstream::push_request_headers() failed, the + // error is handled here. + upstream->remove_downstream(downstream); + // downstream was deleted + + // How to test this case? Request sufficient large download + // and make client send RST_STREAM after it gets first DATA + // frame chunk. + + return 0; +} +} // namespace + +int Http2Upstream::upgrade_upstream(HttpsUpstream *http) { + int rv; + + auto &balloc = http->get_downstream()->get_block_allocator(); + + auto http2_settings = http->get_downstream()->get_http2_settings(); + http2_settings = util::to_base64(balloc, http2_settings); + + auto settings_payload = base64::decode(balloc, std::begin(http2_settings), + std::end(http2_settings)); + + rv = nghttp2_session_upgrade2( + session_, settings_payload.byte(), settings_payload.size(), + http->get_downstream()->request().method == HTTP_HEAD, nullptr); + if (rv != 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "nghttp2_session_upgrade() returned error: " + << nghttp2_strerror(rv); + } + return -1; + } + pre_upstream_.reset(http); + auto downstream = http->pop_downstream(); + downstream->reset_upstream(this); + downstream->set_stream_id(1); + downstream->reset_upstream_rtimer(); + downstream->set_stream_id(1); + + auto ptr = downstream.get(); + + nghttp2_session_set_stream_user_data(session_, 1, ptr); + downstream_queue_.add_pending(std::move(downstream)); + downstream_queue_.mark_active(ptr); + + // TODO This might not be necessary + handler_->stop_read_timer(); + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Connection upgraded to HTTP/2"; + } + + return 0; +} + +void Http2Upstream::start_settings_timer() { + ev_timer_start(handler_->get_loop(), &settings_timer_); +} + +void Http2Upstream::stop_settings_timer() { + ev_timer_stop(handler_->get_loop(), &settings_timer_); +} + +namespace { +int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame, + nghttp2_rcbuf *name, nghttp2_rcbuf *value, + uint8_t flags, void *user_data) { + auto namebuf = nghttp2_rcbuf_get_buf(name); + auto valuebuf = nghttp2_rcbuf_get_buf(value); + auto config = get_config(); + + if (config->http2.upstream.debug.frame_debug) { + verbose_on_header_callback(session, frame, namebuf.base, namebuf.len, + valuebuf.base, valuebuf.len, flags, user_data); + } + if (frame->hd.type != NGHTTP2_HEADERS) { + return 0; + } + auto upstream = static_cast(user_data); + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!downstream) { + return 0; + } + + auto &req = downstream->request(); + + auto &httpconf = config->http; + + if (req.fs.buffer_size() + namebuf.len + valuebuf.len > + httpconf.request_header_field_buffer || + req.fs.num_fields() >= httpconf.max_request_header_fields) { + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large or many header field size=" + << req.fs.buffer_size() + namebuf.len + valuebuf.len + << ", num=" << req.fs.num_fields() + 1; + } + + // just ignore header fields if this is trailer part. + if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { + return 0; + } + + if (upstream->error_reply(downstream, 431) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + return 0; + } + + auto token = http2::lookup_token(namebuf.base, namebuf.len); + auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX; + + downstream->add_rcbuf(name); + downstream->add_rcbuf(value); + + if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { + // just store header fields for trailer part + req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len}, + StringRef{valuebuf.base, valuebuf.len}, no_index, + token); + return 0; + } + + req.fs.add_header_token(StringRef{namebuf.base, namebuf.len}, + StringRef{valuebuf.base, valuebuf.len}, no_index, + token); + return 0; +} +} // namespace + +namespace { +int on_invalid_header_callback2(nghttp2_session *session, + const nghttp2_frame *frame, nghttp2_rcbuf *name, + nghttp2_rcbuf *value, uint8_t flags, + void *user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!downstream) { + return 0; + } + + if (LOG_ENABLED(INFO)) { + auto namebuf = nghttp2_rcbuf_get_buf(name); + auto valuebuf = nghttp2_rcbuf_get_buf(value); + + ULOG(INFO, upstream) << "Invalid header field for stream_id=" + << frame->hd.stream_id << ": name=[" + << StringRef{namebuf.base, namebuf.len} << "], value=[" + << StringRef{valuebuf.base, valuebuf.len} << "]"; + } + + upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); + + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} +} // namespace + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + auto upstream = static_cast(user_data); + + if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Received upstream request HEADERS stream_id=" + << frame->hd.stream_id; + } + + upstream->on_start_request(frame); + + return 0; +} +} // namespace + +void Http2Upstream::on_start_request(const nghttp2_frame *frame) { + auto downstream = std::make_unique(this, handler_->get_mcpool(), + frame->hd.stream_id); + nghttp2_session_set_stream_user_data(session_, frame->hd.stream_id, + downstream.get()); + + downstream->reset_upstream_rtimer(); + + handler_->repeat_read_timer(); + + auto &req = downstream->request(); + + // Although, we deprecated minor version from HTTP/2, we supply + // minor version 0 to use via header field in a conventional way. + req.http_major = 2; + req.http_minor = 0; + + add_pending_downstream(std::move(downstream)); + + ++num_requests_; + + auto config = get_config(); + auto &httpconf = config->http; + if (httpconf.max_requests <= num_requests_) { + start_graceful_shutdown(); + } +} + +int Http2Upstream::on_request_headers(Downstream *downstream, + const nghttp2_frame *frame) { + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + auto &req = downstream->request(); + req.tstamp = lgconf->tstamp; + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + auto &nva = req.fs.headers(); + + if (LOG_ENABLED(INFO)) { + std::stringstream ss; + for (auto &nv : nva) { + if (nv.name == "authorization") { + ss << TTY_HTTP_HD << nv.name << TTY_RST << ": \n"; + continue; + } + ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n"; + } + ULOG(INFO, this) << "HTTP request headers. stream_id=" + << downstream->get_stream_id() << "\n" + << ss.str(); + } + + auto config = get_config(); + auto &dump = config->http2.upstream.debug.dump; + + if (dump.request_header) { + http2::dump_nv(dump.request_header, nva); + } + + auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH); + if (content_length) { + // libnghttp2 guarantees this can be parsed + req.fs.content_length = util::parse_uint(content_length->value); + } + + // presence of mandatory header fields are guaranteed by libnghttp2. + auto authority = req.fs.header(http2::HD__AUTHORITY); + auto path = req.fs.header(http2::HD__PATH); + auto method = req.fs.header(http2::HD__METHOD); + auto scheme = req.fs.header(http2::HD__SCHEME); + + auto method_token = http2::lookup_method_token(method->value); + if (method_token == -1) { + if (error_reply(downstream, 501) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + return 0; + } + + auto faddr = handler_->get_upstream_addr(); + + // For HTTP/2 proxy, we require :authority. + if (method_token != HTTP_CONNECT && config->http2_proxy && + faddr->alt_mode == UpstreamAltMode::NONE && !authority) { + rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); + return 0; + } + + req.method = method_token; + if (scheme) { + req.scheme = scheme->value; + } + + // nghttp2 library guarantees either :authority or host exist + if (!authority) { + req.no_authority = true; + authority = req.fs.header(http2::HD_HOST); + } + + if (authority) { + req.authority = authority->value; + } + + if (path) { + if (method_token == HTTP_OPTIONS && + path->value == StringRef::from_lit("*")) { + // Server-wide OPTIONS request. Path is empty. + } else if (config->http2_proxy && + faddr->alt_mode == UpstreamAltMode::NONE) { + req.path = path->value; + } else { + req.path = http2::rewrite_clean_path(downstream->get_block_allocator(), + path->value); + } + } + + auto connect_proto = req.fs.header(http2::HD__PROTOCOL); + if (connect_proto) { + if (connect_proto->value != "websocket") { + if (error_reply(downstream, 400) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + return 0; + } + req.connect_proto = ConnectProto::WEBSOCKET; + } + + if (!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + req.http2_expect_body = true; + } else if (req.fs.content_length == -1) { + // If END_STREAM flag is set to HEADERS frame, we are sure that + // content-length is 0. + req.fs.content_length = 0; + } + + downstream->inspect_http2_request(); + + downstream->set_request_state(DownstreamState::HEADER_COMPLETE); + + if (config->http.require_http_scheme && + !http::check_http_scheme(req.scheme, handler_->get_ssl() != nullptr)) { + if (error_reply(downstream, 400) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + return 0; + } + +#ifdef HAVE_MRUBY + auto worker = handler_->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_request_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + return 0; + } +#endif // HAVE_MRUBY + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + downstream->disable_upstream_rtimer(); + + downstream->set_request_state(DownstreamState::MSG_COMPLETE); + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + start_downstream(downstream); + + return 0; +} + +void Http2Upstream::start_downstream(Downstream *downstream) { + if (downstream_queue_.can_activate(downstream->request().authority)) { + initiate_downstream(downstream); + return; + } + + downstream_queue_.mark_blocked(downstream); +} + +void Http2Upstream::initiate_downstream(Downstream *downstream) { + int rv; + +#ifdef HAVE_MRUBY + DownstreamConnection *dconn_ptr; +#endif // HAVE_MRUBY + + for (;;) { + auto dconn = handler_->get_downstream_connection(rv, downstream); + if (!dconn) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = redirect_to_https(downstream); + } else { + rv = error_reply(downstream, 502); + } + if (rv != 0) { + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + + downstream->set_request_state(DownstreamState::CONNECT_FAIL); + downstream_queue_.mark_failure(downstream); + + return; + } + +#ifdef HAVE_MRUBY + dconn_ptr = dconn.get(); +#endif // HAVE_MRUBY + rv = downstream->attach_downstream_connection(std::move(dconn)); + if (rv == 0) { + break; + } + } + +#ifdef HAVE_MRUBY + const auto &group = dconn_ptr->get_downstream_addr_group(); + if (group) { + const auto &mruby_ctx = group->shared_addr->mruby_ctx; + if (mruby_ctx->run_on_request_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + + downstream_queue_.mark_failure(downstream); + + return; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return; + } + } +#endif // HAVE_MRUBY + + rv = downstream->push_request_headers(); + if (rv != 0) { + + if (error_reply(downstream, 502) != 0) { + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + + downstream_queue_.mark_failure(downstream); + + return; + } + + downstream_queue_.mark_active(downstream); + + auto &req = downstream->request(); + if (!req.http2_expect_body) { + rv = downstream->end_upload_data(); + if (rv != 0) { + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + } + + return; +} + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + if (get_config()->http2.upstream.debug.frame_debug) { + verbose_on_frame_recv_callback(session, frame, user_data); + } + auto upstream = static_cast(user_data); + auto handler = upstream->get_client_handler(); + + switch (frame->hd.type) { + case NGHTTP2_DATA: { + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!downstream) { + return 0; + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + downstream->disable_upstream_rtimer(); + + if (downstream->end_upload_data() != 0) { + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { + upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + } + + downstream->set_request_state(DownstreamState::MSG_COMPLETE); + } + + return 0; + } + case NGHTTP2_HEADERS: { + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (!downstream) { + return 0; + } + + if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { + downstream->reset_upstream_rtimer(); + + handler->stop_read_timer(); + + return upstream->on_request_headers(downstream, frame); + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + downstream->disable_upstream_rtimer(); + + if (downstream->end_upload_data() != 0) { + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { + upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + } + + downstream->set_request_state(DownstreamState::MSG_COMPLETE); + } + + return 0; + } + case NGHTTP2_SETTINGS: + if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { + return 0; + } + upstream->stop_settings_timer(); + return 0; + case NGHTTP2_GOAWAY: + if (LOG_ENABLED(INFO)) { + auto debug_data = util::ascii_dump(frame->goaway.opaque_data, + frame->goaway.opaque_data_len); + + ULOG(INFO, upstream) << "GOAWAY received: last-stream-id=" + << frame->goaway.last_stream_id + << ", error_code=" << frame->goaway.error_code + << ", debug_data=" << debug_data; + } + return 0; + default: + return 0; + } +} +} // namespace + +namespace { +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + + if (!downstream) { + if (upstream->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; + } + + downstream->reset_upstream_rtimer(); + + if (downstream->push_upload_data_chunk(data, len) != 0) { + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { + upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + + if (upstream->consume(stream_id, len) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return 0; + } + + return 0; +} +} // namespace + +namespace { +int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + if (get_config()->http2.upstream.debug.frame_debug) { + verbose_on_frame_send_callback(session, frame, user_data); + } + auto upstream = static_cast(user_data); + auto handler = upstream->get_client_handler(); + + switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: { + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + return 0; + } + // RST_STREAM if request is still incomplete. + auto stream_id = frame->hd.stream_id; + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + + if (!downstream) { + return 0; + } + + // For tunneling, issue RST_STREAM to finish the stream. + if (downstream->get_upgraded() || + nghttp2_session_get_stream_remote_close(session, stream_id) == 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) + << "Send RST_STREAM to " + << (downstream->get_upgraded() ? "tunneled " : "") + << "stream stream_id=" << downstream->get_stream_id() + << " to finish off incomplete request"; + } + + upstream->rst_stream(downstream, NGHTTP2_NO_ERROR); + } + + return 0; + } + case NGHTTP2_SETTINGS: + if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { + upstream->start_settings_timer(); + } + return 0; + case NGHTTP2_PUSH_PROMISE: { + auto promised_stream_id = frame->push_promise.promised_stream_id; + + if (nghttp2_session_get_stream_user_data(session, promised_stream_id)) { + // In case of push from backend, downstream object was already + // created. + return 0; + } + + auto promised_downstream = std::make_unique( + upstream, handler->get_mcpool(), promised_stream_id); + auto &req = promised_downstream->request(); + + // As long as we use nghttp2_session_mem_send(), setting stream + // user data here should not fail. This is because this callback + // is called just after frame was serialized. So no worries about + // hanging Downstream. + nghttp2_session_set_stream_user_data(session, promised_stream_id, + promised_downstream.get()); + + promised_downstream->set_assoc_stream_id(frame->hd.stream_id); + promised_downstream->disable_upstream_rtimer(); + + req.http_major = 2; + req.http_minor = 0; + + req.fs.content_length = 0; + req.http2_expect_body = false; + + auto &promised_balloc = promised_downstream->get_block_allocator(); + + for (size_t i = 0; i < frame->push_promise.nvlen; ++i) { + auto &nv = frame->push_promise.nva[i]; + + auto name = + make_string_ref(promised_balloc, StringRef{nv.name, nv.namelen}); + auto value = + make_string_ref(promised_balloc, StringRef{nv.value, nv.valuelen}); + + auto token = http2::lookup_token(nv.name, nv.namelen); + switch (token) { + case http2::HD__METHOD: + req.method = http2::lookup_method_token(value); + break; + case http2::HD__SCHEME: + req.scheme = value; + break; + case http2::HD__AUTHORITY: + req.authority = value; + break; + case http2::HD__PATH: + req.path = http2::rewrite_clean_path(promised_balloc, value); + break; + } + req.fs.add_header_token(name, value, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX, + token); + } + + promised_downstream->inspect_http2_request(); + + promised_downstream->set_request_state(DownstreamState::MSG_COMPLETE); + + // a bit weird but start_downstream() expects that given + // downstream is in pending queue. + auto ptr = promised_downstream.get(); + upstream->add_pending_downstream(std::move(promised_downstream)); + +#ifdef HAVE_MRUBY + auto worker = handler->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_request_proc(ptr) != 0) { + if (upstream->error_reply(ptr, 500) != 0) { + upstream->rst_stream(ptr, NGHTTP2_INTERNAL_ERROR); + return 0; + } + return 0; + } +#endif // HAVE_MRUBY + + upstream->start_downstream(ptr); + + return 0; + } + case NGHTTP2_GOAWAY: + if (LOG_ENABLED(INFO)) { + auto debug_data = util::ascii_dump(frame->goaway.opaque_data, + frame->goaway.opaque_data_len); + + ULOG(INFO, upstream) << "Sending GOAWAY: last-stream-id=" + << frame->goaway.last_stream_id + << ", error_code=" << frame->goaway.error_code + << ", debug_data=" << debug_data; + } + return 0; + default: + return 0; + } +} +} // namespace + +namespace { +int on_frame_not_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, int lib_error_code, + void *user_data) { + auto upstream = static_cast(user_data); + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Failed to send control frame type=" + << static_cast(frame->hd.type) + << ", lib_error_code=" << lib_error_code << ":" + << nghttp2_strerror(lib_error_code); + } + if (frame->hd.type == NGHTTP2_HEADERS && + lib_error_code != NGHTTP2_ERR_STREAM_CLOSED && + lib_error_code != NGHTTP2_ERR_STREAM_CLOSING) { + // To avoid stream hanging around, issue RST_STREAM. + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if (downstream) { + upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + } + return 0; +} +} // namespace + +namespace { +constexpr auto PADDING = std::array{}; +} // namespace + +namespace { +int send_data_callback(nghttp2_session *session, nghttp2_frame *frame, + const uint8_t *framehd, size_t length, + nghttp2_data_source *source, void *user_data) { + auto downstream = static_cast(source->ptr); + auto upstream = static_cast(downstream->get_upstream()); + auto body = downstream->get_response_buf(); + + auto wb = upstream->get_response_buf(); + + size_t padlen = 0; + + wb->append(framehd, 9); + if (frame->data.padlen > 0) { + padlen = frame->data.padlen - 1; + wb->append(static_cast(padlen)); + } + + body->remove(*wb, length); + + wb->append(PADDING.data(), padlen); + + if (body->rleft() == 0) { + downstream->disable_upstream_wtimer(); + } else { + downstream->reset_upstream_wtimer(); + } + + if (length > 0 && downstream->resume_read(SHRPX_NO_BUFFER, length) != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + // We have to add length here, so that we can log this amount of + // data transferred. + downstream->response_sent_body_length += length; + + auto max_buffer_size = upstream->get_max_buffer_size(); + + return wb->rleft() >= max_buffer_size ? NGHTTP2_ERR_PAUSE : 0; +} +} // namespace + +namespace { +uint32_t infer_upstream_rst_stream_error_code(uint32_t downstream_error_code) { + // NGHTTP2_REFUSED_STREAM is important because it tells upstream + // client to retry. + switch (downstream_error_code) { + case NGHTTP2_NO_ERROR: + case NGHTTP2_REFUSED_STREAM: + return downstream_error_code; + default: + return NGHTTP2_INTERNAL_ERROR; + } +} +} // namespace + +namespace { +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto upstream = static_cast(w->data); + auto handler = upstream->get_client_handler(); + ULOG(INFO, upstream) << "SETTINGS timeout"; + if (upstream->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) { + delete handler; + return; + } + handler->signal_write(); +} +} // namespace + +namespace { +void shutdown_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto upstream = static_cast(w->data); + auto handler = upstream->get_client_handler(); + upstream->submit_goaway(); + handler->signal_write(); +} +} // namespace + +namespace { +void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revents) { + auto upstream = static_cast(w->data); + upstream->check_shutdown(); +} +} // namespace + +void Http2Upstream::submit_goaway() { + auto last_stream_id = nghttp2_session_get_last_proc_stream_id(session_); + nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE, last_stream_id, + NGHTTP2_NO_ERROR, nullptr, 0); +} + +void Http2Upstream::check_shutdown() { + auto worker = handler_->get_worker(); + + if (!worker->get_graceful_shutdown()) { + return; + } + + ev_prepare_stop(handler_->get_loop(), &prep_); + + start_graceful_shutdown(); +} + +void Http2Upstream::start_graceful_shutdown() { + int rv; + if (ev_is_active(&shutdown_timer_)) { + return; + } + + rv = nghttp2_submit_shutdown_notice(session_); + if (rv != 0) { + ULOG(FATAL, this) << "nghttp2_submit_shutdown_notice() failed: " + << nghttp2_strerror(rv); + return; + } + + handler_->signal_write(); + + ev_timer_start(handler_->get_loop(), &shutdown_timer_); +} + +nghttp2_session_callbacks *create_http2_upstream_callbacks() { + int rv; + nghttp2_session_callbacks *callbacks; + + rv = nghttp2_session_callbacks_new(&callbacks); + + if (rv != 0) { + return nullptr; + } + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + + nghttp2_session_callbacks_set_on_frame_not_send_callback( + callbacks, on_frame_not_send_callback); + + nghttp2_session_callbacks_set_on_header_callback2(callbacks, + on_header_callback2); + + nghttp2_session_callbacks_set_on_invalid_header_callback2( + callbacks, on_invalid_header_callback2); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + + nghttp2_session_callbacks_set_send_data_callback(callbacks, + send_data_callback); + + auto config = get_config(); + + if (config->padding) { + nghttp2_session_callbacks_set_select_padding_callback( + callbacks, http::select_padding_callback); + } + + if (config->http2.upstream.debug.frame_debug) { + nghttp2_session_callbacks_set_error_callback2(callbacks, + verbose_error_callback); + } + + return callbacks; +} + +namespace { +size_t downstream_queue_size(Worker *worker) { + auto &downstreamconf = *worker->get_downstream_config(); + + if (get_config()->http2_proxy) { + return downstreamconf.connections_per_host; + } + + return downstreamconf.connections_per_frontend; +} +} // namespace + +Http2Upstream::Http2Upstream(ClientHandler *handler) + : wb_(handler->get_worker()->get_mcpool()), + downstream_queue_(downstream_queue_size(handler->get_worker()), + !get_config()->http2_proxy), + handler_(handler), + session_(nullptr), + max_buffer_size_(MAX_BUFFER_SIZE), + num_requests_(0) { + int rv; + + auto config = get_config(); + auto &http2conf = config->http2; + + auto faddr = handler_->get_upstream_addr(); + + rv = + nghttp2_session_server_new2(&session_, http2conf.upstream.callbacks, this, + faddr->alt_mode != UpstreamAltMode::NONE + ? http2conf.upstream.alt_mode_option + : http2conf.upstream.option); + + assert(rv == 0); + + flow_control_ = true; + + // TODO Maybe call from outside? + std::array entry; + size_t nentry = 3; + + entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + entry[0].value = http2conf.upstream.max_concurrent_streams; + + entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + if (faddr->alt_mode != UpstreamAltMode::NONE) { + entry[1].value = (1u << 31) - 1; + } else { + entry[1].value = http2conf.upstream.window_size; + } + + entry[2].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + entry[2].value = 1; + + if (!config->http2_proxy) { + entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL; + entry[nentry].value = 1; + ++nentry; + } + + if (http2conf.upstream.decoder_dynamic_table_size != + NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { + entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + entry[nentry].value = http2conf.upstream.decoder_dynamic_table_size; + ++nentry; + } + + rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), + nentry); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp2_submit_settings() returned error: " + << nghttp2_strerror(rv); + } + + auto window_size = faddr->alt_mode != UpstreamAltMode::NONE + ? std::numeric_limits::max() + : http2conf.upstream.optimize_window_size + ? std::min(http2conf.upstream.connection_window_size, + NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE) + : http2conf.upstream.connection_window_size; + + rv = nghttp2_session_set_local_window_size(session_, NGHTTP2_FLAG_NONE, 0, + window_size); + + if (rv != 0) { + ULOG(ERROR, this) + << "nghttp2_session_set_local_window_size() returned error: " + << nghttp2_strerror(rv); + } + + // We wait for SETTINGS ACK at least 10 seconds. + ev_timer_init(&settings_timer_, settings_timeout_cb, + http2conf.upstream.timeout.settings, 0.); + + settings_timer_.data = this; + + // timer for 2nd GOAWAY. HTTP/2 spec recommend 1 RTT. We wait for + // 2 seconds. + ev_timer_init(&shutdown_timer_, shutdown_timeout_cb, 2., 0); + shutdown_timer_.data = this; + + ev_prepare_init(&prep_, prepare_cb); + prep_.data = this; + ev_prepare_start(handler_->get_loop(), &prep_); + +#if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT) + if (http2conf.upstream.optimize_write_buffer_size) { + auto conn = handler_->get_connection(); + conn->tls_dyn_rec_warmup_threshold = 0; + + uint32_t pollout_thres = 1; + rv = setsockopt(conn->fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &pollout_thres, + static_cast(sizeof(pollout_thres))); + + if (rv != 0) { + if (LOG_ENABLED(INFO)) { + auto error = errno; + LOG(INFO) << "setsockopt(TCP_NOTSENT_LOWAT, " << pollout_thres + << ") failed: errno=" << error; + } + } + } +#endif // defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT) + + handler_->reset_upstream_read_timeout( + config->conn.upstream.timeout.http2_read); + + handler_->signal_write(); +} + +Http2Upstream::~Http2Upstream() { + nghttp2_session_del(session_); + ev_prepare_stop(handler_->get_loop(), &prep_); + ev_timer_stop(handler_->get_loop(), &shutdown_timer_); + ev_timer_stop(handler_->get_loop(), &settings_timer_); +} + +int Http2Upstream::on_read() { + ssize_t rv = 0; + auto rb = handler_->get_rb(); + auto rlimit = handler_->get_rlimit(); + + if (rb->rleft()) { + rv = nghttp2_session_mem_recv(session_, rb->pos(), rb->rleft()); + if (rv < 0) { + if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) { + ULOG(ERROR, this) << "nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv); + } + return -1; + } + + // nghttp2_session_mem_recv should consume all input bytes on + // success. + assert(static_cast(rv) == rb->rleft()); + rb->reset(); + rlimit->startw(); + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "No more read/write for this HTTP2 session"; + } + return -1; + } + + handler_->signal_write(); + return 0; +} + +// After this function call, downstream may be deleted. +int Http2Upstream::on_write() { + int rv; + auto config = get_config(); + auto &http2conf = config->http2; + + if ((http2conf.upstream.optimize_write_buffer_size || + http2conf.upstream.optimize_window_size) && + handler_->get_ssl()) { + auto conn = handler_->get_connection(); + TCPHint hint; + rv = conn->get_tcp_hint(&hint); + if (rv == 0) { + if (http2conf.upstream.optimize_write_buffer_size) { + max_buffer_size_ = std::min(MAX_BUFFER_SIZE, hint.write_buffer_size); + } + + if (http2conf.upstream.optimize_window_size) { + auto faddr = handler_->get_upstream_addr(); + if (faddr->alt_mode == UpstreamAltMode::NONE) { + auto window_size = std::min(http2conf.upstream.connection_window_size, + static_cast(hint.rwin * 2)); + + rv = nghttp2_session_set_local_window_size( + session_, NGHTTP2_FLAG_NONE, 0, window_size); + if (rv != 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) + << "nghttp2_session_set_local_window_size() with window_size=" + << window_size << " failed: " << nghttp2_strerror(rv); + } + } + } + } + } + } + + for (;;) { + if (wb_.rleft() >= max_buffer_size_) { + return 0; + } + + const uint8_t *data; + auto datalen = nghttp2_session_mem_send(session_, &data); + + if (datalen < 0) { + ULOG(ERROR, this) << "nghttp2_session_mem_send() returned error: " + << nghttp2_strerror(datalen); + return -1; + } + if (datalen == 0) { + break; + } + wb_.append(data, datalen); + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "No more read/write for this HTTP2 session"; + } + return -1; + } + + return 0; +} + +ClientHandler *Http2Upstream::get_client_handler() const { return handler_; } + +int Http2Upstream::downstream_read(DownstreamConnection *dconn) { + auto downstream = dconn->get_downstream(); + + if (downstream->get_response_state() == DownstreamState::MSG_RESET) { + // The downstream stream was reset (canceled). In this case, + // RST_STREAM to the upstream and delete downstream connection + // here. Deleting downstream will be taken place at + // on_stream_close_callback. + rst_stream(downstream, + infer_upstream_rst_stream_error_code( + downstream->get_response_rst_stream_error_code())); + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + } else if (downstream->get_response_state() == + DownstreamState::MSG_BAD_HEADER) { + if (error_reply(downstream, 502) != 0) { + return -1; + } + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + } else { + auto rv = downstream->on_read(); + if (rv == SHRPX_ERR_EOF) { + if (downstream->get_request_header_sent()) { + return downstream_eof(dconn); + } + return SHRPX_ERR_RETRY; + } + if (rv == SHRPX_ERR_DCONN_CANCELED) { + downstream->pop_downstream_connection(); + handler_->signal_write(); + return 0; + } + if (rv != 0) { + if (rv != SHRPX_ERR_NETWORK) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "HTTP parser failure"; + } + } + return downstream_error(dconn, Downstream::EVENT_ERROR); + } + + if (downstream->can_detach_downstream_connection()) { + // Keep-alive + downstream->detach_downstream_connection(); + } + } + + handler_->signal_write(); + + // At this point, downstream may be deleted. + + return 0; +} + +int Http2Upstream::downstream_write(DownstreamConnection *dconn) { + int rv; + rv = dconn->on_write(); + if (rv == SHRPX_ERR_NETWORK) { + return downstream_error(dconn, Downstream::EVENT_ERROR); + } + if (rv != 0) { + return rv; + } + return 0; +} + +int Http2Upstream::downstream_eof(DownstreamConnection *dconn) { + auto downstream = dconn->get_downstream(); + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id(); + } + + // Delete downstream connection. If we don't delete it here, it will + // be pooled in on_stream_close_callback. + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + // downstream will be deleted in on_stream_close_callback. + if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { + // Server may indicate the end of the request by EOF + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Downstream body was ended by EOF"; + } + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + // For tunneled connection, MSG_COMPLETE signals + // downstream_data_read_callback to send RST_STREAM after pending + // response body is sent. This is needed to ensure that RST_STREAM + // is sent after all pending data are sent. + on_downstream_body_complete(downstream); + } else if (downstream->get_response_state() != + DownstreamState::MSG_COMPLETE) { + // If stream was not closed, then we set MSG_COMPLETE and let + // on_stream_close_callback delete downstream. + if (error_reply(downstream, 502) != 0) { + return -1; + } + } + handler_->signal_write(); + // At this point, downstream may be deleted. + return 0; +} + +int Http2Upstream::downstream_error(DownstreamConnection *dconn, int events) { + auto downstream = dconn->get_downstream(); + + if (LOG_ENABLED(INFO)) { + if (events & Downstream::EVENT_ERROR) { + DCLOG(INFO, dconn) << "Downstream network/general error"; + } else { + DCLOG(INFO, dconn) << "Timeout"; + } + if (downstream->get_upgraded()) { + DCLOG(INFO, dconn) << "Note: this is tunnel connection"; + } + } + + // Delete downstream connection. If we don't delete it here, it will + // be pooled in on_stream_close_callback. + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + // For SSL tunneling, we issue RST_STREAM. For other types of + // stream, we don't have to do anything since response was + // complete. + if (downstream->get_upgraded()) { + rst_stream(downstream, NGHTTP2_NO_ERROR); + } + } else { + if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { + if (downstream->get_upgraded()) { + on_downstream_body_complete(downstream); + } else { + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + } else { + unsigned int status; + if (events & Downstream::EVENT_TIMEOUT) { + if (downstream->get_request_header_sent()) { + status = 504; + } else { + status = 408; + } + } else { + status = 502; + } + if (error_reply(downstream, status) != 0) { + return -1; + } + } + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + } + handler_->signal_write(); + // At this point, downstream may be deleted. + return 0; +} + +int Http2Upstream::rst_stream(Downstream *downstream, uint32_t error_code) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "RST_STREAM stream_id=" << downstream->get_stream_id() + << " with error_code=" << error_code; + } + int rv; + rv = nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, + downstream->get_stream_id(), error_code); + if (rv < NGHTTP2_ERR_FATAL) { + ULOG(FATAL, this) << "nghttp2_submit_rst_stream() failed: " + << nghttp2_strerror(rv); + return -1; + } + return 0; +} + +int Http2Upstream::terminate_session(uint32_t error_code) { + int rv; + rv = nghttp2_session_terminate_session(session_, error_code); + if (rv != 0) { + return -1; + } + return 0; +} + +namespace { +ssize_t downstream_data_read_callback(nghttp2_session *session, + int32_t stream_id, uint8_t *buf, + size_t length, uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + int rv; + auto downstream = static_cast(source->ptr); + auto body = downstream->get_response_buf(); + assert(body); + auto upstream = static_cast(user_data); + + const auto &resp = downstream->response(); + + auto nread = std::min(body->rleft(), length); + + auto max_buffer_size = upstream->get_max_buffer_size(); + + auto buffer = upstream->get_response_buf(); + + if (max_buffer_size < + std::min(nread, static_cast(256)) + 9 + buffer->rleft()) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Buffer is almost full. Skip write DATA"; + } + return NGHTTP2_ERR_PAUSE; + } + + nread = std::min(nread, max_buffer_size - 9 - buffer->rleft()); + + auto body_empty = body->rleft() == nread; + + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + + if (body_empty && + downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + if (!downstream->get_upgraded()) { + const auto &trailers = resp.fs.trailers(); + if (!trailers.empty()) { + std::vector nva; + nva.reserve(trailers.size()); + http2::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL); + if (!nva.empty()) { + rv = nghttp2_submit_trailer(session, stream_id, nva.data(), + nva.size()); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } else { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + } + } + } + } + + if (nread == 0 && ((*data_flags) & NGHTTP2_DATA_FLAG_EOF) == 0) { + downstream->disable_upstream_wtimer(); + return NGHTTP2_ERR_DEFERRED; + } + + return nread; +} +} // namespace + +int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen) { + int rv; + + nghttp2_data_provider data_prd, *data_prd_ptr = nullptr; + + if (bodylen) { + data_prd.source.ptr = downstream; + data_prd.read_callback = downstream_data_read_callback; + data_prd_ptr = &data_prd; + } + + const auto &resp = downstream->response(); + auto config = get_config(); + auto &httpconf = config->http; + + auto &balloc = downstream->get_block_allocator(); + + const auto &headers = resp.fs.headers(); + auto nva = std::vector(); + // 2 for :status and server + nva.reserve(2 + headers.size() + httpconf.add_response_headers.size()); + + auto response_status = http2::stringify_status(balloc, resp.http_status); + + nva.push_back(http2::make_nv_ls_nocopy(":status", response_status)); + + for (auto &kv : headers) { + if (kv.name.empty() || kv.name[0] == ':') { + continue; + } + switch (kv.token) { + case http2::HD_CONNECTION: + case http2::HD_KEEP_ALIVE: + case http2::HD_PROXY_CONNECTION: + case http2::HD_TE: + case http2::HD_TRANSFER_ENCODING: + case http2::HD_UPGRADE: + continue; + } + nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index)); + } + + if (!resp.fs.header(http2::HD_SERVER)) { + nva.push_back(http2::make_nv_ls_nocopy("server", config->http.server_name)); + } + + for (auto &p : httpconf.add_response_headers) { + nva.push_back(http2::make_nv_nocopy(p.name, p.value)); + } + + rv = nghttp2_submit_response(session_, downstream->get_stream_id(), + nva.data(), nva.size(), data_prd_ptr); + if (nghttp2_is_fatal(rv)) { + ULOG(FATAL, this) << "nghttp2_submit_response() failed: " + << nghttp2_strerror(rv); + return -1; + } + + auto buf = downstream->get_response_buf(); + + buf->append(body, bodylen); + + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + if (data_prd_ptr) { + downstream->reset_upstream_wtimer(); + } + + return 0; +} + +int Http2Upstream::error_reply(Downstream *downstream, + unsigned int status_code) { + int rv; + auto &resp = downstream->response(); + + auto &balloc = downstream->get_block_allocator(); + + auto html = http::create_error_html(balloc, status_code); + resp.http_status = status_code; + auto body = downstream->get_response_buf(); + body->append(html); + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + nghttp2_data_provider data_prd; + data_prd.source.ptr = downstream; + data_prd.read_callback = downstream_data_read_callback; + + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + + auto response_status = http2::stringify_status(balloc, status_code); + auto content_length = util::make_string_ref_uint(balloc, html.size()); + auto date = make_string_ref(balloc, lgconf->tstamp->time_http); + + auto nva = std::array{ + {http2::make_nv_ls_nocopy(":status", response_status), + http2::make_nv_ll("content-type", "text/html; charset=UTF-8"), + http2::make_nv_ls_nocopy("server", get_config()->http.server_name), + http2::make_nv_ls_nocopy("content-length", content_length), + http2::make_nv_ls_nocopy("date", date)}}; + + rv = nghttp2_submit_response(session_, downstream->get_stream_id(), + nva.data(), nva.size(), &data_prd); + if (rv < NGHTTP2_ERR_FATAL) { + ULOG(FATAL, this) << "nghttp2_submit_response() failed: " + << nghttp2_strerror(rv); + return -1; + } + + downstream->reset_upstream_wtimer(); + + return 0; +} + +void Http2Upstream::add_pending_downstream( + std::unique_ptr downstream) { + downstream_queue_.add_pending(std::move(downstream)); +} + +void Http2Upstream::remove_downstream(Downstream *downstream) { + if (downstream->accesslog_ready()) { + handler_->write_accesslog(downstream); + } + + nghttp2_session_set_stream_user_data(session_, downstream->get_stream_id(), + nullptr); + + auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream); + + if (next_downstream) { + initiate_downstream(next_downstream); + } + + if (downstream_queue_.get_downstreams() == nullptr) { + // There is no downstream at the moment. Start idle timer now. + handler_->repeat_read_timer(); + } +} + +// WARNING: Never call directly or indirectly nghttp2_session_send or +// nghttp2_session_recv. These calls may delete downstream. +int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { + int rv; + + const auto &req = downstream->request(); + auto &resp = downstream->response(); + + auto &balloc = downstream->get_block_allocator(); + + if (LOG_ENABLED(INFO)) { + if (downstream->get_non_final_response()) { + DLOG(INFO, downstream) << "HTTP non-final response header"; + } else { + DLOG(INFO, downstream) << "HTTP response header completed"; + } + } + + auto config = get_config(); + auto &httpconf = config->http; + + if (!config->http2_proxy && !httpconf.no_location_rewrite) { + downstream->rewrite_location_response_header(req.scheme); + } + +#ifdef HAVE_MRUBY + if (!downstream->get_non_final_response()) { + auto dconn = downstream->get_downstream_connection(); + const auto &group = dconn->get_downstream_addr_group(); + if (group) { + const auto &dmruby_ctx = group->shared_addr->mruby_ctx; + + if (dmruby_ctx->run_on_response_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return -1; + } + // Returning -1 will signal deletion of dconn. + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return -1; + } + } + + auto worker = handler_->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_response_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return -1; + } + // Returning -1 will signal deletion of dconn. + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return -1; + } + } +#endif // HAVE_MRUBY + + auto &http2conf = config->http2; + + // We need some conditions that must be fulfilled to initiate server + // push. + // + // * Server push is disabled for http2 proxy or client proxy, since + // incoming headers are mixed origins. We don't know how to + // reliably determine the authority yet. + // + // * We need non-final response or 200 response code for associated + // resource. This is too restrictive, we will review this later. + // + // * We requires GET or POST for associated resource. Probably we + // don't want to push for HEAD request. Not sure other methods + // are also eligible for push. + if (!http2conf.no_server_push && + nghttp2_session_get_remote_settings(session_, + NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 && + !config->http2_proxy && (downstream->get_stream_id() % 2) && + resp.fs.header(http2::HD_LINK) && + (downstream->get_non_final_response() || resp.http_status == 200) && + (req.method == HTTP_GET || req.method == HTTP_POST)) { + + if (prepare_push_promise(downstream) != 0) { + // Continue to send response even if push was failed. + } + } + + auto nva = std::vector(); + // 6 means :status and possible server, via, x-http2-push, alt-svc, + // and set-cookie (for affinity cookie) header field. + nva.reserve(resp.fs.headers().size() + 6 + + httpconf.add_response_headers.size()); + + if (downstream->get_non_final_response()) { + auto response_status = http2::stringify_status(balloc, resp.http_status); + + nva.push_back(http2::make_nv_ls_nocopy(":status", response_status)); + + http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), + http2::HDOP_STRIP_ALL); + + if (LOG_ENABLED(INFO)) { + log_response_headers(downstream, nva); + } + + rv = nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, + downstream->get_stream_id(), nullptr, + nva.data(), nva.size(), nullptr); + + resp.fs.clear_headers(); + + if (rv != 0) { + ULOG(FATAL, this) << "nghttp2_submit_headers() failed"; + return -1; + } + + return 0; + } + + auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA; + StringRef response_status; + + if (req.connect_proto == ConnectProto::WEBSOCKET && resp.http_status == 101) { + response_status = http2::stringify_status(balloc, 200); + striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT; + } else { + response_status = http2::stringify_status(balloc, resp.http_status); + } + + nva.push_back(http2::make_nv_ls_nocopy(":status", response_status)); + + http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags); + + if (!config->http2_proxy && !httpconf.no_server_rewrite) { + nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name)); + } else { + auto server = resp.fs.header(http2::HD_SERVER); + if (server) { + nva.push_back(http2::make_nv_ls_nocopy("server", (*server).value)); + } + } + + if (!req.regular_connect_method() || !downstream->get_upgraded()) { + auto affinity_cookie = downstream->get_affinity_cookie_to_send(); + if (affinity_cookie) { + auto dconn = downstream->get_downstream_connection(); + assert(dconn); + auto &group = dconn->get_downstream_addr_group(); + auto &shared_addr = group->shared_addr; + auto &cookieconf = shared_addr->affinity.cookie; + auto secure = + http::require_cookie_secure_attribute(cookieconf.secure, req.scheme); + auto cookie_str = http::create_affinity_cookie( + balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure); + nva.push_back(http2::make_nv_ls_nocopy("set-cookie", cookie_str)); + } + } + + if (!resp.fs.header(http2::HD_ALT_SVC)) { + // We won't change or alter alt-svc from backend for now + if (!httpconf.http2_altsvc_header_value.empty()) { + nva.push_back(http2::make_nv_ls_nocopy( + "alt-svc", httpconf.http2_altsvc_header_value)); + } + } + + auto via = resp.fs.header(http2::HD_VIA); + if (httpconf.no_via) { + if (via) { + nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value)); + } + } else { + // we don't create more than 16 bytes in + // http::create_via_header_value. + size_t len = 16; + if (via) { + len += via->value.size() + 2; + } + + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + if (via) { + p = std::copy(std::begin(via->value), std::end(via->value), p); + p = util::copy_lit(p, ", "); + } + p = http::create_via_header_value(p, resp.http_major, resp.http_minor); + *p = '\0'; + + nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p})); + } + + for (auto &p : httpconf.add_response_headers) { + nva.push_back(http2::make_nv_nocopy(p.name, p.value)); + } + + if (downstream->get_stream_id() % 2 == 0) { + // This header field is basically for human on client side to + // figure out that the resource is pushed. + nva.push_back(http2::make_nv_ll("x-http2-push", "1")); + } + + if (LOG_ENABLED(INFO)) { + log_response_headers(downstream, nva); + } + + if (http2conf.upstream.debug.dump.response_header) { + http2::dump_nv(http2conf.upstream.debug.dump.response_header, nva.data(), + nva.size()); + } + + auto priority = resp.fs.header(http2::HD_PRIORITY); + if (priority) { + nghttp2_extpri extpri; + + if (nghttp2_session_get_extpri_stream_priority( + session_, &extpri, downstream->get_stream_id()) == 0 && + nghttp2_extpri_parse_priority(&extpri, priority->value.byte(), + priority->value.size()) == 0) { + rv = nghttp2_session_change_extpri_stream_priority( + session_, downstream->get_stream_id(), &extpri, + /* ignore_client_signal = */ 1); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp2_session_change_extpri_stream_priority: " + << nghttp2_strerror(rv); + } + } + } + + nghttp2_data_provider data_prd; + data_prd.source.ptr = downstream; + data_prd.read_callback = downstream_data_read_callback; + + nghttp2_data_provider *data_prdptr; + + if (downstream->expect_response_body() || + downstream->expect_response_trailer()) { + data_prdptr = &data_prd; + } else { + data_prdptr = nullptr; + } + + rv = nghttp2_submit_response(session_, downstream->get_stream_id(), + nva.data(), nva.size(), data_prdptr); + if (rv != 0) { + ULOG(FATAL, this) << "nghttp2_submit_response() failed"; + return -1; + } + + if (data_prdptr) { + downstream->reset_upstream_wtimer(); + } + + return 0; +} + +// WARNING: Never call directly or indirectly nghttp2_session_send or +// nghttp2_session_recv. These calls may delete downstream. +int Http2Upstream::on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len, + bool flush) { + auto body = downstream->get_response_buf(); + body->append(data, len); + + if (flush) { + nghttp2_session_resume_data(session_, downstream->get_stream_id()); + + downstream->ensure_upstream_wtimer(); + } + + return 0; +} + +// WARNING: Never call directly or indirectly nghttp2_session_send or +// nghttp2_session_recv. These calls may delete downstream. +int Http2Upstream::on_downstream_body_complete(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "HTTP response completed"; + } + + auto &resp = downstream->response(); + + if (!downstream->validate_response_recv_body_length()) { + rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); + resp.connection_close = true; + return 0; + } + + nghttp2_session_resume_data(session_, downstream->get_stream_id()); + downstream->ensure_upstream_wtimer(); + + return 0; +} + +bool Http2Upstream::get_flow_control() const { return flow_control_; } + +void Http2Upstream::pause_read(IOCtrlReason reason) {} + +int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed) { + if (get_flow_control()) { + if (consume(downstream->get_stream_id(), consumed) != 0) { + return -1; + } + + auto &req = downstream->request(); + + req.consume(consumed); + } + + handler_->signal_write(); + return 0; +} + +int Http2Upstream::on_downstream_abort_request(Downstream *downstream, + unsigned int status_code) { + int rv; + + rv = error_reply(downstream, status_code); + + if (rv != 0) { + return -1; + } + + handler_->signal_write(); + return 0; +} + +int Http2Upstream::on_downstream_abort_request_with_https_redirect( + Downstream *downstream) { + int rv; + + rv = redirect_to_https(downstream); + if (rv != 0) { + return -1; + } + + handler_->signal_write(); + return 0; +} + +int Http2Upstream::redirect_to_https(Downstream *downstream) { + auto &req = downstream->request(); + if (req.regular_connect_method() || req.scheme != "http") { + return error_reply(downstream, 400); + } + + auto authority = util::extract_host(req.authority); + if (authority.empty()) { + return error_reply(downstream, 400); + } + + auto &balloc = downstream->get_block_allocator(); + auto config = get_config(); + auto &httpconf = config->http; + + StringRef loc; + if (httpconf.redirect_https_port == StringRef::from_lit("443")) { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + req.path); + } else { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + StringRef::from_lit(":"), + httpconf.redirect_https_port, req.path); + } + + auto &resp = downstream->response(); + resp.http_status = 308; + resp.fs.add_header_token(StringRef::from_lit("location"), loc, false, + http2::HD_LOCATION); + + return send_reply(downstream, nullptr, 0); +} + +int Http2Upstream::consume(int32_t stream_id, size_t len) { + int rv; + + auto faddr = handler_->get_upstream_addr(); + + if (faddr->alt_mode != UpstreamAltMode::NONE) { + return 0; + } + + rv = nghttp2_session_consume(session_, stream_id, len); + + if (rv != 0) { + ULOG(WARN, this) << "nghttp2_session_consume() returned error: " + << nghttp2_strerror(rv); + return -1; + } + + return 0; +} + +void Http2Upstream::log_response_headers( + Downstream *downstream, const std::vector &nva) const { + std::stringstream ss; + for (auto &nv : nva) { + ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": " + << StringRef{nv.value, nv.valuelen} << "\n"; + } + ULOG(INFO, this) << "HTTP response headers. stream_id=" + << downstream->get_stream_id() << "\n" + << ss.str(); +} + +int Http2Upstream::on_timeout(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Stream timeout stream_id=" + << downstream->get_stream_id(); + } + + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + handler_->signal_write(); + + return 0; +} + +void Http2Upstream::on_handler_delete() { + for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) { + if (d->get_dispatch_state() == DispatchState::ACTIVE && + d->accesslog_ready()) { + handler_->write_accesslog(d); + } + } +} + +int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) { + int rv; + + if (downstream->get_dispatch_state() != DispatchState::ACTIVE) { + // This is error condition when we failed push_request_headers() + // in initiate_downstream(). Otherwise, we have + // DispatchState::ACTIVE state, or we did not set + // DownstreamConnection. + downstream->pop_downstream_connection(); + handler_->signal_write(); + + return 0; + } + + if (!downstream->request_submission_ready()) { + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + // We have got all response body already. Send it off. + downstream->pop_downstream_connection(); + return 0; + } + // pushed stream is handled here + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + downstream->pop_downstream_connection(); + + handler_->signal_write(); + + return 0; + } + + downstream->pop_downstream_connection(); + + downstream->add_retry(); + + std::unique_ptr dconn; + + rv = 0; + + if (no_retry || downstream->no_more_retry()) { + goto fail; + } + + // downstream connection is clean; we can retry with new + // downstream connection. + + for (;;) { + auto dconn = handler_->get_downstream_connection(rv, downstream); + if (!dconn) { + goto fail; + } + + rv = downstream->attach_downstream_connection(std::move(dconn)); + if (rv == 0) { + break; + } + } + + rv = downstream->push_request_headers(); + if (rv != 0) { + goto fail; + } + + return 0; + +fail: + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = on_downstream_abort_request_with_https_redirect(downstream); + } else { + rv = on_downstream_abort_request(downstream, 502); + } + if (rv != 0) { + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + downstream->pop_downstream_connection(); + + handler_->signal_write(); + + return 0; +} + +int Http2Upstream::prepare_push_promise(Downstream *downstream) { + int rv; + + const auto &req = downstream->request(); + auto &resp = downstream->response(); + + auto base = http2::get_pure_path_component(req.path); + if (base.empty()) { + return 0; + } + + auto &balloc = downstream->get_block_allocator(); + + for (auto &kv : resp.fs.headers()) { + if (kv.token != http2::HD_LINK) { + continue; + } + for (auto &link : http2::parse_link_header(kv.value)) { + StringRef scheme, authority, path; + + rv = http2::construct_push_component(balloc, scheme, authority, path, + base, link.uri); + if (rv != 0) { + continue; + } + + if (scheme.empty()) { + scheme = req.scheme; + } + + if (authority.empty()) { + authority = req.authority; + } + + if (resp.is_resource_pushed(scheme, authority, path)) { + continue; + } + + rv = submit_push_promise(scheme, authority, path, downstream); + if (rv != 0) { + return -1; + } + + resp.resource_pushed(scheme, authority, path); + } + } + return 0; +} + +int Http2Upstream::submit_push_promise(const StringRef &scheme, + const StringRef &authority, + const StringRef &path, + Downstream *downstream) { + const auto &req = downstream->request(); + + std::vector nva; + // 4 for :method, :scheme, :path and :authority + nva.reserve(4 + req.fs.headers().size()); + + // just use "GET" for now + nva.push_back(http2::make_nv_ll(":method", "GET")); + nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme)); + nva.push_back(http2::make_nv_ls_nocopy(":path", path)); + nva.push_back(http2::make_nv_ls_nocopy(":authority", authority)); + + for (auto &kv : req.fs.headers()) { + switch (kv.token) { + // TODO generate referer + case http2::HD__AUTHORITY: + case http2::HD__SCHEME: + case http2::HD__METHOD: + case http2::HD__PATH: + continue; + case http2::HD_ACCEPT_ENCODING: + case http2::HD_ACCEPT_LANGUAGE: + case http2::HD_CACHE_CONTROL: + case http2::HD_HOST: + case http2::HD_USER_AGENT: + nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index)); + break; + } + } + + auto promised_stream_id = nghttp2_submit_push_promise( + session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(), + nva.size(), nullptr); + + if (promised_stream_id < 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "nghttp2_submit_push_promise() failed: " + << nghttp2_strerror(promised_stream_id); + } + if (nghttp2_is_fatal(promised_stream_id)) { + return -1; + } + return 0; + } + + if (LOG_ENABLED(INFO)) { + std::stringstream ss; + for (auto &nv : nva) { + ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": " + << StringRef{nv.value, nv.valuelen} << "\n"; + } + ULOG(INFO, this) << "HTTP push request headers. promised_stream_id=" + << promised_stream_id << "\n" + << ss.str(); + } + + return 0; +} + +bool Http2Upstream::push_enabled() const { + auto config = get_config(); + return !(config->http2.no_server_push || + nghttp2_session_get_remote_settings( + session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 || + config->http2_proxy); +} + +int Http2Upstream::initiate_push(Downstream *downstream, const StringRef &uri) { + int rv; + + if (uri.empty() || !push_enabled() || + (downstream->get_stream_id() % 2) == 0) { + return 0; + } + + const auto &req = downstream->request(); + + auto base = http2::get_pure_path_component(req.path); + if (base.empty()) { + return -1; + } + + auto &balloc = downstream->get_block_allocator(); + + StringRef scheme, authority, path; + + rv = http2::construct_push_component(balloc, scheme, authority, path, base, + uri); + if (rv != 0) { + return -1; + } + + if (scheme.empty()) { + scheme = req.scheme; + } + + if (authority.empty()) { + authority = req.authority; + } + + auto &resp = downstream->response(); + + if (resp.is_resource_pushed(scheme, authority, path)) { + return 0; + } + + rv = submit_push_promise(scheme, authority, path, downstream); + + if (rv != 0) { + return -1; + } + + resp.resource_pushed(scheme, authority, path); + + return 0; +} + +int Http2Upstream::response_riovec(struct iovec *iov, int iovcnt) const { + if (iovcnt == 0 || wb_.rleft() == 0) { + return 0; + } + + return wb_.riovec(iov, iovcnt); +} + +void Http2Upstream::response_drain(size_t n) { wb_.drain(n); } + +bool Http2Upstream::response_empty() const { return wb_.rleft() == 0; } + +DefaultMemchunks *Http2Upstream::get_response_buf() { return &wb_; } + +Downstream * +Http2Upstream::on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id) { + // promised_stream_id is for backend HTTP/2 session, not for + // frontend. + auto promised_downstream = + std::make_unique(this, handler_->get_mcpool(), 0); + auto &promised_req = promised_downstream->request(); + + promised_downstream->set_downstream_stream_id(promised_stream_id); + // Set associated stream in frontend + promised_downstream->set_assoc_stream_id(downstream->get_stream_id()); + + promised_downstream->disable_upstream_rtimer(); + + promised_req.http_major = 2; + promised_req.http_minor = 0; + + promised_req.fs.content_length = 0; + promised_req.http2_expect_body = false; + + auto ptr = promised_downstream.get(); + add_pending_downstream(std::move(promised_downstream)); + downstream_queue_.mark_active(ptr); + + return ptr; +} + +int Http2Upstream::on_downstream_push_promise_complete( + Downstream *downstream, Downstream *promised_downstream) { + std::vector nva; + + const auto &promised_req = promised_downstream->request(); + const auto &headers = promised_req.fs.headers(); + + nva.reserve(headers.size()); + + for (auto &kv : headers) { + nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); + } + + auto promised_stream_id = nghttp2_submit_push_promise( + session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(), + nva.size(), promised_downstream); + if (promised_stream_id < 0) { + return -1; + } + + promised_downstream->set_stream_id(promised_stream_id); + + return 0; +} + +void Http2Upstream::cancel_premature_downstream( + Downstream *promised_downstream) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Remove premature promised stream " + << promised_downstream; + } + downstream_queue_.remove_and_get_blocked(promised_downstream, false); +} + +size_t Http2Upstream::get_max_buffer_size() const { return max_buffer_size_; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_http2_upstream.h b/lib/nghttp2/src/shrpx_http2_upstream.h new file mode 100644 index 00000000000..739c6ffa509 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http2_upstream.h @@ -0,0 +1,150 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTP2_UPSTREAM_H +#define SHRPX_HTTP2_UPSTREAM_H + +#include "shrpx.h" + +#include + +#include + +#include + +#include "shrpx_upstream.h" +#include "shrpx_downstream_queue.h" +#include "memchunk.h" +#include "buffer.h" + +using namespace nghttp2; + +namespace shrpx { + +class ClientHandler; +class HttpsUpstream; + +class Http2Upstream : public Upstream { +public: + Http2Upstream(ClientHandler *handler); + virtual ~Http2Upstream(); + virtual int on_read(); + virtual int on_write(); + virtual int on_timeout(Downstream *downstream); + virtual int on_downstream_abort_request(Downstream *downstream, + unsigned int status_code); + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream); + virtual ClientHandler *get_client_handler() const; + + virtual int downstream_read(DownstreamConnection *dconn); + virtual int downstream_write(DownstreamConnection *dconn); + virtual int downstream_eof(DownstreamConnection *dconn); + virtual int downstream_error(DownstreamConnection *dconn, int events); + + void add_pending_downstream(std::unique_ptr downstream); + void remove_downstream(Downstream *downstream); + + int rst_stream(Downstream *downstream, uint32_t error_code); + int terminate_session(uint32_t error_code); + int error_reply(Downstream *downstream, unsigned int status_code); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed); + + virtual int on_downstream_header_complete(Downstream *downstream); + virtual int on_downstream_body(Downstream *downstream, const uint8_t *data, + size_t len, bool flush); + virtual int on_downstream_body_complete(Downstream *downstream); + + virtual void on_handler_delete(); + virtual int on_downstream_reset(Downstream *downstream, bool no_retry); + virtual int send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen); + virtual int initiate_push(Downstream *downstream, const StringRef &uri); + virtual int response_riovec(struct iovec *iov, int iovcnt) const; + virtual void response_drain(size_t n); + virtual bool response_empty() const; + + virtual Downstream *on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id); + virtual int + on_downstream_push_promise_complete(Downstream *downstream, + Downstream *promised_downstream); + virtual bool push_enabled() const; + virtual void cancel_premature_downstream(Downstream *promised_downstream); + + bool get_flow_control() const; + // Perform HTTP/2 upgrade from |upstream|. On success, this object + // takes ownership of the |upstream|. This function returns 0 if it + // succeeds, or -1. + int upgrade_upstream(HttpsUpstream *upstream); + void start_settings_timer(); + void stop_settings_timer(); + int consume(int32_t stream_id, size_t len); + void log_response_headers(Downstream *downstream, + const std::vector &nva) const; + void start_downstream(Downstream *downstream); + void initiate_downstream(Downstream *downstream); + + void submit_goaway(); + void check_shutdown(); + // Starts graceful shutdown period. + void start_graceful_shutdown(); + + int prepare_push_promise(Downstream *downstream); + int submit_push_promise(const StringRef &scheme, const StringRef &authority, + const StringRef &path, Downstream *downstream); + + // Called when new request has started. + void on_start_request(const nghttp2_frame *frame); + int on_request_headers(Downstream *downstream, const nghttp2_frame *frame); + + DefaultMemchunks *get_response_buf(); + + size_t get_max_buffer_size() const; + + int redirect_to_https(Downstream *downstream); + +private: + DefaultMemchunks wb_; + std::unique_ptr pre_upstream_; + DownstreamQueue downstream_queue_; + ev_timer settings_timer_; + ev_timer shutdown_timer_; + ev_prepare prep_; + ClientHandler *handler_; + nghttp2_session *session_; + size_t max_buffer_size_; + // The number of requests seen so far. + size_t num_requests_; + bool flow_control_; +}; + +nghttp2_session_callbacks *create_http2_upstream_callbacks(); + +} // namespace shrpx + +#endif // SHRPX_HTTP2_UPSTREAM_H diff --git a/lib/nghttp2/src/shrpx_http3_upstream.cc b/lib/nghttp2/src/shrpx_http3_upstream.cc new file mode 100644 index 00000000000..46800de04f0 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http3_upstream.cc @@ -0,0 +1,2924 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 "shrpx_http3_upstream.h" + +#include +#include +#include +#include + +#include + +#include + +#include "shrpx_client_handler.h" +#include "shrpx_downstream.h" +#include "shrpx_downstream_connection.h" +#include "shrpx_log.h" +#include "shrpx_quic.h" +#include "shrpx_worker.h" +#include "shrpx_http.h" +#include "shrpx_connection_handler.h" +#ifdef HAVE_MRUBY +# include "shrpx_mruby.h" +#endif // HAVE_MRUBY +#include "http3.h" +#include "util.h" +#include "ssl_compat.h" + +namespace shrpx { + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto upstream = static_cast(w->data); + + if (upstream->handle_expiry() != 0 || upstream->on_write() != 0) { + goto fail; + } + + return; + +fail: + auto handler = upstream->get_client_handler(); + + delete handler; +} +} // namespace + +namespace { +void shutdown_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto upstream = static_cast(w->data); + auto handler = upstream->get_client_handler(); + + if (upstream->submit_goaway() != 0) { + delete handler; + } +} +} // namespace + +namespace { +void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revent) { + auto upstream = static_cast(w->data); + auto handler = upstream->get_client_handler(); + + if (upstream->check_shutdown() != 0) { + delete handler; + } +} +} // namespace + +namespace { +size_t downstream_queue_size(Worker *worker) { + auto &downstreamconf = *worker->get_downstream_config(); + + if (get_config()->http2_proxy) { + return downstreamconf.connections_per_host; + } + + return downstreamconf.connections_per_frontend; +} +} // namespace + +namespace { +ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { + auto conn = static_cast(conn_ref->user_data); + auto handler = static_cast(conn->data); + auto upstream = static_cast(handler->get_upstream()); + return upstream->get_conn(); +} +} // namespace + +Http3Upstream::Http3Upstream(ClientHandler *handler) + : handler_{handler}, + qlog_fd_{-1}, + hashed_scid_{}, + conn_{nullptr}, + httpconn_{nullptr}, + downstream_queue_{downstream_queue_size(handler->get_worker()), + !get_config()->http2_proxy}, + retry_close_{false}, + tx_{ + .data = std::unique_ptr(new uint8_t[64_k]), + } { + auto conn = handler_->get_connection(); + conn->conn_ref.get_conn = shrpx::get_conn; + + ev_timer_init(&timer_, timeoutcb, 0., 0.); + timer_.data = this; + + ngtcp2_ccerr_default(&last_error_); + + ev_timer_init(&shutdown_timer_, shutdown_timeout_cb, 0., 0.); + shutdown_timer_.data = this; + + ev_prepare_init(&prep_, prepare_cb); + prep_.data = this; + ev_prepare_start(handler_->get_loop(), &prep_); +} + +Http3Upstream::~Http3Upstream() { + auto loop = handler_->get_loop(); + + ev_prepare_stop(loop, &prep_); + ev_timer_stop(loop, &shutdown_timer_); + ev_timer_stop(loop, &timer_); + + nghttp3_conn_del(httpconn_); + + ngtcp2_conn_del(conn_); + + if (qlog_fd_ != -1) { + close(qlog_fd_); + } +} + +namespace { +void log_printf(void *user_data, const char *fmt, ...) { + va_list ap; + std::array buf; + + va_start(ap, fmt); + auto nwrite = vsnprintf(buf.data(), buf.size(), fmt, ap); + va_end(ap); + + if (static_cast(nwrite) >= buf.size()) { + nwrite = buf.size() - 1; + } + + buf[nwrite++] = '\n'; + + while (write(fileno(stderr), buf.data(), nwrite) == -1 && errno == EINTR) + ; +} +} // namespace + +namespace { +void qlog_write(void *user_data, uint32_t flags, const void *data, + size_t datalen) { + auto upstream = static_cast(user_data); + + upstream->qlog_write(data, datalen, flags & NGTCP2_QLOG_WRITE_FLAG_FIN); +} +} // namespace + +void Http3Upstream::qlog_write(const void *data, size_t datalen, bool fin) { + assert(qlog_fd_ != -1); + + while (write(qlog_fd_, data, datalen) == -1 && errno == EINTR) + ; + + if (fin) { + close(qlog_fd_); + qlog_fd_ = -1; + } +} + +namespace { +void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { + util::random_bytes(dest, dest + destlen, + *static_cast(rand_ctx->native_handle)); +} +} // namespace + +namespace { +int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, + size_t cidlen, void *user_data) { + auto upstream = static_cast(user_data); + auto handler = upstream->get_client_handler(); + auto worker = handler->get_worker(); + auto conn_handler = worker->get_connection_handler(); + auto &qkms = conn_handler->get_quic_keying_materials(); + auto &qkm = qkms->keying_materials.front(); + + if (generate_quic_connection_id(*cid, cidlen, worker->get_cid_prefix(), + qkm.id, qkm.cid_encryption_key.data()) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (generate_quic_stateless_reset_token(token, *cid, qkm.secret.data(), + qkm.secret.size()) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + auto quic_connection_handler = worker->get_quic_connection_handler(); + + quic_connection_handler->add_connection_id(*cid, handler); + + return 0; +} +} // namespace + +namespace { +int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid, + void *user_data) { + auto upstream = static_cast(user_data); + auto handler = upstream->get_client_handler(); + auto worker = handler->get_worker(); + auto quic_conn_handler = worker->get_quic_connection_handler(); + + quic_conn_handler->remove_connection_id(*cid); + + return 0; +} +} // namespace + +void Http3Upstream::http_begin_request_headers(int64_t stream_id) { + auto downstream = + std::make_unique(this, handler_->get_mcpool(), stream_id); + nghttp3_conn_set_stream_user_data(httpconn_, stream_id, downstream.get()); + + downstream->reset_upstream_rtimer(); + + handler_->repeat_read_timer(); + + auto &req = downstream->request(); + req.http_major = 3; + req.http_minor = 0; + + add_pending_downstream(std::move(downstream)); +} + +void Http3Upstream::add_pending_downstream( + std::unique_ptr downstream) { + downstream_queue_.add_pending(std::move(downstream)); +} + +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 upstream = static_cast(user_data); + + if (upstream->recv_stream_data(flags, stream_id, data, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + assert(httpconn_); + + auto nconsumed = nghttp3_conn_read_stream( + httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN); + if (nconsumed < 0) { + ULOG(ERROR, this) << "nghttp3_conn_read_stream: " + << nghttp3_strerror(nconsumed); + ngtcp2_ccerr_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; +} + +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 upstream = static_cast(user_data); + + if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + if (upstream->stream_close(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::stream_close(int64_t stream_id, uint64_t app_error_code) { + if (!httpconn_) { + return 0; + } + + 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)) { + ngtcp2_conn_extend_max_streams_bidi(conn_, 1); + } + break; + default: + ULOG(ERROR, this) << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv); + ngtcp2_ccerr_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); + return -1; + } + + return 0; +} + +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 upstream = static_cast(user_data); + + if (upstream->acked_stream_data_offset(stream_id, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::acked_stream_data_offset(int64_t stream_id, + uint64_t datalen) { + if (!httpconn_) { + return 0; + } + + auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_add_ack_offset: " + << nghttp3_strerror(rv); + return -1; + } + + 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 upstream = static_cast(user_data); + + if (upstream->extend_max_stream_data(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::extend_max_stream_data(int64_t stream_id) { + if (!httpconn_) { + return 0; + } + + auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_unblock_stream: " + << nghttp3_strerror(rv); + return -1; + } + + return 0; +} + +namespace { +int extend_max_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, + void *user_data) { + auto upstream = static_cast(user_data); + + upstream->extend_max_remote_streams_bidi(max_streams); + + return 0; +} +} // namespace + +void Http3Upstream::extend_max_remote_streams_bidi(uint64_t max_streams) { + nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams); +} + +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 upstream = static_cast(user_data); + + if (upstream->stream_reset(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::stream_reset(int64_t stream_id) { + if (http_shutdown_stream_read(stream_id) != 0) { + return -1; + } + + if (ngtcp2_is_bidi_stream(stream_id)) { + auto rv = ngtcp2_conn_shutdown_stream_write(conn_, 0, stream_id, + NGHTTP3_H3_NO_ERROR); + if (rv != 0) { + ULOG(ERROR, this) << "ngtcp2_conn_shutdown_stream_write: " + << ngtcp2_strerror(rv); + return -1; + } + } + + return 0; +} + +int Http3Upstream::http_shutdown_stream_read(int64_t stream_id) { + if (!httpconn_) { + return 0; + } + + auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_shutdown_stream_read: " + << nghttp3_strerror(rv); + 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 upstream = static_cast(user_data); + + if (upstream->http_shutdown_stream_read(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto upstream = static_cast(user_data); + + if (upstream->handshake_completed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::handshake_completed() { + handler_->set_alpn_from_conn(); + + auto alpn = handler_->get_alpn(); + if (alpn.empty()) { + ULOG(ERROR, this) << "NO ALPN was negotiated"; + return -1; + } + + auto path = ngtcp2_conn_get_path(conn_); + + return send_new_token(&path->remote); +} + +namespace { +int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path, + const ngtcp2_path *old_path, + ngtcp2_path_validation_result res, void *user_data) { + if (res != NGTCP2_PATH_VALIDATION_RESULT_SUCCESS || + !(flags & NGTCP2_PATH_VALIDATION_FLAG_NEW_TOKEN)) { + return 0; + } + + auto upstream = static_cast(user_data); + if (upstream->send_new_token(&path->remote) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::send_new_token(const ngtcp2_addr *remote_addr) { + std::array token; + size_t tokenlen; + + auto worker = handler_->get_worker(); + auto conn_handler = worker->get_connection_handler(); + auto &qkms = conn_handler->get_quic_keying_materials(); + auto &qkm = qkms->keying_materials.front(); + + if (generate_token(token.data(), tokenlen, remote_addr->addr, + remote_addr->addrlen, qkm.secret.data(), + qkm.secret.size()) != 0) { + return -1; + } + + assert(tokenlen == NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN); + + token[tokenlen++] = qkm.id; + + auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen); + if (rv != 0) { + ULOG(ERROR, this) << "ngtcp2_conn_submit_new_token: " + << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + +namespace { +int recv_tx_key(ngtcp2_conn *conn, ngtcp2_encryption_level level, + void *user_data) { + if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT) { + return 0; + } + + auto upstream = static_cast(user_data); + if (upstream->setup_httpconn() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, + const ngtcp2_pkt_hd &initial_hd, + const ngtcp2_cid *odcid, const uint8_t *token, + size_t tokenlen, ngtcp2_token_type token_type) { + int rv; + + auto worker = handler_->get_worker(); + auto conn_handler = worker->get_connection_handler(); + + auto callbacks = ngtcp2_callbacks{ + nullptr, // client_initial + ngtcp2_crypto_recv_client_initial_cb, + ngtcp2_crypto_recv_crypto_data_cb, + shrpx::handshake_completed, + nullptr, // recv_version_negotiation + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + shrpx::recv_stream_data, + shrpx::acked_stream_data_offset, + nullptr, // stream_open + shrpx::stream_close, + nullptr, // recv_stateless_reset + nullptr, // recv_retry + nullptr, // extend_max_local_streams_bidi + nullptr, // extend_max_local_streams_uni + rand, + get_new_connection_id, + remove_connection_id, + ngtcp2_crypto_update_key_cb, + shrpx::path_validation, + nullptr, // select_preferred_addr + shrpx::stream_reset, + shrpx::extend_max_remote_streams_bidi, + nullptr, // extend_max_remote_streams_uni + shrpx::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, + shrpx::stream_stop_sending, + nullptr, // version_negotiation + nullptr, // recv_rx_key + shrpx::recv_tx_key, + }; + + auto config = get_config(); + auto &quicconf = config->quic; + auto &http3conf = config->http3; + + auto &qkms = conn_handler->get_quic_keying_materials(); + auto &qkm = qkms->keying_materials.front(); + + ngtcp2_cid scid; + + if (generate_quic_connection_id(scid, SHRPX_QUIC_SCIDLEN, + worker->get_cid_prefix(), qkm.id, + qkm.cid_encryption_key.data()) != 0) { + return -1; + } + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + if (quicconf.upstream.debug.log) { + settings.log_printf = log_printf; + } + + if (!quicconf.upstream.qlog.dir.empty()) { + auto fd = open_qlog_file(quicconf.upstream.qlog.dir, scid); + if (fd != -1) { + qlog_fd_ = fd; + settings.qlog_write = shrpx::qlog_write; + } + } + + settings.initial_ts = quic_timestamp(); + settings.initial_rtt = static_cast( + quicconf.upstream.initial_rtt * NGTCP2_SECONDS); + settings.cc_algo = quicconf.upstream.congestion_controller; + settings.max_window = http3conf.upstream.max_connection_window_size; + settings.max_stream_window = http3conf.upstream.max_window_size; + settings.max_tx_udp_payload_size = SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE; + settings.rand_ctx.native_handle = &worker->get_randgen(); + settings.token = token; + settings.tokenlen = tokenlen; + settings.token_type = token_type; + settings.initial_pkt_num = std::uniform_int_distribution( + 0, std::numeric_limits::max())(worker->get_randgen()); + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + params.initial_max_streams_bidi = http3conf.upstream.max_concurrent_streams; + // The minimum number of unidirectional streams required for HTTP/3. + params.initial_max_streams_uni = 3; + params.initial_max_data = http3conf.upstream.connection_window_size; + params.initial_max_stream_data_bidi_remote = http3conf.upstream.window_size; + params.initial_max_stream_data_uni = http3conf.upstream.window_size; + params.max_idle_timeout = static_cast( + quicconf.upstream.timeout.idle * NGTCP2_SECONDS); + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + if (quicconf.upstream.early_data) { + ngtcp2_transport_params early_data_params; + + ngtcp2_transport_params_default(&early_data_params); + + early_data_params.initial_max_stream_data_bidi_local = + params.initial_max_stream_data_bidi_local; + early_data_params.initial_max_stream_data_bidi_remote = + params.initial_max_stream_data_bidi_remote; + early_data_params.initial_max_stream_data_uni = + params.initial_max_stream_data_uni; + early_data_params.initial_max_data = params.initial_max_data; + early_data_params.initial_max_streams_bidi = + params.initial_max_streams_bidi; + early_data_params.initial_max_streams_uni = params.initial_max_streams_uni; + + // TODO include HTTP/3 SETTINGS + + std::array quic_early_data_ctx; + + auto quic_early_data_ctxlen = ngtcp2_transport_params_encode( + quic_early_data_ctx.data(), quic_early_data_ctx.size(), + &early_data_params); + + assert(quic_early_data_ctxlen > 0); + assert(static_cast(quic_early_data_ctxlen) <= + quic_early_data_ctx.size()); + + if (SSL_set_quic_early_data_context(handler_->get_ssl(), + quic_early_data_ctx.data(), + quic_early_data_ctxlen) != 1) { + ULOG(ERROR, this) << "SSL_set_quic_early_data_context failed"; + return -1; + } + } +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + if (odcid) { + params.original_dcid = *odcid; + params.retry_scid = initial_hd.dcid; + params.retry_scid_present = 1; + } else { + params.original_dcid = initial_hd.dcid; + } + + params.original_dcid_present = 1; + + rv = generate_quic_stateless_reset_token( + params.stateless_reset_token, scid, qkm.secret.data(), qkm.secret.size()); + if (rv != 0) { + ULOG(ERROR, this) << "generate_quic_stateless_reset_token failed"; + return -1; + } + params.stateless_reset_token_present = 1; + + auto path = ngtcp2_path{ + { + const_cast(&local_addr.su.sa), + static_cast(local_addr.len), + }, + { + const_cast(&remote_addr.su.sa), + static_cast(remote_addr.len), + }, + const_cast(faddr), + }; + + rv = ngtcp2_conn_server_new(&conn_, &initial_hd.scid, &scid, &path, + initial_hd.version, &callbacks, &settings, + ¶ms, nullptr, this); + if (rv != 0) { + ULOG(ERROR, this) << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv); + return -1; + } + + ngtcp2_conn_set_tls_native_handle(conn_, handler_->get_ssl()); + + auto quic_connection_handler = worker->get_quic_connection_handler(); + + if (generate_quic_hashed_connection_id(hashed_scid_, remote_addr, local_addr, + initial_hd.dcid) != 0) { + return -1; + } + + quic_connection_handler->add_connection_id(hashed_scid_, handler_); + quic_connection_handler->add_connection_id(scid, handler_); + + return 0; +} + +int Http3Upstream::on_read() { return 0; } + +int Http3Upstream::on_write() { + int rv; + + if (tx_.send_blocked) { + rv = send_blocked_packet(); + if (rv != 0) { + return -1; + } + + if (tx_.send_blocked) { + return 0; + } + } + + handler_->get_connection()->wlimit.stopw(); + + if (write_streams() != 0) { + return -1; + } + + if (httpconn_ && nghttp3_conn_is_drained(httpconn_)) { + return -1; + } + + reset_timer(); + + return 0; +} + +int Http3Upstream::write_streams() { + std::array vec; + auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_); +#ifdef UDP_SEGMENT + auto path_max_udp_payload_size = + ngtcp2_conn_get_path_max_tx_udp_payload_size(conn_); +#endif // UDP_SEGMENT + auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size; + ngtcp2_pkt_info pi, prev_pi; + uint8_t *bufpos = tx_.data.get(); + ngtcp2_path_storage ps, prev_ps; + size_t pktcnt = 0; + int rv; + size_t gso_size = 0; + auto ts = quic_timestamp(); + + ngtcp2_path_storage_zero(&ps); + ngtcp2_path_storage_zero(&prev_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) { + ULOG(ERROR, this) << "nghttp3_conn_writev_stream: " + << nghttp3_strerror(sveccnt); + ngtcp2_ccerr_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(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(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); + rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen); + if (rv != 0) { + ULOG(ERROR, this) + << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv); + ngtcp2_ccerr_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, + 0); + return handle_error(); + } + continue; + } + + assert(ndatalen == -1); + + ULOG(ERROR, this) << "ngtcp2_conn_writev_stream: " + << ngtcp2_strerror(nwrite); + + ngtcp2_ccerr_set_liberr(&last_error_, nwrite, nullptr, 0); + + return handle_error(); + } else if (ndatalen >= 0) { + rv = nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_add_write_offset: " + << nghttp3_strerror(rv); + ngtcp2_ccerr_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 faddr = static_cast(prev_ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data; + + rv = send_packet(faddr, prev_ps.path.remote.addr, + prev_ps.path.remote.addrlen, prev_ps.path.local.addr, + prev_ps.path.local.addrlen, prev_pi, data, datalen, + gso_size); + if (rv == SHRPX_ERR_SEND_BLOCKED) { + on_send_blocked(faddr, prev_ps.path.remote, prev_ps.path.local, + prev_pi, data, datalen, gso_size); + + signal_write_upstream_addr(faddr); + } + } + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + + return 0; + } + + bufpos += nwrite; + +#ifdef UDP_SEGMENT + if (pktcnt == 0) { + ngtcp2_path_copy(&prev_ps.path, &ps.path); + prev_pi = pi; + gso_size = nwrite; + } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) || + prev_pi.ecn != pi.ecn || + static_cast(nwrite) > gso_size || + (gso_size > path_max_udp_payload_size && + static_cast(nwrite) != gso_size)) { + auto faddr = static_cast(prev_ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data - nwrite; + + rv = send_packet(faddr, prev_ps.path.remote.addr, + prev_ps.path.remote.addrlen, prev_ps.path.local.addr, + prev_ps.path.local.addrlen, prev_pi, data, datalen, + gso_size); + switch (rv) { + case SHRPX_ERR_SEND_BLOCKED: + on_send_blocked(faddr, prev_ps.path.remote, prev_ps.path.local, prev_pi, + data, datalen, gso_size); + + on_send_blocked(static_cast(ps.path.user_data), + ps.path.remote, ps.path.local, pi, bufpos - nwrite, + nwrite, 0); + + signal_write_upstream_addr(faddr); + + break; + default: { + auto faddr = static_cast(ps.path.user_data); + auto data = bufpos - nwrite; + + rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen, + ps.path.local.addr, ps.path.local.addrlen, pi, data, + nwrite, 0); + if (rv == SHRPX_ERR_SEND_BLOCKED) { + on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data, + nwrite, 0); + + signal_write_upstream_addr(faddr); + } + } + } + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + + return 0; + } + + if (++pktcnt == max_pktcnt || static_cast(nwrite) < gso_size) { + auto faddr = static_cast(ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data; + + rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen, + ps.path.local.addr, ps.path.local.addrlen, pi, data, + datalen, gso_size); + if (rv == SHRPX_ERR_SEND_BLOCKED) { + on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data, datalen, + gso_size); + + signal_write_upstream_addr(faddr); + } + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + + return 0; + } +#else // !UDP_SEGMENT + auto faddr = static_cast(ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data; + + rv = send_packet(faddr, ps.path.remote.addr, ps.path.remote.addrlen, + ps.path.local.addr, ps.path.local.addrlen, pi, data, + datalen, 0); + if (rv == SHRPX_ERR_SEND_BLOCKED) { + on_send_blocked(faddr, ps.path.remote, ps.path.local, pi, data, datalen, + 0); + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + + signal_write_upstream_addr(faddr); + + return 0; + } + + if (++pktcnt == max_pktcnt) { + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + + return 0; + } + + bufpos = tx_.data.get(); +#endif // !UDP_SEGMENT + } + + return 0; +} + +int Http3Upstream::on_timeout(Downstream *downstream) { return 0; } + +int Http3Upstream::on_downstream_abort_request(Downstream *downstream, + unsigned int status_code) { + int rv; + + rv = error_reply(downstream, status_code); + + if (rv != 0) { + return -1; + } + + handler_->signal_write(); + + return 0; +} + +int Http3Upstream::on_downstream_abort_request_with_https_redirect( + Downstream *downstream) { + assert(0); + abort(); +} + +namespace { +uint64_t +infer_upstream_shutdown_stream_error_code(uint32_t downstream_error_code) { + // NGHTTP2_REFUSED_STREAM is important because it tells upstream + // client to retry. + switch (downstream_error_code) { + case NGHTTP2_NO_ERROR: + return NGHTTP3_H3_NO_ERROR; + case NGHTTP2_REFUSED_STREAM: + return NGHTTP3_H3_REQUEST_REJECTED; + default: + return NGHTTP3_H3_INTERNAL_ERROR; + } +} +} // namespace + +int Http3Upstream::downstream_read(DownstreamConnection *dconn) { + auto downstream = dconn->get_downstream(); + + if (downstream->get_response_state() == DownstreamState::MSG_RESET) { + // The downstream stream was reset (canceled). In this case, + // RST_STREAM to the upstream and delete downstream connection + // here. Deleting downstream will be taken place at + // on_stream_close_callback. + shutdown_stream(downstream, + infer_upstream_shutdown_stream_error_code( + downstream->get_response_rst_stream_error_code())); + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + } else if (downstream->get_response_state() == + DownstreamState::MSG_BAD_HEADER) { + if (error_reply(downstream, 502) != 0) { + return -1; + } + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + } else { + auto rv = downstream->on_read(); + if (rv == SHRPX_ERR_EOF) { + if (downstream->get_request_header_sent()) { + return downstream_eof(dconn); + } + return SHRPX_ERR_RETRY; + } + if (rv == SHRPX_ERR_DCONN_CANCELED) { + downstream->pop_downstream_connection(); + handler_->signal_write(); + return 0; + } + if (rv != 0) { + if (rv != SHRPX_ERR_NETWORK) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "HTTP parser failure"; + } + } + return downstream_error(dconn, Downstream::EVENT_ERROR); + } + + if (downstream->can_detach_downstream_connection()) { + // Keep-alive + downstream->detach_downstream_connection(); + } + } + + handler_->signal_write(); + + // At this point, downstream may be deleted. + + return 0; +} + +int Http3Upstream::downstream_write(DownstreamConnection *dconn) { + int rv; + rv = dconn->on_write(); + if (rv == SHRPX_ERR_NETWORK) { + return downstream_error(dconn, Downstream::EVENT_ERROR); + } + if (rv != 0) { + return rv; + } + return 0; +} + +int Http3Upstream::downstream_eof(DownstreamConnection *dconn) { + auto downstream = dconn->get_downstream(); + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id(); + } + + // Delete downstream connection. If we don't delete it here, it will + // be pooled in on_stream_close_callback. + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + // downstream will be deleted in on_stream_close_callback. + if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { + // Server may indicate the end of the request by EOF + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Downstream body was ended by EOF"; + } + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + // For tunneled connection, MSG_COMPLETE signals + // downstream_read_data_callback to send RST_STREAM after pending + // response body is sent. This is needed to ensure that RST_STREAM + // is sent after all pending data are sent. + if (on_downstream_body_complete(downstream) != 0) { + return -1; + } + } else if (downstream->get_response_state() != + DownstreamState::MSG_COMPLETE) { + // If stream was not closed, then we set MSG_COMPLETE and let + // on_stream_close_callback delete downstream. + if (error_reply(downstream, 502) != 0) { + return -1; + } + } + handler_->signal_write(); + // At this point, downstream may be deleted. + return 0; +} + +int Http3Upstream::downstream_error(DownstreamConnection *dconn, int events) { + auto downstream = dconn->get_downstream(); + + if (LOG_ENABLED(INFO)) { + if (events & Downstream::EVENT_ERROR) { + DCLOG(INFO, dconn) << "Downstream network/general error"; + } else { + DCLOG(INFO, dconn) << "Timeout"; + } + if (downstream->get_upgraded()) { + DCLOG(INFO, dconn) << "Note: this is tunnel connection"; + } + } + + // Delete downstream connection. If we don't delete it here, it will + // be pooled in on_stream_close_callback. + downstream->pop_downstream_connection(); + // dconn was deleted + dconn = nullptr; + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + // For SSL tunneling, we issue RST_STREAM. For other types of + // stream, we don't have to do anything since response was + // complete. + if (downstream->get_upgraded()) { + shutdown_stream(downstream, NGHTTP3_H3_NO_ERROR); + } + } else { + if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { + if (downstream->get_upgraded()) { + if (on_downstream_body_complete(downstream) != 0) { + return -1; + } + } else { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + } else { + unsigned int status; + if (events & Downstream::EVENT_TIMEOUT) { + if (downstream->get_request_header_sent()) { + status = 504; + } else { + status = 408; + } + } else { + status = 502; + } + if (error_reply(downstream, status) != 0) { + return -1; + } + } + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + } + handler_->signal_write(); + // At this point, downstream may be deleted. + return 0; +} + +ClientHandler *Http3Upstream::get_client_handler() const { return handler_; } + +namespace { +nghttp3_ssize downstream_read_data_callback(nghttp3_conn *conn, + int64_t stream_id, nghttp3_vec *vec, + size_t veccnt, uint32_t *pflags, + void *conn_user_data, + void *stream_user_data) { + auto upstream = static_cast(conn_user_data); + auto downstream = static_cast(stream_user_data); + + assert(downstream); + + auto body = downstream->get_response_buf(); + + assert(body); + + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE && + body->rleft_mark() == 0) { + downstream->disable_upstream_wtimer(); + return NGHTTP3_ERR_WOULDBLOCK; + } + + downstream->reset_upstream_wtimer(); + + veccnt = body->riovec_mark(reinterpret_cast(vec), veccnt); + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE && + body->rleft_mark() == 0) { + *pflags |= NGHTTP3_DATA_FLAG_EOF; + } + + assert((*pflags & NGHTTP3_DATA_FLAG_EOF) || veccnt); + + downstream->response_sent_body_length += nghttp3_vec_len(vec, veccnt); + + if ((*pflags & NGHTTP3_DATA_FLAG_EOF) && + upstream->shutdown_stream_read(stream_id, NGHTTP3_H3_NO_ERROR) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return veccnt; +} +} // namespace + +int Http3Upstream::on_downstream_header_complete(Downstream *downstream) { + int rv; + + const auto &req = downstream->request(); + auto &resp = downstream->response(); + + auto &balloc = downstream->get_block_allocator(); + + if (LOG_ENABLED(INFO)) { + if (downstream->get_non_final_response()) { + DLOG(INFO, downstream) << "HTTP non-final response header"; + } else { + DLOG(INFO, downstream) << "HTTP response header completed"; + } + } + + auto config = get_config(); + auto &httpconf = config->http; + + if (!config->http2_proxy && !httpconf.no_location_rewrite) { + downstream->rewrite_location_response_header(req.scheme); + } + +#ifdef HAVE_MRUBY + if (!downstream->get_non_final_response()) { + auto dconn = downstream->get_downstream_connection(); + const auto &group = dconn->get_downstream_addr_group(); + if (group) { + const auto &dmruby_ctx = group->shared_addr->mruby_ctx; + + if (dmruby_ctx->run_on_response_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return -1; + } + // Returning -1 will signal deletion of dconn. + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return -1; + } + } + + auto worker = handler_->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_response_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return -1; + } + // Returning -1 will signal deletion of dconn. + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return -1; + } + } +#endif // HAVE_MRUBY + + auto nva = std::vector(); + // 4 means :status and possible server, via, and set-cookie (for + // affinity cookie) header field. + nva.reserve(resp.fs.headers().size() + 4 + + httpconf.add_response_headers.size()); + + if (downstream->get_non_final_response()) { + auto response_status = http2::stringify_status(balloc, resp.http_status); + + nva.push_back(http3::make_nv_ls_nocopy(":status", response_status)); + + http3::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), + http2::HDOP_STRIP_ALL); + + if (LOG_ENABLED(INFO)) { + log_response_headers(downstream, nva); + } + + rv = nghttp3_conn_submit_info(httpconn_, downstream->get_stream_id(), + nva.data(), nva.size()); + + resp.fs.clear_headers(); + + if (rv != 0) { + ULOG(FATAL, this) << "nghttp3_conn_submit_info() failed"; + return -1; + } + + return 0; + } + + auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA; + StringRef response_status; + + if (req.connect_proto == ConnectProto::WEBSOCKET && resp.http_status == 101) { + response_status = http2::stringify_status(balloc, 200); + striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT; + } else { + response_status = http2::stringify_status(balloc, resp.http_status); + } + + nva.push_back(http3::make_nv_ls_nocopy(":status", response_status)); + + http3::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags); + + if (!config->http2_proxy && !httpconf.no_server_rewrite) { + nva.push_back(http3::make_nv_ls_nocopy("server", httpconf.server_name)); + } else { + auto server = resp.fs.header(http2::HD_SERVER); + if (server) { + nva.push_back(http3::make_nv_ls_nocopy("server", (*server).value)); + } + } + + if (!req.regular_connect_method() || !downstream->get_upgraded()) { + auto affinity_cookie = downstream->get_affinity_cookie_to_send(); + if (affinity_cookie) { + auto dconn = downstream->get_downstream_connection(); + assert(dconn); + auto &group = dconn->get_downstream_addr_group(); + auto &shared_addr = group->shared_addr; + auto &cookieconf = shared_addr->affinity.cookie; + auto secure = + http::require_cookie_secure_attribute(cookieconf.secure, req.scheme); + auto cookie_str = http::create_affinity_cookie( + balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure); + nva.push_back(http3::make_nv_ls_nocopy("set-cookie", cookie_str)); + } + } + + auto via = resp.fs.header(http2::HD_VIA); + if (httpconf.no_via) { + if (via) { + nva.push_back(http3::make_nv_ls_nocopy("via", (*via).value)); + } + } else { + // we don't create more than 16 bytes in + // http::create_via_header_value. + size_t len = 16; + if (via) { + len += via->value.size() + 2; + } + + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + if (via) { + p = std::copy(std::begin(via->value), std::end(via->value), p); + p = util::copy_lit(p, ", "); + } + p = http::create_via_header_value(p, resp.http_major, resp.http_minor); + *p = '\0'; + + nva.push_back(http3::make_nv_ls_nocopy("via", StringRef{iov.base, p})); + } + + for (auto &p : httpconf.add_response_headers) { + nva.push_back(http3::make_nv_nocopy(p.name, p.value)); + } + + if (LOG_ENABLED(INFO)) { + log_response_headers(downstream, nva); + } + + auto priority = resp.fs.header(http2::HD_PRIORITY); + if (priority) { + nghttp3_pri pri; + + if (nghttp3_conn_get_stream_priority(httpconn_, &pri, + downstream->get_stream_id()) == 0 && + nghttp3_pri_parse_priority(&pri, priority->value.byte(), + priority->value.size()) == 0) { + rv = nghttp3_conn_set_server_stream_priority( + httpconn_, downstream->get_stream_id(), &pri); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_set_server_stream_priority: " + << nghttp3_strerror(rv); + } + } + } + + nghttp3_data_reader data_read; + data_read.read_data = downstream_read_data_callback; + + nghttp3_data_reader *data_readptr; + + if (downstream->expect_response_body() || + downstream->expect_response_trailer()) { + data_readptr = &data_read; + } else { + data_readptr = nullptr; + } + + rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(), + nva.data(), nva.size(), data_readptr); + if (rv != 0) { + ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed"; + return -1; + } + + if (data_readptr) { + downstream->reset_upstream_wtimer(); + } else if (shutdown_stream_read(downstream->get_stream_id(), + NGHTTP3_H3_NO_ERROR) != 0) { + return -1; + } + + return 0; +} + +int Http3Upstream::on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len, + bool flush) { + auto body = downstream->get_response_buf(); + body->append(data, len); + + if (flush) { + nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id()); + + downstream->ensure_upstream_wtimer(); + } + + return 0; +} + +int Http3Upstream::on_downstream_body_complete(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "HTTP response completed"; + } + + auto &resp = downstream->response(); + + if (!downstream->validate_response_recv_body_length()) { + shutdown_stream(downstream, NGHTTP3_H3_GENERAL_PROTOCOL_ERROR); + resp.connection_close = true; + return 0; + } + + if (!downstream->get_upgraded()) { + const auto &trailers = resp.fs.trailers(); + if (!trailers.empty()) { + std::vector nva; + nva.reserve(trailers.size()); + http3::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL); + if (!nva.empty()) { + auto rv = nghttp3_conn_submit_trailers( + httpconn_, downstream->get_stream_id(), nva.data(), nva.size()); + if (rv != 0) { + ULOG(FATAL, this) << "nghttp3_conn_submit_trailers() failed: " + << nghttp3_strerror(rv); + return -1; + } + } + } + } + + nghttp3_conn_resume_stream(httpconn_, downstream->get_stream_id()); + downstream->ensure_upstream_wtimer(); + + return 0; +} + +void Http3Upstream::on_handler_delete() { + for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) { + if (d->get_dispatch_state() == DispatchState::ACTIVE && + d->accesslog_ready()) { + handler_->write_accesslog(d); + } + } + + auto worker = handler_->get_worker(); + auto quic_conn_handler = worker->get_quic_connection_handler(); + + std::vector scids(ngtcp2_conn_get_scid(conn_, nullptr) + 1); + ngtcp2_conn_get_scid(conn_, scids.data()); + scids.back() = hashed_scid_; + + for (auto &cid : scids) { + quic_conn_handler->remove_connection_id(cid); + } + + if (retry_close_ || last_error_.type == NGTCP2_CCERR_TYPE_IDLE_CLOSE) { + return; + } + + // If this is not idle close, send CONNECTION_CLOSE. + if (!ngtcp2_conn_in_closing_period(conn_) && + !ngtcp2_conn_in_draining_period(conn_)) { + ngtcp2_path_storage ps; + ngtcp2_pkt_info pi; + conn_close_.resize(SHRPX_QUIC_CONN_CLOSE_PKTLEN); + + ngtcp2_path_storage_zero(&ps); + + ngtcp2_ccerr ccerr; + ngtcp2_ccerr_default(&ccerr); + + if (worker->get_graceful_shutdown() && + !ngtcp2_conn_get_handshake_completed(conn_)) { + ccerr.error_code = NGTCP2_CONNECTION_REFUSED; + } + + auto nwrite = ngtcp2_conn_write_connection_close( + conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(), &ccerr, + quic_timestamp()); + if (nwrite < 0) { + if (nwrite != NGTCP2_ERR_INVALID_STATE) { + ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: " + << ngtcp2_strerror(nwrite); + } + + return; + } + + conn_close_.resize(nwrite); + + send_packet(static_cast(ps.path.user_data), + ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr, + ps.path.local.addrlen, pi, conn_close_.data(), nwrite, 0); + } + + auto d = + static_cast(ngtcp2_conn_get_pto(conn_) * 3) / NGTCP2_SECONDS; + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Enter close-wait period " << d << "s with " + << conn_close_.size() << " bytes sentinel packet"; + } + + auto cw = std::make_unique(worker, std::move(scids), + std::move(conn_close_), d); + + quic_conn_handler->add_close_wait(cw.release()); +} + +int Http3Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) { + int rv; + + if (downstream->get_dispatch_state() != DispatchState::ACTIVE) { + // This is error condition when we failed push_request_headers() + // in initiate_downstream(). Otherwise, we have + // DispatchState::ACTIVE state, or we did not set + // DownstreamConnection. + downstream->pop_downstream_connection(); + handler_->signal_write(); + + return 0; + } + + if (!downstream->request_submission_ready()) { + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + // We have got all response body already. Send it off. + downstream->pop_downstream_connection(); + return 0; + } + // pushed stream is handled here + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + downstream->pop_downstream_connection(); + + handler_->signal_write(); + + return 0; + } + + downstream->pop_downstream_connection(); + + downstream->add_retry(); + + std::unique_ptr dconn; + + rv = 0; + + if (no_retry || downstream->no_more_retry()) { + goto fail; + } + + // downstream connection is clean; we can retry with new + // downstream connection. + + for (;;) { + auto dconn = handler_->get_downstream_connection(rv, downstream); + if (!dconn) { + goto fail; + } + + rv = downstream->attach_downstream_connection(std::move(dconn)); + if (rv == 0) { + break; + } + } + + rv = downstream->push_request_headers(); + if (rv != 0) { + goto fail; + } + + return 0; + +fail: + if (rv == SHRPX_ERR_TLS_REQUIRED) { + assert(0); + abort(); + } + + rv = on_downstream_abort_request(downstream, 502); + if (rv != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + downstream->pop_downstream_connection(); + + handler_->signal_write(); + + return 0; +} + +void Http3Upstream::pause_read(IOCtrlReason reason) {} + +int Http3Upstream::resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed) { + consume(downstream->get_stream_id(), consumed); + + auto &req = downstream->request(); + + req.consume(consumed); + + handler_->signal_write(); + + return 0; +} + +int Http3Upstream::send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen) { + int rv; + + nghttp3_data_reader data_read, *data_read_ptr = nullptr; + + if (bodylen) { + data_read.read_data = downstream_read_data_callback; + data_read_ptr = &data_read; + } + + const auto &resp = downstream->response(); + auto config = get_config(); + auto &httpconf = config->http; + + auto &balloc = downstream->get_block_allocator(); + + const auto &headers = resp.fs.headers(); + auto nva = std::vector(); + // 2 for :status and server + nva.reserve(2 + headers.size() + httpconf.add_response_headers.size()); + + auto response_status = http2::stringify_status(balloc, resp.http_status); + + nva.push_back(http3::make_nv_ls_nocopy(":status", response_status)); + + for (auto &kv : headers) { + if (kv.name.empty() || kv.name[0] == ':') { + continue; + } + switch (kv.token) { + case http2::HD_CONNECTION: + case http2::HD_KEEP_ALIVE: + case http2::HD_PROXY_CONNECTION: + case http2::HD_TE: + case http2::HD_TRANSFER_ENCODING: + case http2::HD_UPGRADE: + continue; + } + nva.push_back(http3::make_nv_nocopy(kv.name, kv.value, kv.no_index)); + } + + if (!resp.fs.header(http2::HD_SERVER)) { + nva.push_back(http3::make_nv_ls_nocopy("server", config->http.server_name)); + } + + for (auto &p : httpconf.add_response_headers) { + nva.push_back(http3::make_nv_nocopy(p.name, p.value)); + } + + rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(), + nva.data(), nva.size(), data_read_ptr); + if (nghttp3_err_is_fatal(rv)) { + ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed: " + << nghttp3_strerror(rv); + return -1; + } + + auto buf = downstream->get_response_buf(); + + buf->append(body, bodylen); + + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + if (data_read_ptr) { + downstream->reset_upstream_wtimer(); + } + + if (shutdown_stream_read(downstream->get_stream_id(), NGHTTP3_H3_NO_ERROR) != + 0) { + return -1; + } + + return 0; +} + +int Http3Upstream::initiate_push(Downstream *downstream, const StringRef &uri) { + return 0; +} + +int Http3Upstream::response_riovec(struct iovec *iov, int iovcnt) const { + return 0; +} + +void Http3Upstream::response_drain(size_t n) {} + +bool Http3Upstream::response_empty() const { return false; } + +Downstream * +Http3Upstream::on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id) { + return nullptr; +} + +int Http3Upstream::on_downstream_push_promise_complete( + Downstream *downstream, Downstream *promised_downstream) { + return 0; +} + +bool Http3Upstream::push_enabled() const { return false; } + +void Http3Upstream::cancel_premature_downstream( + Downstream *promised_downstream) {} + +int Http3Upstream::on_read(const UpstreamAddr *faddr, + const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen) { + int rv; + + auto path = ngtcp2_path{ + { + const_cast(&local_addr.su.sa), + static_cast(local_addr.len), + }, + { + const_cast(&remote_addr.su.sa), + static_cast(remote_addr.len), + }, + const_cast(faddr), + }; + + rv = ngtcp2_conn_read_pkt(conn_, &path, &pi, data, datalen, quic_timestamp()); + if (rv != 0) { + switch (rv) { + case NGTCP2_ERR_DRAINING: + return -1; + case NGTCP2_ERR_RETRY: { + auto worker = handler_->get_worker(); + auto quic_conn_handler = worker->get_quic_connection_handler(); + + if (worker->get_graceful_shutdown()) { + ngtcp2_ccerr_set_transport_error(&last_error_, + NGTCP2_CONNECTION_REFUSED, nullptr, 0); + + return handle_error(); + } + + ngtcp2_version_cid vc; + + rv = + ngtcp2_pkt_decode_version_cid(&vc, data, datalen, SHRPX_QUIC_SCIDLEN); + if (rv != 0) { + return -1; + } + + retry_close_ = true; + + quic_conn_handler->send_retry(handler_->get_upstream_addr(), vc.version, + vc.dcid, vc.dcidlen, vc.scid, vc.scidlen, + remote_addr, local_addr, datalen * 3); + + return -1; + } + case NGTCP2_ERR_CRYPTO: + if (!last_error_.error_code) { + ngtcp2_ccerr_set_tls_alert( + &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0); + } + break; + case NGTCP2_ERR_DROP_CONN: + return -1; + default: + if (!last_error_.error_code) { + ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0); + } + } + + ULOG(ERROR, this) << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv); + + return handle_error(); + } + + return 0; +} + +int Http3Upstream::send_packet(const UpstreamAddr *faddr, + const sockaddr *remote_sa, size_t remote_salen, + const sockaddr *local_sa, size_t local_salen, + const ngtcp2_pkt_info &pi, const uint8_t *data, + size_t datalen, size_t gso_size) { + auto rv = quic_send_packet(faddr, remote_sa, remote_salen, local_sa, + local_salen, pi, data, datalen, gso_size); + switch (rv) { + case 0: + return 0; + // With GSO, sendmsg may fail with EINVAL if UDP payload is too + // large. + case -EINVAL: + case -EMSGSIZE: + // Let the packet lost. + break; + case -EAGAIN: +#if EAGAIN != EWOULDBLOCK + case -EWOULDBLOCK: +#endif // EAGAIN != EWOULDBLOCK + return SHRPX_ERR_SEND_BLOCKED; + default: + break; + } + + return -1; +} + +void Http3Upstream::on_send_blocked(const UpstreamAddr *faddr, + const ngtcp2_addr &remote_addr, + const ngtcp2_addr &local_addr, + const ngtcp2_pkt_info &pi, + 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.faddr = faddr; + p.pi = pi; + p.data = data; + p.datalen = datalen; + p.gso_size = gso_size; +} + +int Http3Upstream::send_blocked_packet() { + int rv; + + assert(tx_.send_blocked); + + for (; tx_.num_blocked_sent < tx_.num_blocked; ++tx_.num_blocked_sent) { + auto &p = tx_.blocked[tx_.num_blocked_sent]; + + rv = send_packet(p.faddr, &p.remote_addr.su.sa, p.remote_addr.len, + &p.local_addr.su.sa, p.local_addr.len, p.pi, p.data, + p.datalen, p.gso_size); + if (rv == SHRPX_ERR_SEND_BLOCKED) { + signal_write_upstream_addr(p.faddr); + + return 0; + } + } + + tx_.send_blocked = false; + tx_.num_blocked = 0; + tx_.num_blocked_sent = 0; + + return 0; +} + +void Http3Upstream::signal_write_upstream_addr(const UpstreamAddr *faddr) { + auto conn = handler_->get_connection(); + + if (faddr->fd != conn->wev.fd) { + if (ev_is_active(&conn->wev)) { + ev_io_stop(handler_->get_loop(), &conn->wev); + } + + ev_io_set(&conn->wev, faddr->fd, EV_WRITE); + } + + conn->wlimit.startw(); +} + +int Http3Upstream::handle_error() { + if (ngtcp2_conn_in_closing_period(conn_) || + ngtcp2_conn_in_draining_period(conn_)) { + return -1; + } + + ngtcp2_path_storage ps; + ngtcp2_pkt_info pi; + + ngtcp2_path_storage_zero(&ps); + + auto ts = quic_timestamp(); + + conn_close_.resize(SHRPX_QUIC_CONN_CLOSE_PKTLEN); + + auto nwrite = ngtcp2_conn_write_connection_close( + conn_, &ps.path, &pi, conn_close_.data(), conn_close_.size(), + &last_error_, ts); + if (nwrite < 0) { + ULOG(ERROR, this) << "ngtcp2_conn_write_connection_close: " + << ngtcp2_strerror(nwrite); + return -1; + } + + conn_close_.resize(nwrite); + + if (nwrite == 0) { + return -1; + } + + send_packet(static_cast(ps.path.user_data), + ps.path.remote.addr, ps.path.remote.addrlen, ps.path.local.addr, + ps.path.local.addrlen, pi, conn_close_.data(), nwrite, 0); + + return -1; +} + +int Http3Upstream::handle_expiry() { + int rv; + + auto ts = quic_timestamp(); + + rv = ngtcp2_conn_handle_expiry(conn_, ts); + if (rv != 0) { + if (rv == NGTCP2_ERR_IDLE_CLOSE) { + ULOG(INFO, this) << "Idle connection timeout"; + } else { + ULOG(ERROR, this) << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv); + } + ngtcp2_ccerr_set_liberr(&last_error_, rv, nullptr, 0); + return handle_error(); + } + + return 0; +} + +void Http3Upstream::reset_timer() { + auto ts = quic_timestamp(); + auto expiry_ts = ngtcp2_conn_get_expiry(conn_); + auto loop = handler_->get_loop(); + + if (expiry_ts <= ts) { + ev_feed_event(loop, &timer_, EV_TIMER); + return; + } + + timer_.repeat = static_cast(expiry_ts - ts) / NGTCP2_SECONDS; + + ev_timer_again(loop, &timer_); +} + +namespace { +int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t nconsumed, void *user_data, + void *stream_user_data) { + auto upstream = static_cast(user_data); + + upstream->consume(stream_id, nconsumed); + + return 0; +} +} // namespace + +namespace { +int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + assert(downstream); + + if (upstream->http_acked_stream_data(downstream, datalen) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_acked_stream_data(Downstream *downstream, + uint64_t datalen) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Stream " << downstream->get_stream_id() << " " + << datalen << " bytes acknowledged"; + } + + auto body = downstream->get_response_buf(); + auto drained = body->drain_mark(datalen); + (void)drained; + + assert(datalen == drained); + + if (downstream->resume_read(SHRPX_NO_BUFFER, datalen) != 0) { + return -1; + } + + return 0; +} + +namespace { +int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id, + void *user_data, void *stream_user_data) { + if (!ngtcp2_is_bidi_stream(stream_id)) { + return 0; + } + + auto upstream = static_cast(user_data); + upstream->http_begin_request_headers(stream_id); + + return 0; +} +} // namespace + +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) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + if (!downstream || downstream->get_stop_reading()) { + return 0; + } + + if (upstream->http_recv_request_header(downstream, token, name, value, flags, + /* trailer = */ false) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int http_recv_request_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) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + if (!downstream || downstream->get_stop_reading()) { + return 0; + } + + if (upstream->http_recv_request_header(downstream, token, name, value, flags, + /* trailer = */ true) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_recv_request_header(Downstream *downstream, + int32_t h3token, + nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + bool trailer) { + auto namebuf = nghttp3_rcbuf_get_buf(name); + auto valuebuf = nghttp3_rcbuf_get_buf(value); + auto &req = downstream->request(); + auto config = get_config(); + auto &httpconf = config->http; + + if (req.fs.buffer_size() + namebuf.len + valuebuf.len > + httpconf.request_header_field_buffer || + req.fs.num_fields() >= httpconf.max_request_header_fields) { + downstream->set_stop_reading(true); + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Too large or many header field size=" + << req.fs.buffer_size() + namebuf.len + valuebuf.len + << ", num=" << req.fs.num_fields() + 1; + } + + // just ignore if this is a trailer part. + if (trailer) { + return 0; + } + + if (error_reply(downstream, 431) != 0) { + return -1; + } + + return 0; + } + + auto token = http2::lookup_token(namebuf.base, namebuf.len); + auto no_index = flags & NGHTTP3_NV_FLAG_NEVER_INDEX; + + downstream->add_rcbuf(name); + downstream->add_rcbuf(value); + + if (trailer) { + req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len}, + StringRef{valuebuf.base, valuebuf.len}, no_index, + token); + return 0; + } + + req.fs.add_header_token(StringRef{namebuf.base, namebuf.len}, + StringRef{valuebuf.base, valuebuf.len}, no_index, + token); + return 0; +} + +namespace { +int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin, + void *user_data, void *stream_user_data) { + auto upstream = static_cast(user_data); + auto handler = upstream->get_client_handler(); + auto downstream = static_cast(stream_user_data); + + if (!downstream || downstream->get_stop_reading()) { + return 0; + } + + if (upstream->http_end_request_headers(downstream, fin) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + downstream->reset_upstream_rtimer(); + handler->stop_read_timer(); + + return 0; +} +} // namespace + +int Http3Upstream::http_end_request_headers(Downstream *downstream, int fin) { + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + auto &req = downstream->request(); + req.tstamp = lgconf->tstamp; + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + auto &nva = req.fs.headers(); + + if (LOG_ENABLED(INFO)) { + std::stringstream ss; + for (auto &nv : nva) { + if (nv.name == "authorization") { + ss << TTY_HTTP_HD << nv.name << TTY_RST << ": \n"; + continue; + } + ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n"; + } + ULOG(INFO, this) << "HTTP request headers. stream_id=" + << downstream->get_stream_id() << "\n" + << ss.str(); + } + + auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH); + if (content_length) { + // libnghttp3 guarantees this can be parsed + req.fs.content_length = util::parse_uint(content_length->value); + } + + // presence of mandatory header fields are guaranteed by libnghttp3. + auto authority = req.fs.header(http2::HD__AUTHORITY); + auto path = req.fs.header(http2::HD__PATH); + auto method = req.fs.header(http2::HD__METHOD); + auto scheme = req.fs.header(http2::HD__SCHEME); + + auto method_token = http2::lookup_method_token(method->value); + if (method_token == -1) { + if (error_reply(downstream, 501) != 0) { + return -1; + } + return 0; + } + + auto faddr = handler_->get_upstream_addr(); + + auto config = get_config(); + + // For HTTP/2 proxy, we require :authority. + if (method_token != HTTP_CONNECT && config->http2_proxy && + faddr->alt_mode == UpstreamAltMode::NONE && !authority) { + shutdown_stream(downstream, NGHTTP3_H3_GENERAL_PROTOCOL_ERROR); + return 0; + } + + req.method = method_token; + if (scheme) { + req.scheme = scheme->value; + } + + // nghttp2 library guarantees either :authority or host exist + if (!authority) { + req.no_authority = true; + authority = req.fs.header(http2::HD_HOST); + } + + if (authority) { + req.authority = authority->value; + } + + if (path) { + if (method_token == HTTP_OPTIONS && + path->value == StringRef::from_lit("*")) { + // Server-wide OPTIONS request. Path is empty. + } else if (config->http2_proxy && + faddr->alt_mode == UpstreamAltMode::NONE) { + req.path = path->value; + } else { + req.path = http2::rewrite_clean_path(downstream->get_block_allocator(), + path->value); + } + } + + auto connect_proto = req.fs.header(http2::HD__PROTOCOL); + if (connect_proto) { + if (connect_proto->value != "websocket") { + if (error_reply(downstream, 400) != 0) { + return -1; + } + return 0; + } + req.connect_proto = ConnectProto::WEBSOCKET; + } + + if (!fin) { + req.http2_expect_body = true; + } else if (req.fs.content_length == -1) { + req.fs.content_length = 0; + } + + downstream->inspect_http2_request(); + + downstream->set_request_state(DownstreamState::HEADER_COMPLETE); + + if (config->http.require_http_scheme && + !http::check_http_scheme(req.scheme, /* encrypted = */ true)) { + if (error_reply(downstream, 400) != 0) { + return -1; + } + } + +#ifdef HAVE_MRUBY + auto worker = handler_->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_request_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + return -1; + } + return 0; + } +#endif // HAVE_MRUBY + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + start_downstream(downstream); + + return 0; +} + +void Http3Upstream::start_downstream(Downstream *downstream) { + if (downstream_queue_.can_activate(downstream->request().authority)) { + initiate_downstream(downstream); + return; + } + + downstream_queue_.mark_blocked(downstream); +} + +void Http3Upstream::initiate_downstream(Downstream *downstream) { + int rv; + +#ifdef HAVE_MRUBY + DownstreamConnection *dconn_ptr; +#endif // HAVE_MRUBY + + for (;;) { + auto dconn = handler_->get_downstream_connection(rv, downstream); + if (!dconn) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + assert(0); + abort(); + } + + rv = error_reply(downstream, 502); + if (rv != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + + downstream->set_request_state(DownstreamState::CONNECT_FAIL); + downstream_queue_.mark_failure(downstream); + + return; + } + +#ifdef HAVE_MRUBY + dconn_ptr = dconn.get(); +#endif // HAVE_MRUBY + rv = downstream->attach_downstream_connection(std::move(dconn)); + if (rv == 0) { + break; + } + } + +#ifdef HAVE_MRUBY + const auto &group = dconn_ptr->get_downstream_addr_group(); + if (group) { + const auto &mruby_ctx = group->shared_addr->mruby_ctx; + if (mruby_ctx->run_on_request_proc(downstream) != 0) { + if (error_reply(downstream, 500) != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + + downstream_queue_.mark_failure(downstream); + + return; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return; + } + } +#endif // HAVE_MRUBY + + rv = downstream->push_request_headers(); + if (rv != 0) { + + if (error_reply(downstream, 502) != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + + downstream_queue_.mark_failure(downstream); + + return; + } + + downstream_queue_.mark_active(downstream); + + auto &req = downstream->request(); + if (!req.http2_expect_body) { + rv = downstream->end_upload_data(); + if (rv != 0) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + } +} + +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) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + if (upstream->http_recv_data(downstream, data, datalen) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_recv_data(Downstream *downstream, const uint8_t *data, + size_t datalen) { + downstream->reset_upstream_rtimer(); + + if (downstream->push_upload_data_chunk(data, datalen) != 0) { + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + + consume(downstream->get_stream_id(), datalen); + + return 0; + } + + return 0; +} + +namespace { +int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + auto upstream = static_cast(user_data); + auto downstream = static_cast(stream_user_data); + + if (!downstream || downstream->get_stop_reading()) { + return 0; + } + + if (upstream->http_end_stream(downstream) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_end_stream(Downstream *downstream) { + downstream->disable_upstream_rtimer(); + + if (downstream->end_upload_data() != 0) { + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { + shutdown_stream(downstream, NGHTTP3_H3_INTERNAL_ERROR); + } + } + + downstream->set_request_state(DownstreamState::MSG_COMPLETE); + + 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 upstream = static_cast(conn_user_data); + auto downstream = static_cast(stream_user_data); + + if (!downstream) { + return 0; + } + + if (upstream->http_stream_close(downstream, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_stream_close(Downstream *downstream, + uint64_t app_error_code) { + auto stream_id = downstream->get_stream_id(); + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Stream stream_id=" << stream_id + << " is being closed with app_error_code=" + << app_error_code; + + auto body = downstream->get_response_buf(); + + ULOG(INFO, this) << "response unacked_left=" << body->rleft() + << " not_sent=" << body->rleft_mark(); + } + + auto &req = downstream->request(); + + consume(stream_id, req.unconsumed_body_length); + + req.unconsumed_body_length = 0; + + ngtcp2_conn_extend_max_streams_bidi(conn_, 1); + + if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) { + remove_downstream(downstream); + // downstream was deleted + + return 0; + } + + if (downstream->can_detach_downstream_connection()) { + // Keep-alive + downstream->detach_downstream_connection(); + } + + downstream->set_request_state(DownstreamState::STREAM_CLOSED); + + // At this point, downstream read may be paused. + + // If shrpx_downstream::push_request_headers() failed, the + // error is handled here. + remove_downstream(downstream); + // downstream was deleted + + return 0; +} + +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 upstream = static_cast(user_data); + + if (upstream->http_stop_sending(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_stop_sending(int64_t stream_id, + uint64_t app_error_code) { + auto rv = + ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id, app_error_code); + if (ngtcp2_err_is_fatal(rv)) { + ULOG(ERROR, this) << "ngtcp2_conn_shutdown_stream_read: " + << ngtcp2_strerror(rv); + 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 upstream = static_cast(user_data); + + if (upstream->http_reset_stream(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::http_reset_stream(int64_t stream_id, + uint64_t app_error_code) { + auto rv = + ngtcp2_conn_shutdown_stream_write(conn_, 0, stream_id, app_error_code); + if (ngtcp2_err_is_fatal(rv)) { + ULOG(ERROR, this) << "ngtcp2_conn_shutdown_stream_write: " + << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + +int Http3Upstream::setup_httpconn() { + int rv; + + if (ngtcp2_conn_get_streams_uni_left(conn_) < 3) { + return -1; + } + + nghttp3_callbacks callbacks{ + shrpx::http_acked_stream_data, + shrpx::http_stream_close, + shrpx::http_recv_data, + http_deferred_consume, + shrpx::http_begin_request_headers, + shrpx::http_recv_request_header, + shrpx::http_end_request_headers, + nullptr, // begin_trailers + shrpx::http_recv_request_trailer, + nullptr, // end_trailers + shrpx::http_stop_sending, + shrpx::http_end_stream, + shrpx::http_reset_stream, + }; + + auto config = get_config(); + + nghttp3_settings settings; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4_k; + + if (!config->http2_proxy) { + settings.enable_connect_protocol = 1; + } + + auto mem = nghttp3_mem_default(); + + rv = nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_server_new: " << nghttp3_strerror(rv); + 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; + + rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr); + if (rv != 0) { + ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv); + return -1; + } + + rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_bind_control_stream: " + << nghttp3_strerror(rv); + return -1; + } + + int64_t qpack_enc_stream_id, qpack_dec_stream_id; + + rv = ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr); + if (rv != 0) { + ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv); + return -1; + } + + rv = ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr); + if (rv != 0) { + ULOG(ERROR, this) << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv); + return -1; + } + + rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id, + qpack_dec_stream_id); + if (rv != 0) { + ULOG(ERROR, this) << "nghttp3_conn_bind_qpack_streams: " + << nghttp3_strerror(rv); + return -1; + } + + return 0; +} + +int Http3Upstream::error_reply(Downstream *downstream, + unsigned int status_code) { + int rv; + auto &resp = downstream->response(); + + auto &balloc = downstream->get_block_allocator(); + + auto html = http::create_error_html(balloc, status_code); + resp.http_status = status_code; + auto body = downstream->get_response_buf(); + body->append(html); + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + nghttp3_data_reader data_read; + data_read.read_data = downstream_read_data_callback; + + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + + auto response_status = http2::stringify_status(balloc, status_code); + auto content_length = util::make_string_ref_uint(balloc, html.size()); + auto date = make_string_ref(balloc, lgconf->tstamp->time_http); + + auto nva = std::array{ + {http3::make_nv_ls_nocopy(":status", response_status), + http3::make_nv_ll("content-type", "text/html; charset=UTF-8"), + http3::make_nv_ls_nocopy("server", get_config()->http.server_name), + http3::make_nv_ls_nocopy("content-length", content_length), + http3::make_nv_ls_nocopy("date", date)}}; + + rv = nghttp3_conn_submit_response(httpconn_, downstream->get_stream_id(), + nva.data(), nva.size(), &data_read); + if (nghttp3_err_is_fatal(rv)) { + ULOG(FATAL, this) << "nghttp3_conn_submit_response() failed: " + << nghttp3_strerror(rv); + return -1; + } + + downstream->reset_upstream_wtimer(); + + if (shutdown_stream_read(downstream->get_stream_id(), NGHTTP3_H3_NO_ERROR) != + 0) { + return -1; + } + + return 0; +} + +int Http3Upstream::shutdown_stream(Downstream *downstream, + uint64_t app_error_code) { + auto stream_id = downstream->get_stream_id(); + + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Shutdown stream_id=" << stream_id + << " with app_error_code=" << app_error_code; + } + + auto rv = ngtcp2_conn_shutdown_stream(conn_, 0, stream_id, app_error_code); + if (rv != 0) { + ULOG(FATAL, this) << "ngtcp2_conn_shutdown_stream() failed: " + << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + +int Http3Upstream::shutdown_stream_read(int64_t stream_id, + uint64_t app_error_code) { + auto rv = ngtcp2_conn_shutdown_stream_read(conn_, 0, stream_id, + NGHTTP3_H3_NO_ERROR); + if (ngtcp2_err_is_fatal(rv)) { + ULOG(FATAL, this) << "ngtcp2_conn_shutdown_stream_read: " + << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + +void Http3Upstream::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 Http3Upstream::remove_downstream(Downstream *downstream) { + if (downstream->accesslog_ready()) { + handler_->write_accesslog(downstream); + } + + nghttp3_conn_set_stream_user_data(httpconn_, downstream->get_stream_id(), + nullptr); + + auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream); + + if (next_downstream) { + initiate_downstream(next_downstream); + } + + if (downstream_queue_.get_downstreams() == nullptr) { + // There is no downstream at the moment. Start idle timer now. + handler_->repeat_read_timer(); + } +} + +void Http3Upstream::log_response_headers( + Downstream *downstream, const std::vector &nva) const { + std::stringstream ss; + for (auto &nv : nva) { + ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": " + << StringRef{nv.value, nv.valuelen} << "\n"; + } + ULOG(INFO, this) << "HTTP response headers. stream_id=" + << downstream->get_stream_id() << "\n" + << ss.str(); +} + +int Http3Upstream::check_shutdown() { + auto worker = handler_->get_worker(); + + if (!worker->get_graceful_shutdown()) { + return 0; + } + + ev_prepare_stop(handler_->get_loop(), &prep_); + + return start_graceful_shutdown(); +} + +int Http3Upstream::start_graceful_shutdown() { + int rv; + + if (ev_is_active(&shutdown_timer_)) { + return 0; + } + + if (!httpconn_) { + return -1; + } + + rv = nghttp3_conn_submit_shutdown_notice(httpconn_); + if (rv != 0) { + ULOG(FATAL, this) << "nghttp3_conn_submit_shutdown_notice: " + << nghttp3_strerror(rv); + return -1; + } + + handler_->signal_write(); + + auto t = ngtcp2_conn_get_pto(conn_); + + ev_timer_set(&shutdown_timer_, static_cast(t * 3) / NGTCP2_SECONDS, + 0.); + ev_timer_start(handler_->get_loop(), &shutdown_timer_); + + return 0; +} + +int Http3Upstream::submit_goaway() { + int rv; + + rv = nghttp3_conn_shutdown(httpconn_); + if (rv != 0) { + ULOG(FATAL, this) << "nghttp3_conn_shutdown: " << nghttp3_strerror(rv); + return -1; + } + + handler_->signal_write(); + + return 0; +} + +int Http3Upstream::open_qlog_file(const StringRef &dir, + const ngtcp2_cid &scid) const { + std::array buf; + + auto path = dir.str(); + path += '/'; + path += + util::format_iso8601_basic(buf.data(), std::chrono::system_clock::now()); + path += '-'; + path += util::format_hex(scid.data, scid.datalen); + path += ".sqlog"; + + int fd; + +#ifdef O_CLOEXEC + while ((fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP)) == -1 && + errno == EINTR) + ; +#else // !O_CLOEXEC + while ((fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP)) == -1 && + errno == EINTR) + ; + + if (fd != -1) { + util::make_socket_closeonexec(fd); + } +#endif // !O_CLOEXEC + + if (fd == -1) { + auto error = errno; + ULOG(ERROR, this) << "Failed to open qlog file " << path + << ": errno=" << error; + return -1; + } + + return fd; +} + +ngtcp2_conn *Http3Upstream::get_conn() const { return conn_; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_http3_upstream.h b/lib/nghttp2/src/shrpx_http3_upstream.h new file mode 100644 index 00000000000..18076522ad0 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http3_upstream.h @@ -0,0 +1,194 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTP3_UPSTREAM_H +#define SHRPX_HTTP3_UPSTREAM_H + +#include "shrpx.h" + +#include +#include + +#include "shrpx_upstream.h" +#include "shrpx_downstream_queue.h" +#include "quic.h" +#include "network.h" + +using namespace nghttp2; + +namespace shrpx { + +struct UpstreamAddr; + +class Http3Upstream : public Upstream { +public: + Http3Upstream(ClientHandler *handler); + virtual ~Http3Upstream(); + + virtual int on_read(); + virtual int on_write(); + virtual int on_timeout(Downstream *downstream); + virtual int on_downstream_abort_request(Downstream *downstream, + unsigned int status_code); + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream); + virtual int downstream_read(DownstreamConnection *dconn); + virtual int downstream_write(DownstreamConnection *dconn); + virtual int downstream_eof(DownstreamConnection *dconn); + virtual int downstream_error(DownstreamConnection *dconn, int events); + virtual ClientHandler *get_client_handler() const; + + virtual int on_downstream_header_complete(Downstream *downstream); + virtual int on_downstream_body(Downstream *downstream, const uint8_t *data, + size_t len, bool flush); + virtual int on_downstream_body_complete(Downstream *downstream); + + virtual void on_handler_delete(); + virtual int on_downstream_reset(Downstream *downstream, bool no_retry); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed); + virtual int send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen); + + virtual int initiate_push(Downstream *downstream, const StringRef &uri); + + virtual int response_riovec(struct iovec *iov, int iovcnt) const; + virtual void response_drain(size_t n); + virtual bool response_empty() const; + + virtual Downstream *on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id); + virtual int + on_downstream_push_promise_complete(Downstream *downstream, + Downstream *promised_downstream); + virtual bool push_enabled() const; + virtual void cancel_premature_downstream(Downstream *promised_downstream); + + int init(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_hd &initial_hd, + const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, + ngtcp2_token_type token_type); + + int on_read(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen); + + int write_streams(); + + int handle_error(); + + int handle_expiry(); + void reset_timer(); + + int setup_httpconn(); + void add_pending_downstream(std::unique_ptr downstream); + 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); + int extend_max_stream_data(int64_t stream_id); + void extend_max_remote_streams_bidi(uint64_t max_streams); + int error_reply(Downstream *downstream, unsigned int status_code); + void http_begin_request_headers(int64_t stream_id); + int http_recv_request_header(Downstream *downstream, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, + uint8_t flags, bool trailer); + int http_end_request_headers(Downstream *downstream, int fin); + int http_end_stream(Downstream *downstream); + void start_downstream(Downstream *downstream); + void initiate_downstream(Downstream *downstream); + int shutdown_stream(Downstream *downstream, uint64_t app_error_code); + int shutdown_stream_read(int64_t stream_id, uint64_t app_error_code); + int http_stream_close(Downstream *downstream, uint64_t app_error_code); + void consume(int64_t stream_id, size_t nconsumed); + void remove_downstream(Downstream *downstream); + int stream_close(int64_t stream_id, uint64_t app_error_code); + int stream_reset(int64_t stream_id); + void log_response_headers(Downstream *downstream, + const std::vector &nva) const; + int http_acked_stream_data(Downstream *downstream, uint64_t datalen); + int http_shutdown_stream_read(int64_t stream_id); + int http_reset_stream(int64_t stream_id, uint64_t app_error_code); + int http_stop_sending(int64_t stream_id, uint64_t app_error_code); + int http_recv_data(Downstream *downstream, const uint8_t *data, + size_t datalen); + int handshake_completed(); + int check_shutdown(); + int start_graceful_shutdown(); + int submit_goaway(); + int send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, + size_t remote_salen, const sockaddr *local_sa, + size_t local_salen, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen, size_t gso_size); + + void qlog_write(const void *data, size_t datalen, bool fin); + int open_qlog_file(const StringRef &dir, const ngtcp2_cid &scid) const; + + void on_send_blocked(const UpstreamAddr *faddr, + const ngtcp2_addr &remote_addr, + const ngtcp2_addr &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen, size_t gso_size); + int send_blocked_packet(); + void signal_write_upstream_addr(const UpstreamAddr *faddr); + + ngtcp2_conn *get_conn() const; + + int send_new_token(const ngtcp2_addr *remote_addr); + +private: + ClientHandler *handler_; + ev_timer timer_; + ev_timer shutdown_timer_; + ev_prepare prep_; + int qlog_fd_; + ngtcp2_cid hashed_scid_; + ngtcp2_conn *conn_; + ngtcp2_ccerr last_error_; + nghttp3_conn *httpconn_; + DownstreamQueue downstream_queue_; + bool retry_close_; + std::vector conn_close_; + + struct { + bool send_blocked; + size_t num_blocked; + size_t num_blocked_sent; + // blocked field is effective only when send_blocked is true. + struct { + const UpstreamAddr *faddr; + Address local_addr; + Address remote_addr; + ngtcp2_pkt_info pi; + const uint8_t *data; + size_t datalen; + size_t gso_size; + } blocked[2]; + std::unique_ptr data; + } tx_; +}; + +} // namespace shrpx + +#endif // SHRPX_HTTP3_UPSTREAM_H diff --git a/lib/nghttp2/src/shrpx_http_downstream_connection.cc b/lib/nghttp2/src/shrpx_http_downstream_connection.cc new file mode 100644 index 00000000000..bdb42943e51 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http_downstream_connection.cc @@ -0,0 +1,1617 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_http_downstream_connection.h" + +#include + +#include "shrpx_client_handler.h" +#include "shrpx_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_config.h" +#include "shrpx_error.h" +#include "shrpx_http.h" +#include "shrpx_log_config.h" +#include "shrpx_connect_blocker.h" +#include "shrpx_downstream_connection_pool.h" +#include "shrpx_worker.h" +#include "shrpx_http2_session.h" +#include "shrpx_tls.h" +#include "shrpx_log.h" +#include "http2.h" +#include "util.h" +#include "ssl_compat.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto conn = static_cast(w->data); + auto dconn = static_cast(conn->data); + + if (w == &conn->rt && !conn->expired_rt()) { + return; + } + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "Time out"; + } + + auto downstream = dconn->get_downstream(); + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto &resp = downstream->response(); + + // Do this so that dconn is not pooled + resp.connection_close = true; + + if (upstream->downstream_error(dconn, Downstream::EVENT_TIMEOUT) != 0) { + delete handler; + } +} +} // namespace + +namespace { +void retry_downstream_connection(Downstream *downstream, + unsigned int status_code) { + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + + assert(!downstream->get_request_header_sent()); + + downstream->add_retry(); + + if (downstream->no_more_retry()) { + delete handler; + return; + } + + downstream->pop_downstream_connection(); + auto buf = downstream->get_request_buf(); + buf->reset(); + + int rv; + + for (;;) { + auto ndconn = handler->get_downstream_connection(rv, downstream); + if (!ndconn) { + break; + } + if (downstream->attach_downstream_connection(std::move(ndconn)) != 0) { + continue; + } + if (downstream->push_request_headers() == 0) { + return; + } + } + + downstream->set_request_state(DownstreamState::CONNECT_FAIL); + + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = upstream->on_downstream_abort_request_with_https_redirect(downstream); + } else { + rv = upstream->on_downstream_abort_request(downstream, status_code); + } + + if (rv != 0) { + delete handler; + } +} +} // namespace + +namespace { +void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto conn = static_cast(w->data); + auto dconn = static_cast(conn->data); + auto addr = dconn->get_addr(); + auto raddr = dconn->get_raddr(); + + DCLOG(WARN, dconn) << "Connect time out; addr=" + << util::to_numeric_addr(raddr); + + downstream_failure(addr, raddr); + + auto downstream = dconn->get_downstream(); + + retry_downstream_connection(downstream, 504); +} +} // namespace + +namespace { +void backend_retry(Downstream *downstream) { + retry_downstream_connection(downstream, 502); +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto conn = static_cast(w->data); + auto dconn = static_cast(conn->data); + auto downstream = dconn->get_downstream(); + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + + rv = upstream->downstream_read(dconn); + if (rv != 0) { + if (rv == SHRPX_ERR_RETRY) { + backend_retry(downstream); + return; + } + + delete handler; + } +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto conn = static_cast(w->data); + auto dconn = static_cast(conn->data); + auto downstream = dconn->get_downstream(); + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + + rv = upstream->downstream_write(dconn); + if (rv == SHRPX_ERR_RETRY) { + backend_retry(downstream); + return; + } + + if (rv != 0) { + delete handler; + } +} +} // namespace + +namespace { +void connectcb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn = static_cast(w->data); + auto dconn = static_cast(conn->data); + auto downstream = dconn->get_downstream(); + if (dconn->connected() != 0) { + backend_retry(downstream); + return; + } + writecb(loop, w, revents); +} +} // namespace + +HttpDownstreamConnection::HttpDownstreamConnection( + const std::shared_ptr &group, DownstreamAddr *addr, + struct ev_loop *loop, Worker *worker) + : conn_(loop, -1, nullptr, worker->get_mcpool(), + group->shared_addr->timeout.write, group->shared_addr->timeout.read, + {}, {}, connectcb, readcb, connect_timeoutcb, this, + get_config()->tls.dyn_rec.warmup_threshold, + get_config()->tls.dyn_rec.idle_timeout, Proto::HTTP1), + on_read_(&HttpDownstreamConnection::noop), + on_write_(&HttpDownstreamConnection::noop), + signal_write_(&HttpDownstreamConnection::noop), + worker_(worker), + ssl_ctx_(worker->get_cl_ssl_ctx()), + group_(group), + addr_(addr), + raddr_(nullptr), + ioctrl_(&conn_.rlimit), + response_htp_{0}, + first_write_done_(false), + reusable_(true), + request_header_written_(false) {} + +HttpDownstreamConnection::~HttpDownstreamConnection() { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Deleted"; + } + + if (dns_query_) { + auto dns_tracker = worker_->get_dns_tracker(); + dns_tracker->cancel(dns_query_.get()); + } +} + +int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { + int rv; + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; + } + + downstream_ = downstream; + + rv = initiate_connection(); + if (rv != 0) { + downstream_ = nullptr; + return rv; + } + + return 0; +} + +namespace { +int htp_msg_begincb(llhttp_t *htp); +int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len); +int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len); +int htp_hdrs_completecb(llhttp_t *htp); +int htp_bodycb(llhttp_t *htp, const char *data, size_t len); +int htp_msg_completecb(llhttp_t *htp); +} // namespace + +namespace { +constexpr llhttp_settings_t htp_hooks = { + htp_msg_begincb, // llhttp_cb on_message_begin; + nullptr, // llhttp_data_cb on_url; + nullptr, // llhttp_data_cb on_status; + nullptr, // llhttp_data_cb on_method; + nullptr, // llhttp_data_cb on_version; + htp_hdr_keycb, // llhttp_data_cb on_header_field; + htp_hdr_valcb, // llhttp_data_cb on_header_value; + nullptr, // llhttp_data_cb on_chunk_extension_name; + nullptr, // llhttp_data_cb on_chunk_extension_value; + htp_hdrs_completecb, // llhttp_cb on_headers_complete; + htp_bodycb, // llhttp_data_cb on_body; + htp_msg_completecb, // llhttp_cb on_message_complete; + nullptr, // llhttp_cb on_url_complete; + nullptr, // llhttp_cb on_status_complete; + nullptr, // llhttp_cb on_method_complete; + nullptr, // llhttp_cb on_version_complete; + nullptr, // llhttp_cb on_header_field_complete; + nullptr, // llhttp_cb on_header_value_complete; + nullptr, // llhttp_cb on_chunk_extension_name_complete; + nullptr, // llhttp_cb on_chunk_extension_value_complete; + nullptr, // llhttp_cb on_chunk_header; + nullptr, // llhttp_cb on_chunk_complete; + nullptr, // llhttp_cb on_reset; +}; +} // namespace + +int HttpDownstreamConnection::initiate_connection() { + int rv; + + auto worker_blocker = worker_->get_connect_blocker(); + if (worker_blocker->blocked()) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) + << "Worker wide backend connection was blocked temporarily"; + } + return SHRPX_ERR_NETWORK; + } + + auto &downstreamconf = *worker_->get_downstream_config(); + + if (conn_.fd == -1) { + auto check_dns_result = dns_query_.get() != nullptr; + + if (check_dns_result) { + assert(addr_->dns); + } + + auto &connect_blocker = addr_->connect_blocker; + + if (connect_blocker->blocked()) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Backend server " << addr_->host << ":" + << addr_->port << " was not available temporarily"; + } + + return SHRPX_ERR_NETWORK; + } + + Address *raddr; + + if (addr_->dns) { + if (!check_dns_result) { + auto dns_query = std::make_unique( + addr_->host, + [this](DNSResolverStatus status, const Address *result) { + int rv; + + if (status == DNSResolverStatus::OK) { + *this->resolved_addr_ = *result; + } + + rv = this->initiate_connection(); + if (rv != 0) { + // This callback destroys |this|. + auto downstream = this->downstream_; + backend_retry(downstream); + } + }); + + auto dns_tracker = worker_->get_dns_tracker(); + + if (!resolved_addr_) { + resolved_addr_ = std::make_unique
(); + } + switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) { + case DNSResolverStatus::ERROR: + downstream_failure(addr_, nullptr); + return SHRPX_ERR_NETWORK; + case DNSResolverStatus::RUNNING: + dns_query_ = std::move(dns_query); + return 0; + case DNSResolverStatus::OK: + break; + default: + assert(0); + } + } else { + switch (dns_query_->status) { + case DNSResolverStatus::ERROR: + dns_query_.reset(); + downstream_failure(addr_, nullptr); + return SHRPX_ERR_NETWORK; + case DNSResolverStatus::OK: + dns_query_.reset(); + break; + default: + assert(0); + } + } + + raddr = resolved_addr_.get(); + util::set_port(*resolved_addr_, addr_->port); + } else { + raddr = &addr_->addr; + } + + conn_.fd = util::create_nonblock_socket(raddr->su.storage.ss_family); + + if (conn_.fd == -1) { + auto error = errno; + DCLOG(WARN, this) << "socket() failed; addr=" + << util::to_numeric_addr(raddr) << ", errno=" << error; + + worker_blocker->on_failure(); + + return SHRPX_ERR_NETWORK; + } + + worker_blocker->on_success(); + + rv = connect(conn_.fd, &raddr->su.sa, raddr->len); + if (rv != 0 && errno != EINPROGRESS) { + auto error = errno; + DCLOG(WARN, this) << "connect() failed; addr=" + << util::to_numeric_addr(raddr) << ", errno=" << error; + + downstream_failure(addr_, raddr); + + return SHRPX_ERR_NETWORK; + } + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Connecting to downstream server"; + } + + raddr_ = raddr; + + if (addr_->tls) { + assert(ssl_ctx_); + + auto ssl = tls::create_ssl(ssl_ctx_); + if (!ssl) { + return -1; + } + + tls::setup_downstream_http1_alpn(ssl); + + conn_.set_ssl(ssl); + conn_.tls.client_session_cache = &addr_->tls_session_cache; + + auto sni_name = + addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni}; + if (!util::numeric_host(sni_name.c_str())) { + SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str()); + } + + auto session = tls::reuse_tls_session(addr_->tls_session_cache); + if (session) { + SSL_set_session(conn_.tls.ssl, session); + SSL_SESSION_free(session); + } + + conn_.prepare_client_handshake(); + } + + ev_io_set(&conn_.wev, conn_.fd, EV_WRITE); + ev_io_set(&conn_.rev, conn_.fd, EV_READ); + + conn_.wlimit.startw(); + + conn_.wt.repeat = downstreamconf.timeout.connect; + ev_timer_again(conn_.loop, &conn_.wt); + } else { + // we may set read timer cb to idle_timeoutcb. Reset again. + ev_set_cb(&conn_.rt, timeoutcb); + if (conn_.read_timeout < group_->shared_addr->timeout.read) { + conn_.read_timeout = group_->shared_addr->timeout.read; + conn_.last_read = std::chrono::steady_clock::now(); + } else { + conn_.again_rt(group_->shared_addr->timeout.read); + } + + ev_set_cb(&conn_.rev, readcb); + + on_write_ = &HttpDownstreamConnection::write_first; + first_write_done_ = false; + request_header_written_ = false; + } + + llhttp_init(&response_htp_, HTTP_RESPONSE, &htp_hooks); + response_htp_.data = downstream_; + + return 0; +} + +int HttpDownstreamConnection::push_request_headers() { + if (request_header_written_) { + signal_write(); + return 0; + } + + const auto &downstream_hostport = addr_->hostport; + const auto &req = downstream_->request(); + + auto &balloc = downstream_->get_block_allocator(); + + auto connect_method = req.regular_connect_method(); + + auto config = get_config(); + auto &httpconf = config->http; + + request_header_written_ = true; + + // For HTTP/1.0 request, there is no authority in request. In that + // case, we use backend server's host nonetheless. + auto authority = StringRef(downstream_hostport); + auto no_host_rewrite = + httpconf.no_host_rewrite || config->http2_proxy || connect_method; + + if (no_host_rewrite && !req.authority.empty()) { + authority = req.authority; + } + + downstream_->set_request_downstream_host(authority); + + auto buf = downstream_->get_request_buf(); + + // Assume that method and request path do not contain \r\n. + auto meth = http2::to_method_string( + req.connect_proto == ConnectProto::WEBSOCKET ? HTTP_GET : req.method); + buf->append(meth); + buf->append(' '); + + if (connect_method) { + buf->append(authority); + } else if (config->http2_proxy) { + // Construct absolute-form request target because we are going to + // send a request to a HTTP/1 proxy. + assert(!req.scheme.empty()); + buf->append(req.scheme); + buf->append("://"); + buf->append(authority); + buf->append(req.path); + } else if (req.method == HTTP_OPTIONS && req.path.empty()) { + // Server-wide OPTIONS + buf->append("*"); + } else { + buf->append(req.path); + } + buf->append(" HTTP/1.1\r\nHost: "); + buf->append(authority); + buf->append("\r\n"); + + auto &fwdconf = httpconf.forwarded; + auto &xffconf = httpconf.xff; + auto &xfpconf = httpconf.xfp; + auto &earlydataconf = httpconf.early_data; + + uint32_t build_flags = + (fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) | + (xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) | + (xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) | + (earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0) | + ((req.http_major == 3 || req.http_major == 2) + ? http2::HDOP_STRIP_SEC_WEBSOCKET_KEY + : 0); + + http2::build_http1_headers_from_headers(buf, req.fs.headers(), build_flags); + + auto cookie = downstream_->assemble_request_cookie(); + if (!cookie.empty()) { + buf->append("Cookie: "); + buf->append(cookie); + buf->append("\r\n"); + } + + // set transfer-encoding only when content-length is unknown and + // request body is expected. + if (req.method != HTTP_CONNECT && req.http2_expect_body && + req.fs.content_length == -1) { + downstream_->set_chunked_request(true); + buf->append("Transfer-Encoding: chunked\r\n"); + } + + if (req.connect_proto == ConnectProto::WEBSOCKET) { + if (req.http_major == 3 || req.http_major == 2) { + std::array nonce; + if (RAND_bytes(nonce.data(), nonce.size()) != 1) { + return -1; + } + auto iov = make_byte_ref(balloc, base64::encode_length(nonce.size()) + 1); + auto p = base64::encode(std::begin(nonce), std::end(nonce), iov.base); + *p = '\0'; + auto key = StringRef{iov.base, p}; + downstream_->set_ws_key(key); + + buf->append("Sec-Websocket-Key: "); + buf->append(key); + buf->append("\r\n"); + } + + buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n"); + } else if (!connect_method && req.upgrade_request) { + auto connection = req.fs.header(http2::HD_CONNECTION); + if (connection) { + buf->append("Connection: "); + buf->append((*connection).value); + buf->append("\r\n"); + } + + auto upgrade = req.fs.header(http2::HD_UPGRADE); + if (upgrade) { + buf->append("Upgrade: "); + buf->append((*upgrade).value); + buf->append("\r\n"); + } + } else if (req.connection_close) { + buf->append("Connection: close\r\n"); + } + + auto upstream = downstream_->get_upstream(); + auto handler = upstream->get_client_handler(); + +#if OPENSSL_1_1_1_API + auto conn = handler->get_connection(); + + if (conn->tls.ssl && !SSL_is_init_finished(conn->tls.ssl)) { + buf->append("Early-Data: 1\r\n"); + } +#endif // OPENSSL_1_1_1_API + + auto fwd = + fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED); + + if (fwdconf.params) { + auto params = fwdconf.params; + + if (config->http2_proxy || connect_method) { + params &= ~FORWARDED_PROTO; + } + + auto value = http::create_forwarded( + balloc, params, handler->get_forwarded_by(), + handler->get_forwarded_for(), req.authority, req.scheme); + + if (fwd || !value.empty()) { + buf->append("Forwarded: "); + if (fwd) { + buf->append(fwd->value); + + if (!value.empty()) { + buf->append(", "); + } + } + buf->append(value); + buf->append("\r\n"); + } + } else if (fwd) { + buf->append("Forwarded: "); + buf->append(fwd->value); + buf->append("\r\n"); + } + + auto xff = xffconf.strip_incoming ? nullptr + : req.fs.header(http2::HD_X_FORWARDED_FOR); + + if (xffconf.add) { + buf->append("X-Forwarded-For: "); + if (xff) { + buf->append((*xff).value); + buf->append(", "); + } + buf->append(client_handler_->get_ipaddr()); + buf->append("\r\n"); + } else if (xff) { + buf->append("X-Forwarded-For: "); + buf->append((*xff).value); + buf->append("\r\n"); + } + if (!config->http2_proxy && !connect_method) { + auto xfp = xfpconf.strip_incoming + ? nullptr + : req.fs.header(http2::HD_X_FORWARDED_PROTO); + + if (xfpconf.add) { + buf->append("X-Forwarded-Proto: "); + if (xfp) { + buf->append((*xfp).value); + buf->append(", "); + } + assert(!req.scheme.empty()); + buf->append(req.scheme); + buf->append("\r\n"); + } else if (xfp) { + buf->append("X-Forwarded-Proto: "); + buf->append((*xfp).value); + buf->append("\r\n"); + } + } + auto via = req.fs.header(http2::HD_VIA); + if (httpconf.no_via) { + if (via) { + buf->append("Via: "); + buf->append((*via).value); + buf->append("\r\n"); + } + } else { + buf->append("Via: "); + if (via) { + buf->append((*via).value); + buf->append(", "); + } + std::array viabuf; + auto end = http::create_via_header_value(viabuf.data(), req.http_major, + req.http_minor); + buf->append(viabuf.data(), end - viabuf.data()); + buf->append("\r\n"); + } + + for (auto &p : httpconf.add_request_headers) { + buf->append(p.name); + buf->append(": "); + buf->append(p.value); + buf->append("\r\n"); + } + + buf->append("\r\n"); + + if (LOG_ENABLED(INFO)) { + std::string nhdrs; + for (auto chunk = buf->head; chunk; chunk = chunk->next) { + nhdrs.append(chunk->pos, chunk->last); + } + if (log_config()->errorlog_tty) { + nhdrs = http::colorizeHeaders(nhdrs.c_str()); + } + DCLOG(INFO, this) << "HTTP request headers. stream_id=" + << downstream_->get_stream_id() << "\n" + << nhdrs; + } + + // Don't call signal_write() if we anticipate request body. We call + // signal_write() when we received request body chunk, and it + // enables us to send headers and data in one writev system call. + if (req.method == HTTP_CONNECT || + downstream_->get_blocked_request_buf()->rleft() || + (!req.http2_expect_body && req.fs.content_length == 0) || + downstream_->get_expect_100_continue()) { + signal_write(); + } + + return 0; +} + +int HttpDownstreamConnection::process_blocked_request_buf() { + auto src = downstream_->get_blocked_request_buf(); + + if (src->rleft()) { + auto dest = downstream_->get_request_buf(); + auto chunked = downstream_->get_chunked_request(); + if (chunked) { + auto chunk_size_hex = util::utox(src->rleft()); + dest->append(chunk_size_hex); + dest->append("\r\n"); + } + + src->copy(*dest); + + if (chunked) { + dest->append("\r\n"); + } + } + + if (downstream_->get_blocked_request_data_eof() && + downstream_->get_chunked_request()) { + end_upload_data_chunk(); + } + + return 0; +} + +int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data, + size_t datalen) { + if (!downstream_->get_request_header_sent()) { + auto output = downstream_->get_blocked_request_buf(); + auto &req = downstream_->request(); + output->append(data, datalen); + req.unconsumed_body_length += datalen; + if (request_header_written_) { + signal_write(); + } + return 0; + } + + auto chunked = downstream_->get_chunked_request(); + auto output = downstream_->get_request_buf(); + + if (chunked) { + auto chunk_size_hex = util::utox(datalen); + output->append(chunk_size_hex); + output->append("\r\n"); + } + + output->append(data, datalen); + + if (chunked) { + output->append("\r\n"); + } + + signal_write(); + + return 0; +} + +int HttpDownstreamConnection::end_upload_data() { + if (!downstream_->get_request_header_sent()) { + downstream_->set_blocked_request_data_eof(true); + if (request_header_written_) { + signal_write(); + } + return 0; + } + + signal_write(); + + if (!downstream_->get_chunked_request()) { + return 0; + } + + end_upload_data_chunk(); + + return 0; +} + +void HttpDownstreamConnection::end_upload_data_chunk() { + const auto &req = downstream_->request(); + + auto output = downstream_->get_request_buf(); + const auto &trailers = req.fs.trailers(); + if (trailers.empty()) { + output->append("0\r\n\r\n"); + } else { + output->append("0\r\n"); + http2::build_http1_headers_from_headers(output, trailers, + http2::HDOP_STRIP_ALL); + output->append("\r\n"); + } +} + +namespace { +void remove_from_pool(HttpDownstreamConnection *dconn) { + auto addr = dconn->get_addr(); + auto &dconn_pool = addr->dconn_pool; + dconn_pool->remove_downstream_connection(dconn); +} +} // namespace + +namespace { +void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn = static_cast(w->data); + auto dconn = static_cast(conn->data); + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "Idle connection EOF"; + } + + remove_from_pool(dconn); + // dconn was deleted +} +} // namespace + +namespace { +void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto conn = static_cast(w->data); + auto dconn = static_cast(conn->data); + + if (w == &conn->rt && !conn->expired_rt()) { + return; + } + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "Idle connection timeout"; + } + + remove_from_pool(dconn); + // dconn was deleted +} +} // namespace + +void HttpDownstreamConnection::detach_downstream(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream; + } + downstream_ = nullptr; + + ev_set_cb(&conn_.rev, idle_readcb); + ioctrl_.force_resume_read(); + + auto &downstreamconf = *worker_->get_downstream_config(); + + ev_set_cb(&conn_.rt, idle_timeoutcb); + if (conn_.read_timeout < downstreamconf.timeout.idle_read) { + conn_.read_timeout = downstreamconf.timeout.idle_read; + conn_.last_read = std::chrono::steady_clock::now(); + } else { + conn_.again_rt(downstreamconf.timeout.idle_read); + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); +} + +void HttpDownstreamConnection::pause_read(IOCtrlReason reason) { + ioctrl_.pause_read(reason); +} + +int HttpDownstreamConnection::resume_read(IOCtrlReason reason, + size_t consumed) { + auto &downstreamconf = *worker_->get_downstream_config(); + + if (downstream_->get_response_buf()->rleft() <= + downstreamconf.request_buffer_size / 2) { + ioctrl_.resume_read(reason); + } + + return 0; +} + +void HttpDownstreamConnection::force_resume_read() { + ioctrl_.force_resume_read(); +} + +namespace { +int htp_msg_begincb(llhttp_t *htp) { + auto downstream = static_cast(htp->data); + + if (downstream->get_response_state() != DownstreamState::INITIAL) { + return -1; + } + + return 0; +} +} // namespace + +namespace { +int htp_hdrs_completecb(llhttp_t *htp) { + auto downstream = static_cast(htp->data); + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + const auto &req = downstream->request(); + auto &resp = downstream->response(); + int rv; + + auto &balloc = downstream->get_block_allocator(); + + for (auto &kv : resp.fs.headers()) { + kv.value = util::rstrip(balloc, kv.value); + + if (kv.token == http2::HD_TRANSFER_ENCODING && + !http2::check_transfer_encoding(kv.value)) { + return -1; + } + } + + auto config = get_config(); + auto &loggingconf = config->logging; + + resp.http_status = htp->status_code; + resp.http_major = htp->http_major; + resp.http_minor = htp->http_minor; + + if (resp.http_major > 1 || req.http_minor > 1) { + resp.http_major = 1; + resp.http_minor = 1; + return -1; + } + + auto dconn = downstream->get_downstream_connection(); + + downstream->set_downstream_addr_group(dconn->get_downstream_addr_group()); + downstream->set_addr(dconn->get_addr()); + + // Server MUST NOT send Transfer-Encoding with a status code 1xx or + // 204. Also server MUST NOT send Transfer-Encoding with a status + // code 2xx to a CONNECT request. Same holds true with + // Content-Length. + if (resp.http_status == 204) { + if (resp.fs.header(http2::HD_TRANSFER_ENCODING)) { + return -1; + } + // Some server send content-length: 0 for 204. Until they get + // fixed, we accept, but ignore it. + + // Calling parse_content_length() detects duplicated + // content-length header fields. + if (resp.fs.parse_content_length() != 0) { + return -1; + } + if (resp.fs.content_length == 0) { + resp.fs.erase_content_length_and_transfer_encoding(); + } else if (resp.fs.content_length != -1) { + return -1; + } + } else if (resp.http_status / 100 == 1 || + (resp.http_status / 100 == 2 && req.method == HTTP_CONNECT)) { + // Server MUST NOT send Content-Length and Transfer-Encoding in + // these responses. + resp.fs.erase_content_length_and_transfer_encoding(); + } else if (resp.fs.parse_content_length() != 0) { + downstream->set_response_state(DownstreamState::MSG_BAD_HEADER); + return -1; + } + + // Check upgrade before processing non-final response, since if + // upgrade succeeded, 101 response is treated as final in nghttpx. + downstream->check_upgrade_fulfilled_http1(); + + if (downstream->get_non_final_response()) { + // Reset content-length because we reuse same Downstream for the + // next response. + resp.fs.content_length = -1; + // For non-final response code, we just call + // on_downstream_header_complete() without changing response + // state. + rv = upstream->on_downstream_header_complete(downstream); + + if (rv != 0) { + return -1; + } + + // Ignore response body for non-final response. + return 1; + } + + resp.connection_close = !llhttp_should_keep_alive(htp); + downstream->set_response_state(DownstreamState::HEADER_COMPLETE); + downstream->inspect_http1_response(); + + if (htp->flags & F_CHUNKED) { + downstream->set_chunked_response(true); + } + + auto transfer_encoding = resp.fs.header(http2::HD_TRANSFER_ENCODING); + if (transfer_encoding && !downstream->get_chunked_response()) { + resp.connection_close = true; + } + + if (downstream->get_upgraded()) { + // content-length must be ignored for upgraded connection. + resp.fs.content_length = -1; + resp.connection_close = true; + // transfer-encoding not applied to upgraded connection + downstream->set_chunked_response(false); + } else if (http2::legacy_http1(req.http_major, req.http_minor)) { + if (resp.fs.content_length == -1) { + resp.connection_close = true; + } + downstream->set_chunked_response(false); + } else if (!downstream->expect_response_body()) { + downstream->set_chunked_response(false); + } + + if (loggingconf.access.write_early && downstream->accesslog_ready()) { + handler->write_accesslog(downstream); + downstream->set_accesslog_written(true); + } + + if (upstream->on_downstream_header_complete(downstream) != 0) { + return -1; + } + + if (downstream->get_upgraded()) { + // Upgrade complete, read until EOF in both ends + if (upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0) != 0) { + return -1; + } + downstream->set_request_state(DownstreamState::HEADER_COMPLETE); + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "HTTP upgrade success. stream_id=" + << downstream->get_stream_id(); + } + } + + // Ignore the response body. HEAD response may contain + // Content-Length or Transfer-Encoding: chunked. Some server send + // 304 status code with nonzero Content-Length, but without response + // body. See + // https://tools.ietf.org/html/rfc7230#section-3.3 + + // TODO It seems that the cases other than HEAD are handled by + // llhttp. Need test. + return !http2::expect_response_body(req.method, resp.http_status); +} +} // namespace + +namespace { +int ensure_header_field_buffer(const Downstream *downstream, + const HttpConfig &httpconf, size_t len) { + auto &resp = downstream->response(); + + if (resp.fs.buffer_size() + len > httpconf.response_header_field_buffer) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "Too large header header field size=" + << resp.fs.buffer_size() + len; + } + return -1; + } + + return 0; +} +} // namespace + +namespace { +int ensure_max_header_fields(const Downstream *downstream, + const HttpConfig &httpconf) { + auto &resp = downstream->response(); + + if (resp.fs.num_fields() >= httpconf.max_response_header_fields) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) + << "Too many header field num=" << resp.fs.num_fields() + 1; + } + return -1; + } + + return 0; +} +} // namespace + +namespace { +int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) { + auto downstream = static_cast(htp->data); + auto &resp = downstream->response(); + auto &httpconf = get_config()->http; + + if (ensure_header_field_buffer(downstream, httpconf, len) != 0) { + return -1; + } + + if (downstream->get_response_state() == DownstreamState::INITIAL) { + if (resp.fs.header_key_prev()) { + resp.fs.append_last_header_key(data, len); + } else { + if (ensure_max_header_fields(downstream, httpconf) != 0) { + return -1; + } + resp.fs.alloc_add_header_name(StringRef{data, len}); + } + } else { + // trailer part + if (resp.fs.trailer_key_prev()) { + resp.fs.append_last_trailer_key(data, len); + } else { + if (ensure_max_header_fields(downstream, httpconf) != 0) { + // Could not ignore this trailer field easily, since we may + // get its value in htp_hdr_valcb, and it will be added to + // wrong place or crash if trailer fields are currently empty. + return -1; + } + resp.fs.alloc_add_trailer_name(StringRef{data, len}); + } + } + return 0; +} +} // namespace + +namespace { +int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) { + auto downstream = static_cast(htp->data); + auto &resp = downstream->response(); + auto &httpconf = get_config()->http; + + if (ensure_header_field_buffer(downstream, httpconf, len) != 0) { + return -1; + } + + if (downstream->get_response_state() == DownstreamState::INITIAL) { + resp.fs.append_last_header_value(data, len); + } else { + resp.fs.append_last_trailer_value(data, len); + } + return 0; +} +} // namespace + +namespace { +int htp_bodycb(llhttp_t *htp, const char *data, size_t len) { + auto downstream = static_cast(htp->data); + auto &resp = downstream->response(); + + resp.recv_body_length += len; + + return downstream->get_upstream()->on_downstream_body( + downstream, reinterpret_cast(data), len, true); +} +} // namespace + +namespace { +int htp_msg_completecb(llhttp_t *htp) { + auto downstream = static_cast(htp->data); + auto &resp = downstream->response(); + auto &balloc = downstream->get_block_allocator(); + + for (auto &kv : resp.fs.trailers()) { + kv.value = util::rstrip(balloc, kv.value); + } + + // llhttp does not treat "200 connection established" response + // against CONNECT request, and in that case, this function is not + // called. But if HTTP Upgrade is made (e.g., WebSocket), this + // function is called, and llhttp_execute() returns just after that. + if (downstream->get_upgraded()) { + return 0; + } + + if (downstream->get_non_final_response()) { + downstream->reset_response(); + + return 0; + } + + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + // Block reading another response message from (broken?) + // server. This callback is not called if the connection is + // tunneled. + downstream->pause_read(SHRPX_MSG_BLOCK); + return downstream->get_upstream()->on_downstream_body_complete(downstream); +} +} // namespace + +int HttpDownstreamConnection::write_first() { + int rv; + + process_blocked_request_buf(); + + if (conn_.tls.ssl) { + rv = write_tls(); + } else { + rv = write_clear(); + } + + if (rv != 0) { + return SHRPX_ERR_RETRY; + } + + if (conn_.tls.ssl) { + on_write_ = &HttpDownstreamConnection::write_tls; + } else { + on_write_ = &HttpDownstreamConnection::write_clear; + } + + first_write_done_ = true; + downstream_->set_request_header_sent(true); + + auto buf = downstream_->get_blocked_request_buf(); + buf->reset(); + + // upstream->resume_read() might be called in + // write_tls()/write_clear(), but before blocked_request_buf_ is + // reset. So upstream read might still be blocked. Let's do it + // again here. + auto input = downstream_->get_request_buf(); + if (input->rleft() == 0) { + auto upstream = downstream_->get_upstream(); + auto &req = downstream_->request(); + + upstream->resume_read(SHRPX_NO_BUFFER, downstream_, + req.unconsumed_body_length); + } + + return 0; +} + +int HttpDownstreamConnection::read_clear() { + conn_.last_read = std::chrono::steady_clock::now(); + + std::array buf; + int rv; + + for (;;) { + auto nread = conn_.read_clear(buf.data(), buf.size()); + if (nread == 0) { + return 0; + } + + if (nread < 0) { + if (nread == SHRPX_ERR_EOF && !downstream_->get_upgraded()) { + auto htperr = llhttp_finish(&response_htp_); + if (htperr != HPE_OK) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "HTTP response ended prematurely: " + << llhttp_errno_name(htperr); + } + + return -1; + } + } + + return nread; + } + + rv = process_input(buf.data(), nread); + if (rv != 0) { + return rv; + } + + if (!ev_is_active(&conn_.rev)) { + return 0; + } + } +} + +int HttpDownstreamConnection::write_clear() { + conn_.last_read = std::chrono::steady_clock::now(); + + auto upstream = downstream_->get_upstream(); + auto input = downstream_->get_request_buf(); + + std::array iov; + + while (input->rleft() > 0) { + auto iovcnt = input->riovec(iov.data(), iov.size()); + + auto nwrite = conn_.writev_clear(iov.data(), iovcnt); + + if (nwrite == 0) { + return 0; + } + + if (nwrite < 0) { + if (!first_write_done_) { + return nwrite; + } + // We may have pending data in receive buffer which may contain + // part of response body. So keep reading. Invoke read event + // to get read(2) error just in case. + ev_feed_event(conn_.loop, &conn_.rev, EV_READ); + on_write_ = &HttpDownstreamConnection::noop; + reusable_ = false; + break; + } + + input->drain(nwrite); + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + if (input->rleft() == 0) { + auto &req = downstream_->request(); + + upstream->resume_read(SHRPX_NO_BUFFER, downstream_, + req.unconsumed_body_length); + } + + return 0; +} + +int HttpDownstreamConnection::tls_handshake() { + ERR_clear_error(); + + conn_.last_read = std::chrono::steady_clock::now(); + + auto rv = conn_.tls_handshake(); + if (rv == SHRPX_ERR_INPROGRESS) { + return 0; + } + + if (rv < 0) { + downstream_failure(addr_, raddr_); + + return rv; + } + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "SSL/TLS handshake completed"; + } + + if (!get_config()->tls.insecure && + tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) { + downstream_failure(addr_, raddr_); + + return -1; + } + + auto &connect_blocker = addr_->connect_blocker; + + signal_write_ = &HttpDownstreamConnection::actual_signal_write; + + connect_blocker->on_success(); + + ev_set_cb(&conn_.rt, timeoutcb); + ev_set_cb(&conn_.wt, timeoutcb); + + on_read_ = &HttpDownstreamConnection::read_tls; + on_write_ = &HttpDownstreamConnection::write_first; + + // TODO Check negotiated ALPN + + return on_write(); +} + +int HttpDownstreamConnection::read_tls() { + conn_.last_read = std::chrono::steady_clock::now(); + + ERR_clear_error(); + + std::array buf; + int rv; + + for (;;) { + auto nread = conn_.read_tls(buf.data(), buf.size()); + if (nread == 0) { + return 0; + } + + if (nread < 0) { + if (nread == SHRPX_ERR_EOF && !downstream_->get_upgraded()) { + auto htperr = llhttp_finish(&response_htp_); + if (htperr != HPE_OK) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "HTTP response ended prematurely: " + << llhttp_errno_name(htperr); + } + + return -1; + } + } + + return nread; + } + + rv = process_input(buf.data(), nread); + if (rv != 0) { + return rv; + } + + if (!ev_is_active(&conn_.rev)) { + return 0; + } + } +} + +int HttpDownstreamConnection::write_tls() { + conn_.last_read = std::chrono::steady_clock::now(); + + ERR_clear_error(); + + auto upstream = downstream_->get_upstream(); + auto input = downstream_->get_request_buf(); + + struct iovec iov; + + while (input->rleft() > 0) { + auto iovcnt = input->riovec(&iov, 1); + if (iovcnt != 1) { + assert(0); + return -1; + } + auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len); + + if (nwrite == 0) { + return 0; + } + + if (nwrite < 0) { + if (!first_write_done_) { + return nwrite; + } + // We may have pending data in receive buffer which may contain + // part of response body. So keep reading. Invoke read event + // to get read(2) error just in case. + ev_feed_event(conn_.loop, &conn_.rev, EV_READ); + on_write_ = &HttpDownstreamConnection::noop; + reusable_ = false; + break; + } + + input->drain(nwrite); + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + if (input->rleft() == 0) { + auto &req = downstream_->request(); + + upstream->resume_read(SHRPX_NO_BUFFER, downstream_, + req.unconsumed_body_length); + } + + return 0; +} + +int HttpDownstreamConnection::process_input(const uint8_t *data, + size_t datalen) { + int rv; + + if (downstream_->get_upgraded()) { + // For upgraded connection, just pass data to the upstream. + rv = downstream_->get_upstream()->on_downstream_body(downstream_, data, + datalen, true); + if (rv != 0) { + return rv; + } + + if (downstream_->response_buf_full()) { + downstream_->pause_read(SHRPX_NO_BUFFER); + return 0; + } + + return 0; + } + + auto htperr = llhttp_execute(&response_htp_, + reinterpret_cast(data), datalen); + auto nproc = + htperr == HPE_OK + ? datalen + : static_cast(reinterpret_cast( + llhttp_get_error_pos(&response_htp_)) - + data); + + if (htperr != HPE_OK && + (!downstream_->get_upgraded() || htperr != HPE_PAUSED_UPGRADE)) { + // Handling early return (in other words, response was hijacked by + // mruby scripting). + if (downstream_->get_response_state() == DownstreamState::MSG_COMPLETE) { + return SHRPX_ERR_DCONN_CANCELED; + } + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "HTTP parser failure: " + << "(" << llhttp_errno_name(htperr) << ") " + << llhttp_get_error_reason(&response_htp_); + } + + return -1; + } + + if (downstream_->get_upgraded()) { + if (nproc < datalen) { + // Data from data + nproc are for upgraded protocol. + rv = downstream_->get_upstream()->on_downstream_body( + downstream_, data + nproc, datalen - nproc, true); + if (rv != 0) { + return rv; + } + + if (downstream_->response_buf_full()) { + downstream_->pause_read(SHRPX_NO_BUFFER); + return 0; + } + } + return 0; + } + + if (downstream_->response_buf_full()) { + downstream_->pause_read(SHRPX_NO_BUFFER); + return 0; + } + + return 0; +} + +int HttpDownstreamConnection::connected() { + auto &connect_blocker = addr_->connect_blocker; + + auto sock_error = util::get_socket_error(conn_.fd); + if (sock_error != 0) { + conn_.wlimit.stopw(); + + DCLOG(WARN, this) << "Backend connect failed; addr=" + << util::to_numeric_addr(raddr_) + << ": errno=" << sock_error; + + downstream_failure(addr_, raddr_); + + return -1; + } + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Connected to downstream host"; + } + + // Reset timeout for write. Previously, we set timeout for connect. + conn_.wt.repeat = group_->shared_addr->timeout.write; + ev_timer_again(conn_.loop, &conn_.wt); + + conn_.rlimit.startw(); + conn_.again_rt(); + + ev_set_cb(&conn_.wev, writecb); + + if (conn_.tls.ssl) { + on_read_ = &HttpDownstreamConnection::tls_handshake; + on_write_ = &HttpDownstreamConnection::tls_handshake; + + return 0; + } + + signal_write_ = &HttpDownstreamConnection::actual_signal_write; + + connect_blocker->on_success(); + + ev_set_cb(&conn_.rt, timeoutcb); + ev_set_cb(&conn_.wt, timeoutcb); + + on_read_ = &HttpDownstreamConnection::read_clear; + on_write_ = &HttpDownstreamConnection::write_first; + + return 0; +} + +int HttpDownstreamConnection::on_read() { return on_read_(*this); } + +int HttpDownstreamConnection::on_write() { return on_write_(*this); } + +void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {} + +void HttpDownstreamConnection::signal_write() { signal_write_(*this); } + +int HttpDownstreamConnection::actual_signal_write() { + ev_feed_event(conn_.loop, &conn_.wev, EV_WRITE); + return 0; +} + +int HttpDownstreamConnection::noop() { return 0; } + +const std::shared_ptr & +HttpDownstreamConnection::get_downstream_addr_group() const { + return group_; +} + +DownstreamAddr *HttpDownstreamConnection::get_addr() const { return addr_; } + +bool HttpDownstreamConnection::poolable() const { + return !group_->retired && reusable_; +} + +const Address *HttpDownstreamConnection::get_raddr() const { return raddr_; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_http_downstream_connection.h b/lib/nghttp2/src/shrpx_http_downstream_connection.h new file mode 100644 index 00000000000..a453f0dc5cf --- /dev/null +++ b/lib/nghttp2/src/shrpx_http_downstream_connection.h @@ -0,0 +1,124 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTP_DOWNSTREAM_CONNECTION_H +#define SHRPX_HTTP_DOWNSTREAM_CONNECTION_H + +#include "shrpx.h" + +#include "llhttp.h" + +#include "shrpx_downstream_connection.h" +#include "shrpx_io_control.h" +#include "shrpx_connection.h" + +namespace shrpx { + +class DownstreamConnectionPool; +class Worker; +struct DownstreamAddrGroup; +struct DownstreamAddr; +struct DNSQuery; + +class HttpDownstreamConnection : public DownstreamConnection { +public: + HttpDownstreamConnection(const std::shared_ptr &group, + DownstreamAddr *addr, struct ev_loop *loop, + Worker *worker); + virtual ~HttpDownstreamConnection(); + virtual int attach_downstream(Downstream *downstream); + virtual void detach_downstream(Downstream *downstream); + + virtual int push_request_headers(); + virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen); + virtual int end_upload_data(); + void end_upload_data_chunk(); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, size_t consumed); + virtual void force_resume_read(); + + virtual int on_read(); + virtual int on_write(); + + virtual void on_upstream_change(Upstream *upstream); + + virtual bool poolable() const; + + virtual const std::shared_ptr & + get_downstream_addr_group() const; + virtual DownstreamAddr *get_addr() const; + + int initiate_connection(); + + int write_first(); + int read_clear(); + int write_clear(); + int read_tls(); + int write_tls(); + + int process_input(const uint8_t *data, size_t datalen); + int tls_handshake(); + + int connected(); + void signal_write(); + int actual_signal_write(); + + // Returns address used to connect to backend. Could be nullptr. + const Address *get_raddr() const; + + int noop(); + + int process_blocked_request_buf(); + +private: + Connection conn_; + std::function on_read_, on_write_, + signal_write_; + Worker *worker_; + // nullptr if TLS is not used. + SSL_CTX *ssl_ctx_; + std::shared_ptr group_; + // Address of remote endpoint + DownstreamAddr *addr_; + // Actual remote address used to contact backend. This is initially + // nullptr, and may point to either &addr_->addr, or + // resolved_addr_.get(). + const Address *raddr_; + // Resolved IP address if dns parameter is used + std::unique_ptr
resolved_addr_; + std::unique_ptr dns_query_; + IOControl ioctrl_; + llhttp_t response_htp_; + // true if first write succeeded. + bool first_write_done_; + // true if this object can be reused + bool reusable_; + // true if request header is written to request buffer. + bool request_header_written_; +}; + +} // namespace shrpx + +#endif // SHRPX_HTTP_DOWNSTREAM_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_http_test.cc b/lib/nghttp2/src/shrpx_http_test.cc new file mode 100644 index 00000000000..3ace8702b7b --- /dev/null +++ b/lib/nghttp2/src/shrpx_http_test.cc @@ -0,0 +1,168 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_http_test.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H + +#include + +#include + +#include "shrpx_http.h" +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +void test_shrpx_http_create_forwarded(void) { + BlockAllocator balloc(1024, 1024); + + CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";" + "proto=https" == + http::create_forwarded(balloc, + FORWARDED_BY | FORWARDED_FOR | + FORWARDED_HOST | FORWARDED_PROTO, + StringRef::from_lit("example.com:3000"), + StringRef::from_lit("[::1]"), + StringRef::from_lit("www.example.com"), + StringRef::from_lit("https"))); + + CU_ASSERT("for=192.168.0.1" == + http::create_forwarded( + balloc, FORWARDED_FOR, StringRef::from_lit("alpha"), + StringRef::from_lit("192.168.0.1"), + StringRef::from_lit("bravo"), StringRef::from_lit("charlie"))); + + CU_ASSERT("by=_hidden;for=\"[::1]\"" == + http::create_forwarded( + balloc, FORWARDED_BY | FORWARDED_FOR, + StringRef::from_lit("_hidden"), StringRef::from_lit("[::1]"), + StringRef::from_lit(""), StringRef::from_lit(""))); + + CU_ASSERT("by=\"[::1]\";for=_hidden" == + http::create_forwarded( + balloc, FORWARDED_BY | FORWARDED_FOR, + StringRef::from_lit("[::1]"), StringRef::from_lit("_hidden"), + StringRef::from_lit(""), StringRef::from_lit(""))); + + CU_ASSERT("" == + http::create_forwarded( + balloc, + FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST | FORWARDED_PROTO, + StringRef::from_lit(""), StringRef::from_lit(""), + StringRef::from_lit(""), StringRef::from_lit(""))); +} + +void test_shrpx_http_create_via_header_value(void) { + std::array buf; + + auto end = http::create_via_header_value(std::begin(buf), 1, 1); + + CU_ASSERT(("1.1 nghttpx" == StringRef{std::begin(buf), end})); + + std::fill(std::begin(buf), std::end(buf), '\0'); + + end = http::create_via_header_value(std::begin(buf), 2, 0); + + CU_ASSERT(("2 nghttpx" == StringRef{std::begin(buf), end})); +} + +void test_shrpx_http_create_affinity_cookie(void) { + BlockAllocator balloc(1024, 1024); + StringRef c; + + c = http::create_affinity_cookie(balloc, StringRef::from_lit("cookie-val"), + 0xf1e2d3c4u, StringRef{}, false); + + CU_ASSERT("cookie-val=f1e2d3c4" == c); + + c = http::create_affinity_cookie(balloc, StringRef::from_lit("alpha"), + 0x00000000u, StringRef{}, true); + + CU_ASSERT("alpha=00000000; Secure" == c); + + c = http::create_affinity_cookie(balloc, StringRef::from_lit("bravo"), + 0x01111111u, StringRef::from_lit("bar"), + false); + + CU_ASSERT("bravo=01111111; Path=bar" == c); + + c = http::create_affinity_cookie(balloc, StringRef::from_lit("charlie"), + 0x01111111u, StringRef::from_lit("bar"), + true); + + CU_ASSERT("charlie=01111111; Path=bar; Secure" == c); +} + +void test_shrpx_http_create_altsvc_header_value(void) { + { + BlockAllocator balloc(1024, 1024); + std::vector altsvcs{ + AltSvc{ + .protocol_id = StringRef::from_lit("h3"), + .host = StringRef::from_lit("127.0.0.1"), + .service = StringRef::from_lit("443"), + .params = StringRef::from_lit("ma=3600"), + }, + }; + + CU_ASSERT(R"(h3="127.0.0.1:443"; ma=3600)" == + http::create_altsvc_header_value(balloc, altsvcs)); + } + + { + BlockAllocator balloc(1024, 1024); + std::vector altsvcs{ + AltSvc{ + .protocol_id = StringRef::from_lit("h3"), + .service = StringRef::from_lit("443"), + .params = StringRef::from_lit("ma=3600"), + }, + AltSvc{ + .protocol_id = StringRef::from_lit("h3%"), + .host = StringRef::from_lit("\"foo\""), + .service = StringRef::from_lit("4433"), + }, + }; + + CU_ASSERT(R"(h3=":443"; ma=3600, h3%25="\"foo\":4433")" == + http::create_altsvc_header_value(balloc, altsvcs)); + } +} + +void test_shrpx_http_check_http_scheme(void) { + CU_ASSERT(http::check_http_scheme(StringRef::from_lit("https"), true)); + CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("https"), false)); + CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("http"), true)); + CU_ASSERT(http::check_http_scheme(StringRef::from_lit("http"), false)); + CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("foo"), true)); + CU_ASSERT(!http::check_http_scheme(StringRef::from_lit("foo"), false)); + CU_ASSERT(!http::check_http_scheme(StringRef{}, true)); + CU_ASSERT(!http::check_http_scheme(StringRef{}, false)); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_http_test.h b/lib/nghttp2/src/shrpx_http_test.h new file mode 100644 index 00000000000..d50ab533971 --- /dev/null +++ b/lib/nghttp2/src/shrpx_http_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTP_TEST_H +#define SHRPX_HTTP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_http_create_forwarded(void); +void test_shrpx_http_create_via_header_value(void); +void test_shrpx_http_create_affinity_cookie(void); +void test_shrpx_http_create_altsvc_header_value(void); +void test_shrpx_http_check_http_scheme(void); + +} // namespace shrpx + +#endif // SHRPX_HTTP_TEST_H diff --git a/lib/nghttp2/src/shrpx_https_upstream.cc b/lib/nghttp2/src/shrpx_https_upstream.cc new file mode 100644 index 00000000000..49d20883430 --- /dev/null +++ b/lib/nghttp2/src/shrpx_https_upstream.cc @@ -0,0 +1,1582 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_https_upstream.h" + +#include +#include +#include + +#include "shrpx_client_handler.h" +#include "shrpx_downstream.h" +#include "shrpx_downstream_connection.h" +#include "shrpx_http.h" +#include "shrpx_config.h" +#include "shrpx_error.h" +#include "shrpx_log_config.h" +#include "shrpx_worker.h" +#include "shrpx_http2_session.h" +#include "shrpx_log.h" +#ifdef HAVE_MRUBY +# include "shrpx_mruby.h" +#endif // HAVE_MRUBY +#include "http2.h" +#include "util.h" +#include "template.h" +#include "base64.h" +#include "url-parser/url_parser.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +int htp_msg_begin(llhttp_t *htp); +int htp_uricb(llhttp_t *htp, const char *data, size_t len); +int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len); +int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len); +int htp_hdrs_completecb(llhttp_t *htp); +int htp_bodycb(llhttp_t *htp, const char *data, size_t len); +int htp_msg_completecb(llhttp_t *htp); +} // namespace + +namespace { +constexpr llhttp_settings_t htp_hooks = { + htp_msg_begin, // llhttp_cb on_message_begin; + htp_uricb, // llhttp_data_cb on_url; + nullptr, // llhttp_data_cb on_status; + nullptr, // llhttp_data_cb on_method; + nullptr, // llhttp_data_cb on_version; + htp_hdr_keycb, // llhttp_data_cb on_header_field; + htp_hdr_valcb, // llhttp_data_cb on_header_value; + nullptr, // llhttp_data_cb on_chunk_extension_name; + nullptr, // llhttp_data_cb on_chunk_extension_value; + htp_hdrs_completecb, // llhttp_cb on_headers_complete; + htp_bodycb, // llhttp_data_cb on_body; + htp_msg_completecb, // llhttp_cb on_message_complete; + nullptr, // llhttp_cb on_url_complete; + nullptr, // llhttp_cb on_status_complete; + nullptr, // llhttp_cb on_method_complete; + nullptr, // llhttp_cb on_version_complete; + nullptr, // llhttp_cb on_header_field_complete; + nullptr, // llhttp_cb on_header_value_complete; + nullptr, // llhttp_cb on_chunk_extension_name_complete; + nullptr, // llhttp_cb on_chunk_extension_value_complete; + nullptr, // llhttp_cb on_chunk_header; + nullptr, // llhttp_cb on_chunk_complete; + nullptr, // llhttp_cb on_reset; +}; +} // namespace + +HttpsUpstream::HttpsUpstream(ClientHandler *handler) + : handler_(handler), + current_header_length_(0), + ioctrl_(handler->get_rlimit()), + num_requests_(0) { + llhttp_init(&htp_, HTTP_REQUEST, &htp_hooks); + htp_.data = this; +} + +HttpsUpstream::~HttpsUpstream() {} + +void HttpsUpstream::reset_current_header_length() { + current_header_length_ = 0; +} + +void HttpsUpstream::on_start_request() { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "HTTP request started"; + } + reset_current_header_length(); + + auto downstream = + std::make_unique(this, handler_->get_mcpool(), 0); + + attach_downstream(std::move(downstream)); + + auto conn = handler_->get_connection(); + auto &upstreamconf = get_config()->conn.upstream; + + conn->rt.repeat = upstreamconf.timeout.read; + + handler_->repeat_read_timer(); + + ++num_requests_; +} + +namespace { +int htp_msg_begin(llhttp_t *htp) { + auto upstream = static_cast(htp->data); + upstream->on_start_request(); + return 0; +} +} // namespace + +namespace { +int htp_uricb(llhttp_t *htp, const char *data, size_t len) { + auto upstream = static_cast(htp->data); + auto downstream = upstream->get_downstream(); + auto &req = downstream->request(); + + auto &balloc = downstream->get_block_allocator(); + + // We happen to have the same value for method token. + req.method = htp->method; + + if (req.fs.buffer_size() + len > + get_config()->http.request_header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large URI size=" + << req.fs.buffer_size() + len; + } + assert(downstream->get_request_state() == DownstreamState::INITIAL); + downstream->set_request_state( + DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE); + llhttp_set_error_reason(htp, "too long request URI"); + return HPE_USER; + } + + req.fs.add_extra_buffer_size(len); + + if (req.method == HTTP_CONNECT) { + req.authority = + concat_string_ref(balloc, req.authority, StringRef{data, len}); + } else { + req.path = concat_string_ref(balloc, req.path, StringRef{data, len}); + } + + return 0; +} +} // namespace + +namespace { +int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) { + auto upstream = static_cast(htp->data); + auto downstream = upstream->get_downstream(); + auto &req = downstream->request(); + auto &httpconf = get_config()->http; + + if (req.fs.buffer_size() + len > httpconf.request_header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large header block size=" + << req.fs.buffer_size() + len; + } + if (downstream->get_request_state() == DownstreamState::INITIAL) { + downstream->set_request_state( + DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE); + } + llhttp_set_error_reason(htp, "too large header"); + return HPE_USER; + } + if (downstream->get_request_state() == DownstreamState::INITIAL) { + if (req.fs.header_key_prev()) { + req.fs.append_last_header_key(data, len); + } else { + if (req.fs.num_fields() >= httpconf.max_request_header_fields) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) + << "Too many header field num=" << req.fs.num_fields() + 1; + } + downstream->set_request_state( + DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE); + llhttp_set_error_reason(htp, "too many headers"); + return HPE_USER; + } + req.fs.alloc_add_header_name(StringRef{data, len}); + } + } else { + // trailer part + if (req.fs.trailer_key_prev()) { + req.fs.append_last_trailer_key(data, len); + } else { + if (req.fs.num_fields() >= httpconf.max_request_header_fields) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) + << "Too many header field num=" << req.fs.num_fields() + 1; + } + llhttp_set_error_reason(htp, "too many headers"); + return HPE_USER; + } + req.fs.alloc_add_trailer_name(StringRef{data, len}); + } + } + return 0; +} +} // namespace + +namespace { +int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) { + auto upstream = static_cast(htp->data); + auto downstream = upstream->get_downstream(); + auto &req = downstream->request(); + + if (req.fs.buffer_size() + len > + get_config()->http.request_header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large header block size=" + << req.fs.buffer_size() + len; + } + if (downstream->get_request_state() == DownstreamState::INITIAL) { + downstream->set_request_state( + DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE); + } + llhttp_set_error_reason(htp, "too large header"); + return HPE_USER; + } + if (downstream->get_request_state() == DownstreamState::INITIAL) { + req.fs.append_last_header_value(data, len); + } else { + req.fs.append_last_trailer_value(data, len); + } + return 0; +} +} // namespace + +namespace { +void rewrite_request_host_path_from_uri(BlockAllocator &balloc, Request &req, + const StringRef &uri, + http_parser_url &u) { + assert(u.field_set & (1 << UF_HOST)); + + // As per https://tools.ietf.org/html/rfc7230#section-5.4, we + // rewrite host header field with authority component. + auto authority = util::get_uri_field(uri.c_str(), u, UF_HOST); + // TODO properly check IPv6 numeric address + auto ipv6 = std::find(std::begin(authority), std::end(authority), ':') != + std::end(authority); + auto authoritylen = authority.size(); + if (ipv6) { + authoritylen += 2; + } + if (u.field_set & (1 << UF_PORT)) { + authoritylen += 1 + str_size("65535"); + } + if (authoritylen > authority.size()) { + auto iovec = make_byte_ref(balloc, authoritylen + 1); + auto p = iovec.base; + if (ipv6) { + *p++ = '['; + } + p = std::copy(std::begin(authority), std::end(authority), p); + if (ipv6) { + *p++ = ']'; + } + + if (u.field_set & (1 << UF_PORT)) { + *p++ = ':'; + p = util::utos(p, u.port); + } + *p = '\0'; + + req.authority = StringRef{iovec.base, p}; + } else { + req.authority = authority; + } + + req.scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA); + + StringRef path; + if (u.field_set & (1 << UF_PATH)) { + path = util::get_uri_field(uri.c_str(), u, UF_PATH); + } else if (req.method == HTTP_OPTIONS) { + // Server-wide OPTIONS takes following form in proxy request: + // + // OPTIONS http://example.org HTTP/1.1 + // + // Notice that no slash after authority. See + // http://tools.ietf.org/html/rfc7230#section-5.3.4 + req.path = StringRef::from_lit(""); + // we ignore query component here + return; + } else { + path = StringRef::from_lit("/"); + } + + if (u.field_set & (1 << UF_QUERY)) { + auto &fdata = u.field_data[UF_QUERY]; + + if (u.field_set & (1 << UF_PATH)) { + auto q = util::get_uri_field(uri.c_str(), u, UF_QUERY); + path = StringRef{std::begin(path), std::end(q)}; + } else { + path = concat_string_ref(balloc, path, StringRef::from_lit("?"), + StringRef{&uri[fdata.off], fdata.len}); + } + } + + req.path = http2::rewrite_clean_path(balloc, path); +} +} // namespace + +namespace { +int htp_hdrs_completecb(llhttp_t *htp) { + int rv; + auto upstream = static_cast(htp->data); + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "HTTP request headers completed"; + } + + auto handler = upstream->get_client_handler(); + + auto downstream = upstream->get_downstream(); + auto &req = downstream->request(); + auto &balloc = downstream->get_block_allocator(); + + for (auto &kv : req.fs.headers()) { + kv.value = util::rstrip(balloc, kv.value); + + if (kv.token == http2::HD_TRANSFER_ENCODING && + !http2::check_transfer_encoding(kv.value)) { + return -1; + } + } + + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + req.tstamp = lgconf->tstamp; + + req.http_major = htp->http_major; + req.http_minor = htp->http_minor; + + req.connection_close = !llhttp_should_keep_alive(htp); + + handler->stop_read_timer(); + + auto method = req.method; + + if (LOG_ENABLED(INFO)) { + std::stringstream ss; + ss << http2::to_method_string(method) << " " + << (method == HTTP_CONNECT ? req.authority : req.path) << " " + << "HTTP/" << req.http_major << "." << req.http_minor << "\n"; + + for (const auto &kv : req.fs.headers()) { + if (kv.name == "authorization") { + ss << TTY_HTTP_HD << kv.name << TTY_RST << ": \n"; + continue; + } + ss << TTY_HTTP_HD << kv.name << TTY_RST << ": " << kv.value << "\n"; + } + + ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str(); + } + + // set content-length if method is not CONNECT, and no + // transfer-encoding is given. If transfer-encoding is given, leave + // req.fs.content_length to -1. + if (method != HTTP_CONNECT && !req.fs.header(http2::HD_TRANSFER_ENCODING)) { + // llhttp sets 0 to htp->content_length if there is no + // content-length header field. If we don't have both + // transfer-encoding and content-length header field, we assume + // that there is no request body. + req.fs.content_length = htp->content_length; + } + + auto host = req.fs.header(http2::HD_HOST); + + if (req.http_major > 1 || req.http_minor > 1) { + req.http_major = 1; + req.http_minor = 1; + return -1; + } + + if (req.http_major == 1 && req.http_minor == 1 && !host) { + return -1; + } + + if (host) { + const auto &value = host->value; + // Not allow at least '"' or '\' in host. They are illegal in + // authority component, also they cause headaches when we put them + // in quoted-string. + if (std::find_if(std::begin(value), std::end(value), [](char c) { + return c == '"' || c == '\\'; + }) != std::end(value)) { + return -1; + } + } + + downstream->inspect_http1_request(); + + if (htp->flags & F_CHUNKED) { + downstream->set_chunked_request(true); + } + + auto transfer_encoding = req.fs.header(http2::HD_TRANSFER_ENCODING); + if (transfer_encoding && + http2::legacy_http1(req.http_major, req.http_minor)) { + return -1; + } + + auto faddr = handler->get_upstream_addr(); + auto config = get_config(); + + if (method != HTTP_CONNECT) { + http_parser_url u{}; + rv = http_parser_parse_url(req.path.c_str(), req.path.size(), 0, &u); + if (rv != 0) { + // Expect to respond with 400 bad request + return -1; + } + // checking UF_HOST could be redundant, but just in case ... + if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) { + req.no_authority = true; + + if (method == HTTP_OPTIONS && req.path == StringRef::from_lit("*")) { + req.path = StringRef{}; + } else { + req.path = http2::rewrite_clean_path(balloc, req.path); + } + + if (host) { + req.authority = host->value; + } + + if (handler->get_ssl()) { + req.scheme = StringRef::from_lit("https"); + } else { + req.scheme = StringRef::from_lit("http"); + } + } else { + rewrite_request_host_path_from_uri(balloc, req, req.path, u); + } + } + + downstream->set_request_state(DownstreamState::HEADER_COMPLETE); + + auto &resp = downstream->response(); + + if (config->http.require_http_scheme && + !http::check_http_scheme(req.scheme, handler->get_ssl() != nullptr)) { + resp.http_status = 400; + return -1; + } + +#ifdef HAVE_MRUBY + auto worker = handler->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_request_proc(downstream) != 0) { + resp.http_status = 500; + return -1; + } +#endif // HAVE_MRUBY + + // mruby hook may change method value + + if (req.no_authority && config->http2_proxy && + faddr->alt_mode == UpstreamAltMode::NONE) { + // Request URI should be absolute-form for client proxy mode + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + +#ifdef HAVE_MRUBY + DownstreamConnection *dconn_ptr; +#endif // HAVE_MRUBY + + for (;;) { + auto dconn = handler->get_downstream_connection(rv, downstream); + + if (!dconn) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + upstream->redirect_to_https(downstream); + } + downstream->set_request_state(DownstreamState::CONNECT_FAIL); + return -1; + } + +#ifdef HAVE_MRUBY + dconn_ptr = dconn.get(); +#endif // HAVE_MRUBY + if (downstream->attach_downstream_connection(std::move(dconn)) == 0) { + break; + } + } + +#ifdef HAVE_MRUBY + const auto &group = dconn_ptr->get_downstream_addr_group(); + if (group) { + const auto &dmruby_ctx = group->shared_addr->mruby_ctx; + + if (dmruby_ctx->run_on_request_proc(downstream) != 0) { + resp.http_status = 500; + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + } +#endif // HAVE_MRUBY + + rv = downstream->push_request_headers(); + + if (rv != 0) { + return -1; + } + + if (faddr->alt_mode != UpstreamAltMode::NONE) { + // Normally, we forward expect: 100-continue to backend server, + // and let them decide whether responds with 100 Continue or not. + // For alternative mode, we have no backend, so just send 100 + // Continue here to make the client happy. + if (downstream->get_expect_100_continue()) { + auto output = downstream->get_response_buf(); + constexpr auto res = StringRef::from_lit("HTTP/1.1 100 Continue\r\n\r\n"); + output->append(res); + handler->signal_write(); + } + } + + return 0; +} +} // namespace + +namespace { +int htp_bodycb(llhttp_t *htp, const char *data, size_t len) { + int rv; + auto upstream = static_cast(htp->data); + auto downstream = upstream->get_downstream(); + rv = downstream->push_upload_data_chunk( + reinterpret_cast(data), len); + if (rv != 0) { + // Ignore error if response has been completed. We will end up in + // htp_msg_completecb, and request will end gracefully. + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return 0; + } + + llhttp_set_error_reason(htp, "could not process request body"); + return HPE_USER; + } + return 0; +} +} // namespace + +namespace { +int htp_msg_completecb(llhttp_t *htp) { + int rv; + auto upstream = static_cast(htp->data); + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "HTTP request completed"; + } + auto handler = upstream->get_client_handler(); + auto downstream = upstream->get_downstream(); + auto &req = downstream->request(); + auto &balloc = downstream->get_block_allocator(); + + for (auto &kv : req.fs.trailers()) { + kv.value = util::rstrip(balloc, kv.value); + } + + downstream->set_request_state(DownstreamState::MSG_COMPLETE); + rv = downstream->end_upload_data(); + if (rv != 0) { + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + // Here both response and request were completed. One of the + // reason why end_upload_data() failed is when we sent response + // in request phase hook. We only delete and proceed to the + // next request handling (if we don't close the connection). We + // first pause parser here just as we normally do, and call + // signal_write() to run on_write(). + return HPE_PAUSED; + } + return -1; + } + + if (handler->get_http2_upgrade_allowed() && + downstream->get_http2_upgrade_request() && + handler->perform_http2_upgrade(upstream) != 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "HTTP Upgrade to HTTP/2 failed"; + } + } + + // Stop further processing to complete this request + return HPE_PAUSED; +} +} // namespace + +// on_read() does not consume all available data in input buffer if +// one http request is fully received. +int HttpsUpstream::on_read() { + auto rb = handler_->get_rb(); + auto rlimit = handler_->get_rlimit(); + auto downstream = get_downstream(); + + if (rb->rleft() == 0 || handler_->get_should_close_after_write()) { + return 0; + } + + // downstream can be nullptr here, because it is initialized in the + // callback chain called by llhttp_execute() + if (downstream && downstream->get_upgraded()) { + + auto rv = downstream->push_upload_data_chunk(rb->pos(), rb->rleft()); + + if (rv != 0) { + return -1; + } + + rb->reset(); + rlimit->startw(); + + if (downstream->request_buf_full()) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Downstream request buf is full"; + } + pause_read(SHRPX_NO_BUFFER); + + return 0; + } + + return 0; + } + + if (downstream) { + // To avoid reading next pipelined request + switch (downstream->get_request_state()) { + case DownstreamState::INITIAL: + case DownstreamState::HEADER_COMPLETE: + break; + default: + return 0; + } + } + + // llhttp_execute() does nothing once it entered error state. + auto htperr = llhttp_execute(&htp_, reinterpret_cast(rb->pos()), + rb->rleft()); + + if (htperr == HPE_PAUSED_UPGRADE && + rb->pos() == + reinterpret_cast(llhttp_get_error_pos(&htp_))) { + llhttp_resume_after_upgrade(&htp_); + + htperr = llhttp_execute(&htp_, reinterpret_cast(rb->pos()), + rb->rleft()); + } + + auto nread = + htperr == HPE_OK + ? rb->rleft() + : reinterpret_cast(llhttp_get_error_pos(&htp_)) - + rb->pos(); + rb->drain(nread); + rlimit->startw(); + + // Well, actually header length + some body bytes + current_header_length_ += nread; + + // Get downstream again because it may be initialized in http parser + // execution + downstream = get_downstream(); + + if (htperr == HPE_PAUSED) { + // We may pause parser in htp_msg_completecb when both side are + // completed. Signal write, so that we can run on_write(). + if (downstream && + downstream->get_request_state() == DownstreamState::MSG_COMPLETE && + downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + handler_->signal_write(); + } + return 0; + } + + if (htperr != HPE_OK) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "HTTP parse failure: " + << "(" << llhttp_errno_name(htperr) << ") " + << llhttp_get_error_reason(&htp_); + } + + if (downstream && + downstream->get_response_state() != DownstreamState::INITIAL) { + handler_->set_should_close_after_write(true); + handler_->signal_write(); + return 0; + } + + unsigned int status_code; + + if (htperr == HPE_INVALID_METHOD) { + status_code = 501; + } else if (downstream) { + status_code = downstream->response().http_status; + if (status_code == 0) { + if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) { + status_code = 502; + } else if (downstream->get_request_state() == + DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE) { + status_code = 431; + } else { + status_code = 400; + } + } + } else { + status_code = 400; + } + + error_reply(status_code); + + handler_->signal_write(); + + return 0; + } + + // downstream can be NULL here. + if (downstream && downstream->request_buf_full()) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "Downstream request buffer is full"; + } + + pause_read(SHRPX_NO_BUFFER); + + return 0; + } + + return 0; +} + +int HttpsUpstream::on_write() { + auto downstream = get_downstream(); + if (!downstream) { + return 0; + } + + auto output = downstream->get_response_buf(); + const auto &resp = downstream->response(); + + if (output->rleft() > 0) { + return 0; + } + + // We need to postpone detachment until all data are sent so that + // we can notify nghttp2 library all data consumed. + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + if (downstream->can_detach_downstream_connection()) { + // Keep-alive + downstream->detach_downstream_connection(); + } else { + // Connection close + downstream->pop_downstream_connection(); + // dconn was deleted + } + // We need this if response ends before request. + if (downstream->get_request_state() == DownstreamState::MSG_COMPLETE) { + delete_downstream(); + + if (handler_->get_should_close_after_write()) { + return 0; + } + + auto conn = handler_->get_connection(); + auto &upstreamconf = get_config()->conn.upstream; + + conn->rt.repeat = upstreamconf.timeout.idle_read; + + handler_->repeat_read_timer(); + + return resume_read(SHRPX_NO_BUFFER, nullptr, 0); + } else { + // If the request is not complete, close the connection. + delete_downstream(); + + handler_->set_should_close_after_write(true); + + return 0; + } + } + + return downstream->resume_read(SHRPX_NO_BUFFER, resp.unconsumed_body_length); +} + +int HttpsUpstream::on_event() { return 0; } + +ClientHandler *HttpsUpstream::get_client_handler() const { return handler_; } + +void HttpsUpstream::pause_read(IOCtrlReason reason) { + ioctrl_.pause_read(reason); +} + +int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed) { + // downstream could be nullptr + if (downstream && downstream->request_buf_full()) { + return 0; + } + if (ioctrl_.resume_read(reason)) { + // Process remaining data in input buffer here because these bytes + // are not notified by readcb until new data arrive. + llhttp_resume(&htp_); + + auto conn = handler_->get_connection(); + ev_feed_event(conn->loop, &conn->rev, EV_READ); + return 0; + } + + return 0; +} + +int HttpsUpstream::downstream_read(DownstreamConnection *dconn) { + auto downstream = dconn->get_downstream(); + int rv; + + rv = downstream->on_read(); + + if (rv == SHRPX_ERR_EOF) { + if (downstream->get_request_header_sent()) { + return downstream_eof(dconn); + } + return SHRPX_ERR_RETRY; + } + + if (rv == SHRPX_ERR_DCONN_CANCELED) { + downstream->pop_downstream_connection(); + goto end; + } + + if (rv < 0) { + return downstream_error(dconn, Downstream::EVENT_ERROR); + } + + if (downstream->get_response_state() == DownstreamState::MSG_RESET) { + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_BAD_HEADER) { + error_reply(502); + downstream->pop_downstream_connection(); + goto end; + } + + if (downstream->can_detach_downstream_connection()) { + // Keep-alive + downstream->detach_downstream_connection(); + } + +end: + handler_->signal_write(); + + return 0; +} + +int HttpsUpstream::downstream_write(DownstreamConnection *dconn) { + int rv; + rv = dconn->on_write(); + if (rv == SHRPX_ERR_NETWORK) { + return downstream_error(dconn, Downstream::EVENT_ERROR); + } + + if (rv != 0) { + return rv; + } + + return 0; +} + +int HttpsUpstream::downstream_eof(DownstreamConnection *dconn) { + auto downstream = dconn->get_downstream(); + + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "EOF"; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + goto end; + } + + if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) { + // Server may indicate the end of the request by EOF + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "The end of the response body was indicated by " + << "EOF"; + } + on_downstream_body_complete(downstream); + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + downstream->pop_downstream_connection(); + goto end; + } + + if (downstream->get_response_state() == DownstreamState::INITIAL) { + // we did not send any response headers, so we can reply error + // message. + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "Return error reply"; + } + error_reply(502); + downstream->pop_downstream_connection(); + goto end; + } + + // Otherwise, we don't know how to recover from this situation. Just + // drop connection. + return -1; +end: + handler_->signal_write(); + + return 0; +} + +int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) { + auto downstream = dconn->get_downstream(); + if (LOG_ENABLED(INFO)) { + if (events & Downstream::EVENT_ERROR) { + DCLOG(INFO, dconn) << "Network error/general error"; + } else { + DCLOG(INFO, dconn) << "Timeout"; + } + } + if (downstream->get_response_state() != DownstreamState::INITIAL) { + return -1; + } + + unsigned int status; + if (events & Downstream::EVENT_TIMEOUT) { + if (downstream->get_request_header_sent()) { + status = 504; + } else { + status = 408; + } + } else { + status = 502; + } + error_reply(status); + + downstream->pop_downstream_connection(); + + handler_->signal_write(); + return 0; +} + +int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen) { + const auto &req = downstream->request(); + auto &resp = downstream->response(); + auto &balloc = downstream->get_block_allocator(); + auto config = get_config(); + auto &httpconf = config->http; + + auto connection_close = false; + + auto worker = handler_->get_worker(); + + if (httpconf.max_requests <= num_requests_ || + worker->get_graceful_shutdown()) { + resp.fs.add_header_token(StringRef::from_lit("connection"), + StringRef::from_lit("close"), false, + http2::HD_CONNECTION); + connection_close = true; + } else if (req.http_major <= 0 || + (req.http_major == 1 && req.http_minor == 0)) { + connection_close = true; + } else { + auto c = resp.fs.header(http2::HD_CONNECTION); + if (c && util::strieq_l("close", c->value)) { + connection_close = true; + } + } + + if (connection_close) { + resp.connection_close = true; + handler_->set_should_close_after_write(true); + } + + auto output = downstream->get_response_buf(); + + output->append("HTTP/1.1 "); + output->append(http2::stringify_status(balloc, resp.http_status)); + output->append(' '); + output->append(http2::get_reason_phrase(resp.http_status)); + output->append("\r\n"); + + for (auto &kv : resp.fs.headers()) { + if (kv.name.empty() || kv.name[0] == ':') { + continue; + } + http2::capitalize(output, kv.name); + output->append(": "); + output->append(kv.value); + output->append("\r\n"); + } + + if (!resp.fs.header(http2::HD_SERVER)) { + output->append("Server: "); + output->append(config->http.server_name); + output->append("\r\n"); + } + + for (auto &p : httpconf.add_response_headers) { + output->append(p.name); + output->append(": "); + output->append(p.value); + output->append("\r\n"); + } + + output->append("\r\n"); + + output->append(body, bodylen); + + downstream->response_sent_body_length += bodylen; + downstream->set_response_state(DownstreamState::MSG_COMPLETE); + + return 0; +} + +void HttpsUpstream::error_reply(unsigned int status_code) { + auto downstream = get_downstream(); + + if (!downstream) { + attach_downstream( + std::make_unique(this, handler_->get_mcpool(), 1)); + downstream = get_downstream(); + } + + auto &resp = downstream->response(); + auto &balloc = downstream->get_block_allocator(); + + auto html = http::create_error_html(balloc, status_code); + + resp.http_status = status_code; + // we are going to close connection for both frontend and backend in + // error condition. This is safest option. + resp.connection_close = true; + handler_->set_should_close_after_write(true); + + auto output = downstream->get_response_buf(); + + output->append("HTTP/1.1 "); + output->append(http2::stringify_status(balloc, status_code)); + output->append(' '); + output->append(http2::get_reason_phrase(status_code)); + output->append("\r\nServer: "); + output->append(get_config()->http.server_name); + output->append("\r\nContent-Length: "); + std::array intbuf; + output->append(StringRef{std::begin(intbuf), + util::utos(std::begin(intbuf), html.size())}); + output->append("\r\nDate: "); + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + output->append(lgconf->tstamp->time_http); + output->append("\r\nContent-Type: text/html; " + "charset=UTF-8\r\nConnection: close\r\n\r\n"); + output->append(html); + + downstream->response_sent_body_length += html.size(); + downstream->set_response_state(DownstreamState::MSG_COMPLETE); +} + +void HttpsUpstream::attach_downstream(std::unique_ptr downstream) { + assert(!downstream_); + downstream_ = std::move(downstream); +} + +void HttpsUpstream::delete_downstream() { + if (downstream_ && downstream_->accesslog_ready()) { + handler_->write_accesslog(downstream_.get()); + } + + downstream_.reset(); +} + +Downstream *HttpsUpstream::get_downstream() const { return downstream_.get(); } + +std::unique_ptr HttpsUpstream::pop_downstream() { + return std::unique_ptr(downstream_.release()); +} + +int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + if (downstream->get_non_final_response()) { + DLOG(INFO, downstream) << "HTTP non-final response header"; + } else { + DLOG(INFO, downstream) << "HTTP response header completed"; + } + } + + const auto &req = downstream->request(); + auto &resp = downstream->response(); + auto &balloc = downstream->get_block_allocator(); + auto dconn = downstream->get_downstream_connection(); + // dconn might be nullptr if this is non-final response from mruby. + + if (downstream->get_non_final_response() && + !downstream->supports_non_final_response()) { + resp.fs.clear_headers(); + return 0; + } + +#ifdef HAVE_MRUBY + if (!downstream->get_non_final_response()) { + assert(dconn); + const auto &group = dconn->get_downstream_addr_group(); + if (group) { + const auto &dmruby_ctx = group->shared_addr->mruby_ctx; + + if (dmruby_ctx->run_on_response_proc(downstream) != 0) { + error_reply(500); + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return -1; + } + } + + auto worker = handler_->get_worker(); + auto mruby_ctx = worker->get_mruby_context(); + + if (mruby_ctx->run_on_response_proc(downstream) != 0) { + error_reply(500); + return -1; + } + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + return -1; + } + } +#endif // HAVE_MRUBY + + auto connect_method = req.method == HTTP_CONNECT; + + auto buf = downstream->get_response_buf(); + buf->append("HTTP/"); + buf->append('0' + req.http_major); + buf->append('.'); + buf->append('0' + req.http_minor); + buf->append(' '); + if (req.connect_proto != ConnectProto::NONE && downstream->get_upgraded()) { + buf->append(http2::stringify_status(balloc, 101)); + buf->append(' '); + buf->append(http2::get_reason_phrase(101)); + } else { + buf->append(http2::stringify_status(balloc, resp.http_status)); + buf->append(' '); + buf->append(http2::get_reason_phrase(resp.http_status)); + } + buf->append("\r\n"); + + auto config = get_config(); + auto &httpconf = config->http; + + if (!config->http2_proxy && !httpconf.no_location_rewrite) { + downstream->rewrite_location_response_header( + get_client_handler()->get_upstream_scheme()); + } + + if (downstream->get_non_final_response()) { + http2::build_http1_headers_from_headers(buf, resp.fs.headers(), + http2::HDOP_STRIP_ALL); + + buf->append("\r\n"); + + if (LOG_ENABLED(INFO)) { + log_response_headers(buf); + } + + resp.fs.clear_headers(); + + return 0; + } + + auto build_flags = (http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA) | + (!http2::legacy_http1(req.http_major, req.http_minor) + ? 0 + : http2::HDOP_STRIP_TRANSFER_ENCODING); + + http2::build_http1_headers_from_headers(buf, resp.fs.headers(), build_flags); + + auto worker = handler_->get_worker(); + + // after graceful shutdown commenced, add connection: close header + // field. + if (httpconf.max_requests <= num_requests_ || + worker->get_graceful_shutdown()) { + resp.connection_close = true; + } + + // We check downstream->get_response_connection_close() in case when + // the Content-Length is not available. + if (!req.connection_close && !resp.connection_close) { + if (req.http_major <= 0 || req.http_minor <= 0) { + // We add this header for HTTP/1.0 or HTTP/0.9 clients + buf->append("Connection: Keep-Alive\r\n"); + } + } else if (!downstream->get_upgraded()) { + buf->append("Connection: close\r\n"); + } + + if (!connect_method && downstream->get_upgraded()) { + if (req.connect_proto == ConnectProto::WEBSOCKET && + resp.http_status / 100 == 2) { + buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n"); + auto key = req.fs.header(http2::HD_SEC_WEBSOCKET_KEY); + if (!key || key->value.size() != base64::encode_length(16)) { + return -1; + } + std::array out; + auto accept = http2::make_websocket_accept_token(out.data(), key->value); + if (accept.empty()) { + return -1; + } + buf->append("Sec-WebSocket-Accept: "); + buf->append(accept); + buf->append("\r\n"); + } else { + auto connection = resp.fs.header(http2::HD_CONNECTION); + if (connection) { + buf->append("Connection: "); + buf->append((*connection).value); + buf->append("\r\n"); + } + + auto upgrade = resp.fs.header(http2::HD_UPGRADE); + if (upgrade) { + buf->append("Upgrade: "); + buf->append((*upgrade).value); + buf->append("\r\n"); + } + } + } + + if (!resp.fs.header(http2::HD_ALT_SVC)) { + // We won't change or alter alt-svc from backend for now + if (!httpconf.altsvcs.empty()) { + buf->append("Alt-Svc: "); + buf->append(httpconf.altsvc_header_value); + buf->append("\r\n"); + } + } + + if (!config->http2_proxy && !httpconf.no_server_rewrite) { + buf->append("Server: "); + buf->append(httpconf.server_name); + buf->append("\r\n"); + } else { + auto server = resp.fs.header(http2::HD_SERVER); + if (server) { + buf->append("Server: "); + buf->append((*server).value); + buf->append("\r\n"); + } + } + + if (req.method != HTTP_CONNECT || !downstream->get_upgraded()) { + auto affinity_cookie = downstream->get_affinity_cookie_to_send(); + if (affinity_cookie) { + auto &group = dconn->get_downstream_addr_group(); + auto &shared_addr = group->shared_addr; + auto &cookieconf = shared_addr->affinity.cookie; + auto secure = + http::require_cookie_secure_attribute(cookieconf.secure, req.scheme); + auto cookie_str = http::create_affinity_cookie( + balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure); + buf->append("Set-Cookie: "); + buf->append(cookie_str); + buf->append("\r\n"); + } + } + + auto via = resp.fs.header(http2::HD_VIA); + if (httpconf.no_via) { + if (via) { + buf->append("Via: "); + buf->append((*via).value); + buf->append("\r\n"); + } + } else { + buf->append("Via: "); + if (via) { + buf->append((*via).value); + buf->append(", "); + } + std::array viabuf; + auto end = http::create_via_header_value(viabuf.data(), resp.http_major, + resp.http_minor); + buf->append(viabuf.data(), end - std::begin(viabuf)); + buf->append("\r\n"); + } + + for (auto &p : httpconf.add_response_headers) { + buf->append(p.name); + buf->append(": "); + buf->append(p.value); + buf->append("\r\n"); + } + + buf->append("\r\n"); + + if (LOG_ENABLED(INFO)) { + log_response_headers(buf); + } + + return 0; +} + +int HttpsUpstream::on_downstream_body(Downstream *downstream, + const uint8_t *data, size_t len, + bool flush) { + if (len == 0) { + return 0; + } + auto output = downstream->get_response_buf(); + if (downstream->get_chunked_response()) { + output->append(util::utox(len)); + output->append("\r\n"); + } + output->append(data, len); + + downstream->response_sent_body_length += len; + + if (downstream->get_chunked_response()) { + output->append("\r\n"); + } + return 0; +} + +int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) { + const auto &req = downstream->request(); + auto &resp = downstream->response(); + + if (downstream->get_chunked_response()) { + auto output = downstream->get_response_buf(); + const auto &trailers = resp.fs.trailers(); + if (trailers.empty()) { + output->append("0\r\n\r\n"); + } else { + output->append("0\r\n"); + http2::build_http1_headers_from_headers(output, trailers, + http2::HDOP_STRIP_ALL); + output->append("\r\n"); + } + } + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "HTTP response completed"; + } + + if (!downstream->validate_response_recv_body_length()) { + resp.connection_close = true; + } + + if (req.connection_close || resp.connection_close || + // To avoid to stall upload body + downstream->get_request_state() != DownstreamState::MSG_COMPLETE) { + auto handler = get_client_handler(); + handler->set_should_close_after_write(true); + } + return 0; +} + +int HttpsUpstream::on_downstream_abort_request(Downstream *downstream, + unsigned int status_code) { + error_reply(status_code); + handler_->signal_write(); + return 0; +} + +int HttpsUpstream::on_downstream_abort_request_with_https_redirect( + Downstream *downstream) { + redirect_to_https(downstream); + handler_->signal_write(); + return 0; +} + +int HttpsUpstream::redirect_to_https(Downstream *downstream) { + auto &req = downstream->request(); + if (req.method == HTTP_CONNECT || req.scheme != "http" || + req.authority.empty()) { + error_reply(400); + return 0; + } + + auto authority = util::extract_host(req.authority); + if (authority.empty()) { + error_reply(400); + return 0; + } + + auto &balloc = downstream->get_block_allocator(); + auto config = get_config(); + auto &httpconf = config->http; + + StringRef loc; + if (httpconf.redirect_https_port == StringRef::from_lit("443")) { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + req.path); + } else { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + StringRef::from_lit(":"), + httpconf.redirect_https_port, req.path); + } + + auto &resp = downstream->response(); + resp.http_status = 308; + resp.fs.add_header_token(StringRef::from_lit("location"), loc, false, + http2::HD_LOCATION); + resp.fs.add_header_token(StringRef::from_lit("connection"), + StringRef::from_lit("close"), false, + http2::HD_CONNECTION); + + return send_reply(downstream, nullptr, 0); +} + +void HttpsUpstream::log_response_headers(DefaultMemchunks *buf) const { + std::string nhdrs; + for (auto chunk = buf->head; chunk; chunk = chunk->next) { + nhdrs.append(chunk->pos, chunk->last); + } + if (log_config()->errorlog_tty) { + nhdrs = http::colorizeHeaders(nhdrs.c_str()); + } + ULOG(INFO, this) << "HTTP response headers\n" << nhdrs; +} + +void HttpsUpstream::on_handler_delete() { + if (downstream_ && downstream_->accesslog_ready()) { + handler_->write_accesslog(downstream_.get()); + } +} + +int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) { + int rv; + std::unique_ptr dconn; + + assert(downstream == downstream_.get()); + + downstream_->pop_downstream_connection(); + + if (!downstream_->request_submission_ready()) { + switch (downstream_->get_response_state()) { + case DownstreamState::MSG_COMPLETE: + // We have got all response body already. Send it off. + return 0; + case DownstreamState::INITIAL: + if (on_downstream_abort_request(downstream_.get(), 502) != 0) { + return -1; + } + return 0; + default: + break; + } + // Return error so that caller can delete handler + return -1; + } + + downstream_->add_retry(); + + rv = 0; + + if (no_retry || downstream_->no_more_retry()) { + goto fail; + } + + for (;;) { + auto dconn = handler_->get_downstream_connection(rv, downstream_.get()); + if (!dconn) { + goto fail; + } + + rv = downstream_->attach_downstream_connection(std::move(dconn)); + if (rv == 0) { + break; + } + } + + rv = downstream_->push_request_headers(); + if (rv != 0) { + goto fail; + } + + return 0; + +fail: + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = on_downstream_abort_request_with_https_redirect(downstream); + } else { + rv = on_downstream_abort_request(downstream_.get(), 502); + } + if (rv != 0) { + return -1; + } + downstream_->pop_downstream_connection(); + + return 0; +} + +int HttpsUpstream::initiate_push(Downstream *downstream, const StringRef &uri) { + return 0; +} + +int HttpsUpstream::response_riovec(struct iovec *iov, int iovcnt) const { + if (!downstream_) { + return 0; + } + + auto buf = downstream_->get_response_buf(); + + return buf->riovec(iov, iovcnt); +} + +void HttpsUpstream::response_drain(size_t n) { + if (!downstream_) { + return; + } + + auto buf = downstream_->get_response_buf(); + + buf->drain(n); +} + +bool HttpsUpstream::response_empty() const { + if (!downstream_) { + return true; + } + + auto buf = downstream_->get_response_buf(); + + return buf->rleft() == 0; +} + +Downstream * +HttpsUpstream::on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id) { + return nullptr; +} + +int HttpsUpstream::on_downstream_push_promise_complete( + Downstream *downstream, Downstream *promised_downstream) { + return -1; +} + +bool HttpsUpstream::push_enabled() const { return false; } + +void HttpsUpstream::cancel_premature_downstream( + Downstream *promised_downstream) {} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_https_upstream.h b/lib/nghttp2/src/shrpx_https_upstream.h new file mode 100644 index 00000000000..d85d2dea4e5 --- /dev/null +++ b/lib/nghttp2/src/shrpx_https_upstream.h @@ -0,0 +1,113 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_HTTPS_UPSTREAM_H +#define SHRPX_HTTPS_UPSTREAM_H + +#include "shrpx.h" + +#include +#include + +#include "llhttp.h" + +#include "shrpx_upstream.h" +#include "memchunk.h" + +using namespace nghttp2; + +namespace shrpx { + +class ClientHandler; + +class HttpsUpstream : public Upstream { +public: + HttpsUpstream(ClientHandler *handler); + virtual ~HttpsUpstream(); + virtual int on_read(); + virtual int on_write(); + virtual int on_event(); + virtual int on_downstream_abort_request(Downstream *downstream, + unsigned int status_code); + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream); + virtual ClientHandler *get_client_handler() const; + + virtual int downstream_read(DownstreamConnection *dconn); + virtual int downstream_write(DownstreamConnection *dconn); + virtual int downstream_eof(DownstreamConnection *dconn); + virtual int downstream_error(DownstreamConnection *dconn, int events); + + void attach_downstream(std::unique_ptr downstream); + void delete_downstream(); + Downstream *get_downstream() const; + std::unique_ptr pop_downstream(); + void error_reply(unsigned int status_code); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed); + + virtual int on_downstream_header_complete(Downstream *downstream); + virtual int on_downstream_body(Downstream *downstream, const uint8_t *data, + size_t len, bool flush); + virtual int on_downstream_body_complete(Downstream *downstream); + + virtual void on_handler_delete(); + virtual int on_downstream_reset(Downstream *downstream, bool no_retry); + virtual int send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen); + virtual int initiate_push(Downstream *downstream, const StringRef &uri); + virtual int response_riovec(struct iovec *iov, int iovcnt) const; + virtual void response_drain(size_t n); + virtual bool response_empty() const; + + virtual Downstream *on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id); + virtual int + on_downstream_push_promise_complete(Downstream *downstream, + Downstream *promised_downstream); + virtual bool push_enabled() const; + virtual void cancel_premature_downstream(Downstream *promised_downstream); + + void reset_current_header_length(); + void log_response_headers(DefaultMemchunks *buf) const; + int redirect_to_https(Downstream *downstream); + + // Called when new request has started. + void on_start_request(); + +private: + ClientHandler *handler_; + llhttp_t htp_; + size_t current_header_length_; + std::unique_ptr downstream_; + IOControl ioctrl_; + // The number of requests seen so far. + size_t num_requests_; +}; + +} // namespace shrpx + +#endif // SHRPX_HTTPS_UPSTREAM_H diff --git a/lib/nghttp2/src/shrpx_io_control.cc b/lib/nghttp2/src/shrpx_io_control.cc new file mode 100644 index 00000000000..f43a25767ca --- /dev/null +++ b/lib/nghttp2/src/shrpx_io_control.cc @@ -0,0 +1,66 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_io_control.h" + +#include + +#include "shrpx_rate_limit.h" +#include "util.h" + +using namespace nghttp2; + +namespace shrpx { + +IOControl::IOControl(RateLimit *lim) : lim_(lim), rdbits_(0) {} + +IOControl::~IOControl() {} + +void IOControl::pause_read(IOCtrlReason reason) { + rdbits_ |= reason; + if (lim_) { + lim_->stopw(); + } +} + +bool IOControl::resume_read(IOCtrlReason reason) { + rdbits_ &= ~reason; + if (rdbits_ == 0) { + if (lim_) { + lim_->startw(); + } + return true; + } + + return false; +} + +void IOControl::force_resume_read() { + rdbits_ = 0; + if (lim_) { + lim_->startw(); + } +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_io_control.h b/lib/nghttp2/src/shrpx_io_control.h new file mode 100644 index 00000000000..d427fcbc34b --- /dev/null +++ b/lib/nghttp2/src/shrpx_io_control.h @@ -0,0 +1,58 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_IO_CONTROL_H +#define SHRPX_IO_CONTROL_H + +#include "shrpx.h" + +#include +#include + +#include + +#include "shrpx_rate_limit.h" + +namespace shrpx { + +enum IOCtrlReason { SHRPX_NO_BUFFER = 1 << 0, SHRPX_MSG_BLOCK = 1 << 1 }; + +class IOControl { +public: + IOControl(RateLimit *lim); + ~IOControl(); + void pause_read(IOCtrlReason reason); + // Returns true if read operation is enabled after this call + bool resume_read(IOCtrlReason reason); + // Clear all pause flags and enable read + void force_resume_read(); + +private: + RateLimit *lim_; + uint32_t rdbits_; +}; + +} // namespace shrpx + +#endif // SHRPX_IO_CONTROL_H diff --git a/lib/nghttp2/src/shrpx_live_check.cc b/lib/nghttp2/src/shrpx_live_check.cc new file mode 100644 index 00000000000..5d1057c6e84 --- /dev/null +++ b/lib/nghttp2/src/shrpx_live_check.cc @@ -0,0 +1,799 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_live_check.h" +#include "shrpx_worker.h" +#include "shrpx_connect_blocker.h" +#include "shrpx_tls.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +constexpr size_t MAX_BUFFER_SIZE = 4_k; +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto conn = static_cast(w->data); + auto live_check = static_cast(conn->data); + + rv = live_check->do_read(); + if (rv != 0) { + live_check->on_failure(); + return; + } +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto conn = static_cast(w->data); + auto live_check = static_cast(conn->data); + + rv = live_check->do_write(); + if (rv != 0) { + live_check->on_failure(); + return; + } +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto conn = static_cast(w->data); + auto live_check = static_cast(conn->data); + + if (w == &conn->rt && !conn->expired_rt()) { + return; + } + + live_check->on_failure(); +} +} // namespace + +namespace { +void backoff_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + int rv; + auto live_check = static_cast(w->data); + + rv = live_check->initiate_connection(); + if (rv != 0) { + live_check->on_failure(); + return; + } +} +} // namespace + +namespace { +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto live_check = static_cast(w->data); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SETTINGS timeout"; + } + + live_check->on_failure(); +} +} // namespace + +LiveCheck::LiveCheck(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker, + DownstreamAddr *addr, std::mt19937 &gen) + : conn_(loop, -1, nullptr, worker->get_mcpool(), + worker->get_downstream_config()->timeout.write, + worker->get_downstream_config()->timeout.read, {}, {}, writecb, + readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold, + get_config()->tls.dyn_rec.idle_timeout, Proto::NONE), + wb_(worker->get_mcpool()), + gen_(gen), + read_(&LiveCheck::noop), + write_(&LiveCheck::noop), + worker_(worker), + ssl_ctx_(ssl_ctx), + addr_(addr), + session_(nullptr), + raddr_(nullptr), + success_count_(0), + fail_count_(0), + settings_ack_received_(false), + session_closing_(false) { + ev_timer_init(&backoff_timer_, backoff_timeoutcb, 0., 0.); + backoff_timer_.data = this; + + // SETTINGS ACK must be received in a short timeout. Otherwise, we + // assume that connection is broken. + ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 0.); + settings_timer_.data = this; +} + +LiveCheck::~LiveCheck() { + disconnect(); + + ev_timer_stop(conn_.loop, &backoff_timer_); +} + +void LiveCheck::disconnect() { + if (dns_query_) { + auto dns_tracker = worker_->get_dns_tracker(); + + dns_tracker->cancel(dns_query_.get()); + } + + dns_query_.reset(); + // We can reuse resolved_addr_ + raddr_ = nullptr; + + conn_.rlimit.stopw(); + conn_.wlimit.stopw(); + + ev_timer_stop(conn_.loop, &settings_timer_); + + read_ = write_ = &LiveCheck::noop; + + conn_.disconnect(); + + nghttp2_session_del(session_); + session_ = nullptr; + + settings_ack_received_ = false; + session_closing_ = false; + + wb_.reset(); +} + +// Use the similar backoff algorithm described in +// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md +namespace { +constexpr size_t MAX_BACKOFF_EXP = 10; +constexpr auto MULTIPLIER = 1.6; +constexpr auto JITTER = 0.2; +} // namespace + +void LiveCheck::schedule() { + auto base_backoff = + util::int_pow(MULTIPLIER, std::min(fail_count_, MAX_BACKOFF_EXP)); + auto dist = std::uniform_real_distribution<>(-JITTER * base_backoff, + JITTER * base_backoff); + + auto &downstreamconf = *get_config()->conn.downstream; + + auto backoff = + std::min(downstreamconf.timeout.max_backoff, base_backoff + dist(gen_)); + + ev_timer_set(&backoff_timer_, backoff, 0.); + ev_timer_start(conn_.loop, &backoff_timer_); +} + +int LiveCheck::do_read() { return read_(*this); } + +int LiveCheck::do_write() { return write_(*this); } + +int LiveCheck::initiate_connection() { + int rv; + + auto worker_blocker = worker_->get_connect_blocker(); + if (worker_blocker->blocked()) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Worker wide backend connection was blocked temporarily"; + } + return -1; + } + + if (!dns_query_ && addr_->tls) { + assert(ssl_ctx_); + + auto ssl = tls::create_ssl(ssl_ctx_); + if (!ssl) { + return -1; + } + + switch (addr_->proto) { + case Proto::HTTP1: + tls::setup_downstream_http1_alpn(ssl); + break; + case Proto::HTTP2: + tls::setup_downstream_http2_alpn(ssl); + break; + default: + assert(0); + } + + conn_.set_ssl(ssl); + conn_.tls.client_session_cache = &addr_->tls_session_cache; + } + + if (addr_->dns) { + if (!dns_query_) { + auto dns_query = std::make_unique( + addr_->host, [this](DNSResolverStatus status, const Address *result) { + int rv; + + if (status == DNSResolverStatus::OK) { + *this->resolved_addr_ = *result; + } + rv = this->initiate_connection(); + if (rv != 0) { + this->on_failure(); + } + }); + auto dns_tracker = worker_->get_dns_tracker(); + + if (!resolved_addr_) { + resolved_addr_ = std::make_unique
(); + } + + switch (dns_tracker->resolve(resolved_addr_.get(), dns_query.get())) { + case DNSResolverStatus::ERROR: + return -1; + case DNSResolverStatus::RUNNING: + dns_query_ = std::move(dns_query); + return 0; + case DNSResolverStatus::OK: + break; + default: + assert(0); + } + } else { + switch (dns_query_->status) { + case DNSResolverStatus::ERROR: + dns_query_.reset(); + return -1; + case DNSResolverStatus::OK: + dns_query_.reset(); + break; + default: + assert(0); + } + } + + util::set_port(*resolved_addr_, addr_->port); + raddr_ = resolved_addr_.get(); + } else { + raddr_ = &addr_->addr; + } + + conn_.fd = util::create_nonblock_socket(raddr_->su.storage.ss_family); + + if (conn_.fd == -1) { + auto error = errno; + LOG(WARN) << "socket() failed; addr=" << util::to_numeric_addr(raddr_) + << ", errno=" << error; + return -1; + } + + rv = connect(conn_.fd, &raddr_->su.sa, raddr_->len); + if (rv != 0 && errno != EINPROGRESS) { + auto error = errno; + LOG(WARN) << "connect() failed; addr=" << util::to_numeric_addr(raddr_) + << ", errno=" << error; + + close(conn_.fd); + conn_.fd = -1; + + return -1; + } + + if (addr_->tls) { + auto sni_name = + addr_->sni.empty() ? StringRef{addr_->host} : StringRef{addr_->sni}; + if (!util::numeric_host(sni_name.c_str())) { + SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str()); + } + + auto session = tls::reuse_tls_session(addr_->tls_session_cache); + if (session) { + SSL_set_session(conn_.tls.ssl, session); + SSL_SESSION_free(session); + } + + conn_.prepare_client_handshake(); + } + + write_ = &LiveCheck::connected; + + ev_io_set(&conn_.wev, conn_.fd, EV_WRITE); + ev_io_set(&conn_.rev, conn_.fd, EV_READ); + + conn_.wlimit.startw(); + + auto &downstreamconf = *get_config()->conn.downstream; + + conn_.wt.repeat = downstreamconf.timeout.connect; + ev_timer_again(conn_.loop, &conn_.wt); + + return 0; +} + +int LiveCheck::connected() { + auto sock_error = util::get_socket_error(conn_.fd); + if (sock_error != 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Backend connect failed; addr=" + << util::to_numeric_addr(raddr_) << ": errno=" << sock_error; + } + + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Connection established"; + } + + auto &downstreamconf = *get_config()->conn.downstream; + + // Reset timeout for write. Previously, we set timeout for connect. + conn_.wt.repeat = downstreamconf.timeout.write; + ev_timer_again(conn_.loop, &conn_.wt); + + conn_.rlimit.startw(); + conn_.again_rt(); + + if (conn_.tls.ssl) { + read_ = &LiveCheck::tls_handshake; + write_ = &LiveCheck::tls_handshake; + + return do_write(); + } + + if (addr_->proto == Proto::HTTP2) { + // For HTTP/2, we try to read SETTINGS ACK from server to make + // sure it is really alive, and serving HTTP/2. + read_ = &LiveCheck::read_clear; + write_ = &LiveCheck::write_clear; + + if (connection_made() != 0) { + return -1; + } + + return 0; + } + + on_success(); + + return 0; +} + +int LiveCheck::tls_handshake() { + conn_.last_read = std::chrono::steady_clock::now(); + + ERR_clear_error(); + + auto rv = conn_.tls_handshake(); + + if (rv == SHRPX_ERR_INPROGRESS) { + return 0; + } + + if (rv < 0) { + return rv; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL/TLS handshake completed"; + } + + if (!get_config()->tls.insecure && + tls::check_cert(conn_.tls.ssl, addr_, raddr_) != 0) { + return -1; + } + + // Check negotiated ALPN + + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len = 0; + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + auto proto = StringRef{next_proto, next_proto_len}; + + switch (addr_->proto) { + case Proto::HTTP1: + if (proto.empty() || proto == StringRef::from_lit("http/1.1")) { + break; + } + return -1; + case Proto::HTTP2: + if (util::check_h2_is_selected(proto)) { + // For HTTP/2, we try to read SETTINGS ACK from server to make + // sure it is really alive, and serving HTTP/2. + read_ = &LiveCheck::read_tls; + write_ = &LiveCheck::write_tls; + + if (connection_made() != 0) { + return -1; + } + + return 0; + } + return -1; + default: + break; + } + + on_success(); + + return 0; +} + +int LiveCheck::read_tls() { + conn_.last_read = std::chrono::steady_clock::now(); + + std::array buf; + + ERR_clear_error(); + + for (;;) { + auto nread = conn_.read_tls(buf.data(), buf.size()); + + if (nread == 0) { + return 0; + } + + if (nread < 0) { + return nread; + } + + if (on_read(buf.data(), nread) != 0) { + return -1; + } + } +} + +int LiveCheck::write_tls() { + conn_.last_read = std::chrono::steady_clock::now(); + + ERR_clear_error(); + + struct iovec iov; + + for (;;) { + if (wb_.rleft() > 0) { + auto iovcnt = wb_.riovec(&iov, 1); + if (iovcnt != 1) { + assert(0); + return -1; + } + auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len); + + if (nwrite == 0) { + return 0; + } + + if (nwrite < 0) { + return nwrite; + } + + wb_.drain(nwrite); + + continue; + } + + if (on_write() != 0) { + return -1; + } + + if (wb_.rleft() == 0) { + conn_.start_tls_write_idle(); + break; + } + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + if (settings_ack_received_) { + on_success(); + } + + return 0; +} + +int LiveCheck::read_clear() { + conn_.last_read = std::chrono::steady_clock::now(); + + std::array buf; + + for (;;) { + auto nread = conn_.read_clear(buf.data(), buf.size()); + + if (nread == 0) { + return 0; + } + + if (nread < 0) { + return nread; + } + + if (on_read(buf.data(), nread) != 0) { + return -1; + } + } +} + +int LiveCheck::write_clear() { + conn_.last_read = std::chrono::steady_clock::now(); + + struct iovec iov; + + for (;;) { + if (wb_.rleft() > 0) { + auto iovcnt = wb_.riovec(&iov, 1); + if (iovcnt != 1) { + assert(0); + return -1; + } + auto nwrite = conn_.write_clear(iov.iov_base, iov.iov_len); + + if (nwrite == 0) { + return 0; + } + + if (nwrite < 0) { + return nwrite; + } + + wb_.drain(nwrite); + + continue; + } + + if (on_write() != 0) { + return -1; + } + + if (wb_.rleft() == 0) { + break; + } + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + if (settings_ack_received_) { + on_success(); + } + + return 0; +} + +int LiveCheck::on_read(const uint8_t *data, size_t len) { + ssize_t rv; + + rv = nghttp2_session_mem_recv(session_, data, len); + if (rv < 0) { + LOG(ERROR) << "nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv); + return -1; + } + + if (settings_ack_received_ && !session_closing_) { + session_closing_ = true; + rv = nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR); + if (rv != 0) { + return -1; + } + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "No more read/write for this session"; + } + + // If we have SETTINGS ACK already, we treat this success. + if (settings_ack_received_) { + return 0; + } + + return -1; + } + + signal_write(); + + return 0; +} + +int LiveCheck::on_write() { + for (;;) { + const uint8_t *data; + auto datalen = nghttp2_session_mem_send(session_, &data); + + if (datalen < 0) { + LOG(ERROR) << "nghttp2_session_mem_send() returned error: " + << nghttp2_strerror(datalen); + return -1; + } + if (datalen == 0) { + break; + } + wb_.append(data, datalen); + + if (wb_.rleft() >= MAX_BUFFER_SIZE) { + break; + } + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "No more read/write for this session"; + } + + if (settings_ack_received_) { + return 0; + } + + return -1; + } + + return 0; +} + +void LiveCheck::on_failure() { + ++fail_count_; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Liveness check for " << addr_->host << ":" << addr_->port + << " failed " << fail_count_ << " time(s) in a row"; + } + + disconnect(); + + schedule(); +} + +void LiveCheck::on_success() { + ++success_count_; + fail_count_ = 0; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Liveness check for " << addr_->host << ":" << addr_->port + << " succeeded " << success_count_ << " time(s) in a row"; + } + + if (success_count_ < addr_->rise) { + disconnect(); + + schedule(); + + return; + } + + LOG(NOTICE) << util::to_numeric_addr(&addr_->addr) << " is considered online"; + + addr_->connect_blocker->online(); + + success_count_ = 0; + fail_count_ = 0; + + disconnect(); +} + +int LiveCheck::noop() { return 0; } + +void LiveCheck::start_settings_timer() { + auto &downstreamconf = get_config()->http2.downstream; + + ev_timer_set(&settings_timer_, downstreamconf.timeout.settings, 0.); + ev_timer_start(conn_.loop, &settings_timer_); +} + +void LiveCheck::stop_settings_timer() { + ev_timer_stop(conn_.loop, &settings_timer_); +} + +void LiveCheck::settings_ack_received() { settings_ack_received_ = true; } + +namespace { +int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + auto live_check = static_cast(user_data); + + if (frame->hd.type != NGHTTP2_SETTINGS || + (frame->hd.flags & NGHTTP2_FLAG_ACK)) { + return 0; + } + + live_check->start_settings_timer(); + + return 0; +} +} // namespace + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + auto live_check = static_cast(user_data); + + if (frame->hd.type != NGHTTP2_SETTINGS || + (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { + return 0; + } + + live_check->stop_settings_timer(); + live_check->settings_ack_received(); + + return 0; +} +} // namespace + +int LiveCheck::connection_made() { + int rv; + + nghttp2_session_callbacks *callbacks; + rv = nghttp2_session_callbacks_new(&callbacks); + if (rv != 0) { + return -1; + } + + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + + rv = nghttp2_session_client_new(&session_, callbacks, this); + + nghttp2_session_callbacks_del(callbacks); + + if (rv != 0) { + return -1; + } + + rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, nullptr, 0); + if (rv != 0) { + return -1; + } + + auto must_terminate = + addr_->tls && !nghttp2::tls::check_http2_requirement(conn_.tls.ssl); + + if (must_terminate) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be negotiated."; + } + + rv = nghttp2_session_terminate_session(session_, + NGHTTP2_INADEQUATE_SECURITY); + if (rv != 0) { + return -1; + } + } + + signal_write(); + + return 0; +} + +void LiveCheck::signal_write() { conn_.wlimit.startw(); } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_live_check.h b/lib/nghttp2/src/shrpx_live_check.h new file mode 100644 index 00000000000..b65ecdcda55 --- /dev/null +++ b/lib/nghttp2/src/shrpx_live_check.h @@ -0,0 +1,125 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_LIVE_CHECK_H +#define SHRPX_LIVE_CHECK_H + +#include "shrpx.h" + +#include +#include + +#include + +#include + +#include + +#include "shrpx_connection.h" + +namespace shrpx { + +class Worker; +struct DownstreamAddr; +struct DNSQuery; + +class LiveCheck { +public: + LiveCheck(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker, + DownstreamAddr *addr, std::mt19937 &gen); + ~LiveCheck(); + + void disconnect(); + + void on_success(); + void on_failure(); + + int initiate_connection(); + + // Schedules next connection attempt + void schedule(); + + // Low level I/O operation callback; they are called from do_read() + // or do_write(). + int noop(); + int connected(); + int tls_handshake(); + int read_tls(); + int write_tls(); + int read_clear(); + int write_clear(); + + int do_read(); + int do_write(); + + // These functions are used to feed / extract data to + // nghttp2_session object. + int on_read(const uint8_t *data, size_t len); + int on_write(); + + // Call this function when HTTP/2 connection was established. We + // don't call this function for HTTP/1 at the moment. + int connection_made(); + + void start_settings_timer(); + void stop_settings_timer(); + + // Call this function when SETTINGS ACK was received from server. + void settings_ack_received(); + + void signal_write(); + +private: + Connection conn_; + DefaultMemchunks wb_; + std::mt19937 &gen_; + ev_timer backoff_timer_; + ev_timer settings_timer_; + std::function read_, write_; + Worker *worker_; + // nullptr if no TLS is configured + SSL_CTX *ssl_ctx_; + // Address of remote endpoint + DownstreamAddr *addr_; + nghttp2_session *session_; + // Actual remote address used to contact backend. This is initially + // nullptr, and may point to either &addr_->addr, or + // resolved_addr_.get(). + const Address *raddr_; + // Resolved IP address if dns parameter is used + std::unique_ptr
resolved_addr_; + std::unique_ptr dns_query_; + // The number of successful connect attempt in a row. + size_t success_count_; + // The number of unsuccessful connect attempt in a row. + size_t fail_count_; + // true when SETTINGS ACK has been received from server. + bool settings_ack_received_; + // true when GOAWAY has been queued. + bool session_closing_; +}; + +} // namespace shrpx + +#endif // SHRPX_LIVE_CHECK_H diff --git a/lib/nghttp2/src/shrpx_log.cc b/lib/nghttp2/src/shrpx_log.cc new file mode 100644 index 00000000000..de5e09fa156 --- /dev/null +++ b/lib/nghttp2/src/shrpx_log.cc @@ -0,0 +1,1008 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_log.h" + +#ifdef HAVE_SYSLOG_H +# include +#endif // HAVE_SYSLOG_H +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#ifdef HAVE_INTTYPES_H +# include +#endif // HAVE_INTTYPES_H +#include +#include +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#include + +#include +#include +#include +#include +#include +#include + +#include "shrpx_config.h" +#include "shrpx_downstream.h" +#include "shrpx_worker.h" +#include "util.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +constexpr StringRef SEVERITY_STR[] = { + StringRef::from_lit("INFO"), StringRef::from_lit("NOTICE"), + StringRef::from_lit("WARN"), StringRef::from_lit("ERROR"), + StringRef::from_lit("FATAL")}; +} // namespace + +namespace { +constexpr const char *SEVERITY_COLOR[] = { + "\033[1;32m", // INFO + "\033[1;36m", // NOTICE + "\033[1;33m", // WARN + "\033[1;31m", // ERROR + "\033[1;35m", // FATAL +}; +} // namespace + +#ifndef NOTHREADS +# ifdef HAVE_THREAD_LOCAL +namespace { +thread_local LogBuffer logbuf_; +} // namespace + +namespace { +LogBuffer *get_logbuf() { return &logbuf_; } +} // namespace +# else // !HAVE_THREAD_LOCAL +namespace { +pthread_key_t lckey; +pthread_once_t lckey_once = PTHREAD_ONCE_INIT; +} // namespace + +namespace { +void make_key() { pthread_key_create(&lckey, nullptr); } +} // namespace + +LogBuffer *get_logbuf() { + pthread_once(&lckey_once, make_key); + auto buf = static_cast(pthread_getspecific(lckey)); + if (!buf) { + buf = new LogBuffer(); + pthread_setspecific(lckey, buf); + } + return buf; +} +# endif // !HAVE_THREAD_LOCAL +#else // NOTHREADS +namespace { +LogBuffer *get_logbuf() { + static LogBuffer logbuf; + return &logbuf; +} +} // namespace +#endif // NOTHREADS + +int Log::severity_thres_ = NOTICE; + +void Log::set_severity_level(int severity) { severity_thres_ = severity; } + +int Log::get_severity_level_by_name(const StringRef &name) { + for (size_t i = 0, max = array_size(SEVERITY_STR); i < max; ++i) { + if (name == SEVERITY_STR[i]) { + return i; + } + } + return -1; +} + +int severity_to_syslog_level(int severity) { + switch (severity) { + case (INFO): + return LOG_INFO; + case (NOTICE): + return LOG_NOTICE; + case (WARN): + return LOG_WARNING; + case (ERROR): + return LOG_ERR; + case (FATAL): + return LOG_CRIT; + default: + return -1; + } +} + +Log::Log(int severity, const char *filename, int linenum) + : buf_(*get_logbuf()), + begin_(buf_.data()), + end_(begin_ + buf_.size()), + last_(begin_), + filename_(filename), + flags_(0), + severity_(severity), + linenum_(linenum), + full_(false) {} + +Log::~Log() { + int rv; + auto config = get_config(); + + if (!config) { + return; + } + + auto lgconf = log_config(); + + auto &errorconf = config->logging.error; + + if (!log_enabled(severity_) || + (lgconf->errorlog_fd == -1 && !errorconf.syslog)) { + return; + } + + if (errorconf.syslog) { + if (severity_ == NOTICE) { + syslog(severity_to_syslog_level(severity_), "[%s] %.*s", + SEVERITY_STR[severity_].c_str(), static_cast(rleft()), + begin_); + } else { + syslog(severity_to_syslog_level(severity_), "[%s] %.*s (%s:%d)", + SEVERITY_STR[severity_].c_str(), static_cast(rleft()), begin_, + filename_, linenum_); + } + + return; + } + + char buf[4_k]; + auto tty = lgconf->errorlog_tty; + + lgconf->update_tstamp_millis(std::chrono::system_clock::now()); + + // Error log format: + // (:) + rv = snprintf(buf, sizeof(buf), "%s %d %d %s %s%s%s (%s:%d) %.*s\n", + lgconf->tstamp->time_iso8601.c_str(), config->pid, lgconf->pid, + lgconf->thread_id.c_str(), tty ? SEVERITY_COLOR[severity_] : "", + SEVERITY_STR[severity_].c_str(), tty ? "\033[0m" : "", + filename_, linenum_, static_cast(rleft()), begin_); + + if (rv < 0) { + return; + } + + auto nwrite = std::min(static_cast(rv), sizeof(buf) - 1); + + while (write(lgconf->errorlog_fd, buf, nwrite) == -1 && errno == EINTR) + ; +} + +Log &Log::operator<<(const std::string &s) { + write_seq(std::begin(s), std::end(s)); + return *this; +} + +Log &Log::operator<<(const StringRef &s) { + write_seq(std::begin(s), std::end(s)); + return *this; +} + +Log &Log::operator<<(const char *s) { + write_seq(s, s + strlen(s)); + return *this; +} + +Log &Log::operator<<(const ImmutableString &s) { + write_seq(std::begin(s), std::end(s)); + return *this; +} + +Log &Log::operator<<(long long n) { + if (n >= 0) { + return *this << static_cast(n); + } + + if (flags_ & fmt_hex) { + write_hex(n); + return *this; + } + + if (full_) { + return *this; + } + + n *= -1; + + size_t nlen = 0; + for (auto t = n; t; t /= 10, ++nlen) + ; + if (wleft() < 1 /* sign */ + nlen) { + full_ = true; + return *this; + } + *last_++ = '-'; + last_ += nlen; + update_full(); + + auto p = last_ - 1; + for (; n; n /= 10) { + *p-- = (n % 10) + '0'; + } + return *this; +} + +Log &Log::operator<<(unsigned long long n) { + if (flags_ & fmt_hex) { + write_hex(n); + return *this; + } + + if (full_) { + return *this; + } + + if (n == 0) { + *last_++ = '0'; + update_full(); + return *this; + } + size_t nlen = 0; + for (auto t = n; t; t /= 10, ++nlen) + ; + if (wleft() < nlen) { + full_ = true; + return *this; + } + + last_ += nlen; + update_full(); + + auto p = last_ - 1; + for (; n; n /= 10) { + *p-- = (n % 10) + '0'; + } + return *this; +} + +Log &Log::operator<<(double n) { + if (full_) { + return *this; + } + + auto left = wleft(); + auto rv = snprintf(reinterpret_cast(last_), left, "%.9f", n); + if (rv > static_cast(left)) { + full_ = true; + return *this; + } + + last_ += rv; + update_full(); + + return *this; +} + +Log &Log::operator<<(long double n) { + if (full_) { + return *this; + } + + auto left = wleft(); + auto rv = snprintf(reinterpret_cast(last_), left, "%.9Lf", n); + if (rv > static_cast(left)) { + full_ = true; + return *this; + } + + last_ += rv; + update_full(); + + return *this; +} + +Log &Log::operator<<(bool n) { + if (full_) { + return *this; + } + + *last_++ = n ? '1' : '0'; + update_full(); + + return *this; +} + +Log &Log::operator<<(const void *p) { + if (full_) { + return *this; + } + + write_hex(reinterpret_cast(p)); + + return *this; +} + +namespace log { +void hex(Log &log) { log.set_flags(Log::fmt_hex); }; + +void dec(Log &log) { log.set_flags(Log::fmt_dec); }; +} // namespace log + +namespace { +template +std::pair copy(const char *src, size_t srclen, + OutputIterator d_first, + OutputIterator d_last) { + auto nwrite = + std::min(static_cast(std::distance(d_first, d_last)), srclen); + return std::make_pair(std::copy_n(src, nwrite, d_first), d_last); +} +} // namespace + +namespace { +template +std::pair +copy(const char *src, OutputIterator d_first, OutputIterator d_last) { + return copy(src, strlen(src), d_first, d_last); +} +} // namespace + +namespace { +template +std::pair +copy(const StringRef &src, OutputIterator d_first, OutputIterator d_last) { + return copy(src.c_str(), src.size(), d_first, d_last); +} +} // namespace + +namespace { +template +std::pair +copy_l(const char (&src)[N], OutputIterator d_first, OutputIterator d_last) { + return copy(src, N - 1, d_first, d_last); +} +} // namespace + +namespace { +template +std::pair copy(char c, OutputIterator d_first, + OutputIterator d_last) { + if (d_first == d_last) { + return std::make_pair(d_last, d_last); + } + *d_first++ = c; + return std::make_pair(d_first, d_last); +} +} // namespace + +namespace { +constexpr char LOWER_XDIGITS[] = "0123456789abcdef"; +} // namespace + +namespace { +template +std::pair +copy_hex_low(const uint8_t *src, size_t srclen, OutputIterator d_first, + OutputIterator d_last) { + auto nwrite = std::min(static_cast(std::distance(d_first, d_last)), + srclen * 2) / + 2; + for (size_t i = 0; i < nwrite; ++i) { + *d_first++ = LOWER_XDIGITS[src[i] >> 4]; + *d_first++ = LOWER_XDIGITS[src[i] & 0xf]; + } + return std::make_pair(d_first, d_last); +} +} // namespace + +namespace { +template +std::pair copy(T n, OutputIterator d_first, + OutputIterator d_last) { + if (static_cast(std::distance(d_first, d_last)) < + NGHTTP2_MAX_UINT64_DIGITS) { + return std::make_pair(d_last, d_last); + } + return std::make_pair(util::utos(d_first, n), d_last); +} +} // namespace + +namespace { +// 1 means that character must be escaped as "\xNN", where NN is ascii +// code of the character in hex notation. +constexpr uint8_t ESCAPE_TBL[] = { + 1 /* NUL */, 1 /* SOH */, 1 /* STX */, 1 /* ETX */, 1 /* EOT */, + 1 /* ENQ */, 1 /* ACK */, 1 /* BEL */, 1 /* BS */, 1 /* HT */, + 1 /* LF */, 1 /* VT */, 1 /* FF */, 1 /* CR */, 1 /* SO */, + 1 /* SI */, 1 /* DLE */, 1 /* DC1 */, 1 /* DC2 */, 1 /* DC3 */, + 1 /* DC4 */, 1 /* NAK */, 1 /* SYN */, 1 /* ETB */, 1 /* CAN */, + 1 /* EM */, 1 /* SUB */, 1 /* ESC */, 1 /* FS */, 1 /* GS */, + 1 /* RS */, 1 /* US */, 0 /* SPC */, 0 /* ! */, 1 /* " */, + 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */, + 0 /* ( */, 0 /* ) */, 0 /* * */, 0 /* + */, 0 /* , */, + 0 /* - */, 0 /* . */, 0 /* / */, 0 /* 0 */, 0 /* 1 */, + 0 /* 2 */, 0 /* 3 */, 0 /* 4 */, 0 /* 5 */, 0 /* 6 */, + 0 /* 7 */, 0 /* 8 */, 0 /* 9 */, 0 /* : */, 0 /* ; */, + 0 /* < */, 0 /* = */, 0 /* > */, 0 /* ? */, 0 /* @ */, + 0 /* A */, 0 /* B */, 0 /* C */, 0 /* D */, 0 /* E */, + 0 /* F */, 0 /* G */, 0 /* H */, 0 /* I */, 0 /* J */, + 0 /* K */, 0 /* L */, 0 /* M */, 0 /* N */, 0 /* O */, + 0 /* P */, 0 /* Q */, 0 /* R */, 0 /* S */, 0 /* T */, + 0 /* U */, 0 /* V */, 0 /* W */, 0 /* X */, 0 /* Y */, + 0 /* Z */, 0 /* [ */, 1 /* \ */, 0 /* ] */, 0 /* ^ */, + 0 /* _ */, 0 /* ` */, 0 /* a */, 0 /* b */, 0 /* c */, + 0 /* d */, 0 /* e */, 0 /* f */, 0 /* g */, 0 /* h */, + 0 /* i */, 0 /* j */, 0 /* k */, 0 /* l */, 0 /* m */, + 0 /* n */, 0 /* o */, 0 /* p */, 0 /* q */, 0 /* r */, + 0 /* s */, 0 /* t */, 0 /* u */, 0 /* v */, 0 /* w */, + 0 /* x */, 0 /* y */, 0 /* z */, 0 /* { */, 0 /* | */, + 0 /* } */, 0 /* ~ */, 1 /* DEL */, 1 /* 0x80 */, 1 /* 0x81 */, + 1 /* 0x82 */, 1 /* 0x83 */, 1 /* 0x84 */, 1 /* 0x85 */, 1 /* 0x86 */, + 1 /* 0x87 */, 1 /* 0x88 */, 1 /* 0x89 */, 1 /* 0x8a */, 1 /* 0x8b */, + 1 /* 0x8c */, 1 /* 0x8d */, 1 /* 0x8e */, 1 /* 0x8f */, 1 /* 0x90 */, + 1 /* 0x91 */, 1 /* 0x92 */, 1 /* 0x93 */, 1 /* 0x94 */, 1 /* 0x95 */, + 1 /* 0x96 */, 1 /* 0x97 */, 1 /* 0x98 */, 1 /* 0x99 */, 1 /* 0x9a */, + 1 /* 0x9b */, 1 /* 0x9c */, 1 /* 0x9d */, 1 /* 0x9e */, 1 /* 0x9f */, + 1 /* 0xa0 */, 1 /* 0xa1 */, 1 /* 0xa2 */, 1 /* 0xa3 */, 1 /* 0xa4 */, + 1 /* 0xa5 */, 1 /* 0xa6 */, 1 /* 0xa7 */, 1 /* 0xa8 */, 1 /* 0xa9 */, + 1 /* 0xaa */, 1 /* 0xab */, 1 /* 0xac */, 1 /* 0xad */, 1 /* 0xae */, + 1 /* 0xaf */, 1 /* 0xb0 */, 1 /* 0xb1 */, 1 /* 0xb2 */, 1 /* 0xb3 */, + 1 /* 0xb4 */, 1 /* 0xb5 */, 1 /* 0xb6 */, 1 /* 0xb7 */, 1 /* 0xb8 */, + 1 /* 0xb9 */, 1 /* 0xba */, 1 /* 0xbb */, 1 /* 0xbc */, 1 /* 0xbd */, + 1 /* 0xbe */, 1 /* 0xbf */, 1 /* 0xc0 */, 1 /* 0xc1 */, 1 /* 0xc2 */, + 1 /* 0xc3 */, 1 /* 0xc4 */, 1 /* 0xc5 */, 1 /* 0xc6 */, 1 /* 0xc7 */, + 1 /* 0xc8 */, 1 /* 0xc9 */, 1 /* 0xca */, 1 /* 0xcb */, 1 /* 0xcc */, + 1 /* 0xcd */, 1 /* 0xce */, 1 /* 0xcf */, 1 /* 0xd0 */, 1 /* 0xd1 */, + 1 /* 0xd2 */, 1 /* 0xd3 */, 1 /* 0xd4 */, 1 /* 0xd5 */, 1 /* 0xd6 */, + 1 /* 0xd7 */, 1 /* 0xd8 */, 1 /* 0xd9 */, 1 /* 0xda */, 1 /* 0xdb */, + 1 /* 0xdc */, 1 /* 0xdd */, 1 /* 0xde */, 1 /* 0xdf */, 1 /* 0xe0 */, + 1 /* 0xe1 */, 1 /* 0xe2 */, 1 /* 0xe3 */, 1 /* 0xe4 */, 1 /* 0xe5 */, + 1 /* 0xe6 */, 1 /* 0xe7 */, 1 /* 0xe8 */, 1 /* 0xe9 */, 1 /* 0xea */, + 1 /* 0xeb */, 1 /* 0xec */, 1 /* 0xed */, 1 /* 0xee */, 1 /* 0xef */, + 1 /* 0xf0 */, 1 /* 0xf1 */, 1 /* 0xf2 */, 1 /* 0xf3 */, 1 /* 0xf4 */, + 1 /* 0xf5 */, 1 /* 0xf6 */, 1 /* 0xf7 */, 1 /* 0xf8 */, 1 /* 0xf9 */, + 1 /* 0xfa */, 1 /* 0xfb */, 1 /* 0xfc */, 1 /* 0xfd */, 1 /* 0xfe */, + 1 /* 0xff */, +}; +} // namespace + +namespace { +template +std::pair +copy_escape(const char *src, size_t srclen, OutputIterator d_first, + OutputIterator d_last) { + auto safe_first = src; + for (auto p = src; p != src + srclen && d_first != d_last; ++p) { + unsigned char c = *p; + if (!ESCAPE_TBL[c]) { + continue; + } + + auto n = + std::min(std::distance(d_first, d_last), std::distance(safe_first, p)); + d_first = std::copy_n(safe_first, n, d_first); + if (std::distance(d_first, d_last) < 4) { + return std::make_pair(d_first, d_last); + } + *d_first++ = '\\'; + *d_first++ = 'x'; + *d_first++ = LOWER_XDIGITS[c >> 4]; + *d_first++ = LOWER_XDIGITS[c & 0xf]; + safe_first = p + 1; + } + + auto n = std::min(std::distance(d_first, d_last), + std::distance(safe_first, src + srclen)); + return std::make_pair(std::copy_n(safe_first, n, d_first), d_last); +} +} // namespace + +namespace { +template +std::pair copy_escape(const StringRef &src, + OutputIterator d_first, + OutputIterator d_last) { + return copy_escape(src.c_str(), src.size(), d_first, d_last); +} +} // namespace + +namespace { +// Construct absolute request URI from |Request|, mainly to log +// request URI for proxy request (HTTP/2 proxy or client proxy). This +// is mostly same routine found in +// HttpDownstreamConnection::push_request_headers(), but vastly +// simplified since we only care about absolute URI. +StringRef construct_absolute_request_uri(BlockAllocator &balloc, + const Request &req) { + if (req.authority.empty()) { + return req.path; + } + + auto len = req.authority.size() + req.path.size(); + if (req.scheme.empty()) { + len += str_size("http://"); + } else { + len += req.scheme.size() + str_size("://"); + } + + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + + if (req.scheme.empty()) { + // We may have to log the request which lacks scheme (e.g., + // http/1.1 with origin form). + p = util::copy_lit(p, "http://"); + } else { + p = std::copy(std::begin(req.scheme), std::end(req.scheme), p); + p = util::copy_lit(p, "://"); + } + p = std::copy(std::begin(req.authority), std::end(req.authority), p); + p = std::copy(std::begin(req.path), std::end(req.path), p); + *p = '\0'; + + return StringRef{iov.base, p}; +} +} // namespace + +void upstream_accesslog(const std::vector &lfv, + const LogSpec &lgsp) { + auto config = get_config(); + auto lgconf = log_config(); + auto &accessconf = get_config()->logging.access; + + if (lgconf->accesslog_fd == -1 && !accessconf.syslog) { + return; + } + + std::array buf; + + auto downstream = lgsp.downstream; + + const auto &req = downstream->request(); + const auto &resp = downstream->response(); + const auto &tstamp = req.tstamp; + auto &balloc = downstream->get_block_allocator(); + + auto downstream_addr = downstream->get_addr(); + auto method = req.method == -1 ? StringRef::from_lit("") + : http2::to_method_string(req.method); + auto path = + req.method == HTTP_CONNECT ? req.authority + : config->http2_proxy ? construct_absolute_request_uri(balloc, req) + : req.path.empty() ? req.method == HTTP_OPTIONS ? StringRef::from_lit("*") + : StringRef::from_lit("-") + : req.path; + auto path_without_query = + req.method == HTTP_CONNECT + ? path + : StringRef{std::begin(path), + std::find(std::begin(path), std::end(path), '?')}; + + auto p = std::begin(buf); + auto last = std::end(buf) - 2; + + for (auto &lf : lfv) { + switch (lf.type) { + case LogFragmentType::LITERAL: + std::tie(p, last) = copy(lf.value, p, last); + break; + case LogFragmentType::REMOTE_ADDR: + std::tie(p, last) = copy(lgsp.remote_addr, p, last); + break; + case LogFragmentType::TIME_LOCAL: + std::tie(p, last) = copy(tstamp->time_local, p, last); + break; + case LogFragmentType::TIME_ISO8601: + std::tie(p, last) = copy(tstamp->time_iso8601, p, last); + break; + case LogFragmentType::REQUEST: + std::tie(p, last) = copy(method, p, last); + std::tie(p, last) = copy(' ', p, last); + std::tie(p, last) = copy_escape(path, p, last); + std::tie(p, last) = copy_l(" HTTP/", p, last); + std::tie(p, last) = copy(req.http_major, p, last); + if (req.http_major < 2) { + std::tie(p, last) = copy('.', p, last); + std::tie(p, last) = copy(req.http_minor, p, last); + } + break; + case LogFragmentType::METHOD: + std::tie(p, last) = copy(method, p, last); + break; + case LogFragmentType::PATH: + std::tie(p, last) = copy_escape(path, p, last); + break; + case LogFragmentType::PATH_WITHOUT_QUERY: + std::tie(p, last) = copy_escape(path_without_query, p, last); + break; + case LogFragmentType::PROTOCOL_VERSION: + std::tie(p, last) = copy_l("HTTP/", p, last); + std::tie(p, last) = copy(req.http_major, p, last); + if (req.http_major < 2) { + std::tie(p, last) = copy('.', p, last); + std::tie(p, last) = copy(req.http_minor, p, last); + } + break; + case LogFragmentType::STATUS: + std::tie(p, last) = copy(resp.http_status, p, last); + break; + case LogFragmentType::BODY_BYTES_SENT: + std::tie(p, last) = copy(downstream->response_sent_body_length, p, last); + break; + case LogFragmentType::HTTP: { + auto hd = req.fs.header(lf.value); + if (hd) { + std::tie(p, last) = copy_escape((*hd).value, p, last); + break; + } + + std::tie(p, last) = copy('-', p, last); + + break; + } + case LogFragmentType::AUTHORITY: + if (!req.authority.empty()) { + std::tie(p, last) = copy(req.authority, p, last); + break; + } + + std::tie(p, last) = copy('-', p, last); + + break; + case LogFragmentType::REMOTE_PORT: + std::tie(p, last) = copy(lgsp.remote_port, p, last); + break; + case LogFragmentType::SERVER_PORT: + std::tie(p, last) = copy(lgsp.server_port, p, last); + break; + case LogFragmentType::REQUEST_TIME: { + auto t = std::chrono::duration_cast( + lgsp.request_end_time - downstream->get_request_start_time()) + .count(); + std::tie(p, last) = copy(t / 1000, p, last); + std::tie(p, last) = copy('.', p, last); + auto frac = t % 1000; + if (frac < 100) { + auto n = frac < 10 ? 2 : 1; + std::tie(p, last) = copy("000", n, p, last); + } + std::tie(p, last) = copy(frac, p, last); + break; + } + case LogFragmentType::PID: + std::tie(p, last) = copy(lgsp.pid, p, last); + break; + case LogFragmentType::ALPN: + std::tie(p, last) = copy_escape(lgsp.alpn, p, last); + break; + case LogFragmentType::TLS_CIPHER: + if (!lgsp.ssl) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy(SSL_get_cipher_name(lgsp.ssl), p, last); + break; + case LogFragmentType::TLS_PROTOCOL: + if (!lgsp.ssl) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = + copy(nghttp2::tls::get_tls_protocol(lgsp.ssl), p, last); + break; + case LogFragmentType::TLS_SESSION_ID: { + auto session = SSL_get_session(lgsp.ssl); + if (!session) { + std::tie(p, last) = copy('-', p, last); + break; + } + unsigned int session_id_length = 0; + auto session_id = SSL_SESSION_get_id(session, &session_id_length); + if (session_id_length == 0) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy_hex_low(session_id, session_id_length, p, last); + break; + } + case LogFragmentType::TLS_SESSION_REUSED: + if (!lgsp.ssl) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = + copy(SSL_session_reused(lgsp.ssl) ? 'r' : '.', p, last); + break; + case LogFragmentType::TLS_SNI: + if (lgsp.sni.empty()) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy_escape(lgsp.sni, p, last); + break; + case LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA1: + case LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256: { + if (!lgsp.ssl) { + std::tie(p, last) = copy('-', p, last); + break; + } +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(lgsp.ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(lgsp.ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::array buf; + auto len = tls::get_x509_fingerprint( + buf.data(), buf.size(), x, + lf.type == LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256 + ? EVP_sha256() + : EVP_sha1()); +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + if (len <= 0) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy_hex_low(buf.data(), len, p, last); + break; + } + case LogFragmentType::TLS_CLIENT_ISSUER_NAME: + case LogFragmentType::TLS_CLIENT_SUBJECT_NAME: { + if (!lgsp.ssl) { + std::tie(p, last) = copy('-', p, last); + break; + } +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(lgsp.ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(lgsp.ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + std::tie(p, last) = copy('-', p, last); + break; + } + auto name = lf.type == LogFragmentType::TLS_CLIENT_ISSUER_NAME + ? tls::get_x509_issuer_name(balloc, x) + : tls::get_x509_subject_name(balloc, x); +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + if (name.empty()) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy(name, p, last); + break; + } + case LogFragmentType::TLS_CLIENT_SERIAL: { + if (!lgsp.ssl) { + std::tie(p, last) = copy('-', p, last); + break; + } +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(lgsp.ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(lgsp.ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + std::tie(p, last) = copy('-', p, last); + break; + } + auto sn = tls::get_x509_serial(balloc, x); +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + if (sn.empty()) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy(sn, p, last); + break; + } + case LogFragmentType::BACKEND_HOST: + if (!downstream_addr) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy(downstream_addr->host, p, last); + break; + case LogFragmentType::BACKEND_PORT: + if (!downstream_addr) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy(downstream_addr->port, p, last); + break; + case LogFragmentType::NONE: + break; + default: + break; + } + } + + *p = '\0'; + + if (accessconf.syslog) { + syslog(LOG_INFO, "%s", buf.data()); + + return; + } + + *p++ = '\n'; + + auto nwrite = std::distance(std::begin(buf), p); + while (write(lgconf->accesslog_fd, buf.data(), nwrite) == -1 && + errno == EINTR) + ; +} + +int reopen_log_files(const LoggingConfig &loggingconf) { + int res = 0; + int new_accesslog_fd = -1; + int new_errorlog_fd = -1; + + auto lgconf = log_config(); + auto &accessconf = loggingconf.access; + auto &errorconf = loggingconf.error; + + if (!accessconf.syslog && !accessconf.file.empty()) { + new_accesslog_fd = open_log_file(accessconf.file.c_str()); + + if (new_accesslog_fd == -1) { + LOG(ERROR) << "Failed to open accesslog file " << accessconf.file; + res = -1; + } + } + + if (!errorconf.syslog && !errorconf.file.empty()) { + new_errorlog_fd = open_log_file(errorconf.file.c_str()); + + if (new_errorlog_fd == -1) { + if (lgconf->errorlog_fd != -1) { + LOG(ERROR) << "Failed to open errorlog file " << errorconf.file; + } else { + std::cerr << "Failed to open errorlog file " << errorconf.file + << std::endl; + } + + res = -1; + } + } + + close_log_file(lgconf->accesslog_fd); + close_log_file(lgconf->errorlog_fd); + + lgconf->accesslog_fd = new_accesslog_fd; + lgconf->errorlog_fd = new_errorlog_fd; + lgconf->errorlog_tty = + (new_errorlog_fd == -1) ? false : isatty(new_errorlog_fd); + + return res; +} + +void log_chld(pid_t pid, int rstatus, const char *msg) { + std::string signalstr; + if (WIFSIGNALED(rstatus)) { + signalstr += "; signal "; + auto sig = WTERMSIG(rstatus); + auto s = strsignal(sig); + if (s) { + signalstr += s; + signalstr += '('; + } else { + signalstr += "UNKNOWN("; + } + signalstr += util::utos(sig); + signalstr += ')'; + } + + LOG(NOTICE) << msg << ": [" << pid << "] exited " + << (WIFEXITED(rstatus) ? "normally" : "abnormally") + << " with status " << log::hex << rstatus << log::dec + << "; exit status " + << (WIFEXITED(rstatus) ? WEXITSTATUS(rstatus) : 0) + << (signalstr.empty() ? "" : signalstr.c_str()); +} + +void redirect_stderr_to_errorlog(const LoggingConfig &loggingconf) { + auto lgconf = log_config(); + auto &errorconf = loggingconf.error; + + if (errorconf.syslog || lgconf->errorlog_fd == -1) { + return; + } + + dup2(lgconf->errorlog_fd, STDERR_FILENO); +} + +namespace { +int STDERR_COPY = -1; +int STDOUT_COPY = -1; +} // namespace + +void store_original_fds() { + // consider dup'ing stdout too + STDERR_COPY = dup(STDERR_FILENO); + STDOUT_COPY = STDOUT_FILENO; + // no race here, since it is called early + util::make_socket_closeonexec(STDERR_COPY); +} + +void restore_original_fds() { dup2(STDERR_COPY, STDERR_FILENO); } + +void close_log_file(int &fd) { + if (fd != STDERR_COPY && fd != STDOUT_COPY && fd != -1) { + close(fd); + } + fd = -1; +} + +int open_log_file(const char *path) { + + if (strcmp(path, "/dev/stdout") == 0 || + strcmp(path, "/proc/self/fd/1") == 0) { + return STDOUT_COPY; + } + + if (strcmp(path, "/dev/stderr") == 0 || + strcmp(path, "/proc/self/fd/2") == 0) { + return STDERR_COPY; + } +#ifdef O_CLOEXEC + + auto fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP); +#else // !O_CLOEXEC + + auto fd = + open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP); + + // We get race condition if execve is called at the same time. + if (fd != -1) { + util::make_socket_closeonexec(fd); + } + +#endif // !O_CLOEXEC + + if (fd == -1) { + return -1; + } + + return fd; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_log.h b/lib/nghttp2/src/shrpx_log.h new file mode 100644 index 00000000000..bc30097c67c --- /dev/null +++ b/lib/nghttp2/src/shrpx_log.h @@ -0,0 +1,318 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_LOG_H +#define SHRPX_LOG_H + +#include "shrpx.h" + +#include + +#include +#include +#include + +#include "shrpx_config.h" +#include "shrpx_log_config.h" +#include "tls.h" +#include "template.h" +#include "util.h" + +using namespace nghttp2; + +#define ENABLE_LOG 1 + +#define LOG_ENABLED(SEVERITY) (ENABLE_LOG && shrpx::Log::log_enabled(SEVERITY)) + +#ifdef __FILE_NAME__ +# define NGHTTP2_FILE_NAME __FILE_NAME__ +#else // !__FILE_NAME__ +# define NGHTTP2_FILE_NAME __FILE__ +#endif // !__FILE_NAME__ + +#define LOG(SEVERITY) shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) + +// Listener log +#define LLOG(SEVERITY, LISTEN) \ + (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \ + << "[LISTEN:" << LISTEN << "] ") + +// Worker log +#define WLOG(SEVERITY, WORKER) \ + (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \ + << "[WORKER:" << WORKER << "] ") + +// ClientHandler log +#define CLOG(SEVERITY, CLIENT_HANDLER) \ + (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \ + << "[CLIENT_HANDLER:" << CLIENT_HANDLER << "] ") + +// Upstream log +#define ULOG(SEVERITY, UPSTREAM) \ + (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \ + << "[UPSTREAM:" << UPSTREAM << "] ") + +// Downstream log +#define DLOG(SEVERITY, DOWNSTREAM) \ + (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \ + << "[DOWNSTREAM:" << DOWNSTREAM << "] ") + +// Downstream connection log +#define DCLOG(SEVERITY, DCONN) \ + (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \ + << "[DCONN:" << DCONN << "] ") + +// Downstream HTTP2 session log +#define SSLOG(SEVERITY, HTTP2) \ + (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \ + << "[DHTTP2:" << HTTP2 << "] ") + +// Memcached connection log +#define MCLOG(SEVERITY, MCONN) \ + (shrpx::Log(SEVERITY, NGHTTP2_FILE_NAME, __LINE__) \ + << "[MCONN:" << MCONN << "] ") + +namespace shrpx { + +class Downstream; +struct DownstreamAddr; + +enum SeverityLevel { INFO, NOTICE, WARN, ERROR, FATAL }; + +using LogBuffer = std::array; + +class Log { +public: + Log(int severity, const char *filename, int linenum); + ~Log(); + Log &operator<<(const std::string &s); + Log &operator<<(const char *s); + Log &operator<<(const StringRef &s); + Log &operator<<(const ImmutableString &s); + Log &operator<<(short n) { return *this << static_cast(n); } + Log &operator<<(int n) { return *this << static_cast(n); } + Log &operator<<(long n) { return *this << static_cast(n); } + Log &operator<<(long long n); + Log &operator<<(unsigned short n) { + return *this << static_cast(n); + } + Log &operator<<(unsigned int n) { + return *this << static_cast(n); + } + Log &operator<<(unsigned long n) { + return *this << static_cast(n); + } + Log &operator<<(unsigned long long n); + Log &operator<<(float n) { return *this << static_cast(n); } + Log &operator<<(double n); + Log &operator<<(long double n); + Log &operator<<(bool n); + Log &operator<<(const void *p); + template Log &operator<<(const std::shared_ptr &ptr) { + return *this << ptr.get(); + } + Log &operator<<(void (*func)(Log &log)) { + func(*this); + return *this; + } + template void write_seq(InputIt first, InputIt last) { + if (full_) { + return; + } + + auto d = std::distance(first, last); + auto n = std::min(wleft(), static_cast(d)); + last_ = std::copy(first, first + n, last_); + update_full(); + } + + template void write_hex(T n) { + if (full_) { + return; + } + + if (n == 0) { + if (wleft() < 4 /* for "0x00" */) { + full_ = true; + return; + } + *last_++ = '0'; + *last_++ = 'x'; + *last_++ = '0'; + *last_++ = '0'; + update_full(); + return; + } + + size_t nlen = 0; + for (auto t = n; t; t >>= 8, ++nlen) + ; + + nlen *= 2; + + if (wleft() < 2 /* for "0x" */ + nlen) { + full_ = true; + return; + } + + *last_++ = '0'; + *last_++ = 'x'; + + last_ += nlen; + update_full(); + + auto p = last_ - 1; + for (; n; n >>= 8) { + uint8_t b = n & 0xff; + *p-- = util::LOWER_XDIGITS[b & 0xf]; + *p-- = util::LOWER_XDIGITS[b >> 4]; + } + } + static void set_severity_level(int severity); + // Returns the severity level by |name|. Returns -1 if |name| is + // unknown. + static int get_severity_level_by_name(const StringRef &name); + static bool log_enabled(int severity) { return severity >= severity_thres_; } + + enum { + fmt_dec = 0x00, + fmt_hex = 0x01, + }; + + void set_flags(int flags) { flags_ = flags; } + +private: + size_t rleft() { return last_ - begin_; } + size_t wleft() { return end_ - last_; } + void update_full() { full_ = last_ == end_; } + + LogBuffer &buf_; + uint8_t *begin_; + uint8_t *end_; + uint8_t *last_; + const char *filename_; + uint32_t flags_; + int severity_; + int linenum_; + bool full_; + static int severity_thres_; +}; + +namespace log { +void hex(Log &log); +void dec(Log &log); +} // namespace log + +#define TTY_HTTP_HD (log_config()->errorlog_tty ? "\033[1;34m" : "") +#define TTY_RST (log_config()->errorlog_tty ? "\033[0m" : "") + +enum class LogFragmentType { + NONE, + LITERAL, + REMOTE_ADDR, + TIME_LOCAL, + TIME_ISO8601, + REQUEST, + STATUS, + BODY_BYTES_SENT, + HTTP, + AUTHORITY, + REMOTE_PORT, + SERVER_PORT, + REQUEST_TIME, + PID, + ALPN, + TLS_CIPHER, + SSL_CIPHER = TLS_CIPHER, + TLS_PROTOCOL, + SSL_PROTOCOL = TLS_PROTOCOL, + TLS_SESSION_ID, + SSL_SESSION_ID = TLS_SESSION_ID, + TLS_SESSION_REUSED, + SSL_SESSION_REUSED = TLS_SESSION_REUSED, + TLS_SNI, + TLS_CLIENT_FINGERPRINT_SHA1, + TLS_CLIENT_FINGERPRINT_SHA256, + TLS_CLIENT_ISSUER_NAME, + TLS_CLIENT_SERIAL, + TLS_CLIENT_SUBJECT_NAME, + BACKEND_HOST, + BACKEND_PORT, + METHOD, + PATH, + PATH_WITHOUT_QUERY, + PROTOCOL_VERSION, +}; + +struct LogFragment { + LogFragment(LogFragmentType type, StringRef value = StringRef::from_lit("")) + : type(type), value(std::move(value)) {} + LogFragmentType type; + StringRef value; +}; + +struct LogSpec { + Downstream *downstream; + StringRef remote_addr; + StringRef alpn; + StringRef sni; + SSL *ssl; + std::chrono::high_resolution_clock::time_point request_end_time; + StringRef remote_port; + uint16_t server_port; + pid_t pid; +}; + +void upstream_accesslog(const std::vector &lf, + const LogSpec &lgsp); + +int reopen_log_files(const LoggingConfig &loggingconf); + +// Logs message when process whose pid is |pid| and exist status is +// |rstatus| exited. The |msg| is prepended to the log message. +void log_chld(pid_t pid, int rstatus, const char *msg); + +void redirect_stderr_to_errorlog(const LoggingConfig &loggingconf); + +// Makes internal copy of stderr (and possibly stdout in the future), +// which is then used as pointer to /dev/stderr or /proc/self/fd/2 +void store_original_fds(); + +// Restores the original stderr that was stored with copy_original_fds +// Used just before execv +void restore_original_fds(); + +// Closes |fd| which was returned by open_log_file (see below) +// and sets it to -1. In the case that |fd| points to stdout or +// stderr, or is -1, the descriptor is not closed (but still set to -1). +void close_log_file(int &fd); + +// Opens |path| with O_APPEND enabled. If file does not exist, it is +// created first. This function returns file descriptor referring the +// opened file if it succeeds, or -1. +int open_log_file(const char *path); + +} // namespace shrpx + +#endif // SHRPX_LOG_H diff --git a/lib/nghttp2/src/shrpx_log_config.cc b/lib/nghttp2/src/shrpx_log_config.cc new file mode 100644 index 00000000000..92eb0559164 --- /dev/null +++ b/lib/nghttp2/src/shrpx_log_config.cc @@ -0,0 +1,127 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "shrpx_log_config.h" + +#include + +#include +#include + +#include "util.h" + +using namespace nghttp2; + +namespace shrpx { + +Timestamp::Timestamp(const std::chrono::system_clock::time_point &tp) { + time_local = util::format_common_log(time_local_buf.data(), tp); + time_iso8601 = util::format_iso8601(time_iso8601_buf.data(), tp); + time_http = util::format_http_date(time_http_buf.data(), tp); +} + +LogConfig::LogConfig() + : time_str_updated(std::chrono::system_clock::now()), + tstamp(std::make_shared(time_str_updated)), + pid(getpid()), + accesslog_fd(-1), + errorlog_fd(-1), + errorlog_tty(false) { + auto tid = std::this_thread::get_id(); + auto tid_hash = + util::hash32(StringRef{reinterpret_cast(&tid), + reinterpret_cast(&tid) + sizeof(tid)}); + thread_id = util::format_hex(reinterpret_cast(&tid_hash), + sizeof(tid_hash)); +} + +#ifndef NOTHREADS +# ifdef HAVE_THREAD_LOCAL +namespace { +thread_local std::unique_ptr config = std::make_unique(); +} // namespace + +LogConfig *log_config() { return config.get(); } +void delete_log_config() {} +# else // !HAVE_THREAD_LOCAL +namespace { +pthread_key_t lckey; +pthread_once_t lckey_once = PTHREAD_ONCE_INIT; +} // namespace + +namespace { +void make_key() { pthread_key_create(&lckey, nullptr); } +} // namespace + +LogConfig *log_config() { + pthread_once(&lckey_once, make_key); + LogConfig *config = (LogConfig *)pthread_getspecific(lckey); + if (!config) { + config = new LogConfig(); + pthread_setspecific(lckey, config); + } + return config; +} + +void delete_log_config() { delete log_config(); } +# endif // !HAVE_THREAD_LOCAL +#else // NOTHREADS +namespace { +std::unique_ptr config = std::make_unique(); +} // namespace + +LogConfig *log_config() { return config.get(); } + +void delete_log_config() {} +#endif // NOTHREADS + +void LogConfig::update_tstamp_millis( + const std::chrono::system_clock::time_point &now) { + if (std::chrono::duration_cast( + now.time_since_epoch()) == + std::chrono::duration_cast( + time_str_updated.time_since_epoch())) { + return; + } + + time_str_updated = now; + + tstamp = std::make_shared(now); +} + +void LogConfig::update_tstamp( + const std::chrono::system_clock::time_point &now) { + if (std::chrono::duration_cast( + now.time_since_epoch()) == + std::chrono::duration_cast( + time_str_updated.time_since_epoch())) { + return; + } + + time_str_updated = now; + + tstamp = std::make_shared(now); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_log_config.h b/lib/nghttp2/src/shrpx_log_config.h new file mode 100644 index 00000000000..76fa78b6594 --- /dev/null +++ b/lib/nghttp2/src/shrpx_log_config.h @@ -0,0 +1,79 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 SHRPX_LOG_CONFIG_H +#define SHRPX_LOG_CONFIG_H + +#include "shrpx.h" + +#include + +#include + +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +struct Timestamp { + Timestamp(const std::chrono::system_clock::time_point &tp); + + std::array time_local_buf; + std::array time_iso8601_buf; + std::array time_http_buf; + StringRef time_local; + StringRef time_iso8601; + StringRef time_http; +}; + +struct LogConfig { + std::chrono::system_clock::time_point time_str_updated; + std::shared_ptr tstamp; + std::string thread_id; + pid_t pid; + int accesslog_fd; + int errorlog_fd; + // true if errorlog_fd is referring to a terminal. + bool errorlog_tty; + + LogConfig(); + // Updates time stamp if difference between time_str_updated and now + // is 1 or more milliseconds. + void update_tstamp_millis(const std::chrono::system_clock::time_point &now); + // Updates time stamp if difference between time_str_updated and + // now, converted to time_t, is 1 or more seconds. + void update_tstamp(const std::chrono::system_clock::time_point &now); +}; + +// We need LogConfig per thread to avoid data race around opening file +// descriptor for log files. +LogConfig *log_config(); + +// Deletes log_config +void delete_log_config(); + +} // namespace shrpx + +#endif // SHRPX_LOG_CONFIG_H diff --git a/lib/nghttp2/src/shrpx_memcached_connection.cc b/lib/nghttp2/src/shrpx_memcached_connection.cc new file mode 100644 index 00000000000..f72cb111c77 --- /dev/null +++ b/lib/nghttp2/src/shrpx_memcached_connection.cc @@ -0,0 +1,777 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_memcached_connection.h" + +#include +#include + +#include + +#include "shrpx_memcached_request.h" +#include "shrpx_memcached_result.h" +#include "shrpx_config.h" +#include "shrpx_tls.h" +#include "shrpx_log.h" +#include "util.h" + +namespace shrpx { + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto conn = static_cast(w->data); + auto mconn = static_cast(conn->data); + + if (w == &conn->rt && !conn->expired_rt()) { + return; + } + + if (LOG_ENABLED(INFO)) { + MCLOG(INFO, mconn) << "Time out"; + } + + mconn->disconnect(); +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn = static_cast(w->data); + auto mconn = static_cast(conn->data); + + if (mconn->on_read() != 0) { + mconn->reconnect_or_fail(); + return; + } +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn = static_cast(w->data); + auto mconn = static_cast(conn->data); + + if (mconn->on_write() != 0) { + mconn->reconnect_or_fail(); + return; + } +} +} // namespace + +namespace { +void connectcb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn = static_cast(w->data); + auto mconn = static_cast(conn->data); + + if (mconn->connected() != 0) { + mconn->disconnect(); + return; + } + + writecb(loop, w, revents); +} +} // namespace + +constexpr auto write_timeout = 10_s; +constexpr auto read_timeout = 10_s; + +MemcachedConnection::MemcachedConnection(const Address *addr, + struct ev_loop *loop, SSL_CTX *ssl_ctx, + const StringRef &sni_name, + MemchunkPool *mcpool, + std::mt19937 &gen) + : conn_(loop, -1, nullptr, mcpool, write_timeout, read_timeout, {}, {}, + connectcb, readcb, timeoutcb, this, 0, 0., Proto::MEMCACHED), + do_read_(&MemcachedConnection::noop), + do_write_(&MemcachedConnection::noop), + sni_name_(sni_name), + connect_blocker_( + gen, loop, [] {}, [] {}), + parse_state_{}, + addr_(addr), + ssl_ctx_(ssl_ctx), + sendsum_(0), + try_count_(0), + connected_(false) {} + +MemcachedConnection::~MemcachedConnection() { conn_.disconnect(); } + +namespace { +void clear_request(std::deque> &q) { + for (auto &req : q) { + if (req->cb) { + req->cb(req.get(), + MemcachedResult(MemcachedStatusCode::EXT_NETWORK_ERROR)); + } + } + q.clear(); +} +} // namespace + +void MemcachedConnection::disconnect() { + clear_request(recvq_); + clear_request(sendq_); + + sendbufv_.clear(); + sendsum_ = 0; + + parse_state_ = {}; + + connected_ = false; + + conn_.disconnect(); + + assert(recvbuf_.rleft() == 0); + recvbuf_.reset(); + + do_read_ = do_write_ = &MemcachedConnection::noop; +} + +int MemcachedConnection::initiate_connection() { + assert(conn_.fd == -1); + + if (ssl_ctx_) { + auto ssl = tls::create_ssl(ssl_ctx_); + if (!ssl) { + return -1; + } + conn_.set_ssl(ssl); + conn_.tls.client_session_cache = &tls_session_cache_; + } + + conn_.fd = util::create_nonblock_socket(addr_->su.storage.ss_family); + + if (conn_.fd == -1) { + auto error = errno; + MCLOG(WARN, this) << "socket() failed; errno=" << error; + + return -1; + } + + int rv; + rv = connect(conn_.fd, &addr_->su.sa, addr_->len); + if (rv != 0 && errno != EINPROGRESS) { + auto error = errno; + MCLOG(WARN, this) << "connect() failed; errno=" << error; + + close(conn_.fd); + conn_.fd = -1; + + return -1; + } + + if (ssl_ctx_) { + if (!util::numeric_host(sni_name_.c_str())) { + SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name_.c_str()); + } + + auto session = tls::reuse_tls_session(tls_session_cache_); + if (session) { + SSL_set_session(conn_.tls.ssl, session); + SSL_SESSION_free(session); + } + + conn_.prepare_client_handshake(); + } + + if (LOG_ENABLED(INFO)) { + MCLOG(INFO, this) << "Connecting to memcached server"; + } + + ev_io_set(&conn_.wev, conn_.fd, EV_WRITE); + ev_io_set(&conn_.rev, conn_.fd, EV_READ); + + ev_set_cb(&conn_.wev, connectcb); + + conn_.wlimit.startw(); + ev_timer_again(conn_.loop, &conn_.wt); + + return 0; +} + +int MemcachedConnection::connected() { + auto sock_error = util::get_socket_error(conn_.fd); + if (sock_error != 0) { + MCLOG(WARN, this) << "memcached connect failed; addr=" + << util::to_numeric_addr(addr_) + << ": errno=" << sock_error; + + connect_blocker_.on_failure(); + + conn_.wlimit.stopw(); + + return -1; + } + + if (LOG_ENABLED(INFO)) { + MCLOG(INFO, this) << "connected to memcached server"; + } + + conn_.rlimit.startw(); + + ev_set_cb(&conn_.wev, writecb); + + if (conn_.tls.ssl) { + conn_.again_rt(); + + do_read_ = &MemcachedConnection::tls_handshake; + do_write_ = &MemcachedConnection::tls_handshake; + + return 0; + } + + ev_timer_stop(conn_.loop, &conn_.wt); + + connected_ = true; + + connect_blocker_.on_success(); + + do_read_ = &MemcachedConnection::read_clear; + do_write_ = &MemcachedConnection::write_clear; + + return 0; +} + +int MemcachedConnection::on_write() { return do_write_(*this); } +int MemcachedConnection::on_read() { return do_read_(*this); } + +int MemcachedConnection::tls_handshake() { + ERR_clear_error(); + + conn_.last_read = std::chrono::steady_clock::now(); + + auto rv = conn_.tls_handshake(); + if (rv == SHRPX_ERR_INPROGRESS) { + return 0; + } + + if (rv < 0) { + connect_blocker_.on_failure(); + return rv; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "SSL/TLS handshake completed"; + } + + auto &tlsconf = get_config()->tls; + + if (!tlsconf.insecure && + tls::check_cert(conn_.tls.ssl, addr_, sni_name_) != 0) { + connect_blocker_.on_failure(); + return -1; + } + + ev_timer_stop(conn_.loop, &conn_.rt); + ev_timer_stop(conn_.loop, &conn_.wt); + + connected_ = true; + + connect_blocker_.on_success(); + + do_read_ = &MemcachedConnection::read_tls; + do_write_ = &MemcachedConnection::write_tls; + + return on_write(); +} + +int MemcachedConnection::write_tls() { + if (!connected_) { + return 0; + } + + conn_.last_read = std::chrono::steady_clock::now(); + + std::array iov; + std::array buf; + + for (; !sendq_.empty();) { + auto iovcnt = fill_request_buffer(iov.data(), iov.size()); + auto p = std::begin(buf); + for (size_t i = 0; i < iovcnt; ++i) { + auto &v = iov[i]; + auto n = std::min(static_cast(std::end(buf) - p), v.iov_len); + p = std::copy_n(static_cast(v.iov_base), n, p); + if (p == std::end(buf)) { + break; + } + } + + auto nwrite = conn_.write_tls(buf.data(), p - std::begin(buf)); + if (nwrite < 0) { + return -1; + } + if (nwrite == 0) { + return 0; + } + + drain_send_queue(nwrite); + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + return 0; +} + +int MemcachedConnection::read_tls() { + if (!connected_) { + return 0; + } + + conn_.last_read = std::chrono::steady_clock::now(); + + for (;;) { + auto nread = conn_.read_tls(recvbuf_.last, recvbuf_.wleft()); + + if (nread == 0) { + return 0; + } + + if (nread < 0) { + return -1; + } + + recvbuf_.write(nread); + + if (parse_packet() != 0) { + return -1; + } + } + + return 0; +} + +int MemcachedConnection::write_clear() { + if (!connected_) { + return 0; + } + + conn_.last_read = std::chrono::steady_clock::now(); + + std::array iov; + + for (; !sendq_.empty();) { + auto iovcnt = fill_request_buffer(iov.data(), iov.size()); + auto nwrite = conn_.writev_clear(iov.data(), iovcnt); + if (nwrite < 0) { + return -1; + } + if (nwrite == 0) { + return 0; + } + + drain_send_queue(nwrite); + } + + conn_.wlimit.stopw(); + ev_timer_stop(conn_.loop, &conn_.wt); + + return 0; +} + +int MemcachedConnection::read_clear() { + if (!connected_) { + return 0; + } + + conn_.last_read = std::chrono::steady_clock::now(); + + for (;;) { + auto nread = conn_.read_clear(recvbuf_.last, recvbuf_.wleft()); + + if (nread == 0) { + return 0; + } + + if (nread < 0) { + return -1; + } + + recvbuf_.write(nread); + + if (parse_packet() != 0) { + return -1; + } + } + + return 0; +} + +int MemcachedConnection::parse_packet() { + auto in = recvbuf_.pos; + + for (;;) { + auto busy = false; + + switch (parse_state_.state) { + case MemcachedParseState::HEADER24: { + if (recvbuf_.last - in < 24) { + recvbuf_.drain_reset(in - recvbuf_.pos); + return 0; + } + + if (recvq_.empty()) { + MCLOG(WARN, this) + << "Response received, but there is no in-flight request."; + return -1; + } + + auto &req = recvq_.front(); + + if (*in != MEMCACHED_RES_MAGIC) { + MCLOG(WARN, this) << "Response has bad magic: " + << static_cast(*in); + return -1; + } + ++in; + + parse_state_.op = static_cast(*in++); + parse_state_.keylen = util::get_uint16(in); + in += 2; + parse_state_.extralen = *in++; + // skip 1 byte reserved data type + ++in; + parse_state_.status_code = + static_cast(util::get_uint16(in)); + in += 2; + parse_state_.totalbody = util::get_uint32(in); + in += 4; + // skip 4 bytes opaque + in += 4; + parse_state_.cas = util::get_uint64(in); + in += 8; + + if (req->op != parse_state_.op) { + MCLOG(WARN, this) + << "opcode in response does not match to the request: want " + << static_cast(req->op) << ", got " + << static_cast(parse_state_.op); + return -1; + } + + if (parse_state_.keylen != 0) { + MCLOG(WARN, this) << "zero length keylen expected: got " + << parse_state_.keylen; + return -1; + } + + if (parse_state_.totalbody > 16_k) { + MCLOG(WARN, this) << "totalbody is too large: got " + << parse_state_.totalbody; + return -1; + } + + if (parse_state_.op == MemcachedOp::GET && + parse_state_.status_code == MemcachedStatusCode::NO_ERROR && + parse_state_.extralen == 0) { + MCLOG(WARN, this) << "response for GET does not have extra"; + return -1; + } + + if (parse_state_.totalbody < + parse_state_.keylen + parse_state_.extralen) { + MCLOG(WARN, this) << "totalbody is too short: totalbody " + << parse_state_.totalbody << ", want min " + << parse_state_.keylen + parse_state_.extralen; + return -1; + } + + if (parse_state_.extralen) { + parse_state_.state = MemcachedParseState::EXTRA; + parse_state_.read_left = parse_state_.extralen; + } else { + parse_state_.state = MemcachedParseState::VALUE; + parse_state_.read_left = parse_state_.totalbody - parse_state_.keylen - + parse_state_.extralen; + } + busy = true; + break; + } + case MemcachedParseState::EXTRA: { + // We don't use extra for now. Just read and forget. + auto n = std::min(static_cast(recvbuf_.last - in), + parse_state_.read_left); + + parse_state_.read_left -= n; + in += n; + if (parse_state_.read_left) { + recvbuf_.reset(); + return 0; + } + parse_state_.state = MemcachedParseState::VALUE; + // since we require keylen == 0, totalbody - extralen == + // valuelen + parse_state_.read_left = + parse_state_.totalbody - parse_state_.keylen - parse_state_.extralen; + busy = true; + break; + } + case MemcachedParseState::VALUE: { + auto n = std::min(static_cast(recvbuf_.last - in), + parse_state_.read_left); + + parse_state_.value.insert(std::end(parse_state_.value), in, in + n); + + parse_state_.read_left -= n; + in += n; + if (parse_state_.read_left) { + recvbuf_.reset(); + return 0; + } + + if (LOG_ENABLED(INFO)) { + if (parse_state_.status_code != MemcachedStatusCode::NO_ERROR) { + MCLOG(INFO, this) << "response returned error status: " + << static_cast(parse_state_.status_code); + } + } + + // We require at least one complete response to clear try count. + try_count_ = 0; + + auto req = std::move(recvq_.front()); + recvq_.pop_front(); + + if (sendq_.empty() && recvq_.empty()) { + ev_timer_stop(conn_.loop, &conn_.rt); + } + + if (!req->canceled && req->cb) { + req->cb(req.get(), MemcachedResult(parse_state_.status_code, + std::move(parse_state_.value))); + } + + parse_state_ = {}; + break; + } + } + + if (!busy && in == recvbuf_.last) { + break; + } + } + + assert(in == recvbuf_.last); + recvbuf_.reset(); + + return 0; +} + +#undef DEFAULT_WR_IOVCNT +#define DEFAULT_WR_IOVCNT 128 + +#if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT +# define MAX_WR_IOVCNT IOV_MAX +#else // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT +# define MAX_WR_IOVCNT DEFAULT_WR_IOVCNT +#endif // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT + +size_t MemcachedConnection::fill_request_buffer(struct iovec *iov, + size_t iovlen) { + if (sendsum_ == 0) { + for (auto &req : sendq_) { + if (req->canceled) { + continue; + } + if (serialized_size(req.get()) + sendsum_ > 1300) { + break; + } + sendbufv_.emplace_back(); + sendbufv_.back().req = req.get(); + make_request(&sendbufv_.back(), req.get()); + sendsum_ += sendbufv_.back().left(); + } + + if (sendsum_ == 0) { + sendq_.clear(); + return 0; + } + } + + size_t iovcnt = 0; + for (auto &buf : sendbufv_) { + if (iovcnt + 2 > iovlen) { + break; + } + + auto req = buf.req; + if (buf.headbuf.rleft()) { + iov[iovcnt++] = {buf.headbuf.pos, buf.headbuf.rleft()}; + } + if (buf.send_value_left) { + iov[iovcnt++] = {req->value.data() + req->value.size() - + buf.send_value_left, + buf.send_value_left}; + } + } + + return iovcnt; +} + +void MemcachedConnection::drain_send_queue(size_t nwrite) { + sendsum_ -= nwrite; + + while (nwrite > 0) { + auto &buf = sendbufv_.front(); + auto &req = sendq_.front(); + if (req->canceled) { + sendq_.pop_front(); + continue; + } + assert(buf.req == req.get()); + auto n = std::min(static_cast(nwrite), buf.headbuf.rleft()); + buf.headbuf.drain(n); + nwrite -= n; + n = std::min(static_cast(nwrite), buf.send_value_left); + buf.send_value_left -= n; + nwrite -= n; + + if (buf.headbuf.rleft() || buf.send_value_left) { + break; + } + sendbufv_.pop_front(); + recvq_.push_back(std::move(sendq_.front())); + sendq_.pop_front(); + } + + // start read timer only when we wait for responses. + if (recvq_.empty()) { + ev_timer_stop(conn_.loop, &conn_.rt); + } else if (!ev_is_active(&conn_.rt)) { + conn_.again_rt(); + } +} + +size_t MemcachedConnection::serialized_size(MemcachedRequest *req) { + switch (req->op) { + case MemcachedOp::GET: + return 24 + req->key.size(); + case MemcachedOp::ADD: + default: + return 24 + 8 + req->key.size() + req->value.size(); + } +} + +void MemcachedConnection::make_request(MemcachedSendbuf *sendbuf, + MemcachedRequest *req) { + auto &headbuf = sendbuf->headbuf; + + std::fill(std::begin(headbuf.buf), std::end(headbuf.buf), 0); + + headbuf[0] = MEMCACHED_REQ_MAGIC; + headbuf[1] = static_cast(req->op); + switch (req->op) { + case MemcachedOp::GET: + util::put_uint16be(&headbuf[2], req->key.size()); + util::put_uint32be(&headbuf[8], req->key.size()); + headbuf.write(24); + break; + case MemcachedOp::ADD: + util::put_uint16be(&headbuf[2], req->key.size()); + headbuf[4] = 8; + util::put_uint32be(&headbuf[8], 8 + req->key.size() + req->value.size()); + util::put_uint32be(&headbuf[28], req->expiry); + headbuf.write(32); + break; + } + + headbuf.write(req->key.c_str(), req->key.size()); + + sendbuf->send_value_left = req->value.size(); +} + +int MemcachedConnection::add_request(std::unique_ptr req) { + if (connect_blocker_.blocked()) { + return -1; + } + + sendq_.push_back(std::move(req)); + + if (connected_) { + signal_write(); + return 0; + } + + if (conn_.fd == -1 && initiate_connection() != 0) { + connect_blocker_.on_failure(); + disconnect(); + return -1; + } + + return 0; +} + +// TODO should we start write timer too? +void MemcachedConnection::signal_write() { conn_.wlimit.startw(); } + +int MemcachedConnection::noop() { return 0; } + +void MemcachedConnection::reconnect_or_fail() { + if (!connected_ || (recvq_.empty() && sendq_.empty())) { + disconnect(); + return; + } + + constexpr size_t MAX_TRY_COUNT = 3; + + if (++try_count_ >= MAX_TRY_COUNT) { + if (LOG_ENABLED(INFO)) { + MCLOG(INFO, this) << "Tried " << MAX_TRY_COUNT + << " times, and all failed. Aborting"; + } + try_count_ = 0; + disconnect(); + return; + } + + std::vector> q; + q.reserve(recvq_.size() + sendq_.size()); + + if (LOG_ENABLED(INFO)) { + MCLOG(INFO, this) << "Retry connection, enqueue " + << recvq_.size() + sendq_.size() << " request(s) again"; + } + + q.insert(std::end(q), std::make_move_iterator(std::begin(recvq_)), + std::make_move_iterator(std::end(recvq_))); + q.insert(std::end(q), std::make_move_iterator(std::begin(sendq_)), + std::make_move_iterator(std::end(sendq_))); + + recvq_.clear(); + sendq_.clear(); + + disconnect(); + + sendq_.insert(std::end(sendq_), std::make_move_iterator(std::begin(q)), + std::make_move_iterator(std::end(q))); + + if (initiate_connection() != 0) { + connect_blocker_.on_failure(); + disconnect(); + return; + } +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_memcached_connection.h b/lib/nghttp2/src/shrpx_memcached_connection.h new file mode 100644 index 00000000000..2e097e1f8f9 --- /dev/null +++ b/lib/nghttp2/src/shrpx_memcached_connection.h @@ -0,0 +1,155 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MEMCACHED_CONNECTION_H +#define SHRPX_MEMCACHED_CONNECTION_H + +#include "shrpx.h" + +#include +#include + +#include + +#include "shrpx_connection.h" +#include "shrpx_tls.h" +#include "shrpx_connect_blocker.h" +#include "buffer.h" +#include "network.h" + +using namespace nghttp2; + +namespace shrpx { + +struct MemcachedRequest; +enum class MemcachedOp : uint8_t; +enum class MemcachedStatusCode : uint16_t; + +enum class MemcachedParseState { + HEADER24, + EXTRA, + VALUE, +}; + +// Stores state when parsing response from memcached server +struct MemcachedParseContext { + // Buffer for value, dynamically allocated. + std::vector value; + // cas in response + uint64_t cas; + // keylen in response + size_t keylen; + // extralen in response + size_t extralen; + // totalbody in response. The length of value is totalbody - + // extralen - keylen. + size_t totalbody; + // Number of bytes left to read variable length field. + size_t read_left; + // Parser state; see enum above + MemcachedParseState state; + // status_code in response + MemcachedStatusCode status_code; + // op in response + MemcachedOp op; +}; + +struct MemcachedSendbuf { + // Buffer for header + extra + key + Buffer<512> headbuf; + // MemcachedRequest associated to this object + MemcachedRequest *req; + // Number of bytes left when sending value + size_t send_value_left; + // Returns the number of bytes this object transmits. + size_t left() const { return headbuf.rleft() + send_value_left; } +}; + +constexpr uint8_t MEMCACHED_REQ_MAGIC = 0x80; +constexpr uint8_t MEMCACHED_RES_MAGIC = 0x81; + +// MemcachedConnection implements part of memcached binary protocol. +// This is not full brown implementation. Just the part we need is +// implemented. We only use GET and ADD. +// +// https://github.com/memcached/memcached/blob/master/doc/protocol-binary.xml +// https://code.google.com/p/memcached/wiki/MemcacheBinaryProtocol +class MemcachedConnection { +public: + MemcachedConnection(const Address *addr, struct ev_loop *loop, + SSL_CTX *ssl_ctx, const StringRef &sni_name, + MemchunkPool *mcpool, std::mt19937 &gen); + ~MemcachedConnection(); + + void disconnect(); + + int add_request(std::unique_ptr req); + int initiate_connection(); + + int connected(); + int on_write(); + int on_read(); + + int write_clear(); + int read_clear(); + + int tls_handshake(); + int write_tls(); + int read_tls(); + + size_t fill_request_buffer(struct iovec *iov, size_t iovlen); + void drain_send_queue(size_t nwrite); + + void make_request(MemcachedSendbuf *sendbuf, MemcachedRequest *req); + int parse_packet(); + size_t serialized_size(MemcachedRequest *req); + + void signal_write(); + + int noop(); + + void reconnect_or_fail(); + +private: + Connection conn_; + std::deque> recvq_; + std::deque> sendq_; + std::deque sendbufv_; + std::function do_read_, do_write_; + StringRef sni_name_; + tls::TLSSessionCache tls_session_cache_; + ConnectBlocker connect_blocker_; + MemcachedParseContext parse_state_; + const Address *addr_; + SSL_CTX *ssl_ctx_; + // Sum of the bytes to be transmitted in sendbufv_. + size_t sendsum_; + size_t try_count_; + bool connected_; + Buffer<8_k> recvbuf_; +}; + +} // namespace shrpx + +#endif // SHRPX_MEMCACHED_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_memcached_dispatcher.cc b/lib/nghttp2/src/shrpx_memcached_dispatcher.cc new file mode 100644 index 00000000000..024bd5ad5ce --- /dev/null +++ b/lib/nghttp2/src/shrpx_memcached_dispatcher.cc @@ -0,0 +1,53 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_memcached_dispatcher.h" + +#include "shrpx_memcached_request.h" +#include "shrpx_memcached_connection.h" +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +MemcachedDispatcher::MemcachedDispatcher(const Address *addr, + struct ev_loop *loop, SSL_CTX *ssl_ctx, + const StringRef &sni_name, + MemchunkPool *mcpool, + std::mt19937 &gen) + : loop_(loop), + mconn_(std::make_unique(addr, loop_, ssl_ctx, + sni_name, mcpool, gen)) {} + +MemcachedDispatcher::~MemcachedDispatcher() {} + +int MemcachedDispatcher::add_request(std::unique_ptr req) { + if (mconn_->add_request(std::move(req)) != 0) { + return -1; + } + + return 0; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_memcached_dispatcher.h b/lib/nghttp2/src/shrpx_memcached_dispatcher.h new file mode 100644 index 00000000000..de1af80ba04 --- /dev/null +++ b/lib/nghttp2/src/shrpx_memcached_dispatcher.h @@ -0,0 +1,63 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MEMCACHED_DISPATCHER_H +#define SHRPX_MEMCACHED_DISPATCHER_H + +#include "shrpx.h" + +#include +#include + +#include + +#include + +#include "memchunk.h" +#include "network.h" + +using namespace nghttp2; + +namespace shrpx { + +struct MemcachedRequest; +class MemcachedConnection; + +class MemcachedDispatcher { +public: + MemcachedDispatcher(const Address *addr, struct ev_loop *loop, + SSL_CTX *ssl_ctx, const StringRef &sni_name, + MemchunkPool *mcpool, std::mt19937 &gen); + ~MemcachedDispatcher(); + + int add_request(std::unique_ptr req); + +private: + struct ev_loop *loop_; + std::unique_ptr mconn_; +}; + +} // namespace shrpx + +#endif // SHRPX_MEMCACHED_DISPATCHER_H diff --git a/lib/nghttp2/src/shrpx_memcached_request.h b/lib/nghttp2/src/shrpx_memcached_request.h new file mode 100644 index 00000000000..36969831943 --- /dev/null +++ b/lib/nghttp2/src/shrpx_memcached_request.h @@ -0,0 +1,59 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MEMCACHED_REQUEST_H +#define SHRPX_MEMCACHED_REQUEST_H + +#include "shrpx.h" + +#include +#include +#include + +#include "shrpx_memcached_result.h" + +namespace shrpx { + +enum class MemcachedOp : uint8_t { + GET = 0x00, + ADD = 0x02, +}; + +struct MemcachedRequest; + +using MemcachedResultCallback = + std::function; + +struct MemcachedRequest { + std::string key; + std::vector value; + MemcachedResultCallback cb; + uint32_t expiry; + MemcachedOp op; + bool canceled; +}; + +} // namespace shrpx + +#endif // SHRPX_MEMCACHED_REQUEST_H diff --git a/lib/nghttp2/src/shrpx_memcached_result.h b/lib/nghttp2/src/shrpx_memcached_result.h new file mode 100644 index 00000000000..60a87afc698 --- /dev/null +++ b/lib/nghttp2/src/shrpx_memcached_result.h @@ -0,0 +1,50 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MEMCACHED_RESULT_H +#define SHRPX_MEMCACHED_RESULT_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +enum class MemcachedStatusCode : uint16_t { + NO_ERROR, + EXT_NETWORK_ERROR = 0x1001, +}; + +struct MemcachedResult { + MemcachedResult(MemcachedStatusCode status_code) : status_code(status_code) {} + MemcachedResult(MemcachedStatusCode status_code, std::vector value) + : value(std::move(value)), status_code(status_code) {} + + std::vector value; + MemcachedStatusCode status_code; +}; + +} // namespace shrpx + +#endif // SHRPX_MEMCACHED_RESULT_H diff --git a/lib/nghttp2/src/shrpx_mruby.cc b/lib/nghttp2/src/shrpx_mruby.cc new file mode 100644 index 00000000000..b5c6ed3c85a --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby.cc @@ -0,0 +1,238 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_mruby.h" + +#include +#include + +#include "shrpx_downstream.h" +#include "shrpx_config.h" +#include "shrpx_mruby_module.h" +#include "shrpx_downstream_connection.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace mruby { + +MRubyContext::MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env) + : mrb_(mrb), app_(std::move(app)), env_(std::move(env)) {} + +MRubyContext::~MRubyContext() { + if (mrb_) { + mrb_close(mrb_); + } +} + +int MRubyContext::run_app(Downstream *downstream, int phase) { + if (!mrb_) { + return 0; + } + + MRubyAssocData data{downstream, phase}; + + mrb_->ud = &data; + + int rv = 0; + auto ai = mrb_gc_arena_save(mrb_); + auto ai_d = defer([ai, this]() { mrb_gc_arena_restore(mrb_, ai); }); + + const char *method; + switch (phase) { + case PHASE_REQUEST: + if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_req"))) { + return 0; + } + method = "on_req"; + break; + case PHASE_RESPONSE: + if (!mrb_respond_to(mrb_, app_, mrb_intern_lit(mrb_, "on_resp"))) { + return 0; + } + method = "on_resp"; + break; + default: + assert(0); + abort(); + } + + auto res = mrb_funcall(mrb_, app_, method, 1, env_); + (void)res; + + if (mrb_->exc) { + // If response has been committed, ignore error + if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) { + rv = -1; + } + + auto exc = mrb_obj_value(mrb_->exc); + auto inspect = mrb_inspect(mrb_, exc); + + LOG(ERROR) << "Exception caught while executing mruby code: " + << mrb_str_to_cstr(mrb_, inspect); + } + + mrb_->ud = nullptr; + + return rv; +} + +int MRubyContext::run_on_request_proc(Downstream *downstream) { + return run_app(downstream, PHASE_REQUEST); +} + +int MRubyContext::run_on_response_proc(Downstream *downstream) { + return run_app(downstream, PHASE_RESPONSE); +} + +void MRubyContext::delete_downstream(Downstream *downstream) { + if (!mrb_) { + return; + } + delete_downstream_from_module(mrb_, downstream); +} + +namespace { +mrb_value instantiate_app(mrb_state *mrb, RProc *proc) { + mrb->ud = nullptr; + + auto res = mrb_top_run(mrb, proc, mrb_top_self(mrb), 0); + + if (mrb->exc) { + auto exc = mrb_obj_value(mrb->exc); + auto inspect = mrb_inspect(mrb, exc); + + LOG(ERROR) << "Exception caught while executing mruby code: " + << mrb_str_to_cstr(mrb, inspect); + + return mrb_nil_value(); + } + + return res; +} +} // namespace + +// Based on +// https://github.com/h2o/h2o/blob/master/lib/handler/mruby.c. It is +// very hard to write these kind of code because mruby has almost no +// documentation about compiling or generating code, at least at the +// time of this writing. +RProc *compile(mrb_state *mrb, const StringRef &filename) { + if (filename.empty()) { + return nullptr; + } + + auto infile = fopen(filename.c_str(), "rb"); + if (infile == nullptr) { + LOG(ERROR) << "Could not open mruby file " << filename; + return nullptr; + } + auto infile_d = defer(fclose, infile); + + auto mrbc = mrbc_context_new(mrb); + if (mrbc == nullptr) { + LOG(ERROR) << "mrb_context_new failed"; + return nullptr; + } + auto mrbc_d = defer(mrbc_context_free, mrb, mrbc); + + auto parser = mrb_parse_file(mrb, infile, nullptr); + if (parser == nullptr) { + LOG(ERROR) << "mrb_parse_nstring failed"; + return nullptr; + } + auto parser_d = defer(mrb_parser_free, parser); + + if (parser->nerr != 0) { + LOG(ERROR) << "mruby parser detected parse error"; + return nullptr; + } + + auto proc = mrb_generate_code(mrb, parser); + if (proc == nullptr) { + LOG(ERROR) << "mrb_generate_code failed"; + return nullptr; + } + + return proc; +} + +std::unique_ptr create_mruby_context(const StringRef &filename) { + if (filename.empty()) { + return std::make_unique(nullptr, mrb_nil_value(), + mrb_nil_value()); + } + + auto mrb = mrb_open(); + if (mrb == nullptr) { + LOG(ERROR) << "mrb_open failed"; + return nullptr; + } + + auto ai = mrb_gc_arena_save(mrb); + + auto req_proc = compile(mrb, filename); + + if (!req_proc) { + mrb_gc_arena_restore(mrb, ai); + LOG(ERROR) << "Could not compile mruby code " << filename; + mrb_close(mrb); + return nullptr; + } + + auto env = init_module(mrb); + + auto app = instantiate_app(mrb, req_proc); + if (mrb_nil_p(app)) { + mrb_gc_arena_restore(mrb, ai); + LOG(ERROR) << "Could not instantiate mruby app from " << filename; + mrb_close(mrb); + return nullptr; + } + + mrb_gc_arena_restore(mrb, ai); + + // TODO These are not necessary, because we retain app and env? + mrb_gc_protect(mrb, env); + mrb_gc_protect(mrb, app); + + return std::make_unique(mrb, std::move(app), std::move(env)); +} + +mrb_sym intern_ptr(mrb_state *mrb, void *ptr) { + auto p = reinterpret_cast(ptr); + + return mrb_intern(mrb, reinterpret_cast(&p), sizeof(p)); +} + +void check_phase(mrb_state *mrb, int phase, int phase_mask) { + if ((phase & phase_mask) == 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "operation was not allowed in this phase"); + } +} + +} // namespace mruby + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_mruby.h b/lib/nghttp2/src/shrpx_mruby.h new file mode 100644 index 00000000000..518063d1348 --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby.h @@ -0,0 +1,89 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MRUBY_H +#define SHRPX_MRUBY_H + +#include "shrpx.h" + +#include + +#include +#include + +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +class Downstream; + +namespace mruby { + +class MRubyContext { +public: + MRubyContext(mrb_state *mrb, mrb_value app, mrb_value env); + ~MRubyContext(); + + int run_on_request_proc(Downstream *downstream); + int run_on_response_proc(Downstream *downstream); + + int run_app(Downstream *downstream, int phase); + + void delete_downstream(Downstream *downstream); + +private: + mrb_state *mrb_; + mrb_value app_; + mrb_value env_; +}; + +enum { + PHASE_NONE = 0, + PHASE_REQUEST = 1, + PHASE_RESPONSE = 1 << 1, +}; + +struct MRubyAssocData { + Downstream *downstream; + int phase; +}; + +RProc *compile(mrb_state *mrb, const StringRef &filename); + +std::unique_ptr create_mruby_context(const StringRef &filename); + +// Return interned |ptr|. +mrb_sym intern_ptr(mrb_state *mrb, void *ptr); + +// Checks that |phase| is set in |phase_mask|. If not set, raise +// exception. +void check_phase(mrb_state *mrb, int phase, int phase_mask); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_H diff --git a/lib/nghttp2/src/shrpx_mruby_module.cc b/lib/nghttp2/src/shrpx_mruby_module.cc new file mode 100644 index 00000000000..27b7769c6df --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby_module.cc @@ -0,0 +1,113 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_mruby_module.h" + +#include + +#include +#include +#include +#include + +#include "shrpx_mruby.h" +#include "shrpx_mruby_module_env.h" +#include "shrpx_mruby_module_request.h" +#include "shrpx_mruby_module_response.h" + +namespace shrpx { + +namespace mruby { + +namespace { +mrb_value create_env(mrb_state *mrb) { + auto module = mrb_module_get(mrb, "Nghttpx"); + + auto env_class = mrb_class_get_under(mrb, module, "Env"); + auto request_class = mrb_class_get_under(mrb, module, "Request"); + auto response_class = mrb_class_get_under(mrb, module, "Response"); + + auto env = mrb_obj_new(mrb, env_class, 0, nullptr); + auto req = mrb_obj_new(mrb, request_class, 0, nullptr); + auto resp = mrb_obj_new(mrb, response_class, 0, nullptr); + + mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "req"), req); + mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "resp"), resp); + + return env; +} +} // namespace + +void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream) { + auto module = mrb_module_get(mrb, "Nghttpx"); + auto env = mrb_obj_iv_get(mrb, reinterpret_cast(module), + mrb_intern_lit(mrb, "env")); + if (mrb_nil_p(env)) { + return; + } + + mrb_iv_remove(mrb, env, intern_ptr(mrb, downstream)); +} + +mrb_value init_module(mrb_state *mrb) { + auto module = mrb_define_module(mrb, "Nghttpx"); + + mrb_define_const(mrb, module, "REQUEST_PHASE", + mrb_fixnum_value(PHASE_REQUEST)); + mrb_define_const(mrb, module, "RESPONSE_PHASE", + mrb_fixnum_value(PHASE_RESPONSE)); + + init_env_class(mrb, module); + init_request_class(mrb, module); + init_response_class(mrb, module); + + return create_env(mrb); +} + +mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers) { + auto hash = mrb_hash_new(mrb); + + for (auto &hd : headers) { + if (hd.name.empty() || hd.name[0] == ':') { + continue; + } + auto ai = mrb_gc_arena_save(mrb); + + auto key = mrb_str_new(mrb, hd.name.c_str(), hd.name.size()); + auto ary = mrb_hash_get(mrb, hash, key); + if (mrb_nil_p(ary)) { + ary = mrb_ary_new(mrb); + mrb_hash_set(mrb, hash, key, ary); + } + mrb_ary_push(mrb, ary, mrb_str_new(mrb, hd.value.c_str(), hd.value.size())); + + mrb_gc_arena_restore(mrb, ai); + } + + return hash; +} + +} // namespace mruby + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_mruby_module.h b/lib/nghttp2/src/shrpx_mruby_module.h new file mode 100644 index 00000000000..a426bead406 --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby_module.h @@ -0,0 +1,52 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MRUBY_MODULE_H +#define SHRPX_MRUBY_MODULE_H + +#include "shrpx.h" + +#include + +#include "http2.h" + +using namespace nghttp2; + +namespace shrpx { + +class Downstream; + +namespace mruby { + +mrb_value init_module(mrb_state *mrb); + +void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream); + +mrb_value create_headers_hash(mrb_state *mrb, const HeaderRefs &headers); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_H diff --git a/lib/nghttp2/src/shrpx_mruby_module_env.cc b/lib/nghttp2/src/shrpx_mruby_module_env.cc new file mode 100644 index 00000000000..5ebd9c053a0 --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby_module_env.cc @@ -0,0 +1,500 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_mruby_module_env.h" + +#include +#include +#include + +#include "shrpx_downstream.h" +#include "shrpx_upstream.h" +#include "shrpx_client_handler.h" +#include "shrpx_mruby.h" +#include "shrpx_mruby_module.h" +#include "shrpx_log.h" +#include "shrpx_tls.h" + +namespace shrpx { + +namespace mruby { + +namespace { +mrb_value env_init(mrb_state *mrb, mrb_value self) { return self; } +} // namespace + +namespace { +mrb_value env_get_req(mrb_state *mrb, mrb_value self) { + return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "req")); +} +} // namespace + +namespace { +mrb_value env_get_resp(mrb_state *mrb, mrb_value self) { + return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "resp")); +} +} // namespace + +namespace { +mrb_value env_get_ctx(mrb_state *mrb, mrb_value self) { + auto data = reinterpret_cast(mrb->ud); + auto downstream = data->downstream; + + auto dsym = intern_ptr(mrb, downstream); + + auto ctx = mrb_iv_get(mrb, self, dsym); + if (mrb_nil_p(ctx)) { + ctx = mrb_hash_new(mrb); + mrb_iv_set(mrb, self, dsym, ctx); + } + + return ctx; +} +} // namespace + +namespace { +mrb_value env_get_phase(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + + return mrb_fixnum_value(data->phase); +} +} // namespace + +namespace { +mrb_value env_get_remote_addr(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + + auto &ipaddr = handler->get_ipaddr(); + + return mrb_str_new(mrb, ipaddr.c_str(), ipaddr.size()); +} +} // namespace + +namespace { +mrb_value env_get_server_port(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto faddr = handler->get_upstream_addr(); + + return mrb_fixnum_value(faddr->port); +} +} // namespace + +namespace { +mrb_value env_get_server_addr(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto faddr = handler->get_upstream_addr(); + + return mrb_str_new(mrb, faddr->host.c_str(), faddr->host.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_used(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + + return handler->get_ssl() ? mrb_true_value() : mrb_false_value(); +} +} // namespace + +namespace { +mrb_value env_get_tls_sni(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto sni = handler->get_tls_sni(); + + return mrb_str_new(mrb, sni.c_str(), sni.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_fingerprint_md(mrb_state *mrb, const EVP_MD *md) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_str_new_static(mrb, "", 0); + } + +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + return mrb_str_new_static(mrb, "", 0); + } + + // Currently the largest hash value is SHA-256, which is 32 bytes. + std::array buf; + auto slen = tls::get_x509_fingerprint(buf.data(), buf.size(), x, md); +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + if (slen == -1) { + mrb_raise(mrb, E_RUNTIME_ERROR, "could not compute client fingerprint"); + } + + // TODO Use template version of format_hex + auto &balloc = downstream->get_block_allocator(); + auto f = util::format_hex(balloc, + StringRef{std::begin(buf), std::begin(buf) + slen}); + return mrb_str_new(mrb, f.c_str(), f.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_fingerprint_sha256(mrb_state *mrb, + mrb_value self) { + return env_get_tls_client_fingerprint_md(mrb, EVP_sha256()); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_fingerprint_sha1(mrb_state *mrb, mrb_value self) { + return env_get_tls_client_fingerprint_md(mrb, EVP_sha1()); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_subject_name(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_str_new_static(mrb, "", 0); + } + +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + return mrb_str_new_static(mrb, "", 0); + } + + auto &balloc = downstream->get_block_allocator(); + auto name = tls::get_x509_subject_name(balloc, x); +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + return mrb_str_new(mrb, name.c_str(), name.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_issuer_name(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_str_new_static(mrb, "", 0); + } + +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + return mrb_str_new_static(mrb, "", 0); + } + + auto &balloc = downstream->get_block_allocator(); + auto name = tls::get_x509_issuer_name(balloc, x); +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + return mrb_str_new(mrb, name.c_str(), name.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_serial(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_str_new_static(mrb, "", 0); + } + +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + return mrb_str_new_static(mrb, "", 0); + } + + auto &balloc = downstream->get_block_allocator(); + auto sn = tls::get_x509_serial(balloc, x); +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + return mrb_str_new(mrb, sn.c_str(), sn.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_not_before(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_fixnum_value(0); + } + +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + return mrb_fixnum_value(0); + } + + time_t t; + if (tls::get_x509_not_before(t, x) != 0) { + t = 0; + } + +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + + return mrb_fixnum_value(t); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_not_after(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_fixnum_value(0); + } + +#if OPENSSL_3_0_0_API + auto x = SSL_get0_peer_certificate(ssl); +#else // !OPENSSL_3_0_0_API + auto x = SSL_get_peer_certificate(ssl); +#endif // !OPENSSL_3_0_0_API + if (!x) { + return mrb_fixnum_value(0); + } + + time_t t; + if (tls::get_x509_not_after(t, x) != 0) { + t = 0; + } + +#if !OPENSSL_3_0_0_API + X509_free(x); +#endif // !OPENSSL_3_0_0_API + + return mrb_fixnum_value(t); +} +} // namespace + +namespace { +mrb_value env_get_tls_cipher(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_str_new_static(mrb, "", 0); + } + + return mrb_str_new_cstr(mrb, SSL_get_cipher_name(ssl)); +} +} // namespace + +namespace { +mrb_value env_get_tls_protocol(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_str_new_static(mrb, "", 0); + } + + return mrb_str_new_cstr(mrb, nghttp2::tls::get_tls_protocol(ssl)); +} +} // namespace + +namespace { +mrb_value env_get_tls_session_id(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_str_new_static(mrb, "", 0); + } + + auto session = SSL_get_session(ssl); + if (!session) { + return mrb_str_new_static(mrb, "", 0); + } + + unsigned int session_id_length = 0; + auto session_id = SSL_SESSION_get_id(session, &session_id_length); + + // TODO Use template version of util::format_hex. + auto &balloc = downstream->get_block_allocator(); + auto id = util::format_hex(balloc, StringRef{session_id, session_id_length}); + return mrb_str_new(mrb, id.c_str(), id.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_session_reused(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_false_value(); + } + + return SSL_session_reused(ssl) ? mrb_true_value() : mrb_false_value(); +} +} // namespace + +namespace { +mrb_value env_get_alpn(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto alpn = handler->get_alpn(); + return mrb_str_new(mrb, alpn.c_str(), alpn.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_handshake_finished(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto conn = handler->get_connection(); + return SSL_is_init_finished(conn->tls.ssl) ? mrb_true_value() + : mrb_false_value(); +} +} // namespace + +void init_env_class(mrb_state *mrb, RClass *module) { + auto env_class = + mrb_define_class_under(mrb, module, "Env", mrb->object_class); + + mrb_define_method(mrb, env_class, "initialize", env_init, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "req", env_get_req, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "resp", env_get_resp, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "ctx", env_get_ctx, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "phase", env_get_phase, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "remote_addr", env_get_remote_addr, + MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "server_addr", env_get_server_addr, + MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "server_port", env_get_server_port, + MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_used", env_get_tls_used, + MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_sni", env_get_tls_sni, + MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_fingerprint_sha256", + env_get_tls_client_fingerprint_sha256, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_fingerprint_sha1", + env_get_tls_client_fingerprint_sha1, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_issuer_name", + env_get_tls_client_issuer_name, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_subject_name", + env_get_tls_client_subject_name, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_serial", + env_get_tls_client_serial, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_not_before", + env_get_tls_client_not_before, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_not_after", + env_get_tls_client_not_after, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_cipher", env_get_tls_cipher, + MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_protocol", env_get_tls_protocol, + MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_session_id", env_get_tls_session_id, + MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_session_reused", + env_get_tls_session_reused, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "alpn", env_get_alpn, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_handshake_finished", + env_get_tls_handshake_finished, MRB_ARGS_NONE()); +} + +} // namespace mruby + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_mruby_module_env.h b/lib/nghttp2/src/shrpx_mruby_module_env.h new file mode 100644 index 00000000000..08846788758 --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby_module_env.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MRUBY_MODULE_ENV_H +#define SHRPX_MRUBY_MODULE_ENV_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +namespace mruby { + +void init_env_class(mrb_state *mrb, RClass *module); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_ENV_H diff --git a/lib/nghttp2/src/shrpx_mruby_module_request.cc b/lib/nghttp2/src/shrpx_mruby_module_request.cc new file mode 100644 index 00000000000..1ed3aa90fa0 --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby_module_request.cc @@ -0,0 +1,367 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_mruby_module_request.h" + +#include +#include +#include +#include + +#include "shrpx_downstream.h" +#include "shrpx_upstream.h" +#include "shrpx_client_handler.h" +#include "shrpx_mruby.h" +#include "shrpx_mruby_module.h" +#include "shrpx_log.h" +#include "util.h" +#include "http2.h" + +namespace shrpx { + +namespace mruby { + +namespace { +mrb_value request_init(mrb_state *mrb, mrb_value self) { return self; } +} // namespace + +namespace { +mrb_value request_get_http_version_major(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &req = downstream->request(); + return mrb_fixnum_value(req.http_major); +} +} // namespace + +namespace { +mrb_value request_get_http_version_minor(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &req = downstream->request(); + return mrb_fixnum_value(req.http_minor); +} +} // namespace + +namespace { +mrb_value request_get_method(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &req = downstream->request(); + auto method = http2::to_method_string(req.method); + + return mrb_str_new(mrb, method.c_str(), method.size()); +} +} // namespace + +namespace { +mrb_value request_set_method(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &req = downstream->request(); + + check_phase(mrb, data->phase, PHASE_REQUEST); + + const char *method; + mrb_int n; + mrb_get_args(mrb, "s", &method, &n); + if (n == 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "method must not be empty string"); + } + auto token = + http2::lookup_method_token(reinterpret_cast(method), n); + if (token == -1) { + mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported"); + } + + req.method = token; + + return self; +} +} // namespace + +namespace { +mrb_value request_get_authority(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &req = downstream->request(); + + return mrb_str_new(mrb, req.authority.c_str(), req.authority.size()); +} +} // namespace + +namespace { +mrb_value request_set_authority(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &req = downstream->request(); + + auto &balloc = downstream->get_block_allocator(); + + check_phase(mrb, data->phase, PHASE_REQUEST); + + const char *authority; + mrb_int n; + mrb_get_args(mrb, "s", &authority, &n); + if (n == 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string"); + } + + req.authority = + make_string_ref(balloc, StringRef{authority, static_cast(n)}); + + return self; +} +} // namespace + +namespace { +mrb_value request_get_scheme(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &req = downstream->request(); + + return mrb_str_new(mrb, req.scheme.c_str(), req.scheme.size()); +} +} // namespace + +namespace { +mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &req = downstream->request(); + + auto &balloc = downstream->get_block_allocator(); + + check_phase(mrb, data->phase, PHASE_REQUEST); + + const char *scheme; + mrb_int n; + mrb_get_args(mrb, "s", &scheme, &n); + if (n == 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string"); + } + + req.scheme = + make_string_ref(balloc, StringRef{scheme, static_cast(n)}); + + return self; +} +} // namespace + +namespace { +mrb_value request_get_path(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &req = downstream->request(); + + return mrb_str_new(mrb, req.path.c_str(), req.path.size()); +} +} // namespace + +namespace { +mrb_value request_set_path(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &req = downstream->request(); + + auto &balloc = downstream->get_block_allocator(); + + check_phase(mrb, data->phase, PHASE_REQUEST); + + const char *path; + mrb_int pathlen; + mrb_get_args(mrb, "s", &path, &pathlen); + + req.path = + make_string_ref(balloc, StringRef{path, static_cast(pathlen)}); + + return self; +} +} // namespace + +namespace { +mrb_value request_get_headers(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &req = downstream->request(); + return create_headers_hash(mrb, req.fs.headers()); +} +} // namespace + +namespace { +mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &req = downstream->request(); + auto &balloc = downstream->get_block_allocator(); + + check_phase(mrb, data->phase, PHASE_REQUEST); + + mrb_value key, values; + mrb_get_args(mrb, "So", &key, &values); + + if (RSTRING_LEN(key) == 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed"); + } + + auto ai = mrb_gc_arena_save(mrb); + + key = mrb_funcall(mrb, key, "downcase", 0); + + auto keyref = + make_string_ref(balloc, StringRef{RSTRING_PTR(key), + static_cast(RSTRING_LEN(key))}); + + mrb_gc_arena_restore(mrb, ai); + + auto token = http2::lookup_token(keyref.byte(), keyref.size()); + + if (repl) { + size_t p = 0; + auto &headers = req.fs.headers(); + for (size_t i = 0; i < headers.size(); ++i) { + auto &kv = headers[i]; + if (kv.name == keyref) { + continue; + } + if (i != p) { + headers[p] = std::move(kv); + } + ++p; + } + headers.resize(p); + } + + if (mrb_array_p(values)) { + auto n = RARRAY_LEN(values); + for (int i = 0; i < n; ++i) { + auto value = mrb_ary_ref(mrb, values, i); + if (!mrb_string_p(value)) { + mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string"); + } + + req.fs.add_header_token( + keyref, + make_string_ref(balloc, + StringRef{RSTRING_PTR(value), + static_cast(RSTRING_LEN(value))}), + false, token); + } + } else if (mrb_string_p(values)) { + req.fs.add_header_token( + keyref, + make_string_ref(balloc, + StringRef{RSTRING_PTR(values), + static_cast(RSTRING_LEN(values))}), + false, token); + } else { + mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string"); + } + + return mrb_nil_value(); +} +} // namespace + +namespace { +mrb_value request_set_header(mrb_state *mrb, mrb_value self) { + return request_mod_header(mrb, self, true); +} +} // namespace + +namespace { +mrb_value request_add_header(mrb_state *mrb, mrb_value self) { + return request_mod_header(mrb, self, false); +} +} // namespace + +namespace { +mrb_value request_clear_headers(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &req = downstream->request(); + + check_phase(mrb, data->phase, PHASE_REQUEST); + + req.fs.clear_headers(); + + return mrb_nil_value(); +} +} // namespace + +namespace { +mrb_value request_push(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + + const char *uri; + mrb_int len; + mrb_get_args(mrb, "s", &uri, &len); + + upstream->initiate_push(downstream, StringRef{uri, static_cast(len)}); + + return mrb_nil_value(); +} +} // namespace + +void init_request_class(mrb_state *mrb, RClass *module) { + auto request_class = + mrb_define_class_under(mrb, module, "Request", mrb->object_class); + + mrb_define_method(mrb, request_class, "initialize", request_init, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "http_version_major", + request_get_http_version_major, MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "http_version_minor", + request_get_http_version_minor, MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "method", request_get_method, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "method=", request_set_method, + MRB_ARGS_REQ(1)); + mrb_define_method(mrb, request_class, "authority", request_get_authority, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "authority=", request_set_authority, + MRB_ARGS_REQ(1)); + mrb_define_method(mrb, request_class, "scheme", request_get_scheme, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "scheme=", request_set_scheme, + MRB_ARGS_REQ(1)); + mrb_define_method(mrb, request_class, "path", request_get_path, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "path=", request_set_path, + MRB_ARGS_REQ(1)); + mrb_define_method(mrb, request_class, "headers", request_get_headers, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "add_header", request_add_header, + MRB_ARGS_REQ(2)); + mrb_define_method(mrb, request_class, "set_header", request_set_header, + MRB_ARGS_REQ(2)); + mrb_define_method(mrb, request_class, "clear_headers", request_clear_headers, + MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "push", request_push, MRB_ARGS_REQ(1)); +} + +} // namespace mruby + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_mruby_module_request.h b/lib/nghttp2/src/shrpx_mruby_module_request.h new file mode 100644 index 00000000000..e74f335131b --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby_module_request.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MRUBY_MODULE_REQUEST_H +#define SHRPX_MRUBY_MODULE_REQUEST_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +namespace mruby { + +void init_request_class(mrb_state *mrb, RClass *module); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_REQUEST_H diff --git a/lib/nghttp2/src/shrpx_mruby_module_response.cc b/lib/nghttp2/src/shrpx_mruby_module_response.cc new file mode 100644 index 00000000000..1de1d5f07fd --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby_module_response.cc @@ -0,0 +1,398 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_mruby_module_response.h" + +#include +#include +#include +#include + +#include "shrpx_downstream.h" +#include "shrpx_upstream.h" +#include "shrpx_client_handler.h" +#include "shrpx_mruby.h" +#include "shrpx_mruby_module.h" +#include "shrpx_log.h" +#include "util.h" +#include "http2.h" + +namespace shrpx { + +namespace mruby { + +namespace { +mrb_value response_init(mrb_state *mrb, mrb_value self) { return self; } +} // namespace + +namespace { +mrb_value response_get_http_version_major(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &resp = downstream->response(); + return mrb_fixnum_value(resp.http_major); +} +} // namespace + +namespace { +mrb_value response_get_http_version_minor(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &resp = downstream->response(); + return mrb_fixnum_value(resp.http_minor); +} +} // namespace + +namespace { +mrb_value response_get_status(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &resp = downstream->response(); + return mrb_fixnum_value(resp.http_status); +} +} // namespace + +namespace { +mrb_value response_set_status(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &resp = downstream->response(); + + mrb_int status; + mrb_get_args(mrb, "i", &status); + // We don't support 1xx status code for mruby scripting yet. + if (status < 200 || status > 999) { + mrb_raise(mrb, E_RUNTIME_ERROR, + "invalid status; it should be [200, 999], inclusive"); + } + + resp.http_status = status; + + return self; +} +} // namespace + +namespace { +mrb_value response_get_headers(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + const auto &resp = downstream->response(); + + return create_headers_hash(mrb, resp.fs.headers()); +} +} // namespace + +namespace { +mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &resp = downstream->response(); + auto &balloc = downstream->get_block_allocator(); + + mrb_value key, values; + mrb_get_args(mrb, "So", &key, &values); + + if (RSTRING_LEN(key) == 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed"); + } + + auto ai = mrb_gc_arena_save(mrb); + + key = mrb_funcall(mrb, key, "downcase", 0); + + auto keyref = + make_string_ref(balloc, StringRef{RSTRING_PTR(key), + static_cast(RSTRING_LEN(key))}); + + mrb_gc_arena_restore(mrb, ai); + + auto token = http2::lookup_token(keyref.byte(), keyref.size()); + + if (repl) { + size_t p = 0; + auto &headers = resp.fs.headers(); + for (size_t i = 0; i < headers.size(); ++i) { + auto &kv = headers[i]; + if (kv.name == keyref) { + continue; + } + if (i != p) { + headers[p] = std::move(kv); + } + ++p; + } + headers.resize(p); + } + + if (mrb_array_p(values)) { + auto n = RARRAY_LEN(values); + for (int i = 0; i < n; ++i) { + auto value = mrb_ary_ref(mrb, values, i); + if (!mrb_string_p(value)) { + mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string"); + } + + resp.fs.add_header_token( + keyref, + make_string_ref(balloc, + StringRef{RSTRING_PTR(value), + static_cast(RSTRING_LEN(value))}), + false, token); + } + } else if (mrb_string_p(values)) { + resp.fs.add_header_token( + keyref, + make_string_ref(balloc, + StringRef{RSTRING_PTR(values), + static_cast(RSTRING_LEN(values))}), + false, token); + } else { + mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string"); + } + + return mrb_nil_value(); +} +} // namespace + +namespace { +mrb_value response_set_header(mrb_state *mrb, mrb_value self) { + return response_mod_header(mrb, self, true); +} +} // namespace + +namespace { +mrb_value response_add_header(mrb_state *mrb, mrb_value self) { + return response_mod_header(mrb, self, false); +} +} // namespace + +namespace { +mrb_value response_clear_headers(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &resp = downstream->response(); + + resp.fs.clear_headers(); + + return mrb_nil_value(); +} +} // namespace + +namespace { +mrb_value response_return(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &req = downstream->request(); + auto &resp = downstream->response(); + int rv; + + auto &balloc = downstream->get_block_allocator(); + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed"); + } + + const char *val; + mrb_int vallen; + mrb_get_args(mrb, "|s", &val, &vallen); + + const uint8_t *body = nullptr; + size_t bodylen = 0; + + if (resp.http_status == 0) { + resp.http_status = 200; + } + + if (downstream->expect_response_body() && vallen > 0) { + body = reinterpret_cast(val); + bodylen = vallen; + } + + auto cl = resp.fs.header(http2::HD_CONTENT_LENGTH); + + if (resp.http_status == 204 || + (resp.http_status == 200 && req.method == HTTP_CONNECT)) { + if (cl) { + // Delete content-length here + http2::erase_header(cl); + } + + resp.fs.content_length = -1; + } else { + auto content_length = util::make_string_ref_uint(balloc, vallen); + + if (cl) { + cl->value = content_length; + } else { + resp.fs.add_header_token(StringRef::from_lit("content-length"), + content_length, false, http2::HD_CONTENT_LENGTH); + } + + resp.fs.content_length = vallen; + } + + auto date = resp.fs.header(http2::HD_DATE); + if (!date) { + auto lgconf = log_config(); + lgconf->update_tstamp(std::chrono::system_clock::now()); + resp.fs.add_header_token(StringRef::from_lit("date"), + make_string_ref(balloc, lgconf->tstamp->time_http), + false, http2::HD_DATE); + } + + auto upstream = downstream->get_upstream(); + + rv = upstream->send_reply(downstream, body, bodylen); + if (rv != 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "could not send response"); + } + + auto handler = upstream->get_client_handler(); + + handler->signal_write(); + + return self; +} +} // namespace + +namespace { +mrb_value response_send_info(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto &resp = downstream->response(); + int rv; + + if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) { + mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed"); + } + + mrb_int http_status; + mrb_value hash; + mrb_get_args(mrb, "iH", &http_status, &hash); + + if (http_status / 100 != 1) { + mrb_raise(mrb, E_RUNTIME_ERROR, + "status_code must be in range [100, 199], inclusive"); + } + + auto &balloc = downstream->get_block_allocator(); + + auto keys = mrb_hash_keys(mrb, hash); + auto keyslen = RARRAY_LEN(keys); + + for (int i = 0; i < keyslen; ++i) { + auto key = mrb_ary_ref(mrb, keys, i); + if (!mrb_string_p(key)) { + mrb_raise(mrb, E_RUNTIME_ERROR, "key must be string"); + } + + auto values = mrb_hash_get(mrb, hash, key); + + auto ai = mrb_gc_arena_save(mrb); + + key = mrb_funcall(mrb, key, "downcase", 0); + + auto keyref = make_string_ref( + balloc, + StringRef{RSTRING_PTR(key), static_cast(RSTRING_LEN(key))}); + + mrb_gc_arena_restore(mrb, ai); + + auto token = http2::lookup_token(keyref.byte(), keyref.size()); + + if (mrb_array_p(values)) { + auto n = RARRAY_LEN(values); + for (int i = 0; i < n; ++i) { + auto value = mrb_ary_ref(mrb, values, i); + if (!mrb_string_p(value)) { + mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string"); + } + + resp.fs.add_header_token( + keyref, + make_string_ref(balloc, + StringRef{RSTRING_PTR(value), + static_cast(RSTRING_LEN(value))}), + false, token); + } + } else if (mrb_string_p(values)) { + resp.fs.add_header_token( + keyref, + make_string_ref(balloc, + StringRef{RSTRING_PTR(values), + static_cast(RSTRING_LEN(values))}), + false, token); + } else { + mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string"); + } + } + + resp.http_status = http_status; + + auto upstream = downstream->get_upstream(); + + rv = upstream->on_downstream_header_complete(downstream); + if (rv != 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "could not send non-final response"); + } + + auto handler = upstream->get_client_handler(); + + handler->signal_write(); + + return self; +} +} // namespace + +void init_response_class(mrb_state *mrb, RClass *module) { + auto response_class = + mrb_define_class_under(mrb, module, "Response", mrb->object_class); + + mrb_define_method(mrb, response_class, "initialize", response_init, + MRB_ARGS_NONE()); + mrb_define_method(mrb, response_class, "http_version_major", + response_get_http_version_major, MRB_ARGS_NONE()); + mrb_define_method(mrb, response_class, "http_version_minor", + response_get_http_version_minor, MRB_ARGS_NONE()); + mrb_define_method(mrb, response_class, "status", response_get_status, + MRB_ARGS_NONE()); + mrb_define_method(mrb, response_class, "status=", response_set_status, + MRB_ARGS_REQ(1)); + mrb_define_method(mrb, response_class, "headers", response_get_headers, + MRB_ARGS_NONE()); + mrb_define_method(mrb, response_class, "add_header", response_add_header, + MRB_ARGS_REQ(2)); + mrb_define_method(mrb, response_class, "set_header", response_set_header, + MRB_ARGS_REQ(2)); + mrb_define_method(mrb, response_class, "clear_headers", + response_clear_headers, MRB_ARGS_NONE()); + mrb_define_method(mrb, response_class, "return", response_return, + MRB_ARGS_OPT(1)); + mrb_define_method(mrb, response_class, "send_info", response_send_info, + MRB_ARGS_REQ(2)); +} + +} // namespace mruby + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_mruby_module_response.h b/lib/nghttp2/src/shrpx_mruby_module_response.h new file mode 100644 index 00000000000..a35b42b1199 --- /dev/null +++ b/lib/nghttp2/src/shrpx_mruby_module_response.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_MRUBY_MODULE_RESPONSE_H +#define SHRPX_MRUBY_MODULE_RESPONSE_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +namespace mruby { + +void init_response_class(mrb_state *mrb, RClass *module); + +} // namespace mruby + +} // namespace shrpx + +#endif // SHRPX_MRUBY_MODULE_RESPONSE_H diff --git a/lib/nghttp2/src/shrpx_null_downstream_connection.cc b/lib/nghttp2/src/shrpx_null_downstream_connection.cc new file mode 100644 index 00000000000..cd81c8aa0e6 --- /dev/null +++ b/lib/nghttp2/src/shrpx_null_downstream_connection.cc @@ -0,0 +1,88 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 "shrpx_null_downstream_connection.h" +#include "shrpx_upstream.h" +#include "shrpx_downstream.h" +#include "shrpx_log.h" + +namespace shrpx { + +NullDownstreamConnection::NullDownstreamConnection( + const std::shared_ptr &group) + : group_(group) {} + +NullDownstreamConnection::~NullDownstreamConnection() {} + +int NullDownstreamConnection::attach_downstream(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; + } + + downstream_ = downstream; + + return 0; +} + +void NullDownstreamConnection::detach_downstream(Downstream *downstream) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream; + } + downstream_ = nullptr; +} + +int NullDownstreamConnection::push_request_headers() { return 0; } + +int NullDownstreamConnection::push_upload_data_chunk(const uint8_t *data, + size_t datalen) { + return 0; +} + +int NullDownstreamConnection::end_upload_data() { return 0; } + +void NullDownstreamConnection::pause_read(IOCtrlReason reason) {} + +int NullDownstreamConnection::resume_read(IOCtrlReason reason, + size_t consumed) { + return 0; +} + +void NullDownstreamConnection::force_resume_read() {} + +int NullDownstreamConnection::on_read() { return 0; } + +int NullDownstreamConnection::on_write() { return 0; } + +void NullDownstreamConnection::on_upstream_change(Upstream *upstream) {} + +bool NullDownstreamConnection::poolable() const { return false; } + +const std::shared_ptr & +NullDownstreamConnection::get_downstream_addr_group() const { + return group_; +} + +DownstreamAddr *NullDownstreamConnection::get_addr() const { return nullptr; } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_null_downstream_connection.h b/lib/nghttp2/src/shrpx_null_downstream_connection.h new file mode 100644 index 00000000000..7defcc33313 --- /dev/null +++ b/lib/nghttp2/src/shrpx_null_downstream_connection.h @@ -0,0 +1,68 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 SHRPX_NULL_DOWNSTREAM_CONNECTION_H +#define SHRPX_NULL_DOWNSTREAM_CONNECTION_H + +#include "shrpx_downstream_connection.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +class NullDownstreamConnection : public DownstreamConnection { +public: + NullDownstreamConnection(const std::shared_ptr &group); + virtual ~NullDownstreamConnection(); + virtual int attach_downstream(Downstream *downstream); + virtual void detach_downstream(Downstream *downstream); + + virtual int push_request_headers(); + virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen); + virtual int end_upload_data(); + + virtual void pause_read(IOCtrlReason reason); + virtual int resume_read(IOCtrlReason reason, size_t consumed); + virtual void force_resume_read(); + + virtual int on_read(); + virtual int on_write(); + + virtual void on_upstream_change(Upstream *upstream); + + // true if this object is poolable. + virtual bool poolable() const; + + virtual const std::shared_ptr & + get_downstream_addr_group() const; + virtual DownstreamAddr *get_addr() const; + +private: + std::shared_ptr group_; +}; + +} // namespace shrpx + +#endif // SHRPX_NULL_DOWNSTREAM_CONNECTION_H diff --git a/lib/nghttp2/src/shrpx_process.h b/lib/nghttp2/src/shrpx_process.h new file mode 100644 index 00000000000..d35461b9aad --- /dev/null +++ b/lib/nghttp2/src/shrpx_process.h @@ -0,0 +1,37 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_PROCESS_H +#define SHRPX_PROCESS_H + +#include "shrpx.h" + +namespace shrpx { + +constexpr uint8_t SHRPX_IPC_REOPEN_LOG = 1; +constexpr uint8_t SHRPX_IPC_GRACEFUL_SHUTDOWN = 2; + +} // namespace shrpx + +#endif // SHRPX_PROCESS_H diff --git a/lib/nghttp2/src/shrpx_quic.cc b/lib/nghttp2/src/shrpx_quic.cc new file mode 100644 index 00000000000..2d4de593369 --- /dev/null +++ b/lib/nghttp2/src/shrpx_quic.cc @@ -0,0 +1,393 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 "shrpx_quic.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#include "shrpx_config.h" +#include "shrpx_log.h" +#include "util.h" +#include "xsi_strerror.h" + +bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs) { + return ngtcp2_cid_eq(&lhs, &rhs); +} + +namespace shrpx { + +ngtcp2_tstamp quic_timestamp() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} + +int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, + size_t remote_salen, const sockaddr *local_sa, + size_t local_salen, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen, size_t gso_size) { + iovec msg_iov = {const_cast(data), datalen}; + msghdr msg{}; + msg.msg_name = const_cast(remote_sa); + msg.msg_namelen = remote_salen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) + +#ifdef UDP_SEGMENT + CMSG_SPACE(sizeof(uint16_t)) + +#endif // UDP_SEGMENT + 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_sa->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)); + in_pktinfo pktinfo{}; + auto addrin = + reinterpret_cast(const_cast(local_sa)); + pktinfo.ipi_spec_dst = addrin->sin_addr; + memcpy(CMSG_DATA(cm), &pktinfo, sizeof(pktinfo)); + + 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)); + in6_pktinfo pktinfo{}; + auto addrin = + reinterpret_cast(const_cast(local_sa)); + pktinfo.ipi6_addr = addrin->sin6_addr; + memcpy(CMSG_DATA(cm), &pktinfo, sizeof(pktinfo)); + + break; + } + default: + assert(0); + } + +#ifdef UDP_SEGMENT + if (gso_size && datalen > gso_size) { + controllen += CMSG_SPACE(sizeof(uint16_t)); + cm = CMSG_NXTHDR(&msg, cm); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + uint16_t n = gso_size; + memcpy(CMSG_DATA(cm), &n, sizeof(n)); + } +#endif // UDP_SEGMENT + + controllen += CMSG_SPACE(sizeof(int)); + cm = CMSG_NXTHDR(&msg, cm); + cm->cmsg_len = CMSG_LEN(sizeof(int)); + unsigned int tos = pi.ecn; + memcpy(CMSG_DATA(cm), &tos, sizeof(tos)); + + switch (local_sa->sa_family) { + case AF_INET: + cm->cmsg_level = IPPROTO_IP; + cm->cmsg_type = IP_TOS; + + break; + case AF_INET6: + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_TCLASS; + + break; + default: + assert(0); + } + + msg.msg_controllen = controllen; + + ssize_t nwrite; + + do { + nwrite = sendmsg(faddr->fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + if (LOG_ENABLED(INFO)) { + auto error = errno; + LOG(INFO) << "sendmsg failed: errno=" << error; + } + + return -errno; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "QUIC sent packet: local=" + << util::to_numeric_addr(local_sa, local_salen) + << " remote=" << util::to_numeric_addr(remote_sa, remote_salen) + << " ecn=" << log::hex << pi.ecn << log::dec << " " << nwrite + << " bytes"; + } + + return 0; +} + +int generate_quic_retry_connection_id(ngtcp2_cid &cid, size_t cidlen, + const uint8_t *server_id, uint8_t km_id, + const uint8_t *key) { + assert(cidlen == SHRPX_QUIC_SCIDLEN); + + if (RAND_bytes(cid.data, cidlen) != 1) { + return -1; + } + + cid.datalen = cidlen; + + cid.data[0] = (cid.data[0] & 0x3f) | km_id; + + auto p = cid.data + SHRPX_QUIC_CID_PREFIX_OFFSET; + + std::copy_n(server_id, SHRPX_QUIC_SERVER_IDLEN, p); + + return encrypt_quic_connection_id(p, p, key); +} + +int generate_quic_connection_id(ngtcp2_cid &cid, size_t cidlen, + const uint8_t *cid_prefix, uint8_t km_id, + const uint8_t *key) { + assert(cidlen == SHRPX_QUIC_SCIDLEN); + + if (RAND_bytes(cid.data, cidlen) != 1) { + return -1; + } + + cid.datalen = cidlen; + + cid.data[0] = (cid.data[0] & 0x3f) | km_id; + + auto p = cid.data + SHRPX_QUIC_CID_PREFIX_OFFSET; + + std::copy_n(cid_prefix, SHRPX_QUIC_CID_PREFIXLEN, p); + + return encrypt_quic_connection_id(p, p, key); +} + +int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src, + const uint8_t *key) { + auto ctx = EVP_CIPHER_CTX_new(); + auto d = defer(EVP_CIPHER_CTX_free, ctx); + + if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) { + return -1; + } + + EVP_CIPHER_CTX_set_padding(ctx, 0); + + int len; + + if (!EVP_EncryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) || + !EVP_EncryptFinal_ex(ctx, dest + len, &len)) { + return -1; + } + + return 0; +} + +int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src, + const uint8_t *key) { + auto ctx = EVP_CIPHER_CTX_new(); + auto d = defer(EVP_CIPHER_CTX_free, ctx); + + if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) { + return -1; + } + + EVP_CIPHER_CTX_set_padding(ctx, 0); + + int len; + + if (!EVP_DecryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) || + !EVP_DecryptFinal_ex(ctx, dest + len, &len)) { + return -1; + } + + return 0; +} + +int generate_quic_hashed_connection_id(ngtcp2_cid &dest, + const Address &remote_addr, + const Address &local_addr, + const ngtcp2_cid &cid) { + auto ctx = EVP_MD_CTX_new(); + auto d = defer(EVP_MD_CTX_free, ctx); + + std::array h; + unsigned int hlen = EVP_MD_size(EVP_sha256()); + + if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) || + !EVP_DigestUpdate(ctx, &remote_addr.su.sa, remote_addr.len) || + !EVP_DigestUpdate(ctx, &local_addr.su.sa, local_addr.len) || + !EVP_DigestUpdate(ctx, cid.data, cid.datalen) || + !EVP_DigestFinal_ex(ctx, h.data(), &hlen)) { + return -1; + } + + assert(hlen == h.size()); + + std::copy_n(std::begin(h), sizeof(dest.data), std::begin(dest.data)); + dest.datalen = sizeof(dest.data); + + return 0; +} + +int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid &cid, + const uint8_t *secret, + size_t secretlen) { + if (ngtcp2_crypto_generate_stateless_reset_token(token, secret, secretlen, + &cid) != 0) { + return -1; + } + + return 0; +} + +int generate_retry_token(uint8_t *token, size_t &tokenlen, uint32_t version, + const sockaddr *sa, socklen_t salen, + const ngtcp2_cid &retry_scid, const ngtcp2_cid &odcid, + const uint8_t *secret, size_t secretlen) { + auto t = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + auto stokenlen = ngtcp2_crypto_generate_retry_token( + token, secret, secretlen, version, sa, salen, &retry_scid, &odcid, t); + if (stokenlen < 0) { + return -1; + } + + tokenlen = stokenlen; + + return 0; +} + +int verify_retry_token(ngtcp2_cid &odcid, const uint8_t *token, size_t tokenlen, + uint32_t version, const ngtcp2_cid &dcid, + const sockaddr *sa, socklen_t salen, + const uint8_t *secret, size_t secretlen) { + + auto t = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + if (ngtcp2_crypto_verify_retry_token(&odcid, token, tokenlen, secret, + secretlen, version, sa, salen, &dcid, + 10 * NGTCP2_SECONDS, t) != 0) { + return -1; + } + + return 0; +} + +int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, + size_t salen, const uint8_t *secret, size_t secretlen) { + auto t = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + auto stokenlen = ngtcp2_crypto_generate_regular_token( + token, secret, secretlen, sa, salen, t); + if (stokenlen < 0) { + return -1; + } + + tokenlen = stokenlen; + + return 0; +} + +int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa, + socklen_t salen, const uint8_t *secret, size_t secretlen) { + auto t = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + if (ngtcp2_crypto_verify_regular_token(token, tokenlen, secret, secretlen, sa, + salen, 3600 * NGTCP2_SECONDS, + t) != 0) { + return -1; + } + + return 0; +} + +int generate_quic_connection_id_encryption_key(uint8_t *key, size_t keylen, + const uint8_t *secret, + size_t secretlen, + const uint8_t *salt, + size_t saltlen) { + constexpr uint8_t info[] = "connection id encryption key"; + ngtcp2_crypto_md sha256; + ngtcp2_crypto_md_init( + &sha256, reinterpret_cast(const_cast(EVP_sha256()))); + + if (ngtcp2_crypto_hkdf(key, keylen, &sha256, secret, secretlen, salt, saltlen, + info, str_size(info)) != 0) { + return -1; + } + + return 0; +} + +const QUICKeyingMaterial * +select_quic_keying_material(const QUICKeyingMaterials &qkms, uint8_t km_id) { + for (auto &qkm : qkms.keying_materials) { + if (km_id == qkm.id) { + return &qkm; + } + } + + return &qkms.keying_materials.front(); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_quic.h b/lib/nghttp2/src/shrpx_quic.h new file mode 100644 index 00000000000..b2f60879bc5 --- /dev/null +++ b/lib/nghttp2/src/shrpx_quic.h @@ -0,0 +1,138 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 SHRPX_QUIC_H +#define SHRPX_QUIC_H + +#include "shrpx.h" + +#include + +#include + +#include + +#include "network.h" + +using namespace nghttp2; + +namespace std { +template <> struct hash { + std::size_t operator()(const ngtcp2_cid &cid) const noexcept { + // FNV-1a 64bits variant + constexpr uint64_t basis = 0xCBF29CE484222325ULL; + const uint8_t *p = cid.data, *end = cid.data + cid.datalen; + uint64_t h = basis; + + for (; p != end;) { + h ^= *p++; + h *= basis; + } + + return static_cast(h); + } +}; +} // namespace std + +bool operator==(const ngtcp2_cid &lhs, const ngtcp2_cid &rhs); + +namespace shrpx { + +struct UpstreamAddr; +struct QUICKeyingMaterials; +struct QUICKeyingMaterial; + +constexpr size_t SHRPX_QUIC_SCIDLEN = 20; +constexpr size_t SHRPX_QUIC_SERVER_IDLEN = 4; +// SHRPX_QUIC_CID_PREFIXLEN includes SHRPX_QUIC_SERVER_IDLEN. +constexpr size_t SHRPX_QUIC_CID_PREFIXLEN = 8; +constexpr size_t SHRPX_QUIC_CID_PREFIX_OFFSET = 1; +constexpr size_t SHRPX_QUIC_DECRYPTED_DCIDLEN = 16; +constexpr size_t SHRPX_QUIC_CID_ENCRYPTION_KEYLEN = 16; +constexpr size_t SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE = 1472; +constexpr size_t SHRPX_QUIC_CONN_CLOSE_PKTLEN = 256; +constexpr size_t SHRPX_QUIC_STATELESS_RESET_BURST = 100; +constexpr size_t SHRPX_QUIC_SECRET_RESERVEDLEN = 4; +constexpr size_t SHRPX_QUIC_SECRETLEN = 32; +constexpr size_t SHRPX_QUIC_SALTLEN = 32; +constexpr uint8_t SHRPX_QUIC_DCID_KM_ID_MASK = 0xc0; + +ngtcp2_tstamp quic_timestamp(); + +int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa, + size_t remote_salen, const sockaddr *local_sa, + size_t local_salen, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen, size_t gso_size); + +int generate_quic_retry_connection_id(ngtcp2_cid &cid, size_t cidlen, + const uint8_t *server_id, uint8_t km_id, + const uint8_t *key); + +int generate_quic_connection_id(ngtcp2_cid &cid, size_t cidlen, + const uint8_t *cid_prefix, uint8_t km_id, + const uint8_t *key); + +int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src, + const uint8_t *key); + +int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src, + const uint8_t *key); + +int generate_quic_hashed_connection_id(ngtcp2_cid &dest, + const Address &remote_addr, + const Address &local_addr, + const ngtcp2_cid &cid); + +int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid &cid, + const uint8_t *secret, + size_t secretlen); + +int generate_retry_token(uint8_t *token, size_t &tokenlen, uint32_t version, + const sockaddr *sa, socklen_t salen, + const ngtcp2_cid &retry_scid, const ngtcp2_cid &odcid, + const uint8_t *secret, size_t secretlen); + +int verify_retry_token(ngtcp2_cid &odcid, const uint8_t *token, size_t tokenlen, + uint32_t version, const ngtcp2_cid &dcid, + const sockaddr *sa, socklen_t salen, + const uint8_t *secret, size_t secretlen); + +int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, + size_t salen, const uint8_t *secret, size_t secretlen); + +int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa, + socklen_t salen, const uint8_t *secret, size_t secretlen); + +int generate_quic_connection_id_encryption_key(uint8_t *key, size_t keylen, + const uint8_t *secret, + size_t secretlen, + const uint8_t *salt, + size_t saltlen); + +const QUICKeyingMaterial * +select_quic_keying_material(const QUICKeyingMaterials &qkms, uint8_t km_id); + +} // namespace shrpx + +#endif // SHRPX_QUIC_H diff --git a/lib/nghttp2/src/shrpx_quic_connection_handler.cc b/lib/nghttp2/src/shrpx_quic_connection_handler.cc new file mode 100644 index 00000000000..1a4e6a8bb32 --- /dev/null +++ b/lib/nghttp2/src/shrpx_quic_connection_handler.cc @@ -0,0 +1,761 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 "shrpx_quic_connection_handler.h" + +#include + +#include +#include + +#include "shrpx_worker.h" +#include "shrpx_client_handler.h" +#include "shrpx_log.h" +#include "shrpx_http3_upstream.h" +#include "shrpx_connection_handler.h" +#include "ssl_compat.h" + +namespace shrpx { + +namespace { +void stateless_reset_bucket_regen_timercb(struct ev_loop *loop, ev_timer *w, + int revents) { + auto quic_conn_handler = static_cast(w->data); + + quic_conn_handler->on_stateless_reset_bucket_regen(); +} +} // namespace + +QUICConnectionHandler::QUICConnectionHandler(Worker *worker) + : worker_{worker}, + stateless_reset_bucket_{SHRPX_QUIC_STATELESS_RESET_BURST} { + ev_timer_init(&stateless_reset_bucket_regen_timer_, + stateless_reset_bucket_regen_timercb, 0., 1.); + stateless_reset_bucket_regen_timer_.data = this; +} + +QUICConnectionHandler::~QUICConnectionHandler() { + ev_timer_stop(worker_->get_loop(), &stateless_reset_bucket_regen_timer_); +} + +int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, + const Address &remote_addr, + const Address &local_addr, + const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen) { + int rv; + ngtcp2_version_cid vc; + + rv = ngtcp2_pkt_decode_version_cid(&vc, data, datalen, SHRPX_QUIC_SCIDLEN); + switch (rv) { + case 0: + break; + case NGTCP2_ERR_VERSION_NEGOTIATION: + send_version_negotiation(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid, + vc.scidlen, remote_addr, local_addr); + + return 0; + default: + return 0; + } + + auto config = get_config(); + + ngtcp2_cid dcid_key; + ngtcp2_cid_init(&dcid_key, vc.dcid, vc.dcidlen); + + auto conn_handler = worker_->get_connection_handler(); + + ClientHandler *handler; + + auto &quicconf = config->quic; + + auto it = connections_.find(dcid_key); + if (it == std::end(connections_)) { + auto cwit = close_waits_.find(dcid_key); + if (cwit != std::end(close_waits_)) { + auto cw = (*cwit).second; + + cw->handle_packet(faddr, remote_addr, local_addr, pi, data, datalen); + + return 0; + } + + if (data[0] & 0x80) { + if (generate_quic_hashed_connection_id(dcid_key, remote_addr, local_addr, + dcid_key) != 0) { + return 0; + } + + it = connections_.find(dcid_key); + if (it == std::end(connections_)) { + auto cwit = close_waits_.find(dcid_key); + if (cwit != std::end(close_waits_)) { + auto cw = (*cwit).second; + + cw->handle_packet(faddr, remote_addr, local_addr, pi, data, datalen); + + return 0; + } + } + } + } + + if (it == std::end(connections_)) { + std::array decrypted_dcid; + + auto &qkms = conn_handler->get_quic_keying_materials(); + const QUICKeyingMaterial *qkm = nullptr; + + if (vc.dcidlen == SHRPX_QUIC_SCIDLEN) { + qkm = select_quic_keying_material( + *qkms.get(), vc.dcid[0] & SHRPX_QUIC_DCID_KM_ID_MASK); + + if (decrypt_quic_connection_id(decrypted_dcid.data(), + vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET, + qkm->cid_encryption_key.data()) != 0) { + return 0; + } + + if (qkm != &qkms->keying_materials.front() || + !std::equal(std::begin(decrypted_dcid), + std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN, + worker_->get_cid_prefix())) { + auto quic_lwp = + conn_handler->match_quic_lingering_worker_process_cid_prefix( + decrypted_dcid.data(), decrypted_dcid.size()); + if (quic_lwp) { + if (conn_handler->forward_quic_packet_to_lingering_worker_process( + quic_lwp, remote_addr, local_addr, pi, data, datalen) == 0) { + return 0; + } + + return 0; + } + } + } + + // new connection + + auto &upstreamconf = config->conn.upstream; + if (worker_->get_worker_stat()->num_connections >= + upstreamconf.worker_connections) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Too many connections >=" + << upstreamconf.worker_connections; + } + + return 0; + } + + ngtcp2_pkt_hd hd; + ngtcp2_cid odcid, *podcid = nullptr; + const uint8_t *token = nullptr; + size_t tokenlen = 0; + ngtcp2_token_type token_type = NGTCP2_TOKEN_TYPE_UNKNOWN; + + switch (ngtcp2_accept(&hd, data, datalen)) { + case 0: { + // If we get Initial and it has the CID prefix of this worker, + // it is likely that client is intentionally use the prefix. + // Just drop it. + if (vc.dcidlen == SHRPX_QUIC_SCIDLEN) { + if (qkm != &qkms->keying_materials.front()) { + qkm = &qkms->keying_materials.front(); + + if (decrypt_quic_connection_id(decrypted_dcid.data(), + vc.dcid + SHRPX_QUIC_CID_PREFIX_OFFSET, + qkm->cid_encryption_key.data()) != 0) { + return 0; + } + } + + if (std::equal(std::begin(decrypted_dcid), + std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN, + worker_->get_cid_prefix())) { + return 0; + } + } + + if (worker_->get_graceful_shutdown()) { + send_connection_close(faddr, hd.version, hd.dcid, hd.scid, remote_addr, + local_addr, NGTCP2_CONNECTION_REFUSED, + datalen * 3); + return 0; + } + + if (hd.tokenlen == 0) { + if (quicconf.upstream.require_token) { + send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid, + vc.scidlen, remote_addr, local_addr, datalen * 3); + + return 0; + } + + break; + } + + switch (hd.token[0]) { + case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY: { + if (vc.dcidlen != SHRPX_QUIC_SCIDLEN) { + // Initial packets with Retry token must have DCID chosen by + // server. + return 0; + } + + auto qkm = select_quic_keying_material( + *qkms.get(), vc.dcid[0] & SHRPX_QUIC_DCID_KM_ID_MASK); + + if (verify_retry_token(odcid, hd.token, hd.tokenlen, hd.version, + hd.dcid, &remote_addr.su.sa, remote_addr.len, + qkm->secret.data(), qkm->secret.size()) != 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Failed to validate Retry token from remote=" + << util::to_numeric_addr(&remote_addr); + } + + // 2nd Retry packet is not allowed, so send CONNECTION_CLOSE + // with INVALID_TOKEN. + send_connection_close(faddr, hd.version, hd.dcid, hd.scid, + remote_addr, local_addr, NGTCP2_INVALID_TOKEN, + datalen * 3); + return 0; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Successfully validated Retry token from remote=" + << util::to_numeric_addr(&remote_addr); + } + + podcid = &odcid; + token = hd.token; + tokenlen = hd.tokenlen; + token_type = NGTCP2_TOKEN_TYPE_RETRY; + + break; + } + case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: { + // If a token is a regular token, it must be at least + // NGTCP2_MIN_INITIAL_DCIDLEN bytes long. + if (vc.dcidlen < NGTCP2_MIN_INITIAL_DCIDLEN) { + return 0; + } + + if (hd.tokenlen != NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN + 1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Failed to validate token from remote=" + << util::to_numeric_addr(&remote_addr); + } + + if (quicconf.upstream.require_token) { + send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid, + vc.scidlen, remote_addr, local_addr, datalen * 3); + + return 0; + } + + break; + } + + auto qkm = select_quic_keying_material( + *qkms.get(), hd.token[NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN]); + + if (verify_token(hd.token, hd.tokenlen - 1, &remote_addr.su.sa, + remote_addr.len, qkm->secret.data(), + qkm->secret.size()) != 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Failed to validate token from remote=" + << util::to_numeric_addr(&remote_addr); + } + + if (quicconf.upstream.require_token) { + send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid, + vc.scidlen, remote_addr, local_addr, datalen * 3); + + return 0; + } + + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Successfully validated token from remote=" + << util::to_numeric_addr(&remote_addr); + } + + token = hd.token; + tokenlen = hd.tokenlen; + token_type = NGTCP2_TOKEN_TYPE_NEW_TOKEN; + + break; + } + default: + if (quicconf.upstream.require_token) { + send_retry(faddr, vc.version, vc.dcid, vc.dcidlen, vc.scid, + vc.scidlen, remote_addr, local_addr, datalen * 3); + + return 0; + } + + break; + } + + break; + } + default: + if (!config->single_thread && !(data[0] & 0x80) && + vc.dcidlen == SHRPX_QUIC_SCIDLEN && + !std::equal(std::begin(decrypted_dcid), + std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN, + worker_->get_cid_prefix())) { + if (conn_handler->forward_quic_packet(faddr, remote_addr, local_addr, + pi, decrypted_dcid.data(), data, + datalen) == 0) { + return 0; + } + } + + if (!(data[0] & 0x80)) { + // TODO Must be rate limited + send_stateless_reset(faddr, vc.dcid, vc.dcidlen, remote_addr, + local_addr); + } + + return 0; + } + + handler = handle_new_connection(faddr, remote_addr, local_addr, hd, podcid, + token, tokenlen, token_type); + if (handler == nullptr) { + return 0; + } + } else { + handler = (*it).second; + } + + if (handler->read_quic(faddr, remote_addr, local_addr, pi, data, datalen) != + 0) { + delete handler; + return 0; + } + + handler->signal_write(); + + return 0; +} + +ClientHandler *QUICConnectionHandler::handle_new_connection( + const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_hd &hd, const ngtcp2_cid *odcid, + const uint8_t *token, size_t tokenlen, ngtcp2_token_type token_type) { + std::array host; + std::array service; + int rv; + + rv = getnameinfo(&remote_addr.su.sa, remote_addr.len, host.data(), + host.size(), service.data(), service.size(), + NI_NUMERICHOST | NI_NUMERICSERV); + if (rv != 0) { + LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv); + + return nullptr; + } + + auto ssl_ctx = worker_->get_quic_sv_ssl_ctx(); + + assert(ssl_ctx); + + auto ssl = tls::create_ssl(ssl_ctx); + if (ssl == nullptr) { + return nullptr; + } + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + assert(SSL_is_quic(ssl)); +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + SSL_set_accept_state(ssl); + + auto config = get_config(); + auto &quicconf = config->quic; + + if (quicconf.upstream.early_data) { +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + SSL_set_quic_early_data_enabled(ssl, 1); +#else // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + SSL_set_early_data_enabled(ssl, 1); +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + } + + // Disable TLS session ticket if we don't have working ticket + // keys. + if (!worker_->get_ticket_keys()) { + SSL_set_options(ssl, SSL_OP_NO_TICKET); + } + + auto handler = std::make_unique( + worker_, faddr->fd, ssl, StringRef{host.data()}, + StringRef{service.data()}, remote_addr.su.sa.sa_family, faddr); + + auto upstream = std::make_unique(handler.get()); + if (upstream->init(faddr, remote_addr, local_addr, hd, odcid, token, tokenlen, + token_type) != 0) { + return nullptr; + } + + handler->setup_http3_upstream(std::move(upstream)); + + return handler.release(); +} + +namespace { +uint32_t generate_reserved_version(const Address &addr, uint32_t version) { + uint32_t h = 0x811C9DC5u; + const uint8_t *p = reinterpret_cast(&addr.su.sa); + const uint8_t *ep = p + addr.len; + + 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 QUICConnectionHandler::send_retry( + const UpstreamAddr *faddr, uint32_t version, const uint8_t *ini_dcid, + size_t ini_dcidlen, const uint8_t *ini_scid, size_t ini_scidlen, + const Address &remote_addr, const Address &local_addr, size_t max_pktlen) { + std::array host; + std::array port; + + if (getnameinfo(&remote_addr.su.sa, remote_addr.len, host.data(), host.size(), + port.data(), port.size(), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) { + return -1; + } + + auto config = get_config(); + auto &quicconf = config->quic; + + auto conn_handler = worker_->get_connection_handler(); + auto &qkms = conn_handler->get_quic_keying_materials(); + auto &qkm = qkms->keying_materials.front(); + + ngtcp2_cid retry_scid; + + if (generate_quic_retry_connection_id(retry_scid, SHRPX_QUIC_SCIDLEN, + quicconf.server_id.data(), qkm.id, + qkm.cid_encryption_key.data()) != 0) { + return -1; + } + + std::array token; + size_t tokenlen; + + ngtcp2_cid idcid, iscid; + ngtcp2_cid_init(&idcid, ini_dcid, ini_dcidlen); + ngtcp2_cid_init(&iscid, ini_scid, ini_scidlen); + + if (generate_retry_token(token.data(), tokenlen, version, &remote_addr.su.sa, + remote_addr.len, retry_scid, idcid, + qkm.secret.data(), qkm.secret.size()) != 0) { + return -1; + } + + std::vector buf; + buf.resize(std::min(max_pktlen, static_cast(256))); + + auto nwrite = + ngtcp2_crypto_write_retry(buf.data(), buf.size(), version, &iscid, + &retry_scid, &idcid, token.data(), tokenlen); + if (nwrite < 0) { + LOG(ERROR) << "ngtcp2_crypto_write_retry: " << ngtcp2_strerror(nwrite); + return -1; + } + + buf.resize(nwrite); + + quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, + &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{}, + buf.data(), buf.size(), 0); + + if (generate_quic_hashed_connection_id(idcid, remote_addr, local_addr, + idcid) != 0) { + return -1; + } + + auto d = + static_cast(NGTCP2_DEFAULT_INITIAL_RTT * 3) / NGTCP2_SECONDS; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Enter close-wait period " << d << "s with " << buf.size() + << " bytes sentinel packet"; + } + + auto cw = std::make_unique(worker_, std::vector{idcid}, + std::move(buf), d); + + add_close_wait(cw.release()); + + return 0; +} + +int QUICConnectionHandler::send_version_negotiation( + const UpstreamAddr *faddr, uint32_t version, const uint8_t *ini_dcid, + size_t ini_dcidlen, const uint8_t *ini_scid, size_t ini_scidlen, + const Address &remote_addr, const Address &local_addr) { + std::array sv{ + generate_reserved_version(remote_addr, version), + NGTCP2_PROTO_VER_V1, + }; + + std::array buf; + + uint8_t rand_byte; + util::random_bytes(&rand_byte, &rand_byte + 1, worker_->get_randgen()); + + auto nwrite = ngtcp2_pkt_write_version_negotiation( + buf.data(), buf.size(), rand_byte, ini_scid, ini_scidlen, ini_dcid, + ini_dcidlen, sv.data(), sv.size()); + if (nwrite < 0) { + LOG(ERROR) << "ngtcp2_pkt_write_version_negotiation: " + << ngtcp2_strerror(nwrite); + return -1; + } + + return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, + &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{}, + buf.data(), nwrite, 0); +} + +int QUICConnectionHandler::send_stateless_reset(const UpstreamAddr *faddr, + const uint8_t *dcid, + size_t dcidlen, + const Address &remote_addr, + const Address &local_addr) { + if (stateless_reset_bucket_ == 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Stateless Reset bucket has been depleted"; + } + + return 0; + } + + --stateless_reset_bucket_; + + if (!ev_is_active(&stateless_reset_bucket_regen_timer_)) { + ev_timer_again(worker_->get_loop(), &stateless_reset_bucket_regen_timer_); + } + + int rv; + std::array token; + ngtcp2_cid cid; + + ngtcp2_cid_init(&cid, dcid, dcidlen); + + auto conn_handler = worker_->get_connection_handler(); + auto &qkms = conn_handler->get_quic_keying_materials(); + auto &qkm = qkms->keying_materials.front(); + + rv = generate_quic_stateless_reset_token(token.data(), cid, qkm.secret.data(), + qkm.secret.size()); + if (rv != 0) { + return -1; + } + + std::array rand_bytes; + + if (RAND_bytes(rand_bytes.data(), rand_bytes.size()) != 1) { + return -1; + } + + std::array buf; + + auto nwrite = + ngtcp2_pkt_write_stateless_reset(buf.data(), buf.size(), token.data(), + rand_bytes.data(), rand_bytes.size()); + if (nwrite < 0) { + LOG(ERROR) << "ngtcp2_pkt_write_stateless_reset: " + << ngtcp2_strerror(nwrite); + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Send stateless_reset to remote=" + << util::to_numeric_addr(&remote_addr) + << " dcid=" << util::format_hex(dcid, dcidlen); + } + + return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, + &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{}, + buf.data(), nwrite, 0); +} + +int QUICConnectionHandler::send_connection_close( + const UpstreamAddr *faddr, uint32_t version, const ngtcp2_cid &ini_dcid, + const ngtcp2_cid &ini_scid, const Address &remote_addr, + const Address &local_addr, uint64_t error_code, size_t max_pktlen) { + std::array buf; + + max_pktlen = std::min(max_pktlen, buf.size()); + + auto nwrite = ngtcp2_crypto_write_connection_close( + buf.data(), max_pktlen, version, &ini_scid, &ini_dcid, error_code, + nullptr, 0); + if (nwrite < 0) { + LOG(ERROR) << "ngtcp2_crypto_write_connection_close failed"; + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Send Initial CONNECTION_CLOSE with error_code=" << log::hex + << error_code << log::dec + << " to remote=" << util::to_numeric_addr(&remote_addr) + << " dcid=" << util::format_hex(ini_scid.data, ini_scid.datalen) + << " scid=" << util::format_hex(ini_dcid.data, ini_dcid.datalen); + } + + return quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, + &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{}, + buf.data(), nwrite, 0); +} + +void QUICConnectionHandler::add_connection_id(const ngtcp2_cid &cid, + ClientHandler *handler) { + connections_.emplace(cid, handler); +} + +void QUICConnectionHandler::remove_connection_id(const ngtcp2_cid &cid) { + connections_.erase(cid); +} + +void QUICConnectionHandler::add_close_wait(CloseWait *cw) { + for (auto &cid : cw->scids) { + close_waits_.emplace(cid, cw); + } +} + +void QUICConnectionHandler::remove_close_wait(const CloseWait *cw) { + for (auto &cid : cw->scids) { + close_waits_.erase(cid); + } +} + +void QUICConnectionHandler::on_stateless_reset_bucket_regen() { + assert(stateless_reset_bucket_ < SHRPX_QUIC_STATELESS_RESET_BURST); + + if (++stateless_reset_bucket_ == SHRPX_QUIC_STATELESS_RESET_BURST) { + ev_timer_stop(worker_->get_loop(), &stateless_reset_bucket_regen_timer_); + } +} + +static void close_wait_timeoutcb(struct ev_loop *loop, ev_timer *w, + int revents) { + auto cw = static_cast(w->data); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "close-wait period finished"; + } + + auto quic_conn_handler = cw->worker->get_quic_connection_handler(); + quic_conn_handler->remove_close_wait(cw); + + delete cw; +} + +CloseWait::CloseWait(Worker *worker, std::vector scids, + std::vector pkt, ev_tstamp period) + : worker{worker}, + scids{std::move(scids)}, + pkt{std::move(pkt)}, + bytes_recv{0}, + bytes_sent{0}, + num_pkts_recv{0}, + next_pkts_recv{1} { + ++worker->get_worker_stat()->num_close_waits; + + ev_timer_init(&timer, close_wait_timeoutcb, period, 0.); + timer.data = this; + + ev_timer_start(worker->get_loop(), &timer); +} + +CloseWait::~CloseWait() { + auto loop = worker->get_loop(); + + ev_timer_stop(loop, &timer); + + auto worker_stat = worker->get_worker_stat(); + --worker_stat->num_close_waits; + + if (worker->get_graceful_shutdown() && worker_stat->num_connections == 0 && + worker_stat->num_close_waits == 0) { + ev_break(loop); + } +} + +int CloseWait::handle_packet(const UpstreamAddr *faddr, + const Address &remote_addr, + const Address &local_addr, + const ngtcp2_pkt_info &pi, const uint8_t *data, + size_t datalen) { + if (pkt.empty()) { + return 0; + } + + ++num_pkts_recv; + bytes_recv += datalen; + + if (bytes_sent + pkt.size() > 3 * bytes_recv || + next_pkts_recv > num_pkts_recv) { + return 0; + } + + if (quic_send_packet(faddr, &remote_addr.su.sa, remote_addr.len, + &local_addr.su.sa, local_addr.len, ngtcp2_pkt_info{}, + pkt.data(), pkt.size(), 0) != 0) { + return -1; + } + + next_pkts_recv *= 2; + bytes_sent += pkt.size(); + + return 0; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_quic_connection_handler.h b/lib/nghttp2/src/shrpx_quic_connection_handler.h new file mode 100644 index 00000000000..29e73a41c1d --- /dev/null +++ b/lib/nghttp2/src/shrpx_quic_connection_handler.h @@ -0,0 +1,142 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 SHRPX_QUIC_CONNECTION_HANDLER_H +#define SHRPX_QUIC_CONNECTION_HANDLER_H + +#include "shrpx.h" + +#include +#include +#include +#include + +#include + +#include + +#include "shrpx_quic.h" +#include "network.h" + +using namespace nghttp2; + +namespace shrpx { + +struct UpstreamAddr; +class ClientHandler; +class Worker; + +// CloseWait handles packets received in close-wait (draining or +// closing period). +struct CloseWait { + CloseWait(Worker *worker, std::vector scids, + std::vector pkt, ev_tstamp period); + ~CloseWait(); + + int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen); + + Worker *worker; + // Source Connection IDs of the connection. + std::vector scids; + // QUIC packet which is sent in response to the incoming packet. It + // might be empty. + std::vector pkt; + // Close-wait (draining or closing period) timer. + ev_timer timer; + // The number of bytes received during close-wait period. + size_t bytes_recv; + // The number of bytes sent during close-wait period. + size_t bytes_sent; + // The number of packets received during close-wait period. + size_t num_pkts_recv; + // If the number of packets received reaches this number, send a + // QUIC packet. + size_t next_pkts_recv; +}; + +class QUICConnectionHandler { +public: + QUICConnectionHandler(Worker *worker); + ~QUICConnectionHandler(); + int handle_packet(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen); + // Send Retry packet. |ini_dcid| is the destination Connection ID + // which appeared in Client Initial packet and its length is + // |dcidlen|. |ini_scid| is the source Connection ID which appeared + // in Client Initial packet and its length is |scidlen|. + int send_retry(const UpstreamAddr *faddr, uint32_t version, + const uint8_t *ini_dcid, size_t ini_dcidlen, + const uint8_t *ini_scid, size_t ini_scidlen, + const Address &remote_addr, const Address &local_addr, + size_t max_pktlen); + // Send Version Negotiation packet. |ini_dcid| is the destination + // Connection ID which appeared in Client Initial packet and its + // length is |dcidlen|. |ini_scid| is the source Connection ID + // which appeared in Client Initial packet and its length is + // |scidlen|. + int send_version_negotiation(const UpstreamAddr *faddr, uint32_t version, + const uint8_t *ini_dcid, size_t ini_dcidlen, + const uint8_t *ini_scid, size_t ini_scidlen, + const Address &remote_addr, + const Address &local_addr); + int send_stateless_reset(const UpstreamAddr *faddr, const uint8_t *dcid, + size_t dcidlen, const Address &remote_addr, + const Address &local_addr); + // Send Initial CONNECTION_CLOSE. |ini_dcid| is the destination + // Connection ID which appeared in Client Initial packet. + // |ini_scid| is the source Connection ID which appeared in Client + // Initial packet. + int send_connection_close(const UpstreamAddr *faddr, uint32_t version, + const ngtcp2_cid &ini_dcid, + const ngtcp2_cid &ini_scid, + const Address &remote_addr, + const Address &local_addr, uint64_t error_code, + size_t max_pktlen); + ClientHandler * + handle_new_connection(const UpstreamAddr *faddr, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_hd &hd, + const ngtcp2_cid *odcid, const uint8_t *token, + size_t tokenlen, ngtcp2_token_type token_type); + void add_connection_id(const ngtcp2_cid &cid, ClientHandler *handler); + void remove_connection_id(const ngtcp2_cid &cid); + + void add_close_wait(CloseWait *cw); + void remove_close_wait(const CloseWait *cw); + + void on_stateless_reset_bucket_regen(); + +private: + Worker *worker_; + std::unordered_map connections_; + std::unordered_map close_waits_; + ev_timer stateless_reset_bucket_regen_timer_; + size_t stateless_reset_bucket_; +}; + +} // namespace shrpx + +#endif // SHRPX_QUIC_CONNECTION_HANDLER_H diff --git a/lib/nghttp2/src/shrpx_quic_listener.cc b/lib/nghttp2/src/shrpx_quic_listener.cc new file mode 100644 index 00000000000..9b9f1203aa7 --- /dev/null +++ b/lib/nghttp2/src/shrpx_quic_listener.cc @@ -0,0 +1,132 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 "shrpx_quic_listener.h" +#include "shrpx_worker.h" +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revent) { + auto l = static_cast(w->data); + l->on_read(); +} +} // namespace + +QUICListener::QUICListener(const UpstreamAddr *faddr, Worker *worker) + : faddr_{faddr}, worker_{worker} { + ev_io_init(&rev_, readcb, faddr_->fd, EV_READ); + rev_.data = this; + ev_io_start(worker_->get_loop(), &rev_); +} + +QUICListener::~QUICListener() { + ev_io_stop(worker_->get_loop(), &rev_); + close(faddr_->fd); +} + +void QUICListener::on_read() { + sockaddr_union su; + std::array buf; + size_t pktcnt = 0; + iovec msg_iov{buf.data(), buf.size()}; + + msghdr msg{}; + msg.msg_name = &su; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t msg_ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(in6_pktinfo)) + + CMSG_SPACE(sizeof(uint16_t))]; + msg.msg_control = msg_ctrl; + + auto quic_conn_handler = worker_->get_quic_connection_handler(); + + for (; pktcnt < 10;) { + msg.msg_namelen = sizeof(su); + msg.msg_controllen = sizeof(msg_ctrl); + + auto nread = recvmsg(faddr_->fd, &msg, 0); + if (nread == -1) { + return; + } + + Address local_addr{}; + if (util::msghdr_get_local_addr(local_addr, &msg, su.storage.ss_family) != + 0) { + ++pktcnt; + + continue; + } + + util::set_port(local_addr, faddr_->port); + + ngtcp2_pkt_info pi{ + .ecn = util::msghdr_get_ecn(&msg, su.storage.ss_family), + }; + + auto gso_size = util::msghdr_get_udp_gro(&msg); + if (gso_size == 0) { + gso_size = static_cast(nread); + } + + auto data = buf.data(); + + for (;;) { + auto datalen = std::min(static_cast(nread), gso_size); + + ++pktcnt; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "QUIC received packet: local=" + << util::to_numeric_addr(&local_addr) << " remote=" + << util::to_numeric_addr(&su.sa, msg.msg_namelen) + << " ecn=" << log::hex << pi.ecn << log::dec << " " << datalen + << " bytes"; + } + + if (datalen == 0) { + break; + } + + Address remote_addr; + remote_addr.su = su; + remote_addr.len = msg.msg_namelen; + + quic_conn_handler->handle_packet(faddr_, remote_addr, local_addr, pi, + data, datalen); + + nread -= datalen; + if (nread == 0) { + break; + } + + data += datalen; + } + } +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_quic_listener.h b/lib/nghttp2/src/shrpx_quic_listener.h new file mode 100644 index 00000000000..3d709216a10 --- /dev/null +++ b/lib/nghttp2/src/shrpx_quic_listener.h @@ -0,0 +1,51 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2021 Tatsuhiro Tsujikawa + * + * 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 SHRPX_QUIC_LISTENER_H +#define SHRPX_QUIC_LISTENER_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +struct UpstreamAddr; +class Worker; + +class QUICListener { +public: + QUICListener(const UpstreamAddr *faddr, Worker *worker); + ~QUICListener(); + void on_read(); + +private: + const UpstreamAddr *faddr_; + Worker *worker_; + ev_io rev_; +}; + +} // namespace shrpx + +#endif // SHRPX_QUIC_LISTENER_H diff --git a/lib/nghttp2/src/shrpx_rate_limit.cc b/lib/nghttp2/src/shrpx_rate_limit.cc new file mode 100644 index 00000000000..0d4f92126b7 --- /dev/null +++ b/lib/nghttp2/src/shrpx_rate_limit.cc @@ -0,0 +1,123 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_rate_limit.h" + +#include + +#include "shrpx_connection.h" +#include "shrpx_log.h" + +namespace shrpx { + +namespace { +void regencb(struct ev_loop *loop, ev_timer *w, int revents) { + auto r = static_cast(w->data); + r->regen(); +} +} // namespace + +RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst, + Connection *conn) + : w_(w), + loop_(loop), + conn_(conn), + rate_(rate), + burst_(burst), + avail_(burst), + startw_req_(false) { + ev_timer_init(&t_, regencb, 0., 1.); + t_.data = this; + if (rate_ > 0) { + ev_timer_again(loop_, &t_); + } +} + +RateLimit::~RateLimit() { ev_timer_stop(loop_, &t_); } + +size_t RateLimit::avail() const { + if (rate_ == 0) { + return std::numeric_limits::max(); + } + return avail_; +} + +void RateLimit::drain(size_t n) { + if (rate_ == 0) { + return; + } + n = std::min(avail_, n); + avail_ -= n; + if (avail_ == 0) { + ev_io_stop(loop_, w_); + } +} + +void RateLimit::regen() { + if (rate_ == 0) { + return; + } + if (avail_ + rate_ > burst_) { + avail_ = burst_; + } else { + avail_ += rate_; + } + + if (w_->fd >= 0 && avail_ > 0 && startw_req_) { + ev_io_start(loop_, w_); + handle_tls_pending_read(); + } +} + +void RateLimit::startw() { + if (w_->fd < 0) { + return; + } + startw_req_ = true; + if (rate_ == 0 || avail_ > 0) { + ev_io_start(loop_, w_); + handle_tls_pending_read(); + return; + } +} + +void RateLimit::stopw() { + startw_req_ = false; + ev_io_stop(loop_, w_); +} + +void RateLimit::handle_tls_pending_read() { + if (!conn_ || !conn_->tls.ssl || + (SSL_pending(conn_->tls.ssl) == 0 && conn_->tls.rbuf.rleft() == 0 && + (!conn_->tls.initial_handshake_done || + conn_->tls.earlybuf.rleft() == 0))) { + return; + } + + // Note that ev_feed_event works without starting watcher, but we + // only call this function if watcher is active. + ev_feed_event(loop_, w_, EV_READ); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_rate_limit.h b/lib/nghttp2/src/shrpx_rate_limit.h new file mode 100644 index 00000000000..7502a27a2b1 --- /dev/null +++ b/lib/nghttp2/src/shrpx_rate_limit.h @@ -0,0 +1,68 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_RATE_LIMIT_H +#define SHRPX_RATE_LIMIT_H + +#include "shrpx.h" + +#include + +#include + +namespace shrpx { + +struct Connection; + +class RateLimit { +public: + // We need |conn| object to check that it has unread bytes for TLS + // connection. + RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst, + Connection *conn = nullptr); + ~RateLimit(); + size_t avail() const; + void drain(size_t n); + void regen(); + void startw(); + void stopw(); + // Feeds event if conn_->tls object has unread bytes. This is + // required since it is buffered in conn_->tls object, io event is + // not generated unless new incoming data is received. + void handle_tls_pending_read(); + +private: + ev_timer t_; + ev_io *w_; + struct ev_loop *loop_; + Connection *conn_; + size_t rate_; + size_t burst_; + size_t avail_; + bool startw_req_; +}; + +} // namespace shrpx + +#endif // SHRPX_RATE_LIMIT_H diff --git a/lib/nghttp2/src/shrpx_router.cc b/lib/nghttp2/src/shrpx_router.cc new file mode 100644 index 00000000000..d3565db8913 --- /dev/null +++ b/lib/nghttp2/src/shrpx_router.cc @@ -0,0 +1,420 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_router.h" + +#include + +#include "shrpx_config.h" +#include "shrpx_log.h" + +namespace shrpx { + +RNode::RNode() : s(nullptr), len(0), index(-1), wildcard_index(-1) {} + +RNode::RNode(const char *s, size_t len, ssize_t index, ssize_t wildcard_index) + : s(s), len(len), index(index), wildcard_index(wildcard_index) {} + +Router::Router() : balloc_(1024, 1024), root_{} {} + +Router::~Router() {} + +namespace { +RNode *find_next_node(const RNode *node, char c) { + auto itr = std::lower_bound(std::begin(node->next), std::end(node->next), c, + [](const std::unique_ptr &lhs, + const char c) { return lhs->s[0] < c; }); + if (itr == std::end(node->next) || (*itr)->s[0] != c) { + return nullptr; + } + + return (*itr).get(); +} +} // namespace + +namespace { +void add_next_node(RNode *node, std::unique_ptr new_node) { + auto itr = std::lower_bound(std::begin(node->next), std::end(node->next), + new_node->s[0], + [](const std::unique_ptr &lhs, + const char c) { return lhs->s[0] < c; }); + node->next.insert(itr, std::move(new_node)); +} +} // namespace + +void Router::add_node(RNode *node, const char *pattern, size_t patlen, + ssize_t index, ssize_t wildcard_index) { + auto pat = make_string_ref(balloc_, StringRef{pattern, patlen}); + auto new_node = + std::make_unique(pat.c_str(), pat.size(), index, wildcard_index); + add_next_node(node, std::move(new_node)); +} + +size_t Router::add_route(const StringRef &pattern, size_t idx, bool wildcard) { + ssize_t index = -1, wildcard_index = -1; + if (wildcard) { + wildcard_index = idx; + } else { + index = idx; + } + + auto node = &root_; + size_t i = 0; + + for (;;) { + auto next_node = find_next_node(node, pattern[i]); + if (next_node == nullptr) { + add_node(node, pattern.c_str() + i, pattern.size() - i, index, + wildcard_index); + return idx; + } + + node = next_node; + + auto slen = pattern.size() - i; + auto s = pattern.c_str() + i; + auto n = std::min(node->len, slen); + size_t j; + for (j = 0; j < n && node->s[j] == s[j]; ++j) + ; + if (j == n) { + // The common prefix was matched + if (slen == node->len) { + // Complete match + if (index != -1) { + if (node->index != -1) { + // Return the existing index for duplicates. + return node->index; + } + node->index = index; + return idx; + } + + assert(wildcard_index != -1); + + if (node->wildcard_index != -1) { + return node->wildcard_index; + } + node->wildcard_index = wildcard_index; + return idx; + } + + if (slen > node->len) { + // We still have pattern to add + i += j; + + continue; + } + } + + if (node->len > j) { + // node must be split into 2 nodes. new_node is now the child + // of node. + auto new_node = std::make_unique( + &node->s[j], node->len - j, node->index, node->wildcard_index); + std::swap(node->next, new_node->next); + + node->len = j; + node->index = -1; + node->wildcard_index = -1; + + add_next_node(node, std::move(new_node)); + + if (slen == j) { + node->index = index; + node->wildcard_index = wildcard_index; + return idx; + } + } + + i += j; + + assert(pattern.size() > i); + add_node(node, pattern.c_str() + i, pattern.size() - i, index, + wildcard_index); + + return idx; + } +} + +namespace { +const RNode *match_complete(size_t *offset, const RNode *node, + const char *first, const char *last) { + *offset = 0; + + if (first == last) { + return node; + } + + auto p = first; + + for (;;) { + auto next_node = find_next_node(node, *p); + if (next_node == nullptr) { + return nullptr; + } + + node = next_node; + + auto n = std::min(node->len, static_cast(last - p)); + if (memcmp(node->s, p, n) != 0) { + return nullptr; + } + p += n; + if (p == last) { + *offset = n; + return node; + } + } +} +} // namespace + +namespace { +const RNode *match_partial(bool *pattern_is_wildcard, const RNode *node, + size_t offset, const char *first, const char *last) { + *pattern_is_wildcard = false; + + if (first == last) { + if (node->len == offset) { + return node; + } + return nullptr; + } + + auto p = first; + + const RNode *found_node = nullptr; + + if (offset > 0) { + auto n = std::min(node->len - offset, static_cast(last - first)); + if (memcmp(node->s + offset, first, n) != 0) { + return nullptr; + } + + p += n; + + if (p == last) { + if (node->len == offset + n) { + if (node->index != -1) { + return node; + } + + // The last '/' handling, see below. + node = find_next_node(node, '/'); + if (node != nullptr && node->index != -1 && node->len == 1) { + return node; + } + + return nullptr; + } + + // The last '/' handling, see below. + if (node->index != -1 && offset + n + 1 == node->len && + node->s[node->len - 1] == '/') { + return node; + } + + return nullptr; + } + + if (node->wildcard_index != -1) { + found_node = node; + *pattern_is_wildcard = true; + } else if (node->index != -1 && node->s[node->len - 1] == '/') { + found_node = node; + *pattern_is_wildcard = false; + } + + assert(node->len == offset + n); + } + + for (;;) { + auto next_node = find_next_node(node, *p); + if (next_node == nullptr) { + return found_node; + } + + node = next_node; + + auto n = std::min(node->len, static_cast(last - p)); + if (memcmp(node->s, p, n) != 0) { + return found_node; + } + + p += n; + + if (p == last) { + if (node->len == n) { + // Complete match with this node + if (node->index != -1) { + *pattern_is_wildcard = false; + return node; + } + + // The last '/' handling, see below. + node = find_next_node(node, '/'); + if (node != nullptr && node->index != -1 && node->len == 1) { + *pattern_is_wildcard = false; + return node; + } + + return found_node; + } + + // We allow match without trailing "/" at the end of pattern. + // So, if pattern ends with '/', and pattern and path matches + // without that slash, we consider they match to deal with + // request to the directory without trailing slash. That is if + // pattern is "/foo/" and path is "/foo", we consider they + // match. + if (node->index != -1 && n + 1 == node->len && node->s[n] == '/') { + *pattern_is_wildcard = false; + return node; + } + + return found_node; + } + + if (node->wildcard_index != -1) { + found_node = node; + *pattern_is_wildcard = true; + } else if (node->index != -1 && node->s[node->len - 1] == '/') { + // This is the case when pattern which ends with "/" is included + // in query. + found_node = node; + *pattern_is_wildcard = false; + } + + assert(node->len == n); + } +} +} // namespace + +ssize_t Router::match(const StringRef &host, const StringRef &path) const { + const RNode *node; + size_t offset; + + node = match_complete(&offset, &root_, std::begin(host), std::end(host)); + if (node == nullptr) { + return -1; + } + + bool pattern_is_wildcard; + node = match_partial(&pattern_is_wildcard, node, offset, std::begin(path), + std::end(path)); + if (node == nullptr || node == &root_) { + return -1; + } + + return pattern_is_wildcard ? node->wildcard_index : node->index; +} + +ssize_t Router::match(const StringRef &s) const { + const RNode *node; + size_t offset; + + node = match_complete(&offset, &root_, std::begin(s), std::end(s)); + if (node == nullptr) { + return -1; + } + + if (node->len != offset) { + return -1; + } + + return node->index; +} + +namespace { +const RNode *match_prefix(size_t *nread, const RNode *node, const char *first, + const char *last) { + if (first == last) { + return nullptr; + } + + auto p = first; + + for (;;) { + auto next_node = find_next_node(node, *p); + if (next_node == nullptr) { + return nullptr; + } + + node = next_node; + + auto n = std::min(node->len, static_cast(last - p)); + if (memcmp(node->s, p, n) != 0) { + return nullptr; + } + + p += n; + + if (p != last) { + if (node->index != -1) { + *nread = p - first; + return node; + } + continue; + } + + if (node->len == n) { + *nread = p - first; + return node; + } + + return nullptr; + } +} +} // namespace + +ssize_t Router::match_prefix(size_t *nread, const RNode **last_node, + const StringRef &s) const { + if (*last_node == nullptr) { + *last_node = &root_; + } + + auto node = + ::shrpx::match_prefix(nread, *last_node, std::begin(s), std::end(s)); + if (node == nullptr) { + return -1; + } + + *last_node = node; + + return node->index; +} + +namespace { +void dump_node(const RNode *node, int depth) { + fprintf(stderr, "%*ss='%.*s', len=%zu, index=%zd\n", depth, "", + (int)node->len, node->s, node->len, node->index); + for (auto &nd : node->next) { + dump_node(nd.get(), depth + 4); + } +} +} // namespace + +void Router::dump() const { dump_node(&root_, 0); } + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_router.h b/lib/nghttp2/src/shrpx_router.h new file mode 100644 index 00000000000..295db7e669a --- /dev/null +++ b/lib/nghttp2/src/shrpx_router.h @@ -0,0 +1,110 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_ROUTER_H +#define SHRPX_ROUTER_H + +#include "shrpx.h" + +#include +#include + +#include "allocator.h" + +using namespace nghttp2; + +namespace shrpx { + +struct RNode { + RNode(); + RNode(const char *s, size_t len, ssize_t index, ssize_t wildcard_index); + RNode(RNode &&) = default; + RNode(const RNode &) = delete; + RNode &operator=(RNode &&) = default; + RNode &operator=(const RNode &) = delete; + + // Next RNode, sorted by s[0]. + std::vector> next; + // Stores pointer to the string this node represents. Not + // NULL-terminated. + const char *s; + // Length of |s| + size_t len; + // Index of pattern if match ends in this node. Note that we don't + // store duplicated pattern. + ssize_t index; + // Index of wildcard pattern if query includes this node as prefix + // and it still has suffix to match. Note that we don't store + // duplicated pattern. + ssize_t wildcard_index; +}; + +class Router { +public: + Router(); + ~Router(); + Router(Router &&) = default; + Router(const Router &) = delete; + Router &operator=(Router &&) = default; + Router &operator=(const Router &) = delete; + + // Adds route |pattern| with its |index|. If same pattern has + // already been added, the existing index is returned. If + // |wildcard| is true, |pattern| is considered as wildcard pattern, + // and all paths which have the |pattern| as prefix and are strictly + // longer than |pattern| match. The wildcard pattern only works + // with match(const StringRef&, const StringRef&). + size_t add_route(const StringRef &pattern, size_t index, + bool wildcard = false); + // Returns the matched index of pattern. -1 if there is no match. + ssize_t match(const StringRef &host, const StringRef &path) const; + // Returns the matched index of pattern |s|. -1 if there is no + // match. + ssize_t match(const StringRef &s) const; + // Returns the matched index of pattern if a pattern is a suffix of + // |s|, otherwise -1. If |*last_node| is not nullptr, it specifies + // the first node to start matching. If it is nullptr, match will + // start from scratch. When the match was found (the return value + // is not -1), |*nread| has the number of bytes matched in |s|, and + // |*last_node| has the last matched node. One can continue to + // match the longer pattern using the returned |*last_node| to the + // another invocation of this function until it returns -1. + ssize_t match_prefix(size_t *nread, const RNode **last_node, + const StringRef &s) const; + + void add_node(RNode *node, const char *pattern, size_t patlen, ssize_t index, + ssize_t wildcard_index); + + void dump() const; + +private: + BlockAllocator balloc_; + // The root node of Patricia tree. This is special node and its s + // field is nulptr, and len field is 0. + RNode root_; +}; + +} // namespace shrpx + +#endif // SHRPX_ROUTER_H diff --git a/lib/nghttp2/src/shrpx_router_test.cc b/lib/nghttp2/src/shrpx_router_test.cc new file mode 100644 index 00000000000..21c2f51964d --- /dev/null +++ b/lib/nghttp2/src/shrpx_router_test.cc @@ -0,0 +1,184 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_router_test.h" + +#include + +#include "shrpx_router.h" + +namespace shrpx { + +struct Pattern { + StringRef pattern; + size_t idx; + bool wildcard; +}; + +void test_shrpx_router_match(void) { + auto patterns = std::vector{ + {StringRef::from_lit("nghttp2.org/"), 0}, + {StringRef::from_lit("nghttp2.org/alpha"), 1}, + {StringRef::from_lit("nghttp2.org/alpha/"), 2}, + {StringRef::from_lit("nghttp2.org/alpha/bravo/"), 3}, + {StringRef::from_lit("www.nghttp2.org/alpha/"), 4}, + {StringRef::from_lit("/alpha"), 5}, + {StringRef::from_lit("example.com/alpha/"), 6}, + {StringRef::from_lit("nghttp2.org/alpha/bravo2/"), 7}, + {StringRef::from_lit("www2.nghttp2.org/alpha/"), 8}, + {StringRef::from_lit("www2.nghttp2.org/alpha2/"), 9}, + }; + + Router router; + + for (auto &p : patterns) { + router.add_route(p.pattern, p.idx); + } + + ssize_t idx; + + idx = router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/")); + + CU_ASSERT(0 == idx); + + idx = router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha")); + + CU_ASSERT(1 == idx); + + idx = router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/")); + + CU_ASSERT(2 == idx); + + idx = router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/charlie")); + + CU_ASSERT(2 == idx); + + idx = router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/bravo/")); + + CU_ASSERT(3 == idx); + + // matches pattern when last '/' is missing in path + idx = router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/bravo")); + + CU_ASSERT(3 == idx); + + idx = router.match(StringRef::from_lit("www2.nghttp2.org"), + StringRef::from_lit("/alpha")); + + CU_ASSERT(8 == idx); + + idx = router.match(StringRef{}, StringRef::from_lit("/alpha")); + + CU_ASSERT(5 == idx); +} + +void test_shrpx_router_match_wildcard(void) { + constexpr auto patterns = std::array{{ + {StringRef::from_lit("nghttp2.org/"), 0}, + {StringRef::from_lit("nghttp2.org/"), 1, true}, + {StringRef::from_lit("nghttp2.org/alpha/"), 2}, + {StringRef::from_lit("nghttp2.org/alpha/"), 3, true}, + {StringRef::from_lit("nghttp2.org/bravo"), 4}, + {StringRef::from_lit("nghttp2.org/bravo"), 5, true}, + }}; + + Router router; + + for (auto &p : patterns) { + router.add_route(p.pattern, p.idx, p.wildcard); + } + + CU_ASSERT(0 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/"))); + + CU_ASSERT(1 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/a"))); + + CU_ASSERT(1 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/charlie"))); + + CU_ASSERT(2 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha"))); + + CU_ASSERT(2 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/"))); + + CU_ASSERT(3 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/b"))); + + CU_ASSERT(4 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/bravo"))); + + CU_ASSERT(5 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/bravocharlie"))); + + CU_ASSERT(5 == router.match(StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/bravo/"))); +} + +void test_shrpx_router_match_prefix(void) { + auto patterns = std::vector{ + {StringRef::from_lit("gro.2ptthgn."), 0}, + {StringRef::from_lit("gro.2ptthgn.www."), 1}, + {StringRef::from_lit("gro.2ptthgn.gmi."), 2}, + {StringRef::from_lit("gro.2ptthgn.gmi.ahpla."), 3}, + }; + + Router router; + + for (auto &p : patterns) { + router.add_route(p.pattern, p.idx); + } + + ssize_t idx; + const RNode *node; + size_t nread; + + node = nullptr; + + idx = router.match_prefix(&nread, &node, + StringRef::from_lit("gro.2ptthgn.gmi.ahpla.ovarb")); + + CU_ASSERT(0 == idx); + CU_ASSERT(12 == nread); + + idx = router.match_prefix(&nread, &node, + StringRef::from_lit("gmi.ahpla.ovarb")); + + CU_ASSERT(2 == idx); + CU_ASSERT(4 == nread); + + idx = router.match_prefix(&nread, &node, StringRef::from_lit("ahpla.ovarb")); + + CU_ASSERT(3 == idx); + CU_ASSERT(6 == nread); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_router_test.h b/lib/nghttp2/src/shrpx_router_test.h new file mode 100644 index 00000000000..d39cb87b4a6 --- /dev/null +++ b/lib/nghttp2/src/shrpx_router_test.h @@ -0,0 +1,40 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_ROUTER_TEST_H +#define SHRPX_ROUTER_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_router_match(void); +void test_shrpx_router_match_wildcard(void); +void test_shrpx_router_match_prefix(void); + +} // namespace shrpx + +#endif // SHRPX_ROUTER_TEST_H diff --git a/lib/nghttp2/src/shrpx_signal.cc b/lib/nghttp2/src/shrpx_signal.cc new file mode 100644 index 00000000000..63fcc077600 --- /dev/null +++ b/lib/nghttp2/src/shrpx_signal.cc @@ -0,0 +1,138 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_signal.h" + +#include + +#include "shrpx_log.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +int shrpx_signal_block_all(sigset_t *oldset) { + sigset_t newset; + + sigfillset(&newset); + +#ifndef NOTHREADS + int rv; + + rv = pthread_sigmask(SIG_SETMASK, &newset, oldset); + + if (rv != 0) { + errno = rv; + return -1; + } + + return 0; +#else // NOTHREADS + return sigprocmask(SIG_SETMASK, &newset, oldset); +#endif // NOTHREADS +} + +int shrpx_signal_unblock_all() { + sigset_t newset; + + sigemptyset(&newset); + +#ifndef NOTHREADS + int rv; + + rv = pthread_sigmask(SIG_SETMASK, &newset, nullptr); + + if (rv != 0) { + errno = rv; + return -1; + } + + return 0; +#else // NOTHREADS + return sigprocmask(SIG_SETMASK, &newset, nullptr); +#endif // NOTHREADS +} + +int shrpx_signal_set(sigset_t *set) { +#ifndef NOTHREADS + int rv; + + rv = pthread_sigmask(SIG_SETMASK, set, nullptr); + + if (rv != 0) { + errno = rv; + return -1; + } + + return 0; +#else // NOTHREADS + return sigprocmask(SIG_SETMASK, set, nullptr); +#endif // NOTHREADS +} + +namespace { +template +int signal_set_handler(void (*handler)(int), Signals &&sigs) { + struct sigaction act {}; + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + int rv; + for (auto sig : sigs) { + rv = sigaction(sig, &act, nullptr); + if (rv != 0) { + return -1; + } + } + return 0; +} +} // namespace + +namespace { +constexpr auto main_proc_ign_signals = std::array{SIGPIPE}; +} // namespace + +namespace { +constexpr auto worker_proc_ign_signals = + std::array{REOPEN_LOG_SIGNAL, EXEC_BINARY_SIGNAL, + GRACEFUL_SHUTDOWN_SIGNAL, RELOAD_SIGNAL, SIGPIPE}; +} // namespace + +int shrpx_signal_set_main_proc_ign_handler() { + return signal_set_handler(SIG_IGN, main_proc_ign_signals); +} + +int shrpx_signal_unset_main_proc_ign_handler() { + return signal_set_handler(SIG_DFL, main_proc_ign_signals); +} + +int shrpx_signal_set_worker_proc_ign_handler() { + return signal_set_handler(SIG_IGN, worker_proc_ign_signals); +} + +int shrpx_signal_unset_worker_proc_ign_handler() { + return signal_set_handler(SIG_DFL, worker_proc_ign_signals); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_signal.h b/lib/nghttp2/src/shrpx_signal.h new file mode 100644 index 00000000000..152ca369744 --- /dev/null +++ b/lib/nghttp2/src/shrpx_signal.h @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_SIGNAL_H +#define SHRPX_SIGNAL_H + +#include "shrpx.h" + +#include + +namespace shrpx { + +constexpr int REOPEN_LOG_SIGNAL = SIGUSR1; +constexpr int EXEC_BINARY_SIGNAL = SIGUSR2; +constexpr int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT; +constexpr int RELOAD_SIGNAL = SIGHUP; + +// Blocks all signals. The previous signal mask is stored into +// |oldset| if it is not nullptr. This function returns 0 if it +// succeeds, or -1. The errno will indicate the error. +int shrpx_signal_block_all(sigset_t *oldset); + +// Unblocks all signals. This function returns 0 if it succeeds, or +// -1. The errno will indicate the error. +int shrpx_signal_unblock_all(); + +// Sets signal mask |set|. This function returns 0 if it succeeds, or +// -1. The errno will indicate the error. +int shrpx_signal_set(sigset_t *set); + +int shrpx_signal_set_main_proc_ign_handler(); +int shrpx_signal_unset_main_proc_ign_handler(); + +int shrpx_signal_set_worker_proc_ign_handler(); +int shrpx_signal_unset_worker_proc_ign_handler(); + +} // namespace shrpx + +#endif // SHRPX_SIGNAL_H diff --git a/lib/nghttp2/src/shrpx_tls.cc b/lib/nghttp2/src/shrpx_tls.cc new file mode 100644 index 00000000000..5dd2d150614 --- /dev/null +++ b/lib/nghttp2/src/shrpx_tls.cc @@ -0,0 +1,2691 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_tls.h" + +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#include +#include +#include + +#include +#include +#include + +#include + +#include "ssl_compat.h" + +#include +#include +#include +#include +#include +#ifndef OPENSSL_NO_OCSP +# include +#endif // OPENSSL_NO_OCSP +#if OPENSSL_3_0_0_API +# include +# include +# include +#endif // OPENSSL_3_0_0_API +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL +# include +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + +#include + +#ifdef ENABLE_HTTP3 +# include +# include +# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# include +# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +# include +# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL +#endif // ENABLE_HTTP3 + +#include "shrpx_log.h" +#include "shrpx_client_handler.h" +#include "shrpx_config.h" +#include "shrpx_worker.h" +#include "shrpx_downstream_connection_pool.h" +#include "shrpx_http2_session.h" +#include "shrpx_memcached_request.h" +#include "shrpx_memcached_dispatcher.h" +#include "shrpx_connection_handler.h" +#ifdef ENABLE_HTTP3 +# include "shrpx_http3_upstream.h" +#endif // ENABLE_HTTP3 +#include "util.h" +#include "tls.h" +#include "template.h" +#include "timegm.h" + +using namespace nghttp2; +using namespace std::chrono_literals; + +namespace shrpx { + +namespace tls { + +#if !OPENSSL_1_1_API +namespace { +const unsigned char *ASN1_STRING_get0_data(ASN1_STRING *x) { + return ASN1_STRING_data(x); +} +} // namespace +#endif // !OPENSSL_1_1_API + +#ifndef OPENSSL_NO_NEXTPROTONEG +namespace { +int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len, + void *arg) { + auto &prefs = get_config()->tls.alpn_prefs; + *data = prefs.data(); + *len = prefs.size(); + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // !OPENSSL_NO_NEXTPROTONEG + +namespace { +int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { + if (!preverify_ok) { + int err = X509_STORE_CTX_get_error(ctx); + int depth = X509_STORE_CTX_get_error_depth(ctx); + if (err == X509_V_ERR_CERT_HAS_EXPIRED && depth == 0 && + get_config()->tls.client_verify.tolerate_expired) { + LOG(INFO) << "The client certificate has expired, but is accepted by " + "configuration"; + return 1; + } + LOG(ERROR) << "client certificate verify error:num=" << err << ":" + << X509_verify_cert_error_string(err) << ":depth=" << depth; + } + return preverify_ok; +} +} // namespace + +int set_alpn_prefs(std::vector &out, + const std::vector &protos) { + size_t len = 0; + + for (const auto &proto : protos) { + if (proto.size() > 255) { + LOG(FATAL) << "Too long ALPN identifier: " << proto.size(); + return -1; + } + + len += 1 + proto.size(); + } + + if (len > (1 << 16) - 1) { + LOG(FATAL) << "Too long ALPN identifier list: " << len; + return -1; + } + + out.resize(len); + auto ptr = out.data(); + + for (const auto &proto : protos) { + *ptr++ = proto.size(); + ptr = std::copy(std::begin(proto), std::end(proto), ptr); + } + + return 0; +} + +namespace { +int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *user_data) { + auto config = static_cast(user_data); + auto len = static_cast(config->tls.private_key_passwd.size()); + if (size < len + 1) { + LOG(ERROR) << "ssl_pem_passwd_cb: buf is too small " << size; + return 0; + } + // Copy string including last '\0'. + memcpy(buf, config->tls.private_key_passwd.c_str(), len + 1); + return len; +} +} // namespace + +namespace { +// *al is set to SSL_AD_UNRECOGNIZED_NAME by openssl, so we don't have +// to set it explicitly. +int servername_callback(SSL *ssl, int *al, void *arg) { + auto conn = static_cast(SSL_get_app_data(ssl)); + auto handler = static_cast(conn->data); + auto worker = handler->get_worker(); + + auto rawhost = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (rawhost == nullptr) { + return SSL_TLSEXT_ERR_NOACK; + } + + auto len = strlen(rawhost); + // NI_MAXHOST includes terminal NULL. + if (len == 0 || len + 1 > NI_MAXHOST) { + return SSL_TLSEXT_ERR_NOACK; + } + + std::array buf; + + auto end_buf = std::copy_n(rawhost, len, std::begin(buf)); + + util::inp_strlower(std::begin(buf), end_buf); + + auto hostname = StringRef{std::begin(buf), end_buf}; + +#ifdef ENABLE_HTTP3 + auto cert_tree = conn->proto == Proto::HTTP3 + ? worker->get_quic_cert_lookup_tree() + : worker->get_cert_lookup_tree(); +#else // !ENABLE_HTTP3 + auto cert_tree = worker->get_cert_lookup_tree(); +#endif // !ENABLE_HTTP3 + + auto idx = cert_tree->lookup(hostname); + if (idx == -1) { + return SSL_TLSEXT_ERR_NOACK; + } + + handler->set_tls_sni(hostname); + + auto conn_handler = worker->get_connection_handler(); + +#ifdef ENABLE_HTTP3 + const auto &ssl_ctx_list = conn->proto == Proto::HTTP3 + ? conn_handler->get_quic_indexed_ssl_ctx(idx) + : conn_handler->get_indexed_ssl_ctx(idx); +#else // !ENABLE_HTTP3 + const auto &ssl_ctx_list = conn_handler->get_indexed_ssl_ctx(idx); +#endif // !ENABLE_HTTP3 + + assert(!ssl_ctx_list.empty()); + +#if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !LIBRESSL_IN_USE && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L + auto num_sigalgs = + SSL_get_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr, nullptr); + + for (idx = 0; idx < num_sigalgs; ++idx) { + int signhash; + + SSL_get_sigalgs(ssl, idx, nullptr, nullptr, &signhash, nullptr, nullptr); + switch (signhash) { + case NID_ecdsa_with_SHA256: + case NID_ecdsa_with_SHA384: + case NID_ecdsa_with_SHA512: + break; + default: + continue; + } + + break; + } + + if (idx == num_sigalgs) { + SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]); + + return SSL_TLSEXT_ERR_OK; + } + + auto num_shared_curves = SSL_get_shared_curve(ssl, -1); + + for (auto i = 0; i < num_shared_curves; ++i) { + auto shared_curve = SSL_get_shared_curve(ssl, i); +# if OPENSSL_3_0_0_API + // It looks like only short name is defined in OpenSSL. No idea + // which one to use because it is unknown that which one + // EVP_PKEY_get_utf8_string_param("group") returns. + auto shared_curve_name = OBJ_nid2sn(shared_curve); + if (shared_curve_name == nullptr) { + continue; + } +# endif // OPENSSL_3_0_0_API + + for (auto ssl_ctx : ssl_ctx_list) { + auto cert = SSL_CTX_get0_certificate(ssl_ctx); + +# if OPENSSL_1_1_API + auto pubkey = X509_get0_pubkey(cert); +# else // !OPENSSL_1_1_API + auto pubkey = X509_get_pubkey(cert); +# endif // !OPENSSL_1_1_API + + if (EVP_PKEY_base_id(pubkey) != EVP_PKEY_EC) { + continue; + } + +# if OPENSSL_3_0_0_API + std::array curve_name; + if (!EVP_PKEY_get_utf8_string_param(pubkey, "group", curve_name.data(), + curve_name.size(), nullptr)) { + continue; + } + + if (strcmp(shared_curve_name, curve_name.data()) == 0) { + SSL_set_SSL_CTX(ssl, ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } +# else // !OPENSSL_3_0_0_API +# if OPENSSL_1_1_API + auto eckey = EVP_PKEY_get0_EC_KEY(pubkey); +# else // !OPENSSL_1_1_API + auto eckey = EVP_PKEY_get1_EC_KEY(pubkey); +# endif // !OPENSSL_1_1_API + + if (eckey == nullptr) { + continue; + } + + auto ecgroup = EC_KEY_get0_group(eckey); + auto cert_curve = EC_GROUP_get_curve_name(ecgroup); + +# if !OPENSSL_1_1_API + EC_KEY_free(eckey); + EVP_PKEY_free(pubkey); +# endif // !OPENSSL_1_1_API + + if (shared_curve == cert_curve) { + SSL_set_SSL_CTX(ssl, ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } +# endif // !OPENSSL_3_0_0_API + } + } +#endif // !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !LIBRESSL_IN_USE && + // OPENSSL_VERSION_NUMBER >= 0x10002000L + + SSL_set_SSL_CTX(ssl, ssl_ctx_list[0]); + + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL +namespace { +std::shared_ptr> +get_ocsp_data(TLSContextData *tls_ctx_data) { +# ifdef HAVE_ATOMIC_STD_SHARED_PTR + return std::atomic_load_explicit(&tls_ctx_data->ocsp_data, + std::memory_order_acquire); +# else // !HAVE_ATOMIC_STD_SHARED_PTR + std::lock_guard g(tls_ctx_data->mu); + return tls_ctx_data->ocsp_data; +# endif // !HAVE_ATOMIC_STD_SHARED_PTR +} +} // namespace + +namespace { +int ocsp_resp_cb(SSL *ssl, void *arg) { + auto ssl_ctx = SSL_get_SSL_CTX(ssl); + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + + auto data = get_ocsp_data(tls_ctx_data); + + if (!data) { + return SSL_TLSEXT_ERR_OK; + } + + auto buf = static_cast( + CRYPTO_malloc(data->size(), NGHTTP2_FILE_NAME, __LINE__)); + + if (!buf) { + return SSL_TLSEXT_ERR_OK; + } + + std::copy(std::begin(*data), std::end(*data), buf); + + SSL_set_tlsext_status_ocsp_resp(ssl, buf, data->size()); + + return SSL_TLSEXT_ERR_OK; +} +} // namespace +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + +constexpr auto MEMCACHED_SESSION_CACHE_KEY_PREFIX = + StringRef::from_lit("nghttpx:tls-session-cache:"); + +namespace { +int tls_session_client_new_cb(SSL *ssl, SSL_SESSION *session) { + auto conn = static_cast(SSL_get_app_data(ssl)); + if (conn->tls.client_session_cache == nullptr) { + return 0; + } + + try_cache_tls_session(conn->tls.client_session_cache, session, + std::chrono::steady_clock::now()); + + return 0; +} +} // namespace + +namespace { +int tls_session_new_cb(SSL *ssl, SSL_SESSION *session) { + auto conn = static_cast(SSL_get_app_data(ssl)); + auto handler = static_cast(conn->data); + auto worker = handler->get_worker(); + auto dispatcher = worker->get_session_cache_memcached_dispatcher(); + auto &balloc = handler->get_block_allocator(); + +#ifdef TLS1_3_VERSION + if (SSL_version(ssl) == TLS1_3_VERSION) { + return 0; + } +#endif // TLS1_3_VERSION + + const unsigned char *id; + unsigned int idlen; + + id = SSL_SESSION_get_id(session, &idlen); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Memcached: cache session, id=" << util::format_hex(id, idlen); + } + + auto req = std::make_unique(); + req->op = MemcachedOp::ADD; + req->key = MEMCACHED_SESSION_CACHE_KEY_PREFIX.str(); + req->key += + util::format_hex(balloc, StringRef{id, static_cast(idlen)}); + + auto sessionlen = i2d_SSL_SESSION(session, nullptr); + req->value.resize(sessionlen); + auto buf = &req->value[0]; + i2d_SSL_SESSION(session, &buf); + req->expiry = 12_h; + req->cb = [](MemcachedRequest *req, MemcachedResult res) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Memcached: session cache done. key=" << req->key + << ", status_code=" << static_cast(res.status_code) + << ", value=" + << std::string(std::begin(res.value), std::end(res.value)); + } + if (res.status_code != MemcachedStatusCode::NO_ERROR) { + LOG(WARN) << "Memcached: failed to cache session key=" << req->key + << ", status_code=" << static_cast(res.status_code) + << ", value=" + << std::string(std::begin(res.value), std::end(res.value)); + } + }; + assert(!req->canceled); + + dispatcher->add_request(std::move(req)); + + return 0; +} +} // namespace + +namespace { +SSL_SESSION *tls_session_get_cb(SSL *ssl, +#if OPENSSL_1_1_API || LIBRESSL_2_7_API + const unsigned char *id, +#else // !(OPENSSL_1_1_API || LIBRESSL_2_7_API) + unsigned char *id, +#endif // !(OPENSSL_1_1_API || LIBRESSL_2_7_API) + int idlen, int *copy) { + auto conn = static_cast(SSL_get_app_data(ssl)); + auto handler = static_cast(conn->data); + auto worker = handler->get_worker(); + auto dispatcher = worker->get_session_cache_memcached_dispatcher(); + auto &balloc = handler->get_block_allocator(); + + if (idlen == 0) { + return nullptr; + } + + if (conn->tls.cached_session) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Memcached: found cached session, id=" + << util::format_hex(id, idlen); + } + + // This is required, without this, memory leak occurs. + *copy = 0; + + auto session = conn->tls.cached_session; + conn->tls.cached_session = nullptr; + return session; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Memcached: get cached session, id=" + << util::format_hex(id, idlen); + } + + auto req = std::make_unique(); + req->op = MemcachedOp::GET; + req->key = MEMCACHED_SESSION_CACHE_KEY_PREFIX.str(); + req->key += + util::format_hex(balloc, StringRef{id, static_cast(idlen)}); + req->cb = [conn](MemcachedRequest *, MemcachedResult res) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Memcached: returned status code " + << static_cast(res.status_code); + } + + // We might stop reading, so start it again + conn->rlimit.startw(); + ev_timer_again(conn->loop, &conn->rt); + + conn->wlimit.startw(); + ev_timer_again(conn->loop, &conn->wt); + + conn->tls.cached_session_lookup_req = nullptr; + if (res.status_code != MemcachedStatusCode::NO_ERROR) { + conn->tls.handshake_state = TLSHandshakeState::CANCEL_SESSION_CACHE; + return; + } + + const uint8_t *p = res.value.data(); + + auto session = d2i_SSL_SESSION(nullptr, &p, res.value.size()); + if (!session) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "cannot materialize session"; + } + conn->tls.handshake_state = TLSHandshakeState::CANCEL_SESSION_CACHE; + return; + } + + conn->tls.cached_session = session; + conn->tls.handshake_state = TLSHandshakeState::GOT_SESSION_CACHE; + }; + + conn->tls.handshake_state = TLSHandshakeState::WAIT_FOR_SESSION_CACHE; + conn->tls.cached_session_lookup_req = req.get(); + + dispatcher->add_request(std::move(req)); + + return nullptr; +} +} // namespace + +namespace { +int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv, + EVP_CIPHER_CTX *ctx, +#if OPENSSL_3_0_0_API + EVP_MAC_CTX *hctx, +#else // !OPENSSL_3_0_0_API + HMAC_CTX *hctx, +#endif // !OPENSSL_3_0_0_API + int enc) { + auto conn = static_cast(SSL_get_app_data(ssl)); + auto handler = static_cast(conn->data); + auto worker = handler->get_worker(); + auto ticket_keys = worker->get_ticket_keys(); + + if (!ticket_keys) { + // No ticket keys available. + return -1; + } + + auto &keys = ticket_keys->keys; + assert(!keys.empty()); + + if (enc) { + if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) == 0) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "session ticket key: RAND_bytes failed"; + } + return -1; + } + + auto &key = keys[0]; + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "encrypt session ticket key: " + << util::format_hex(key.data.name); + } + + std::copy(std::begin(key.data.name), std::end(key.data.name), key_name); + + EVP_EncryptInit_ex(ctx, get_config()->tls.ticket.cipher, nullptr, + key.data.enc_key.data(), iv); +#if OPENSSL_3_0_0_API + std::array params{ + OSSL_PARAM_construct_octet_string( + OSSL_MAC_PARAM_KEY, key.data.hmac_key.data(), key.hmac_keylen), + OSSL_PARAM_construct_utf8_string( + OSSL_MAC_PARAM_DIGEST, + const_cast(EVP_MD_get0_name(key.hmac)), 0), + OSSL_PARAM_construct_end(), + }; + if (!EVP_MAC_CTX_set_params(hctx, params.data())) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "EVP_MAC_CTX_set_params failed"; + } + return -1; + } +#else // !OPENSSL_3_0_0_API + HMAC_Init_ex(hctx, key.data.hmac_key.data(), key.hmac_keylen, key.hmac, + nullptr); +#endif // !OPENSSL_3_0_0_API + return 1; + } + + size_t i; + for (i = 0; i < keys.size(); ++i) { + auto &key = keys[i]; + if (std::equal(std::begin(key.data.name), std::end(key.data.name), + key_name)) { + break; + } + } + + if (i == keys.size()) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "session ticket key " + << util::format_hex(key_name, 16) << " not found"; + } + return 0; + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "decrypt session ticket key: " + << util::format_hex(key_name, 16); + } + + auto &key = keys[i]; +#if OPENSSL_3_0_0_API + std::array params{ + OSSL_PARAM_construct_octet_string( + OSSL_MAC_PARAM_KEY, key.data.hmac_key.data(), key.hmac_keylen), + OSSL_PARAM_construct_utf8_string( + OSSL_MAC_PARAM_DIGEST, const_cast(EVP_MD_get0_name(key.hmac)), + 0), + OSSL_PARAM_construct_end(), + }; + if (!EVP_MAC_CTX_set_params(hctx, params.data())) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "EVP_MAC_CTX_set_params failed"; + } + return -1; + } +#else // !OPENSSL_3_0_0_API + HMAC_Init_ex(hctx, key.data.hmac_key.data(), key.hmac_keylen, key.hmac, + nullptr); +#endif // !OPENSSL_3_0_0_API + EVP_DecryptInit_ex(ctx, key.cipher, nullptr, key.data.enc_key.data(), iv); + +#ifdef TLS1_3_VERSION + // If ticket_key_cb is not set, OpenSSL always renew ticket for + // TLSv1.3. + if (SSL_version(ssl) == TLS1_3_VERSION) { + return 2; + } +#endif // TLS1_3_VERSION + + return i == 0 ? 1 : 2; +} +} // namespace + +namespace { +void info_callback(const SSL *ssl, int where, int ret) { +#ifdef TLS1_3_VERSION + // TLSv1.3 has no renegotiation. + if (SSL_version(ssl) == TLS1_3_VERSION) { + return; + } +#endif // TLS1_3_VERSION + + // To mitigate possible DOS attack using lots of renegotiations, we + // disable renegotiation. Since OpenSSL does not provide an easy way + // to disable it, we check that renegotiation is started in this + // callback. + if (where & SSL_CB_HANDSHAKE_START) { + auto conn = static_cast(SSL_get_app_data(ssl)); + if (conn && conn->tls.initial_handshake_done) { + auto handler = static_cast(conn->data); + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "TLS renegotiation started"; + } + handler->start_immediate_shutdown(); + } + } +} +} // namespace + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +namespace { +int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + // We assume that get_config()->npn_list contains ALPN protocol + // identifier sorted by preference order. So we just break when we + // found the first overlap. + for (const auto &target_proto_id : get_config()->tls.npn_list) { + for (auto p = in, end = in + inlen; p < end;) { + auto proto_id = p + 1; + auto proto_len = *p; + + if (proto_id + proto_len <= end && + util::streq(target_proto_id, StringRef{proto_id, proto_len})) { + + *out = reinterpret_cast(proto_id); + *outlen = proto_len; + + return SSL_TLSEXT_ERR_OK; + } + + p += 1 + proto_len; + } + } + + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + +#ifdef ENABLE_HTTP3 +# if OPENSSL_VERSION_NUMBER >= 0x10002000L +namespace { +int quic_alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + constexpr StringRef alpnlist[] = { + StringRef::from_lit("h3"), + StringRef::from_lit("h3-29"), + }; + + for (auto &alpn : alpnlist) { + for (auto p = in, end = in + inlen; p < end;) { + auto proto_id = p + 1; + auto proto_len = *p; + + if (alpn.size() == proto_len && + memcmp(alpn.byte(), proto_id, alpn.size()) == 0) { + *out = proto_id; + *outlen = proto_len; + + return SSL_TLSEXT_ERR_OK; + } + + p += 1 + proto_len; + } + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +} // namespace +# endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +#endif // ENABLE_HTTP3 + +#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +# ifndef TLSEXT_TYPE_signed_certificate_timestamp +# define TLSEXT_TYPE_signed_certificate_timestamp 18 +# endif // !TLSEXT_TYPE_signed_certificate_timestamp + +namespace { +int sct_add_cb(SSL *ssl, unsigned int ext_type, unsigned int context, + const unsigned char **out, size_t *outlen, X509 *x, + size_t chainidx, int *al, void *add_arg) { + assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp); + + auto conn = static_cast(SSL_get_app_data(ssl)); + if (!conn->tls.sct_requested) { + return 0; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "sct_add_cb is called, chainidx=" << chainidx << ", x=" << x + << ", context=" << log::hex << context; + } + + // We only have SCTs for leaf certificate. + if (chainidx != 0) { + return 0; + } + + auto ssl_ctx = SSL_get_SSL_CTX(ssl); + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + + *out = tls_ctx_data->sct_data.data(); + *outlen = tls_ctx_data->sct_data.size(); + + return 1; +} +} // namespace + +namespace { +void sct_free_cb(SSL *ssl, unsigned int ext_type, unsigned int context, + const unsigned char *out, void *add_arg) { + assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp); +} +} // namespace + +namespace { +int sct_parse_cb(SSL *ssl, unsigned int ext_type, unsigned int context, + const unsigned char *in, size_t inlen, X509 *x, + size_t chainidx, int *al, void *parse_arg) { + assert(ext_type == TLSEXT_TYPE_signed_certificate_timestamp); + // client SHOULD send 0 length extension_data, but it is still + // SHOULD, and not MUST. + + // For TLSv1.3 Certificate message, sct_add_cb is called even if + // client has not sent signed_certificate_timestamp extension in its + // ClientHello. Explicitly remember that client has included it + // here. + auto conn = static_cast(SSL_get_app_data(ssl)); + conn->tls.sct_requested = true; + + return 1; +} +} // namespace + +# if !OPENSSL_1_1_1_API + +namespace { +int legacy_sct_add_cb(SSL *ssl, unsigned int ext_type, + const unsigned char **out, size_t *outlen, int *al, + void *add_arg) { + return sct_add_cb(ssl, ext_type, 0, out, outlen, nullptr, 0, al, add_arg); +} +} // namespace + +namespace { +void legacy_sct_free_cb(SSL *ssl, unsigned int ext_type, + const unsigned char *out, void *add_arg) { + sct_free_cb(ssl, ext_type, 0, out, add_arg); +} +} // namespace + +namespace { +int legacy_sct_parse_cb(SSL *ssl, unsigned int ext_type, + const unsigned char *in, size_t inlen, int *al, + void *parse_arg) { + return sct_parse_cb(ssl, ext_type, 0, in, inlen, nullptr, 0, al, parse_arg); +} +} // namespace + +# endif // !OPENSSL_1_1_1_API +#endif // !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && + // !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#ifndef OPENSSL_NO_PSK +namespace { +unsigned int psk_server_cb(SSL *ssl, const char *identity, unsigned char *psk, + unsigned int max_psk_len) { + auto config = get_config(); + auto &tlsconf = config->tls; + + auto it = tlsconf.psk_secrets.find(StringRef{identity}); + if (it == std::end(tlsconf.psk_secrets)) { + return 0; + } + + auto &secret = (*it).second; + if (secret.size() > max_psk_len) { + LOG(ERROR) << "The size of PSK secret is " << secret.size() + << ", but the acceptable maximum size is" << max_psk_len; + return 0; + } + + std::copy(std::begin(secret), std::end(secret), psk); + + return static_cast(secret.size()); +} +} // namespace +#endif // !OPENSSL_NO_PSK + +#ifndef OPENSSL_NO_PSK +namespace { +unsigned int psk_client_cb(SSL *ssl, const char *hint, char *identity_out, + unsigned int max_identity_len, unsigned char *psk, + unsigned int max_psk_len) { + auto config = get_config(); + auto &tlsconf = config->tls; + + auto &identity = tlsconf.client.psk.identity; + auto &secret = tlsconf.client.psk.secret; + + if (identity.empty()) { + return 0; + } + + if (identity.size() + 1 > max_identity_len) { + LOG(ERROR) << "The size of PSK identity is " << identity.size() + << ", but the acceptable maximum size is " << max_identity_len; + return 0; + } + + if (secret.size() > max_psk_len) { + LOG(ERROR) << "The size of PSK secret is " << secret.size() + << ", but the acceptable maximum size is " << max_psk_len; + return 0; + } + + *std::copy(std::begin(identity), std::end(identity), identity_out) = '\0'; + std::copy(std::begin(secret), std::end(secret), psk); + + return static_cast(secret.size()); +} +} // namespace +#endif // !OPENSSL_NO_PSK + +struct TLSProtocol { + StringRef name; + long int mask; +}; + +constexpr TLSProtocol TLS_PROTOS[] = { + TLSProtocol{StringRef::from_lit("TLSv1.2"), SSL_OP_NO_TLSv1_2}, + TLSProtocol{StringRef::from_lit("TLSv1.1"), SSL_OP_NO_TLSv1_1}, + TLSProtocol{StringRef::from_lit("TLSv1.0"), SSL_OP_NO_TLSv1}}; + +long int create_tls_proto_mask(const std::vector &tls_proto_list) { + long int res = 0; + + for (auto &supported : TLS_PROTOS) { + auto ok = false; + for (auto &name : tls_proto_list) { + if (util::strieq(supported.name, name)) { + ok = true; + break; + } + } + if (!ok) { + res |= supported.mask; + } + } + return res; +} + +SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file, + const std::vector &sct_data +#ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +) { + auto ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | + SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + // The reason for disabling built-in anti-replay in + // OpenSSL is that it only works if client gets back + // to the same server. The freshness check + // described in + // https://tools.ietf.org/html/rfc8446#section-8.3 + // is still performed. + | SSL_OP_NO_ANTI_REPLAY +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + ; + + auto config = mod_config(); + auto &tlsconf = config->tls; + +#ifdef SSL_OP_ENABLE_KTLS + if (tlsconf.ktls) { + ssl_opts |= SSL_OP_ENABLE_KTLS; + } +#endif // SSL_OP_ENABLE_KTLS + + SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask); + + if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, tlsconf.min_proto_version, tlsconf.max_proto_version) != 0) { + LOG(FATAL) << "Could not set TLS protocol version"; + DIE(); + } + + const unsigned char sid_ctx[] = "shrpx"; + SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER); + + if (!tlsconf.session_cache.memcached.host.empty()) { + SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_new_cb); + SSL_CTX_sess_set_get_cb(ssl_ctx, tls_session_get_cb); + } + + SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count()); + + if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#ifndef OPENSSL_NO_EC +# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) { + LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves + << " failed"; + DIE(); + } +# if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API + // It looks like we need this function call for OpenSSL 1.0.2. This + // function was deprecated in OpenSSL 1.1.0 and BoringSSL. + SSL_CTX_set_ecdh_auto(ssl_ctx, 1); +# endif // !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API +# else // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L + // Use P-256, which is sufficiently secure at the time of this + // writing. + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh == nullptr) { + LOG(FATAL) << "EC_KEY_new_by_curv_name failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); +# endif // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L +#endif // OPENSSL_NO_EC + + if (!tlsconf.dh_param_file.empty()) { + // Read DH parameters from file + auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "rb"); + if (bio == nullptr) { + LOG(FATAL) << "BIO_new_file() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#if OPENSSL_3_0_0_API + EVP_PKEY *dh = nullptr; + auto dctx = OSSL_DECODER_CTX_new_for_pkey( + &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + nullptr, nullptr); + + if (!OSSL_DECODER_from_bio(dctx, bio)) { + LOG(FATAL) << "OSSL_DECODER_from_bio() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) { + LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#else // !OPENSSL_3_0_0_API + auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + if (dh == nullptr) { + LOG(FATAL) << "PEM_read_bio_DHparams() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_dh(ssl_ctx, dh); + DH_free(dh); +#endif // !OPENSSL_3_0_0_API + BIO_free(bio); + } + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + LOG(WARN) << "Could not load system trusted ca certificates: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + if (!tlsconf.cacert.empty()) { + if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(), + nullptr) != 1) { + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!tlsconf.private_key_passwd.empty()) { + SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb); + SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config); + } + +#ifndef HAVE_NEVERBLEED + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#else // HAVE_NEVERBLEED + std::array errbuf; + if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file, + errbuf.data()) != 1) { + LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data(); + DIE(); + } +#endif // HAVE_NEVERBLEED + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + LOG(FATAL) << "SSL_CTX_use_certificate_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + LOG(FATAL) << "SSL_CTX_check_private_key failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (tlsconf.client_verify.enabled) { + if (!tlsconf.client_verify.cacert.empty()) { + if (SSL_CTX_load_verify_locations( + ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) { + + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + // It is heard that SSL_CTX_load_verify_locations() may leave + // error even though it returns success. See + // http://forum.nginx.org/read.php?29,242540 + ERR_clear_error(); + auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.c_str()); + if (!list) { + LOG(FATAL) << "Could not load ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_client_CA_list(ssl_ctx, list); + } + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + } + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback); +#if OPENSSL_3_0_0_API + SSL_CTX_set_tlsext_ticket_key_evp_cb(ssl_ctx, ticket_key_cb); +#else // !OPENSSL_3_0_0_API + SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb); +#endif // !OPENSSL_3_0_0_API +#ifndef NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_info_callback(ssl_ctx, info_callback); + +#ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_early_data_enabled(ssl_ctx, 1); +#endif // NGHTTP2_OPENSSL_IS_BORINGSSL + + // NPN advertisement +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, nullptr); +#endif // !OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + auto tls_ctx_data = new TLSContextData(); + tls_ctx_data->cert_file = cert_file; + tls_ctx_data->sct_data = sct_data; + + SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data); + +#if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp) + // returns 1, which means OpenSSL internally handles it. But + // OpenSSL handles signed_certificate_timestamp extension specially, + // and it lets custom handler to process the extension. + if (!sct_data.empty()) { +# if OPENSSL_1_1_1_API + // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is + // required here. sct_parse_cb is called without + // SSL_EXT_CLIENT_HELLO being set. But the passed context value + // is SSL_EXT_CLIENT_HELLO. + if (SSL_CTX_add_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | + SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION, + sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# else // !OPENSSL_1_1_1_API + if (SSL_CTX_add_server_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + legacy_sct_add_cb, legacy_sct_free_cb, nullptr, legacy_sct_parse_cb, + nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_server_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // !OPENSSL_1_1_1_API + } +#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (!tls_ctx_data->sct_data.empty() && + SSL_CTX_set_signed_cert_timestamp_list( + ssl_ctx, tls_ctx_data->sct_data.data(), + tls_ctx_data->sct_data.size()) != 1) { + LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (SSL_CTX_set_max_early_data(ssl_ctx, tlsconf.max_early_data) != 1) { + LOG(FATAL) << "SSL_CTX_set_max_early_data failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +#ifndef OPENSSL_NO_PSK + SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb); +#endif // !LIBRESSL_NO_PSK + + return ssl_ctx; +} + +#ifdef ENABLE_HTTP3 +SSL_CTX *create_quic_ssl_context(const char *private_key_file, + const char *cert_file, + const std::vector &sct_data +# ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +# endif // HAVE_NEVERBLEED +) { + auto ssl_ctx = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx) { + LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + constexpr auto ssl_opts = + (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE | + SSL_OP_SINGLE_DH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE +# if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + // The reason for disabling built-in anti-replay in OpenSSL is + // that it only works if client gets back to the same server. + // The freshness check described in + // https://tools.ietf.org/html/rfc8446#section-8.3 is still + // performed. + | SSL_OP_NO_ANTI_REPLAY +# endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + ; + + auto config = mod_config(); + auto &tlsconf = config->tls; + + SSL_CTX_set_options(ssl_ctx, ssl_opts); + +# ifdef HAVE_LIBNGTCP2_CRYPTO_QUICTLS + if (ngtcp2_crypto_quictls_configure_server_context(ssl_ctx) != 0) { + LOG(FATAL) << "ngtcp2_crypto_quictls_configure_server_context failed"; + DIE(); + } +# endif // HAVE_LIBNGTCP2_CRYPTO_QUICTLS +# ifdef HAVE_LIBNGTCP2_CRYPTO_BORINGSSL + if (ngtcp2_crypto_boringssl_configure_server_context(ssl_ctx) != 0) { + LOG(FATAL) << "ngtcp2_crypto_boringssl_configure_server_context failed"; + DIE(); + } +# endif // HAVE_LIBNGTCP2_CRYPTO_BORINGSSL + + const unsigned char sid_ctx[] = "shrpx"; + SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_OFF); + + SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count()); + + if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + +# if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.tls13_ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +# ifndef OPENSSL_NO_EC +# if !LIBRESSL_LEGACY_API && OPENSSL_VERSION_NUMBER >= 0x10002000L + if (SSL_CTX_set1_curves_list(ssl_ctx, tlsconf.ecdh_curves.c_str()) != 1) { + LOG(FATAL) << "SSL_CTX_set1_curves_list " << tlsconf.ecdh_curves + << " failed"; + DIE(); + } +# if !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API + // It looks like we need this function call for OpenSSL 1.0.2. This + // function was deprecated in OpenSSL 1.1.0 and BoringSSL. + SSL_CTX_set_ecdh_auto(ssl_ctx, 1); +# endif // !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) && !OPENSSL_1_1_API +# else // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L + // Use P-256, which is sufficiently secure at the time of this + // writing. + auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (ecdh == nullptr) { + LOG(FATAL) << "EC_KEY_new_by_curv_name failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); +# endif // LIBRESSL_LEGACY_API || OPENSSL_VERSION_NUBMER < 0x10002000L +# endif // OPENSSL_NO_EC + + if (!tlsconf.dh_param_file.empty()) { + // Read DH parameters from file + auto bio = BIO_new_file(tlsconf.dh_param_file.c_str(), "rb"); + if (bio == nullptr) { + LOG(FATAL) << "BIO_new_file() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# if OPENSSL_3_0_0_API + EVP_PKEY *dh = nullptr; + auto dctx = OSSL_DECODER_CTX_new_for_pkey( + &dh, "PEM", nullptr, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + nullptr, nullptr); + + if (!OSSL_DECODER_from_bio(dctx, bio)) { + LOG(FATAL) << "OSSL_DECODER_from_bio() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + if (SSL_CTX_set0_tmp_dh_pkey(ssl_ctx, dh) != 1) { + LOG(FATAL) << "SSL_CTX_set0_tmp_dh_pkey failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# else // !OPENSSL_3_0_0_API + auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + if (dh == nullptr) { + LOG(FATAL) << "PEM_read_bio_DHparams() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_tmp_dh(ssl_ctx, dh); + DH_free(dh); +# endif // !OPENSSL_3_0_0_API + BIO_free(bio); + } + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + LOG(WARN) << "Could not load system trusted ca certificates: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + if (!tlsconf.cacert.empty()) { + if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.c_str(), + nullptr) != 1) { + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!tlsconf.private_key_passwd.empty()) { + SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb); + SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, config); + } + +# ifndef HAVE_NEVERBLEED + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# else // HAVE_NEVERBLEED + std::array errbuf; + if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file, + errbuf.data()) != 1) { + LOG(FATAL) << "neverbleed_load_private_key_file failed: " << errbuf.data(); + DIE(); + } +# endif // HAVE_NEVERBLEED + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + LOG(FATAL) << "SSL_CTX_use_certificate_file failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + LOG(FATAL) << "SSL_CTX_check_private_key failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (tlsconf.client_verify.enabled) { + if (!tlsconf.client_verify.cacert.empty()) { + if (SSL_CTX_load_verify_locations( + ssl_ctx, tlsconf.client_verify.cacert.c_str(), nullptr) != 1) { + + LOG(FATAL) << "Could not load trusted ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + // It is heard that SSL_CTX_load_verify_locations() may leave + // error even though it returns success. See + // http://forum.nginx.org/read.php?29,242540 + ERR_clear_error(); + auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.c_str()); + if (!list) { + LOG(FATAL) << "Could not load ca certificates from " + << tlsconf.client_verify.cacert << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + SSL_CTX_set_client_CA_list(ssl_ctx, list); + } + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback); + } + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback); +# if OPENSSL_3_0_0_API + SSL_CTX_set_tlsext_ticket_key_evp_cb(ssl_ctx, ticket_key_cb); +# else // !OPENSSL_3_0_0_API + SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb); +# endif // !OPENSSL_3_0_0_API +# ifndef NGHTTP2_OPENSSL_IS_BORINGSSL + SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); +# endif // NGHTTP2_OPENSSL_IS_BORINGSSL + +# if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN selection callback + SSL_CTX_set_alpn_select_cb(ssl_ctx, quic_alpn_select_proto_cb, nullptr); +# endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + auto tls_ctx_data = new TLSContextData(); + tls_ctx_data->cert_file = cert_file; + tls_ctx_data->sct_data = sct_data; + + SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data); + +# if !LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L && \ + !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + // SSL_extension_supported(TLSEXT_TYPE_signed_certificate_timestamp) + // returns 1, which means OpenSSL internally handles it. But + // OpenSSL handles signed_certificate_timestamp extension specially, + // and it lets custom handler to process the extension. + if (!sct_data.empty()) { +# if OPENSSL_1_1_1_API + // It is not entirely clear to me that SSL_EXT_CLIENT_HELLO is + // required here. sct_parse_cb is called without + // SSL_EXT_CLIENT_HELLO being set. But the passed context value + // is SSL_EXT_CLIENT_HELLO. + if (SSL_CTX_add_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_2_SERVER_HELLO | + SSL_EXT_TLS1_3_CERTIFICATE | SSL_EXT_IGNORE_ON_RESUMPTION, + sct_add_cb, sct_free_cb, nullptr, sct_parse_cb, nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# else // !OPENSSL_1_1_1_API + if (SSL_CTX_add_server_custom_ext( + ssl_ctx, TLSEXT_TYPE_signed_certificate_timestamp, + legacy_sct_add_cb, legacy_sct_free_cb, nullptr, legacy_sct_parse_cb, + nullptr) != 1) { + LOG(FATAL) << "SSL_CTX_add_server_custom_ext failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // !OPENSSL_1_1_1_API + } +# elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (!tls_ctx_data->sct_data.empty() && + SSL_CTX_set_signed_cert_timestamp_list( + ssl_ctx, tls_ctx_data->sct_data.data(), + tls_ctx_data->sct_data.size()) != 1) { + LOG(FATAL) << "SSL_CTX_set_signed_cert_timestamp_list failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +# if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto &quicconf = config->quic; + + if (quicconf.upstream.early_data && + SSL_CTX_set_max_early_data(ssl_ctx, + std::numeric_limits::max()) != 1) { + LOG(FATAL) << "SSL_CTX_set_max_early_data failed: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +# endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + +# ifndef OPENSSL_NO_PSK + SSL_CTX_set_psk_server_callback(ssl_ctx, psk_server_cb); +# endif // !LIBRESSL_NO_PSK + + return ssl_ctx; +} +#endif // ENABLE_HTTP3 + +namespace { +int select_h2_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + if (!util::select_h2(const_cast(out), outlen, in, + inlen)) { + return SSL_TLSEXT_ERR_NOACK; + } + + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +namespace { +int select_h1_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto end = in + inlen; + for (; in < end;) { + if (util::streq(NGHTTP2_H1_1_ALPN, StringRef{in, in + (in[0] + 1)})) { + *out = const_cast(in) + 1; + *outlen = in[0]; + return SSL_TLSEXT_ERR_OK; + } + in += in[0] + 1; + } + + return SSL_TLSEXT_ERR_NOACK; +} +} // namespace + +namespace { +int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + void *arg) { + auto conn = static_cast(SSL_get_app_data(ssl)); + switch (conn->proto) { + case Proto::HTTP1: + return select_h1_next_proto_cb(ssl, out, outlen, in, inlen, arg); + case Proto::HTTP2: + return select_h2_next_proto_cb(ssl, out, outlen, in, inlen, arg); + default: + return SSL_TLSEXT_ERR_NOACK; + } +} +} // namespace + +SSL_CTX *create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb, +#endif // HAVE_NEVERBLEED + const StringRef &cacert, const StringRef &cert_file, + const StringRef &private_key_file, + int (*next_proto_select_cb)(SSL *s, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg)) { + auto ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx) { + LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + + auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION; + + auto &tlsconf = get_config()->tls; + +#ifdef SSL_OP_ENABLE_KTLS + if (tlsconf.ktls) { + ssl_opts |= SSL_OP_ENABLE_KTLS; + } +#endif // SSL_OP_ENABLE_KTLS + + SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask); + + SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL_STORE); + SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_client_new_cb); + + if (nghttp2::tls::ssl_ctx_set_proto_versions( + ssl_ctx, tlsconf.min_proto_version, tlsconf.max_proto_version) != 0) { + LOG(FATAL) << "Could not set TLS protocol version"; + DIE(); + } + + if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.client.ciphers.c_str()) == 0) { + LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.client.ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (SSL_CTX_set_ciphersuites(ssl_ctx, tlsconf.client.tls13_ciphers.c_str()) == + 0) { + LOG(FATAL) << "SSL_CTX_set_ciphersuites " << tlsconf.client.tls13_ciphers + << " failed: " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#endif // OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) { + LOG(WARN) << "Could not load system trusted ca certificates: " + << ERR_error_string(ERR_get_error(), nullptr); + } + + if (!cacert.empty()) { + if (SSL_CTX_load_verify_locations(ssl_ctx, cacert.c_str(), nullptr) != 1) { + + LOG(FATAL) << "Could not load trusted ca certificates from " << cacert + << ": " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!tlsconf.insecure) { + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr); + } + + if (!cert_file.empty()) { + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file.c_str()) != 1) { + + LOG(FATAL) << "Could not load client certificate from " << cert_file + << ": " << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + } + + if (!private_key_file.empty()) { +#ifndef HAVE_NEVERBLEED + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file.c_str(), + SSL_FILETYPE_PEM) != 1) { + LOG(FATAL) << "Could not load client private key from " + << private_key_file << ": " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } +#else // HAVE_NEVERBLEED + std::array errbuf; + if (neverbleed_load_private_key_file(nb, ssl_ctx, private_key_file.c_str(), + errbuf.data()) != 1) { + LOG(FATAL) << "neverbleed_load_private_key_file: could not load client " + "private key from " + << private_key_file << ": " << errbuf.data(); + DIE(); + } +#endif // HAVE_NEVERBLEED + } + +#ifndef OPENSSL_NO_PSK + SSL_CTX_set_psk_client_callback(ssl_ctx, psk_client_cb); +#endif // !OPENSSL_NO_PSK + + // NPN selection callback. This is required to set SSL_CTX because + // OpenSSL does not offer SSL_set_next_proto_select_cb. +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_cb, nullptr); +#endif // !OPENSSL_NO_NEXTPROTONEG + + return ssl_ctx; +} + +SSL *create_ssl(SSL_CTX *ssl_ctx) { + auto ssl = SSL_new(ssl_ctx); + if (!ssl) { + LOG(ERROR) << "SSL_new() failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return nullptr; + } + + return ssl; +} + +ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr, + int addrlen, const UpstreamAddr *faddr) { + std::array host; + std::array service; + int rv; + + if (addr->sa_family == AF_UNIX) { + std::copy_n("localhost", sizeof("localhost"), std::begin(host)); + service[0] = '\0'; + } else { + rv = getnameinfo(addr, addrlen, host.data(), host.size(), service.data(), + service.size(), NI_NUMERICHOST | NI_NUMERICSERV); + if (rv != 0) { + LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv); + + return nullptr; + } + + rv = util::make_socket_nodelay(fd); + if (rv == -1) { + LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno; + } + } + SSL *ssl = nullptr; + if (faddr->tls) { + auto ssl_ctx = worker->get_sv_ssl_ctx(); + + assert(ssl_ctx); + + ssl = create_ssl(ssl_ctx); + if (!ssl) { + return nullptr; + } + // Disable TLS session ticket if we don't have working ticket + // keys. + if (!worker->get_ticket_keys()) { + SSL_set_options(ssl, SSL_OP_NO_TICKET); + } + } + + return new ClientHandler(worker, fd, ssl, StringRef{host.data()}, + StringRef{service.data()}, addr->sa_family, faddr); +} + +bool tls_hostname_match(const StringRef &pattern, const StringRef &hostname) { + auto ptWildcard = std::find(std::begin(pattern), std::end(pattern), '*'); + if (ptWildcard == std::end(pattern)) { + return util::strieq(pattern, hostname); + } + + auto ptLeftLabelEnd = std::find(std::begin(pattern), std::end(pattern), '.'); + auto wildcardEnabled = true; + // Do case-insensitive match. At least 2 dots are required to enable + // wildcard match. Also wildcard must be in the left-most label. + // Don't attempt to match a presented identifier where the wildcard + // character is embedded within an A-label. + if (ptLeftLabelEnd == std::end(pattern) || + std::find(ptLeftLabelEnd + 1, std::end(pattern), '.') == + std::end(pattern) || + ptLeftLabelEnd < ptWildcard || util::istarts_with_l(pattern, "xn--")) { + wildcardEnabled = false; + } + + if (!wildcardEnabled) { + return util::strieq(pattern, hostname); + } + + auto hnLeftLabelEnd = + std::find(std::begin(hostname), std::end(hostname), '.'); + if (hnLeftLabelEnd == std::end(hostname) || + !util::strieq(StringRef{ptLeftLabelEnd, std::end(pattern)}, + StringRef{hnLeftLabelEnd, std::end(hostname)})) { + return false; + } + // Perform wildcard match. Here '*' must match at least one + // character. + if (hnLeftLabelEnd - std::begin(hostname) < + ptLeftLabelEnd - std::begin(pattern)) { + return false; + } + return util::istarts_with(StringRef{std::begin(hostname), hnLeftLabelEnd}, + StringRef{std::begin(pattern), ptWildcard}) && + util::iends_with(StringRef{std::begin(hostname), hnLeftLabelEnd}, + StringRef{ptWildcard + 1, ptLeftLabelEnd}); +} + +namespace { +// if return value is not empty, StringRef.c_str() must be freed using +// OPENSSL_free(). +StringRef get_common_name(X509 *cert) { + auto subjectname = X509_get_subject_name(cert); + if (!subjectname) { + LOG(WARN) << "Could not get X509 name object from the certificate."; + return StringRef{}; + } + int lastpos = -1; + for (;;) { + lastpos = X509_NAME_get_index_by_NID(subjectname, NID_commonName, lastpos); + if (lastpos == -1) { + break; + } + auto entry = X509_NAME_get_entry(subjectname, lastpos); + + unsigned char *p; + auto plen = ASN1_STRING_to_UTF8(&p, X509_NAME_ENTRY_get_data(entry)); + if (plen < 0) { + continue; + } + if (std::find(p, p + plen, '\0') != p + plen) { + // Embedded NULL is not permitted. + continue; + } + if (plen == 0) { + LOG(WARN) << "X509 name is empty"; + OPENSSL_free(p); + continue; + } + + return StringRef{p, static_cast(plen)}; + } + return StringRef{}; +} +} // namespace + +int verify_numeric_hostname(X509 *cert, const StringRef &hostname, + const Address *addr) { + const void *saddr; + size_t saddrlen; + switch (addr->su.storage.ss_family) { + case AF_INET: + saddr = &addr->su.in.sin_addr; + saddrlen = sizeof(addr->su.in.sin_addr); + break; + case AF_INET6: + saddr = &addr->su.in6.sin6_addr; + saddrlen = sizeof(addr->su.in6.sin6_addr); + break; + default: + return -1; + } + + auto altnames = static_cast( + X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr)); + if (altnames) { + auto altnames_deleter = defer(GENERAL_NAMES_free, altnames); + size_t n = sk_GENERAL_NAME_num(altnames); + auto ip_found = false; + for (size_t i = 0; i < n; ++i) { + auto altname = sk_GENERAL_NAME_value(altnames, i); + if (altname->type != GEN_IPADD) { + continue; + } + + auto ip_addr = altname->d.iPAddress->data; + if (!ip_addr) { + continue; + } + size_t ip_addrlen = altname->d.iPAddress->length; + + ip_found = true; + if (saddrlen == ip_addrlen && memcmp(saddr, ip_addr, ip_addrlen) == 0) { + return 0; + } + } + + if (ip_found) { + return -1; + } + } + + auto cn = get_common_name(cert); + if (cn.empty()) { + return -1; + } + + // cn is not NULL terminated + auto rv = util::streq(hostname, cn); + OPENSSL_free(const_cast(cn.c_str())); + + if (rv) { + return 0; + } + + return -1; +} + +int verify_dns_hostname(X509 *cert, const StringRef &hostname) { + auto altnames = static_cast( + X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr)); + if (altnames) { + auto dns_found = false; + auto altnames_deleter = defer(GENERAL_NAMES_free, altnames); + size_t n = sk_GENERAL_NAME_num(altnames); + for (size_t i = 0; i < n; ++i) { + auto altname = sk_GENERAL_NAME_value(altnames, i); + if (altname->type != GEN_DNS) { + continue; + } + + auto name = ASN1_STRING_get0_data(altname->d.ia5); + if (!name) { + continue; + } + + auto len = ASN1_STRING_length(altname->d.ia5); + if (len == 0) { + continue; + } + if (std::find(name, name + len, '\0') != name + len) { + // Embedded NULL is not permitted. + continue; + } + + if (name[len - 1] == '.') { + --len; + if (len == 0) { + continue; + } + } + + dns_found = true; + + if (tls_hostname_match(StringRef{name, static_cast(len)}, + hostname)) { + return 0; + } + } + + // RFC 6125, section 6.4.4. says that client MUST not seek a match + // for CN if a dns dNSName is found. + if (dns_found) { + return -1; + } + } + + auto cn = get_common_name(cert); + if (cn.empty()) { + return -1; + } + + if (cn[cn.size() - 1] == '.') { + if (cn.size() == 1) { + OPENSSL_free(const_cast(cn.c_str())); + + return -1; + } + cn = StringRef{cn.c_str(), cn.size() - 1}; + } + + auto rv = tls_hostname_match(cn, hostname); + OPENSSL_free(const_cast(cn.c_str())); + + return rv ? 0 : -1; +} + +namespace { +int verify_hostname(X509 *cert, const StringRef &hostname, + const Address *addr) { + if (util::numeric_host(hostname.c_str())) { + return verify_numeric_hostname(cert, hostname, addr); + } + + return verify_dns_hostname(cert, hostname); +} +} // namespace + +int check_cert(SSL *ssl, const Address *addr, const StringRef &host) { +#if OPENSSL_3_0_0_API + auto cert = SSL_get0_peer_certificate(ssl); +#else // !OPENSSL_3_0_0_API + auto cert = SSL_get_peer_certificate(ssl); +#endif // !OPENSSL_3_0_0_API + if (!cert) { + // By the protocol definition, TLS server always sends certificate + // if it has. If certificate cannot be retrieved, authentication + // without certificate is used, such as PSK. + return 0; + } +#if !OPENSSL_3_0_0_API + auto cert_deleter = defer(X509_free, cert); +#endif // !OPENSSL_3_0_0_API + + if (verify_hostname(cert, host, addr) != 0) { + LOG(ERROR) << "Certificate verification failed: hostname does not match"; + return -1; + } + return 0; +} + +int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr) { + auto hostname = + addr->sni.empty() ? StringRef{addr->host} : StringRef{addr->sni}; + return check_cert(ssl, raddr, hostname); +} + +CertLookupTree::CertLookupTree() {} + +ssize_t CertLookupTree::add_cert(const StringRef &hostname, size_t idx) { + std::array buf; + + // NI_MAXHOST includes terminal NULL byte + if (hostname.empty() || hostname.size() + 1 > buf.size()) { + return -1; + } + + auto wildcard_it = std::find(std::begin(hostname), std::end(hostname), '*'); + if (wildcard_it != std::end(hostname) && + wildcard_it + 1 != std::end(hostname)) { + auto wildcard_prefix = StringRef{std::begin(hostname), wildcard_it}; + auto wildcard_suffix = StringRef{wildcard_it + 1, std::end(hostname)}; + + auto rev_suffix = StringRef{std::begin(buf), + std::reverse_copy(std::begin(wildcard_suffix), + std::end(wildcard_suffix), + std::begin(buf))}; + + WildcardPattern *wpat; + + if (wildcard_patterns_.size() != + rev_wildcard_router_.add_route(rev_suffix, wildcard_patterns_.size())) { + auto wcidx = rev_wildcard_router_.match(rev_suffix); + + assert(wcidx != -1); + + wpat = &wildcard_patterns_[wcidx]; + } else { + wildcard_patterns_.emplace_back(); + wpat = &wildcard_patterns_.back(); + } + + auto rev_prefix = StringRef{std::begin(buf), + std::reverse_copy(std::begin(wildcard_prefix), + std::end(wildcard_prefix), + std::begin(buf))}; + + for (auto &p : wpat->rev_prefix) { + if (p.prefix == rev_prefix) { + return p.idx; + } + } + + wpat->rev_prefix.emplace_back(rev_prefix, idx); + + return idx; + } + + return router_.add_route(hostname, idx); +} + +ssize_t CertLookupTree::lookup(const StringRef &hostname) { + std::array buf; + + // NI_MAXHOST includes terminal NULL byte + if (hostname.empty() || hostname.size() + 1 > buf.size()) { + return -1; + } + + // Always prefer exact match + auto idx = router_.match(hostname); + if (idx != -1) { + return idx; + } + + if (wildcard_patterns_.empty()) { + return -1; + } + + ssize_t best_idx = -1; + size_t best_prefixlen = 0; + const RNode *last_node = nullptr; + + auto rev_host = StringRef{ + std::begin(buf), std::reverse_copy(std::begin(hostname), + std::end(hostname), std::begin(buf))}; + + for (;;) { + size_t nread = 0; + + auto wcidx = + rev_wildcard_router_.match_prefix(&nread, &last_node, rev_host); + if (wcidx == -1) { + return best_idx; + } + + // '*' must match at least one byte + if (nread == rev_host.size()) { + return best_idx; + } + + rev_host = StringRef{std::begin(rev_host) + nread, std::end(rev_host)}; + + auto rev_prefix = StringRef{std::begin(rev_host) + 1, std::end(rev_host)}; + + auto &wpat = wildcard_patterns_[wcidx]; + for (auto &wprefix : wpat.rev_prefix) { + if (!util::ends_with(rev_prefix, wprefix.prefix)) { + continue; + } + + auto prefixlen = + wprefix.prefix.size() + + (reinterpret_cast(&rev_host[0]) - &buf[0]); + + // Breaking a tie with longer suffix + if (prefixlen < best_prefixlen) { + continue; + } + + best_idx = wprefix.idx; + best_prefixlen = prefixlen; + } + } +} + +void CertLookupTree::dump() const { + std::cerr << "exact:" << std::endl; + router_.dump(); + std::cerr << "wildcard suffix (reversed):" << std::endl; + rev_wildcard_router_.dump(); +} + +int cert_lookup_tree_add_ssl_ctx( + CertLookupTree *lt, std::vector> &indexed_ssl_ctx, + SSL_CTX *ssl_ctx) { + std::array buf; + +#if LIBRESSL_2_7_API || \ + (!LIBRESSL_IN_USE && OPENSSL_VERSION_NUMBER >= 0x10002000L) + auto cert = SSL_CTX_get0_certificate(ssl_ctx); +#else // !LIBRESSL_2_7_API && OPENSSL_VERSION_NUMBER < 0x10002000L + auto tls_ctx_data = + static_cast(SSL_CTX_get_app_data(ssl_ctx)); + auto cert = load_certificate(tls_ctx_data->cert_file); + auto cert_deleter = defer(X509_free, cert); +#endif // !LIBRESSL_2_7_API && OPENSSL_VERSION_NUMBER < 0x10002000L + + auto altnames = static_cast( + X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr)); + if (altnames) { + auto altnames_deleter = defer(GENERAL_NAMES_free, altnames); + size_t n = sk_GENERAL_NAME_num(altnames); + auto dns_found = false; + for (size_t i = 0; i < n; ++i) { + auto altname = sk_GENERAL_NAME_value(altnames, i); + if (altname->type != GEN_DNS) { + continue; + } + + auto name = ASN1_STRING_get0_data(altname->d.ia5); + if (!name) { + continue; + } + + auto len = ASN1_STRING_length(altname->d.ia5); + if (len == 0) { + continue; + } + if (std::find(name, name + len, '\0') != name + len) { + // Embedded NULL is not permitted. + continue; + } + + if (name[len - 1] == '.') { + --len; + if (len == 0) { + continue; + } + } + + dns_found = true; + + if (static_cast(len) + 1 > buf.size()) { + continue; + } + + auto end_buf = std::copy_n(name, len, std::begin(buf)); + util::inp_strlower(std::begin(buf), end_buf); + + auto idx = lt->add_cert(StringRef{std::begin(buf), end_buf}, + indexed_ssl_ctx.size()); + if (idx == -1) { + continue; + } + + if (static_cast(idx) < indexed_ssl_ctx.size()) { + indexed_ssl_ctx[idx].push_back(ssl_ctx); + } else { + assert(static_cast(idx) == indexed_ssl_ctx.size()); + indexed_ssl_ctx.emplace_back(std::vector{ssl_ctx}); + } + } + + // Don't bother CN if we have dNSName. + if (dns_found) { + return 0; + } + } + + auto cn = get_common_name(cert); + if (cn.empty()) { + return 0; + } + + if (cn[cn.size() - 1] == '.') { + if (cn.size() == 1) { + OPENSSL_free(const_cast(cn.c_str())); + + return 0; + } + + cn = StringRef{cn.c_str(), cn.size() - 1}; + } + + auto end_buf = std::copy(std::begin(cn), std::end(cn), std::begin(buf)); + + OPENSSL_free(const_cast(cn.c_str())); + + util::inp_strlower(std::begin(buf), end_buf); + + auto idx = + lt->add_cert(StringRef{std::begin(buf), end_buf}, indexed_ssl_ctx.size()); + if (idx == -1) { + return 0; + } + + if (static_cast(idx) < indexed_ssl_ctx.size()) { + indexed_ssl_ctx[idx].push_back(ssl_ctx); + } else { + assert(static_cast(idx) == indexed_ssl_ctx.size()); + indexed_ssl_ctx.emplace_back(std::vector{ssl_ctx}); + } + + return 0; +} + +bool in_proto_list(const std::vector &protos, + const StringRef &needle) { + for (auto &proto : protos) { + if (util::streq(proto, needle)) { + return true; + } + } + return false; +} + +bool upstream_tls_enabled(const ConnectionConfig &connconf) { +#ifdef ENABLE_HTTP3 + if (connconf.quic_listener.addrs.size()) { + return true; + } +#endif // ENABLE_HTTP3 + + const auto &faddrs = connconf.listener.addrs; + return std::any_of(std::begin(faddrs), std::end(faddrs), + [](const UpstreamAddr &faddr) { return faddr.tls; }); +} + +X509 *load_certificate(const char *filename) { + auto bio = BIO_new(BIO_s_file()); + if (!bio) { + fprintf(stderr, "BIO_new() failed\n"); + return nullptr; + } + auto bio_deleter = defer(BIO_vfree, bio); + if (!BIO_read_filename(bio, filename)) { + fprintf(stderr, "Could not read certificate file '%s'\n", filename); + return nullptr; + } + auto cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + if (!cert) { + fprintf(stderr, "Could not read X509 structure from file '%s'\n", filename); + return nullptr; + } + + return cert; +} + +SSL_CTX * +setup_server_ssl_context(std::vector &all_ssl_ctx, + std::vector> &indexed_ssl_ctx, + CertLookupTree *cert_tree +#ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +) { + auto config = get_config(); + + if (!upstream_tls_enabled(config->conn)) { + return nullptr; + } + + auto &tlsconf = config->tls; + + auto ssl_ctx = create_ssl_context(tlsconf.private_key_file.c_str(), + tlsconf.cert_file.c_str(), tlsconf.sct_data +#ifdef HAVE_NEVERBLEED + , + nb +#endif // HAVE_NEVERBLEED + ); + + all_ssl_ctx.push_back(ssl_ctx); + + assert(cert_tree); + + if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == -1) { + LOG(FATAL) << "Failed to add default certificate."; + DIE(); + } + + for (auto &c : tlsconf.subcerts) { + auto ssl_ctx = create_ssl_context(c.private_key_file.c_str(), + c.cert_file.c_str(), c.sct_data +#ifdef HAVE_NEVERBLEED + , + nb +#endif // HAVE_NEVERBLEED + ); + all_ssl_ctx.push_back(ssl_ctx); + + if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == + -1) { + LOG(FATAL) << "Failed to add sub certificate."; + DIE(); + } + } + + return ssl_ctx; +} + +#ifdef ENABLE_HTTP3 +SSL_CTX *setup_quic_server_ssl_context( + std::vector &all_ssl_ctx, + std::vector> &indexed_ssl_ctx, + CertLookupTree *cert_tree +# ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +# endif // HAVE_NEVERBLEED +) { + auto config = get_config(); + + if (!upstream_tls_enabled(config->conn)) { + return nullptr; + } + + auto &tlsconf = config->tls; + + auto ssl_ctx = + create_quic_ssl_context(tlsconf.private_key_file.c_str(), + tlsconf.cert_file.c_str(), tlsconf.sct_data +# ifdef HAVE_NEVERBLEED + , + nb +# endif // HAVE_NEVERBLEED + ); + + all_ssl_ctx.push_back(ssl_ctx); + + assert(cert_tree); + + if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == -1) { + LOG(FATAL) << "Failed to add default certificate."; + DIE(); + } + + for (auto &c : tlsconf.subcerts) { + auto ssl_ctx = create_quic_ssl_context(c.private_key_file.c_str(), + c.cert_file.c_str(), c.sct_data +# ifdef HAVE_NEVERBLEED + , + nb +# endif // HAVE_NEVERBLEED + ); + all_ssl_ctx.push_back(ssl_ctx); + + if (cert_lookup_tree_add_ssl_ctx(cert_tree, indexed_ssl_ctx, ssl_ctx) == + -1) { + LOG(FATAL) << "Failed to add sub certificate."; + DIE(); + } + } + + return ssl_ctx; +} +#endif // ENABLE_HTTP3 + +SSL_CTX *setup_downstream_client_ssl_context( +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +) { + auto &tlsconf = get_config()->tls; + + return create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + nb, +#endif // HAVE_NEVERBLEED + tlsconf.cacert, tlsconf.client.cert_file, tlsconf.client.private_key_file, + select_next_proto_cb); +} + +void setup_downstream_http2_alpn(SSL *ssl) { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN advertisement + auto alpn = util::get_default_alpn(); + SSL_set_alpn_protos(ssl, alpn.data(), alpn.size()); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} + +void setup_downstream_http1_alpn(SSL *ssl) { +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + // ALPN advertisement + SSL_set_alpn_protos(ssl, NGHTTP2_H1_1_ALPN.byte(), NGHTTP2_H1_1_ALPN.size()); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} + +std::unique_ptr create_cert_lookup_tree() { + auto config = get_config(); + if (!upstream_tls_enabled(config->conn)) { + return nullptr; + } + return std::make_unique(); +} + +namespace { +std::vector serialize_ssl_session(SSL_SESSION *session) { + auto len = i2d_SSL_SESSION(session, nullptr); + auto buf = std::vector(len); + auto p = buf.data(); + i2d_SSL_SESSION(session, &p); + + return buf; +} +} // namespace + +void try_cache_tls_session(TLSSessionCache *cache, SSL_SESSION *session, + const std::chrono::steady_clock::time_point &t) { + if (cache->last_updated + 1min > t) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Client session cache entry is still fresh."; + } + return; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Update client cache entry " + << "timestamp = " << t.time_since_epoch().count(); + } + + cache->session_data = serialize_ssl_session(session); + cache->last_updated = t; +} + +SSL_SESSION *reuse_tls_session(const TLSSessionCache &cache) { + if (cache.session_data.empty()) { + return nullptr; + } + + auto p = cache.session_data.data(); + return d2i_SSL_SESSION(nullptr, &p, cache.session_data.size()); +} + +int proto_version_from_string(const StringRef &v) { +#ifdef TLS1_3_VERSION + if (util::strieq_l("TLSv1.3", v)) { + return TLS1_3_VERSION; + } +#endif // TLS1_3_VERSION + if (util::strieq_l("TLSv1.2", v)) { + return TLS1_2_VERSION; + } + if (util::strieq_l("TLSv1.1", v)) { + return TLS1_1_VERSION; + } + if (util::strieq_l("TLSv1.0", v)) { + return TLS1_VERSION; + } + return -1; +} + +int verify_ocsp_response(SSL_CTX *ssl_ctx, const uint8_t *ocsp_resp, + size_t ocsp_resplen) { + +#if !defined(OPENSSL_NO_OCSP) && !LIBRESSL_IN_USE && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L + int rv; + + STACK_OF(X509) * chain_certs; + SSL_CTX_get0_chain_certs(ssl_ctx, &chain_certs); + + auto resp = d2i_OCSP_RESPONSE(nullptr, &ocsp_resp, ocsp_resplen); + if (resp == nullptr) { + LOG(ERROR) << "d2i_OCSP_RESPONSE failed"; + return -1; + } + auto resp_deleter = defer(OCSP_RESPONSE_free, resp); + + if (OCSP_response_status(resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + LOG(ERROR) << "OCSP response status is not successful"; + return -1; + } + + ERR_clear_error(); + + auto bs = OCSP_response_get1_basic(resp); + if (bs == nullptr) { + LOG(ERROR) << "OCSP_response_get1_basic failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return -1; + } + auto bs_deleter = defer(OCSP_BASICRESP_free, bs); + + auto store = SSL_CTX_get_cert_store(ssl_ctx); + + ERR_clear_error(); + + rv = OCSP_basic_verify(bs, chain_certs, store, 0); + + if (rv != 1) { + LOG(ERROR) << "OCSP_basic_verify failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return -1; + } + + auto sresp = OCSP_resp_get0(bs, 0); + if (sresp == nullptr) { + LOG(ERROR) << "OCSP response verification failed: no single response"; + return -1; + } + +# if OPENSSL_1_1_API + auto certid = OCSP_SINGLERESP_get0_id(sresp); +# else // !OPENSSL_1_1_API + auto certid = sresp->certId; +# endif // !OPENSSL_1_1_API + assert(certid != nullptr); + + ASN1_INTEGER *serial; + rv = OCSP_id_get0_info(nullptr, nullptr, nullptr, &serial, + const_cast(certid)); + if (rv != 1) { + LOG(ERROR) << "OCSP_id_get0_info failed"; + return -1; + } + + if (serial == nullptr) { + LOG(ERROR) << "OCSP response does not contain serial number"; + return -1; + } + + auto cert = SSL_CTX_get0_certificate(ssl_ctx); + auto cert_serial = X509_get_serialNumber(cert); + + if (ASN1_INTEGER_cmp(cert_serial, serial)) { + LOG(ERROR) << "OCSP verification serial numbers do not match"; + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "OCSP verification succeeded"; + } +#endif // !defined(OPENSSL_NO_OCSP) && !LIBRESSL_IN_USE + // && OPENSSL_VERSION_NUMBER >= 0x10002000L + + return 0; +} + +ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, const X509 *x, + const EVP_MD *md) { + unsigned int len = dstlen; + if (X509_digest(x, md, dst, &len) != 1) { + return -1; + } + return len; +} + +namespace { +StringRef get_x509_name(BlockAllocator &balloc, X509_NAME *nm) { + auto b = BIO_new(BIO_s_mem()); + if (!b) { + return StringRef{}; + } + + auto b_deleter = defer(BIO_free, b); + + // Not documented, but it seems that X509_NAME_print_ex returns the + // number of bytes written into b. + auto slen = X509_NAME_print_ex(b, nm, 0, XN_FLAG_RFC2253); + if (slen <= 0) { + return StringRef{}; + } + + auto iov = make_byte_ref(balloc, slen + 1); + BIO_read(b, iov.base, slen); + iov.base[slen] = '\0'; + return StringRef{iov.base, static_cast(slen)}; +} +} // namespace + +StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x) { + return get_x509_name(balloc, X509_get_subject_name(x)); +} + +StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x) { + return get_x509_name(balloc, X509_get_issuer_name(x)); +} + +StringRef get_x509_serial(BlockAllocator &balloc, X509 *x) { + auto sn = X509_get_serialNumber(x); + auto bn = BN_new(); + auto bn_d = defer(BN_free, bn); + if (!ASN1_INTEGER_to_BN(sn, bn) || BN_num_bytes(bn) > 20) { + return StringRef{}; + } + + std::array b; + auto n = BN_bn2bin(bn, b.data()); + assert(n <= 20); + + return util::format_hex(balloc, StringRef{b.data(), static_cast(n)}); +} + +namespace { +// Performs conversion from |at| to time_t. The result is stored in +// |t|. This function returns 0 if it succeeds, or -1. +int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) { + int rv; + +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + struct tm tm; + rv = ASN1_TIME_to_tm(at, &tm); + if (rv != 1) { + return -1; + } + + t = nghttp2_timegm(&tm); +#else // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + auto b = BIO_new(BIO_s_mem()); + if (!b) { + return -1; + } + + auto bio_deleter = defer(BIO_free, b); + + rv = ASN1_TIME_print(b, at); + if (rv != 1) { + return -1; + } + +# ifdef NGHTTP2_OPENSSL_IS_BORINGSSL + char *s; +# else + unsigned char *s; +# endif + auto slen = BIO_get_mem_data(b, &s); + auto tt = util::parse_openssl_asn1_time_print( + StringRef{s, static_cast(slen)}); + if (tt == 0) { + return -1; + } + + t = tt; +#endif // !(OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL)) + + return 0; +} +} // namespace + +int get_x509_not_before(time_t &t, X509 *x) { +#if OPENSSL_1_1_API + auto at = X509_get0_notBefore(x); +#else // !OPENSSL_1_1_API + auto at = X509_get_notBefore(x); +#endif // !OPENSSL_1_1_API + if (!at) { + return -1; + } + + return time_t_from_asn1_time(t, at); +} + +int get_x509_not_after(time_t &t, X509 *x) { +#if OPENSSL_1_1_API + auto at = X509_get0_notAfter(x); +#else // !OPENSSL_1_1_API + auto at = X509_get_notAfter(x); +#endif // !OPENSSL_1_1_API + if (!at) { + return -1; + } + + return time_t_from_asn1_time(t, at); +} + +} // namespace tls + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_tls.h b/lib/nghttp2/src/shrpx_tls.h new file mode 100644 index 00000000000..00265d55d7f --- /dev/null +++ b/lib/nghttp2/src/shrpx_tls.h @@ -0,0 +1,337 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_TLS_H +#define SHRPX_TLS_H + +#include "shrpx.h" + +#include +#include + +#include +#include + +#include + +#ifdef HAVE_NEVERBLEED +# include +#endif // HAVE_NEVERBLEED + +#include "network.h" +#include "shrpx_config.h" +#include "shrpx_router.h" + +namespace shrpx { + +class ClientHandler; +class Worker; +class DownstreamConnectionPool; +struct DownstreamAddr; +struct UpstreamAddr; + +namespace tls { + +struct TLSSessionCache { + // ASN1 representation of SSL_SESSION object. See + // i2d_SSL_SESSION(3SSL). + std::vector session_data; + // The last time stamp when this cache entry is created or updated. + std::chrono::steady_clock::time_point last_updated; +}; + +// This struct stores the additional information per SSL_CTX. This is +// attached to SSL_CTX using SSL_CTX_set_app_data(). +struct TLSContextData { + // SCT data formatted so that this can be directly sent as + // extension_data of signed_certificate_timestamp. + std::vector sct_data; +#ifndef HAVE_ATOMIC_STD_SHARED_PTR + // Protects ocsp_data; + std::mutex mu; +#endif // !HAVE_ATOMIC_STD_SHARED_PTR + // OCSP response + std::shared_ptr> ocsp_data; + + // Path to certificate file + const char *cert_file; +}; + +// Create server side SSL_CTX +SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file, + const std::vector &sct_data + +#ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +); + +// Create client side SSL_CTX. This does not configure ALPN settings. +// |next_proto_select_cb| is for NPN. +SSL_CTX *create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb, +#endif // HAVE_NEVERBLEED + const StringRef &cacert, const StringRef &cert_file, + const StringRef &private_key_file, + int (*next_proto_select_cb)(SSL *s, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg)); + +#ifdef ENABLE_HTTP3 +SSL_CTX *create_quic_ssl_client_context( +# ifdef HAVE_NEVERBLEED + neverbleed_t *nb, +# endif // HAVE_NEVERBLEED + const StringRef &cacert, const StringRef &cert_file, + const StringRef &private_key_file, + int (*next_proto_select_cb)(SSL *s, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg)); +#endif // ENABLE_HTTP3 + +ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr, + int addrlen, const UpstreamAddr *faddr); + +// Check peer's certificate against given |address| and |host|. +int check_cert(SSL *ssl, const Address *addr, const StringRef &host); +// Check peer's certificate against given host name described in +// |addr| and numeric address in |raddr|. Note that |raddr| might not +// point to &addr->addr. +int check_cert(SSL *ssl, const DownstreamAddr *addr, const Address *raddr); + +// Verify |cert| using numeric IP address. |hostname| and |addr| +// should contain the same numeric IP address. This function returns +// 0 if it succeeds, or -1. +int verify_numeric_hostname(X509 *cert, const StringRef &hostname, + const Address *addr); + +// Verify |cert| using DNS name hostname. This function returns 0 if +// it succeeds, or -1. +int verify_dns_hostname(X509 *cert, const StringRef &hostname); + +struct WildcardRevPrefix { + WildcardRevPrefix(const StringRef &prefix, size_t idx) + : prefix(std::begin(prefix), std::end(prefix)), idx(idx) {} + + // "Prefix" of wildcard pattern. It is reversed from original form. + // For example, if the original wildcard is "test*.nghttp2.org", + // prefix would be "tset". + ImmutableString prefix; + // The index of SSL_CTX. See ConnectionHandler::get_ssl_ctx(). + size_t idx; +}; + +struct WildcardPattern { + // Wildcard host sharing only suffix is probably rare, so we just do + // linear search. + std::vector rev_prefix; +}; + +class CertLookupTree { +public: + CertLookupTree(); + + // Adds hostname pattern |hostname| to the lookup tree, associating + // value |index|. When the queried host matches this pattern, + // |index| is returned. We support wildcard pattern. The left most + // '*' is considered as wildcard character, and it must match at + // least one character. If the same pattern has been already added, + // this function does not alter the tree, and returns the existing + // matching index. + // + // The caller should lower-case |hostname| since this function does + // do that, and lookup function performs case-sensitive match. + // + // TODO Treat wildcard pattern described as RFC 6125. + // + // This function returns the index. It returns -1 if it fails + // (e.g., hostname is too long). If the returned index equals to + // |index|, then hostname is added to the tree with the value + // |index|. If it is not -1, and does not equal to |index|, same + // hostname has already been added to the tree. + ssize_t add_cert(const StringRef &hostname, size_t index); + + // Looks up index using the given |hostname|. The exact match takes + // precedence over wildcard match. For wildcard match, longest + // match (sum of matched suffix and prefix length in bytes) is + // preferred, breaking a tie with longer suffix. + // + // The caller should lower-case |hostname| since this function + // performs case-sensitive match. + ssize_t lookup(const StringRef &hostname); + + // Dumps the contents of this lookup tree to stderr. + void dump() const; + +private: + // Exact match + Router router_; + // Wildcard reversed suffix match. The returned index is into + // wildcard_patterns_. + Router rev_wildcard_router_; + // Stores wildcard suffix patterns. + std::vector wildcard_patterns_; +}; + +// Adds hostnames in certificate in |ssl_ctx| to lookup tree |lt|. +// The subjectAltNames and commonName are considered as eligible +// hostname. If there is at least one dNSName in subjectAltNames, +// commonName is not considered. |ssl_ctx| is also added to +// |indexed_ssl_ctx|. This function returns 0 if it succeeds, or -1. +int cert_lookup_tree_add_ssl_ctx( + CertLookupTree *lt, std::vector> &indexed_ssl_ctx, + SSL_CTX *ssl_ctx); + +// Returns true if |proto| is included in the +// protocol list |protos|. +bool in_proto_list(const std::vector &protos, + const StringRef &proto); + +// Returns true if security requirement for HTTP/2 is fulfilled. +bool check_http2_requirement(SSL *ssl); + +// Returns SSL/TLS option mask to disable SSL/TLS protocol version not +// included in |tls_proto_list|. The returned mask can be directly +// passed to SSL_CTX_set_options(). +long int create_tls_proto_mask(const std::vector &tls_proto_list); + +int set_alpn_prefs(std::vector &out, + const std::vector &protos); + +// Setups server side SSL_CTX. This function inspects get_config() +// and if upstream_no_tls is true, returns nullptr. Otherwise +// construct default SSL_CTX. If subcerts are available +// (get_config()->subcerts), caller should provide CertLookupTree +// object as |cert_tree| parameter, otherwise SNI does not work. All +// the created SSL_CTX is stored into |all_ssl_ctx|. They are also +// added to |indexed_ssl_ctx|. |cert_tree| uses its index to +// associate hostname to the SSL_CTX. +SSL_CTX * +setup_server_ssl_context(std::vector &all_ssl_ctx, + std::vector> &indexed_ssl_ctx, + CertLookupTree *cert_tree +#ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +); + +#ifdef ENABLE_HTTP3 +SSL_CTX *setup_quic_server_ssl_context( + std::vector &all_ssl_ctx, + std::vector> &indexed_ssl_ctx, + CertLookupTree *cert_tree +# ifdef HAVE_NEVERBLEED + , + neverbleed_t *nb +# endif // HAVE_NEVERBLEED +); +#endif // ENABLE_HTTP3 + +// Setups client side SSL_CTX. +SSL_CTX *setup_downstream_client_ssl_context( +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +); + +// Sets ALPN settings in |SSL| suitable for HTTP/2 use. +void setup_downstream_http2_alpn(SSL *ssl); +// Sets ALPN settings in |SSL| suitable for HTTP/1.1 use. +void setup_downstream_http1_alpn(SSL *ssl); + +// Creates CertLookupTree. If frontend is configured not to use TLS, +// this function returns nullptr. +std::unique_ptr create_cert_lookup_tree(); + +SSL *create_ssl(SSL_CTX *ssl_ctx); + +// Returns true if SSL/TLS is enabled on upstream +bool upstream_tls_enabled(const ConnectionConfig &connconf); + +// Performs TLS hostname match. |pattern| can contain wildcard +// character '*', which matches prefix of target hostname. There are +// several restrictions to make wildcard work. The matching algorithm +// is based on RFC 6125. +bool tls_hostname_match(const StringRef &pattern, const StringRef &hostname); + +// Caches |session|. |session| is serialized into ASN1 +// representation, and stored. |t| is used as a time stamp. +// Depending on the existing cache's time stamp, |session| might not +// be cached. +void try_cache_tls_session(TLSSessionCache *cache, SSL_SESSION *session, + const std::chrono::steady_clock::time_point &t); + +// Returns cached session associated |addr|. If no cache entry is +// found associated to |addr|, nullptr will be returned. +SSL_SESSION *reuse_tls_session(const TLSSessionCache &addr); + +// Loads certificate form file |filename|. The caller should delete +// the returned object using X509_free(). +X509 *load_certificate(const char *filename); + +// Returns TLS version from |v|. The returned value is defined in +// OpenSSL header file. This function returns -1 if |v| is not valid +// TLS version string. +int proto_version_from_string(const StringRef &v); + +// Verifies OCSP response |ocsp_resp| of length |ocsp_resplen|. This +// function returns 0 if it succeeds, or -1. +int verify_ocsp_response(SSL_CTX *ssl_ctx, const uint8_t *ocsp_resp, + size_t ocsp_resplen); + +// Stores fingerprint of |x| in |dst| of length |dstlen|. |md| +// specifies hash function to use, and |dstlen| must be large enough +// to include hash value (e.g., 32 bytes for SHA-256). This function +// returns the number of bytes written in |dst|, or -1. +ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, const X509 *x, + const EVP_MD *md); + +// Returns subject name of |x|. If this function fails to get subject +// name, it returns an empty string. +StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x); + +// Returns issuer name of |x|. If this function fails to get issuer +// name, it returns an empty string. +StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x); + +// Returns serial number of |x|. If this function fails to get serial +// number, it returns an empty string. number +StringRef get_x509_serial(BlockAllocator &balloc, X509 *x); + +// Fills NotBefore of |x| in |t|. This function returns 0 if it +// succeeds, or -1. +int get_x509_not_before(time_t &t, X509 *x); + +// Fills NotAfter of |x| in |t|. This function returns 0 if it +// succeeds, or -1. +int get_x509_not_after(time_t &t, X509 *x); + +} // namespace tls + +} // namespace shrpx + +#endif // SHRPX_TLS_H diff --git a/lib/nghttp2/src/shrpx_tls_test.cc b/lib/nghttp2/src/shrpx_tls_test.cc new file mode 100644 index 00000000000..02fb1681fa7 --- /dev/null +++ b/lib/nghttp2/src/shrpx_tls_test.cc @@ -0,0 +1,339 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "shrpx_tls_test.h" + +#include + +#include "shrpx_tls.h" +#include "shrpx_log.h" +#include "util.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +void test_shrpx_tls_create_lookup_tree(void) { + auto tree = std::make_unique(); + + constexpr StringRef hostnames[] = { + StringRef::from_lit("example.com"), // 0 + StringRef::from_lit("www.example.org"), // 1 + StringRef::from_lit("*www.example.org"), // 2 + StringRef::from_lit("xy*.host.domain"), // 3 + StringRef::from_lit("*yy.host.domain"), // 4 + StringRef::from_lit("nghttp2.sourceforge.net"), // 5 + StringRef::from_lit("sourceforge.net"), // 6 + StringRef::from_lit("sourceforge.net"), // 7, duplicate + StringRef::from_lit("*.foo.bar"), // 8, oo.bar is suffix of *.foo.bar + StringRef::from_lit("oo.bar") // 9 + }; + auto num = array_size(hostnames); + + for (size_t idx = 0; idx < num; ++idx) { + tree->add_cert(hostnames[idx], idx); + } + + tree->dump(); + + CU_ASSERT(0 == tree->lookup(hostnames[0])); + CU_ASSERT(1 == tree->lookup(hostnames[1])); + CU_ASSERT(2 == tree->lookup(StringRef::from_lit("2www.example.org"))); + CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("www2.example.org"))); + CU_ASSERT(3 == tree->lookup(StringRef::from_lit("xy1.host.domain"))); + // Does not match *yy.host.domain, because * must match at least 1 + // character. + CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("yy.host.domain"))); + CU_ASSERT(4 == tree->lookup(StringRef::from_lit("xyy.host.domain"))); + CU_ASSERT(-1 == tree->lookup(StringRef{})); + CU_ASSERT(5 == tree->lookup(hostnames[5])); + CU_ASSERT(6 == tree->lookup(hostnames[6])); + static constexpr char h6[] = "pdylay.sourceforge.net"; + for (int i = 0; i < 7; ++i) { + CU_ASSERT(-1 == tree->lookup(StringRef{h6 + i, str_size(h6) - i})); + } + CU_ASSERT(8 == tree->lookup(StringRef::from_lit("x.foo.bar"))); + CU_ASSERT(9 == tree->lookup(hostnames[9])); + + constexpr StringRef names[] = { + StringRef::from_lit("rab"), // 1 + StringRef::from_lit("zab"), // 2 + StringRef::from_lit("zzub"), // 3 + StringRef::from_lit("ab") // 4 + }; + num = array_size(names); + + tree = std::make_unique(); + for (size_t idx = 0; idx < num; ++idx) { + tree->add_cert(names[idx], idx); + } + for (size_t i = 0; i < num; ++i) { + CU_ASSERT((ssize_t)i == tree->lookup(names[i])); + } +} + +// We use cfssl to generate key pairs. +// +// CA self-signed key pairs generation: +// +// $ cfssl genkey -initca ca.nghttp2.org.csr.json | +// cfssljson -bare ca.nghttp2.org +// +// Create CSR: +// +// $ cfssl genkey test.nghttp2.org.csr.json | cfssljson -bare test.nghttp2.org +// $ cfssl genkey test.example.com.csr.json | cfssljson -bare test.example.com +// +// Sign CSR: +// +// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem +// -config=ca-config.json -profile=server test.nghttp2.org.csr | +// cfssljson -bare test.nghttp2.org +// +// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem +// -config=ca-config.json -profile=server test.example.com.csr | +// cfssljson -bare test.example.com +// +void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void) { + int rv; + + static constexpr char nghttp2_certfile[] = + NGHTTP2_SRC_DIR "/test.nghttp2.org.pem"; + auto nghttp2_ssl_ctx = SSL_CTX_new(TLS_server_method()); + auto nghttp2_ssl_ctx_del = defer(SSL_CTX_free, nghttp2_ssl_ctx); + auto nghttp2_tls_ctx_data = std::make_unique(); + nghttp2_tls_ctx_data->cert_file = nghttp2_certfile; + SSL_CTX_set_app_data(nghttp2_ssl_ctx, nghttp2_tls_ctx_data.get()); + rv = SSL_CTX_use_certificate_chain_file(nghttp2_ssl_ctx, nghttp2_certfile); + + CU_ASSERT(1 == rv); + + static constexpr char examples_certfile[] = + NGHTTP2_SRC_DIR "/test.example.com.pem"; + auto examples_ssl_ctx = SSL_CTX_new(TLS_server_method()); + auto examples_ssl_ctx_del = defer(SSL_CTX_free, examples_ssl_ctx); + auto examples_tls_ctx_data = std::make_unique(); + examples_tls_ctx_data->cert_file = examples_certfile; + SSL_CTX_set_app_data(examples_ssl_ctx, examples_tls_ctx_data.get()); + rv = SSL_CTX_use_certificate_chain_file(examples_ssl_ctx, examples_certfile); + + CU_ASSERT(1 == rv); + + tls::CertLookupTree tree; + std::vector> indexed_ssl_ctx; + + rv = tls::cert_lookup_tree_add_ssl_ctx(&tree, indexed_ssl_ctx, + nghttp2_ssl_ctx); + + CU_ASSERT(0 == rv); + + rv = tls::cert_lookup_tree_add_ssl_ctx(&tree, indexed_ssl_ctx, + examples_ssl_ctx); + + CU_ASSERT(0 == rv); + + CU_ASSERT(-1 == tree.lookup(StringRef::from_lit("not-used.nghttp2.org"))); + CU_ASSERT(0 == tree.lookup(StringRef::from_lit("test.nghttp2.org"))); + CU_ASSERT(1 == tree.lookup(StringRef::from_lit("w.test.nghttp2.org"))); + CU_ASSERT(2 == tree.lookup(StringRef::from_lit("www.test.nghttp2.org"))); + CU_ASSERT(3 == tree.lookup(StringRef::from_lit("test.example.com"))); +} + +template +bool tls_hostname_match_wrapper(const char (&pattern)[N], + const char (&hostname)[M]) { + return tls::tls_hostname_match(StringRef{pattern, N}, StringRef{hostname, M}); +} + +void test_shrpx_tls_tls_hostname_match(void) { + CU_ASSERT(tls_hostname_match_wrapper("example.com", "example.com")); + CU_ASSERT(tls_hostname_match_wrapper("example.com", "EXAMPLE.com")); + + // check wildcard + CU_ASSERT(tls_hostname_match_wrapper("*.example.com", "www.example.com")); + CU_ASSERT(tls_hostname_match_wrapper("*w.example.com", "www.example.com")); + CU_ASSERT(tls_hostname_match_wrapper("www*.example.com", "www1.example.com")); + CU_ASSERT( + tls_hostname_match_wrapper("www*.example.com", "WWW12.EXAMPLE.com")); + // at least 2 dots are required after '*' + CU_ASSERT(!tls_hostname_match_wrapper("*.com", "example.com")); + CU_ASSERT(!tls_hostname_match_wrapper("*", "example.com")); + // '*' must be in left most label + CU_ASSERT( + !tls_hostname_match_wrapper("blog.*.example.com", "blog.my.example.com")); + // prefix is wrong + CU_ASSERT( + !tls_hostname_match_wrapper("client*.example.com", "server.example.com")); + // '*' must match at least one character + CU_ASSERT(!tls_hostname_match_wrapper("www*.example.com", "www.example.com")); + + CU_ASSERT(!tls_hostname_match_wrapper("example.com", "nghttp2.org")); + CU_ASSERT(!tls_hostname_match_wrapper("www.example.com", "example.com")); + CU_ASSERT(!tls_hostname_match_wrapper("example.com", "www.example.com")); +} + +static X509 *load_cert(const char *path) { + auto f = fopen(path, "r"); + auto cert = PEM_read_X509(f, nullptr, nullptr, nullptr); + + fclose(f); + + return cert; +} + +static Address parse_addr(const char *ipaddr) { + addrinfo hints{}; + + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + + addrinfo *res = nullptr; + + auto rv = getaddrinfo(ipaddr, "443", &hints, &res); + + CU_ASSERT(0 == rv); + CU_ASSERT(nullptr != res); + + Address addr; + addr.len = res->ai_addrlen; + memcpy(&addr.su, res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + + return addr; +} + +void test_shrpx_tls_verify_numeric_hostname(void) { + { + // Successful IPv4 address match in SAN + static constexpr char ipaddr[] = "127.0.0.1"; + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); + auto addr = parse_addr(ipaddr); + auto rv = + tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); + + CU_ASSERT(0 == rv); + + X509_free(cert); + } + + { + // Successful IPv6 address match in SAN + static constexpr char ipaddr[] = "::1"; + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); + auto addr = parse_addr(ipaddr); + auto rv = + tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); + + CU_ASSERT(0 == rv); + + X509_free(cert); + } + + { + // Unsuccessful IPv4 address match in SAN + static constexpr char ipaddr[] = "192.168.0.127"; + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); + auto addr = parse_addr(ipaddr); + auto rv = + tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); + + CU_ASSERT(-1 == rv); + + X509_free(cert); + } + + { + // CommonName is not used if SAN is available + static constexpr char ipaddr[] = "192.168.0.1"; + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/ipaddr.crt"); + auto addr = parse_addr(ipaddr); + auto rv = + tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); + + CU_ASSERT(-1 == rv); + + X509_free(cert); + } + + { + // Successful IPv4 address match in CommonName + static constexpr char ipaddr[] = "127.0.0.1"; + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/nosan_ip.crt"); + auto addr = parse_addr(ipaddr); + auto rv = + tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); + + CU_ASSERT(0 == rv); + + X509_free(cert); + } +} + +void test_shrpx_tls_verify_dns_hostname(void) { + { + // Successful exact DNS name match in SAN + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); + auto rv = tls::verify_dns_hostname( + cert, StringRef::from_lit("nghttp2.example.com")); + + CU_ASSERT(0 == rv); + + X509_free(cert); + } + + { + // Successful wildcard DNS name match in SAN + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); + auto rv = tls::verify_dns_hostname( + cert, StringRef::from_lit("www.nghttp2.example.com")); + + CU_ASSERT(0 == rv); + + X509_free(cert); + } + + { + // CommonName is not used if SAN is available. + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); + auto rv = tls::verify_dns_hostname(cert, StringRef::from_lit("localhost")); + + CU_ASSERT(-1 == rv); + + X509_free(cert); + } + + { + // Successful DNS name match in CommonName + auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/nosan.crt"); + auto rv = tls::verify_dns_hostname(cert, StringRef::from_lit("localhost")); + + CU_ASSERT(0 == rv); + + X509_free(cert); + } +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_tls_test.h b/lib/nghttp2/src/shrpx_tls_test.h new file mode 100644 index 00000000000..7edc742b7f2 --- /dev/null +++ b/lib/nghttp2/src/shrpx_tls_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 SHRPX_TLS_TEST_H +#define SHRPX_TLS_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_tls_create_lookup_tree(void); +void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void); +void test_shrpx_tls_tls_hostname_match(void); +void test_shrpx_tls_verify_numeric_hostname(void); +void test_shrpx_tls_verify_dns_hostname(void); + +} // namespace shrpx + +#endif // SHRPX_TLS_TEST_H diff --git a/lib/nghttp2/src/shrpx_upstream.h b/lib/nghttp2/src/shrpx_upstream.h new file mode 100644 index 00000000000..3da62c9e9dc --- /dev/null +++ b/lib/nghttp2/src/shrpx_upstream.h @@ -0,0 +1,112 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_UPSTREAM_H +#define SHRPX_UPSTREAM_H + +#include "shrpx.h" +#include "shrpx_io_control.h" +#include "memchunk.h" + +using namespace nghttp2; + +namespace shrpx { + +class ClientHandler; +class Downstream; +class DownstreamConnection; + +class Upstream { +public: + virtual ~Upstream() {} + virtual int on_read() = 0; + virtual int on_write() = 0; + virtual int on_timeout(Downstream *downstream) { return 0; }; + virtual int on_downstream_abort_request(Downstream *downstream, + unsigned int status_code) = 0; + // Called when the current request is aborted without forwarding it + // to backend, and it should be redirected to https URI. + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream) = 0; + virtual int downstream_read(DownstreamConnection *dconn) = 0; + virtual int downstream_write(DownstreamConnection *dconn) = 0; + virtual int downstream_eof(DownstreamConnection *dconn) = 0; + virtual int downstream_error(DownstreamConnection *dconn, int events) = 0; + virtual ClientHandler *get_client_handler() const = 0; + + virtual int on_downstream_header_complete(Downstream *downstream) = 0; + virtual int on_downstream_body(Downstream *downstream, const uint8_t *data, + size_t len, bool flush) = 0; + virtual int on_downstream_body_complete(Downstream *downstream) = 0; + + virtual void on_handler_delete() = 0; + // Called when downstream connection for |downstream| is reset. + // Currently this is only used by Http2Session. If |no_retry| is + // true, another connection attempt using new DownstreamConnection + // is not allowed. + virtual int on_downstream_reset(Downstream *downstream, bool no_retry) = 0; + + virtual void pause_read(IOCtrlReason reason) = 0; + virtual int resume_read(IOCtrlReason reason, Downstream *downstream, + size_t consumed) = 0; + virtual int send_reply(Downstream *downstream, const uint8_t *body, + size_t bodylen) = 0; + + // Starts server push. The |downstream| is an associated stream for + // the pushed resource. This function returns 0 if it succeeds, + // otherwise -1. + virtual int initiate_push(Downstream *downstream, const StringRef &uri) = 0; + + // Fills response data in |iov| whose capacity is |iovcnt|. Returns + // the number of iovs filled. + virtual int response_riovec(struct iovec *iov, int iovcnt) const = 0; + virtual void response_drain(size_t n) = 0; + virtual bool response_empty() const = 0; + + // Called when PUSH_PROMISE was started in downstream. The + // associated downstream is given as |downstream|. The promised + // stream ID is given as |promised_stream_id|. If upstream supports + // server push for the corresponding upstream connection, it should + // return Downstream object for pushed stream. Otherwise, returns + // nullptr. + virtual Downstream * + on_downstream_push_promise(Downstream *downstream, + int32_t promised_stream_id) = 0; + // Called when PUSH_PROMISE frame was completely received in + // downstream. The associated downstream is given as |downstream|. + // This function returns 0 if it succeeds, or -1. + virtual int + on_downstream_push_promise_complete(Downstream *downstream, + Downstream *promised_downstream) = 0; + // Returns true if server push is enabled in upstream connection. + virtual bool push_enabled() const = 0; + // Cancels promised downstream. This function is called when + // PUSH_PROMISE for |promised_downstream| is not submitted to + // upstream session. + virtual void cancel_premature_downstream(Downstream *promised_downstream) = 0; +}; + +} // namespace shrpx + +#endif // SHRPX_UPSTREAM_H diff --git a/lib/nghttp2/src/shrpx_worker.cc b/lib/nghttp2/src/shrpx_worker.cc new file mode 100644 index 00000000000..a2b19fbfa76 --- /dev/null +++ b/lib/nghttp2/src/shrpx_worker.cc @@ -0,0 +1,1347 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "shrpx_worker.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include + +#include +#include + +#include + +#ifdef HAVE_LIBBPF +# include +# include +#endif // HAVE_LIBBPF + +#include "shrpx_tls.h" +#include "shrpx_log.h" +#include "shrpx_client_handler.h" +#include "shrpx_http2_session.h" +#include "shrpx_log_config.h" +#include "shrpx_memcached_dispatcher.h" +#ifdef HAVE_MRUBY +# include "shrpx_mruby.h" +#endif // HAVE_MRUBY +#ifdef ENABLE_HTTP3 +# include "shrpx_quic_listener.h" +#endif // ENABLE_HTTP3 +#include "shrpx_connection_handler.h" +#include "util.h" +#include "template.h" +#include "xsi_strerror.h" + +namespace shrpx { + +namespace { +void eventcb(struct ev_loop *loop, ev_async *w, int revents) { + auto worker = static_cast(w->data); + worker->process_events(); +} +} // namespace + +namespace { +void mcpool_clear_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto worker = static_cast(w->data); + if (worker->get_worker_stat()->num_connections != 0) { + return; + } + auto mcpool = worker->get_mcpool(); + if (mcpool->freelistsize == mcpool->poolsize) { + worker->get_mcpool()->clear(); + } +} +} // namespace + +namespace { +void proc_wev_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto worker = static_cast(w->data); + worker->process_events(); +} +} // namespace + +DownstreamAddrGroup::DownstreamAddrGroup() : retired{false} {} + +DownstreamAddrGroup::~DownstreamAddrGroup() {} + +// DownstreamKey is used to index SharedDownstreamAddr in order to +// find the same configuration. +using DownstreamKey = std::tuple< + std::vector< + std::tuple>, + bool, SessionAffinity, StringRef, StringRef, SessionAffinityCookieSecure, + SessionAffinityCookieStickiness, int64_t, int64_t, StringRef, bool>; + +namespace { +DownstreamKey +create_downstream_key(const std::shared_ptr &shared_addr, + const StringRef &mruby_file) { + DownstreamKey dkey; + + auto &addrs = std::get<0>(dkey); + addrs.resize(shared_addr->addrs.size()); + auto p = std::begin(addrs); + for (auto &a : shared_addr->addrs) { + std::get<0>(*p) = a.host; + std::get<1>(*p) = a.sni; + std::get<2>(*p) = a.group; + std::get<3>(*p) = a.fall; + std::get<4>(*p) = a.rise; + std::get<5>(*p) = a.proto; + std::get<6>(*p) = a.port; + std::get<7>(*p) = a.weight; + std::get<8>(*p) = a.group_weight; + std::get<9>(*p) = a.host_unix; + std::get<10>(*p) = a.tls; + std::get<11>(*p) = a.dns; + std::get<12>(*p) = a.upgrade_scheme; + ++p; + } + std::sort(std::begin(addrs), std::end(addrs)); + + std::get<1>(dkey) = shared_addr->redirect_if_not_tls; + + auto &affinity = shared_addr->affinity; + std::get<2>(dkey) = affinity.type; + std::get<3>(dkey) = affinity.cookie.name; + std::get<4>(dkey) = affinity.cookie.path; + std::get<5>(dkey) = affinity.cookie.secure; + std::get<6>(dkey) = affinity.cookie.stickiness; + auto &timeout = shared_addr->timeout; + std::get<7>(dkey) = timeout.read; + std::get<8>(dkey) = timeout.write; + std::get<9>(dkey) = mruby_file; + std::get<10>(dkey) = shared_addr->dnf; + + return dkey; +} +} // namespace + +Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, + SSL_CTX *tls_session_cache_memcached_ssl_ctx, + tls::CertLookupTree *cert_tree, +#ifdef ENABLE_HTTP3 + SSL_CTX *quic_sv_ssl_ctx, tls::CertLookupTree *quic_cert_tree, + const uint8_t *cid_prefix, size_t cid_prefixlen, +# ifdef HAVE_LIBBPF + size_t index, +# endif // HAVE_LIBBPF +#endif // ENABLE_HTTP3 + const std::shared_ptr &ticket_keys, + ConnectionHandler *conn_handler, + std::shared_ptr downstreamconf) + : +#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF) + index_{index}, +#endif // ENABLE_HTTP3 && HAVE_LIBBPF + randgen_(util::make_mt19937()), + worker_stat_{}, + dns_tracker_(loop, get_config()->conn.downstream->family), +#ifdef ENABLE_HTTP3 + quic_upstream_addrs_{get_config()->conn.quic_listener.addrs}, +#endif // ENABLE_HTTP3 + loop_(loop), + sv_ssl_ctx_(sv_ssl_ctx), + cl_ssl_ctx_(cl_ssl_ctx), + cert_tree_(cert_tree), + conn_handler_(conn_handler), +#ifdef ENABLE_HTTP3 + quic_sv_ssl_ctx_{quic_sv_ssl_ctx}, + quic_cert_tree_{quic_cert_tree}, + quic_conn_handler_{this}, +#endif // ENABLE_HTTP3 + ticket_keys_(ticket_keys), + connect_blocker_( + std::make_unique(randgen_, loop_, nullptr, nullptr)), + graceful_shutdown_(false) { +#ifdef ENABLE_HTTP3 + std::copy_n(cid_prefix, cid_prefixlen, std::begin(cid_prefix_)); +#endif // ENABLE_HTTP3 + + ev_async_init(&w_, eventcb); + w_.data = this; + ev_async_start(loop_, &w_); + + ev_timer_init(&mcpool_clear_timer_, mcpool_clear_cb, 0., 0.); + mcpool_clear_timer_.data = this; + + ev_timer_init(&proc_wev_timer_, proc_wev_cb, 0., 0.); + proc_wev_timer_.data = this; + + auto &session_cacheconf = get_config()->tls.session_cache; + + if (!session_cacheconf.memcached.host.empty()) { + session_cache_memcached_dispatcher_ = std::make_unique( + &session_cacheconf.memcached.addr, loop, + tls_session_cache_memcached_ssl_ctx, + StringRef{session_cacheconf.memcached.host}, &mcpool_, randgen_); + } + + replace_downstream_config(std::move(downstreamconf)); +} + +namespace { +void ensure_enqueue_addr( + std::priority_queue, + WeightGroupEntryGreater> &wgpq, + WeightGroup *wg, DownstreamAddr *addr) { + uint32_t cycle; + if (!wg->pq.empty()) { + auto &top = wg->pq.top(); + cycle = top.cycle; + } else { + cycle = 0; + } + + addr->cycle = cycle; + addr->pending_penalty = 0; + wg->pq.push(DownstreamAddrEntry{addr, addr->seq, addr->cycle}); + addr->queued = true; + + if (!wg->queued) { + if (!wgpq.empty()) { + auto &top = wgpq.top(); + cycle = top.cycle; + } else { + cycle = 0; + } + + wg->cycle = cycle; + wg->pending_penalty = 0; + wgpq.push(WeightGroupEntry{wg, wg->seq, wg->cycle}); + wg->queued = true; + } +} +} // namespace + +void Worker::replace_downstream_config( + std::shared_ptr downstreamconf) { + for (auto &g : downstream_addr_groups_) { + g->retired = true; + + auto &shared_addr = g->shared_addr; + for (auto &addr : shared_addr->addrs) { + addr.dconn_pool->remove_all(); + } + } + + downstreamconf_ = downstreamconf; + + // Making a copy is much faster with multiple thread on + // backendconfig API call. + auto groups = downstreamconf->addr_groups; + + downstream_addr_groups_ = + std::vector>(groups.size()); + + std::map addr_groups_indexer; +#ifdef HAVE_MRUBY + // TODO It is a bit less efficient because + // mruby::create_mruby_context returns std::unique_ptr and we cannot + // use std::make_shared. + std::map> shared_mruby_ctxs; +#endif // HAVE_MRUBY + + for (size_t i = 0; i < groups.size(); ++i) { + auto &src = groups[i]; + auto &dst = downstream_addr_groups_[i]; + + dst = std::make_shared(); + dst->pattern = + ImmutableString{std::begin(src.pattern), std::end(src.pattern)}; + + auto shared_addr = std::make_shared(); + + shared_addr->addrs.resize(src.addrs.size()); + shared_addr->affinity.type = src.affinity.type; + if (src.affinity.type == SessionAffinity::COOKIE) { + shared_addr->affinity.cookie.name = + make_string_ref(shared_addr->balloc, src.affinity.cookie.name); + if (!src.affinity.cookie.path.empty()) { + shared_addr->affinity.cookie.path = + make_string_ref(shared_addr->balloc, src.affinity.cookie.path); + } + shared_addr->affinity.cookie.secure = src.affinity.cookie.secure; + shared_addr->affinity.cookie.stickiness = src.affinity.cookie.stickiness; + } + shared_addr->affinity_hash = src.affinity_hash; + shared_addr->affinity_hash_map = src.affinity_hash_map; + shared_addr->redirect_if_not_tls = src.redirect_if_not_tls; + shared_addr->dnf = src.dnf; + shared_addr->timeout.read = src.timeout.read; + shared_addr->timeout.write = src.timeout.write; + + for (size_t j = 0; j < src.addrs.size(); ++j) { + auto &src_addr = src.addrs[j]; + auto &dst_addr = shared_addr->addrs[j]; + + dst_addr.addr = src_addr.addr; + dst_addr.host = make_string_ref(shared_addr->balloc, src_addr.host); + dst_addr.hostport = + make_string_ref(shared_addr->balloc, src_addr.hostport); + dst_addr.port = src_addr.port; + dst_addr.host_unix = src_addr.host_unix; + dst_addr.weight = src_addr.weight; + dst_addr.group = make_string_ref(shared_addr->balloc, src_addr.group); + dst_addr.group_weight = src_addr.group_weight; + dst_addr.affinity_hash = src_addr.affinity_hash; + dst_addr.proto = src_addr.proto; + dst_addr.tls = src_addr.tls; + dst_addr.sni = make_string_ref(shared_addr->balloc, src_addr.sni); + dst_addr.fall = src_addr.fall; + dst_addr.rise = src_addr.rise; + dst_addr.dns = src_addr.dns; + dst_addr.upgrade_scheme = src_addr.upgrade_scheme; + } + +#ifdef HAVE_MRUBY + auto mruby_ctx_it = shared_mruby_ctxs.find(src.mruby_file); + if (mruby_ctx_it == std::end(shared_mruby_ctxs)) { + shared_addr->mruby_ctx = mruby::create_mruby_context(src.mruby_file); + assert(shared_addr->mruby_ctx); + shared_mruby_ctxs.emplace(src.mruby_file, shared_addr->mruby_ctx); + } else { + shared_addr->mruby_ctx = (*mruby_ctx_it).second; + } +#endif // HAVE_MRUBY + + // share the connection if patterns have the same set of backend + // addresses. + + auto dkey = create_downstream_key(shared_addr, src.mruby_file); + auto it = addr_groups_indexer.find(dkey); + + if (it == std::end(addr_groups_indexer)) { + auto shared_addr_ptr = shared_addr.get(); + + for (auto &addr : shared_addr->addrs) { + addr.connect_blocker = std::make_unique( + randgen_, loop_, nullptr, [shared_addr_ptr, &addr]() { + if (!addr.queued) { + if (!addr.wg) { + return; + } + ensure_enqueue_addr(shared_addr_ptr->pq, addr.wg, &addr); + } + }); + + addr.live_check = std::make_unique(loop_, cl_ssl_ctx_, this, + &addr, randgen_); + } + + size_t seq = 0; + for (auto &addr : shared_addr->addrs) { + addr.dconn_pool = std::make_unique(); + addr.seq = seq++; + } + + util::shuffle(std::begin(shared_addr->addrs), + std::end(shared_addr->addrs), randgen_, + [](auto i, auto j) { std::swap((*i).seq, (*j).seq); }); + + if (shared_addr->affinity.type == SessionAffinity::NONE) { + std::map wgs; + size_t num_wgs = 0; + for (auto &addr : shared_addr->addrs) { + if (wgs.find(addr.group) == std::end(wgs)) { + ++num_wgs; + wgs.emplace(addr.group, nullptr); + } + } + + shared_addr->wgs = std::vector(num_wgs); + + for (auto &addr : shared_addr->addrs) { + auto &wg = wgs[addr.group]; + if (wg == nullptr) { + wg = &shared_addr->wgs[--num_wgs]; + wg->seq = num_wgs; + } + + wg->weight = addr.group_weight; + wg->pq.push(DownstreamAddrEntry{&addr, addr.seq, addr.cycle}); + addr.queued = true; + addr.wg = wg; + } + + assert(num_wgs == 0); + + for (auto &kv : wgs) { + shared_addr->pq.push( + WeightGroupEntry{kv.second, kv.second->seq, kv.second->cycle}); + kv.second->queued = true; + } + } + + dst->shared_addr = shared_addr; + + addr_groups_indexer.emplace(std::move(dkey), i); + } else { + auto &g = *(std::begin(downstream_addr_groups_) + (*it).second); + if (LOG_ENABLED(INFO)) { + LOG(INFO) << dst->pattern << " shares the same backend group with " + << g->pattern; + } + dst->shared_addr = g->shared_addr; + } + } +} + +Worker::~Worker() { + ev_async_stop(loop_, &w_); + ev_timer_stop(loop_, &mcpool_clear_timer_); + ev_timer_stop(loop_, &proc_wev_timer_); +} + +void Worker::schedule_clear_mcpool() { + // libev manual says: "If the watcher is already active nothing will + // happen." Since we don't change any timeout here, we don't have + // to worry about querying ev_is_active. + ev_timer_start(loop_, &mcpool_clear_timer_); +} + +void Worker::wait() { +#ifndef NOTHREADS + fut_.get(); +#endif // !NOTHREADS +} + +void Worker::run_async() { +#ifndef NOTHREADS + fut_ = std::async(std::launch::async, [this] { + (void)reopen_log_files(get_config()->logging); + ev_run(loop_); + delete_log_config(); + }); +#endif // !NOTHREADS +} + +void Worker::send(WorkerEvent event) { + { + std::lock_guard g(m_); + + q_.emplace_back(std::move(event)); + } + + ev_async_send(loop_, &w_); +} + +void Worker::process_events() { + WorkerEvent wev; + { + std::lock_guard g(m_); + + // Process event one at a time. This is important for + // WorkerEventType::NEW_CONNECTION event since accepting large + // number of new connections at once may delay time to 1st byte + // for existing connections. + + if (q_.empty()) { + ev_timer_stop(loop_, &proc_wev_timer_); + return; + } + + wev = std::move(q_.front()); + q_.pop_front(); + } + + ev_timer_start(loop_, &proc_wev_timer_); + + auto config = get_config(); + + auto worker_connections = config->conn.upstream.worker_connections; + + switch (wev.type) { + case WorkerEventType::NEW_CONNECTION: { + if (LOG_ENABLED(INFO)) { + WLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd + << ", addrlen=" << wev.client_addrlen; + } + + if (worker_stat_.num_connections >= worker_connections) { + + if (LOG_ENABLED(INFO)) { + WLOG(INFO, this) << "Too many connections >= " << worker_connections; + } + + close(wev.client_fd); + + break; + } + + auto client_handler = + tls::accept_connection(this, wev.client_fd, &wev.client_addr.sa, + wev.client_addrlen, wev.faddr); + if (!client_handler) { + if (LOG_ENABLED(INFO)) { + WLOG(ERROR, this) << "ClientHandler creation failed"; + } + close(wev.client_fd); + break; + } + + if (LOG_ENABLED(INFO)) { + WLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created "; + } + + break; + } + case WorkerEventType::REOPEN_LOG: + WLOG(NOTICE, this) << "Reopening log files: worker process (thread " << this + << ")"; + + reopen_log_files(config->logging); + + break; + case WorkerEventType::GRACEFUL_SHUTDOWN: + WLOG(NOTICE, this) << "Graceful shutdown commencing"; + + graceful_shutdown_ = true; + + if (worker_stat_.num_connections == 0 && + worker_stat_.num_close_waits == 0) { + ev_break(loop_); + + return; + } + + break; + case WorkerEventType::REPLACE_DOWNSTREAM: + WLOG(NOTICE, this) << "Replace downstream"; + + replace_downstream_config(wev.downstreamconf); + + break; +#ifdef ENABLE_HTTP3 + case WorkerEventType::QUIC_PKT_FORWARD: { + const UpstreamAddr *faddr; + + if (wev.quic_pkt->upstream_addr_index == static_cast(-1)) { + faddr = find_quic_upstream_addr(wev.quic_pkt->local_addr); + if (faddr == nullptr) { + LOG(ERROR) << "No suitable upstream address found"; + + break; + } + } else if (quic_upstream_addrs_.size() <= + wev.quic_pkt->upstream_addr_index) { + LOG(ERROR) << "upstream_addr_index is too large"; + + break; + } else { + faddr = &quic_upstream_addrs_[wev.quic_pkt->upstream_addr_index]; + } + + quic_conn_handler_.handle_packet( + faddr, wev.quic_pkt->remote_addr, wev.quic_pkt->local_addr, + wev.quic_pkt->pi, wev.quic_pkt->data.data(), wev.quic_pkt->data.size()); + + break; + } +#endif // ENABLE_HTTP3 + default: + if (LOG_ENABLED(INFO)) { + WLOG(INFO, this) << "unknown event type " << static_cast(wev.type); + } + } +} + +tls::CertLookupTree *Worker::get_cert_lookup_tree() const { return cert_tree_; } + +#ifdef ENABLE_HTTP3 +tls::CertLookupTree *Worker::get_quic_cert_lookup_tree() const { + return quic_cert_tree_; +} +#endif // ENABLE_HTTP3 + +std::shared_ptr Worker::get_ticket_keys() { +#ifdef HAVE_ATOMIC_STD_SHARED_PTR + return std::atomic_load_explicit(&ticket_keys_, std::memory_order_acquire); +#else // !HAVE_ATOMIC_STD_SHARED_PTR + std::lock_guard g(ticket_keys_m_); + return ticket_keys_; +#endif // !HAVE_ATOMIC_STD_SHARED_PTR +} + +void Worker::set_ticket_keys(std::shared_ptr ticket_keys) { +#ifdef HAVE_ATOMIC_STD_SHARED_PTR + // This is single writer + std::atomic_store_explicit(&ticket_keys_, std::move(ticket_keys), + std::memory_order_release); +#else // !HAVE_ATOMIC_STD_SHARED_PTR + std::lock_guard g(ticket_keys_m_); + ticket_keys_ = std::move(ticket_keys); +#endif // !HAVE_ATOMIC_STD_SHARED_PTR +} + +WorkerStat *Worker::get_worker_stat() { return &worker_stat_; } + +struct ev_loop *Worker::get_loop() const { return loop_; } + +SSL_CTX *Worker::get_sv_ssl_ctx() const { return sv_ssl_ctx_; } + +SSL_CTX *Worker::get_cl_ssl_ctx() const { return cl_ssl_ctx_; } + +#ifdef ENABLE_HTTP3 +SSL_CTX *Worker::get_quic_sv_ssl_ctx() const { return quic_sv_ssl_ctx_; } +#endif // ENABLE_HTTP3 + +void Worker::set_graceful_shutdown(bool f) { graceful_shutdown_ = f; } + +bool Worker::get_graceful_shutdown() const { return graceful_shutdown_; } + +MemchunkPool *Worker::get_mcpool() { return &mcpool_; } + +MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() { + return session_cache_memcached_dispatcher_.get(); +} + +std::mt19937 &Worker::get_randgen() { return randgen_; } + +#ifdef HAVE_MRUBY +int Worker::create_mruby_context() { + mruby_ctx_ = mruby::create_mruby_context(StringRef{get_config()->mruby_file}); + if (!mruby_ctx_) { + return -1; + } + + return 0; +} + +mruby::MRubyContext *Worker::get_mruby_context() const { + return mruby_ctx_.get(); +} +#endif // HAVE_MRUBY + +std::vector> & +Worker::get_downstream_addr_groups() { + return downstream_addr_groups_; +} + +ConnectBlocker *Worker::get_connect_blocker() const { + return connect_blocker_.get(); +} + +const DownstreamConfig *Worker::get_downstream_config() const { + return downstreamconf_.get(); +} + +ConnectionHandler *Worker::get_connection_handler() const { + return conn_handler_; +} + +#ifdef ENABLE_HTTP3 +QUICConnectionHandler *Worker::get_quic_connection_handler() { + return &quic_conn_handler_; +} +#endif // ENABLE_HTTP3 + +DNSTracker *Worker::get_dns_tracker() { return &dns_tracker_; } + +#ifdef ENABLE_HTTP3 +# ifdef HAVE_LIBBPF +bool Worker::should_attach_bpf() const { + auto config = get_config(); + auto &quicconf = config->quic; + auto &apiconf = config->api; + + if (quicconf.bpf.disabled) { + return false; + } + + if (!config->single_thread && apiconf.enabled) { + return index_ == 1; + } + + return index_ == 0; +} + +bool Worker::should_update_bpf_map() const { + auto config = get_config(); + auto &quicconf = config->quic; + + return !quicconf.bpf.disabled; +} + +uint32_t Worker::compute_sk_index() const { + auto config = get_config(); + auto &apiconf = config->api; + + if (!config->single_thread && apiconf.enabled) { + return index_ - 1; + } + + return index_; +} +# endif // HAVE_LIBBPF + +int Worker::setup_quic_server_socket() { + size_t n = 0; + + for (auto &addr : quic_upstream_addrs_) { + assert(!addr.host_unix); + if (create_quic_server_socket(addr) != 0) { + return -1; + } + + // Make sure that each endpoint has a unique address. + for (size_t i = 0; i < n; ++i) { + const auto &a = quic_upstream_addrs_[i]; + + if (addr.hostport == a.hostport) { + LOG(FATAL) + << "QUIC frontend endpoint must be unique: a duplicate found for " + << addr.hostport; + + return -1; + } + } + + ++n; + + quic_listeners_.emplace_back(std::make_unique(&addr, this)); + } + + return 0; +} + +int Worker::create_quic_server_socket(UpstreamAddr &faddr) { + std::array errbuf; + int fd = -1; + int rv; + + auto service = util::utos(faddr.port); + addrinfo hints{}; + hints.ai_family = faddr.family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; +# ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +# endif // AI_ADDRCONFIG + + auto node = + faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str(); + + addrinfo *res, *rp; + rv = getaddrinfo(node, service.c_str(), &hints, &res); +# ifdef AI_ADDRCONFIG + if (rv != 0) { + // Retry without AI_ADDRCONFIG + hints.ai_flags &= ~AI_ADDRCONFIG; + rv = getaddrinfo(node, service.c_str(), &hints, &res); + } +# endif // AI_ADDRCONFIG + if (rv != 0) { + LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6") + << " address for " << faddr.host << ", port " << faddr.port + << ": " << gai_strerror(rv); + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + std::array host; + + for (rp = res; rp; rp = rp->ai_next) { + rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(), + nullptr, 0, NI_NUMERICHOST); + if (rv != 0) { + LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv); + continue; + } + +# ifdef SOCK_NONBLOCK + fd = socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC, + rp->ai_protocol); + if (fd == -1) { + auto error = errno; + LOG(WARN) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } +# else // !SOCK_NONBLOCK + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + auto error = errno; + LOG(WARN) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } + util::make_socket_nonblocking(fd); + util::make_socket_closeonexec(fd); +# endif // !SOCK_NONBLOCK + + int val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set SO_REUSEPORT option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + if (faddr.family == AF_INET6) { +# ifdef IPV6_V6ONLY + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } +# endif // IPV6_V6ONLY + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) + << "Failed to set IPV6_RECVPKTINFO option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IPV6_RECVTCLASS option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + +# if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) + int mtu_disc = IPV6_PMTUDISC_DO; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &mtu_disc, + static_cast(sizeof(mtu_disc))) == -1) { + auto error = errno; + LOG(WARN) + << "Failed to set IPV6_MTU_DISCOVER option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } +# endif // defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) + } else { + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IP_PKTINFO option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &val, + static_cast(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IP_RECVTOS option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + +# if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) + int mtu_disc = IP_PMTUDISC_DO; + if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &mtu_disc, + static_cast(sizeof(mtu_disc))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IP_MTU_DISCOVER option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } +# endif // defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) + } + +# ifdef UDP_GRO + if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val)) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set UDP_GRO option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } +# endif // UDP_GRO + + if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) { + auto error = errno; + LOG(WARN) << "bind() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + +# ifdef HAVE_LIBBPF + auto config = get_config(); + + auto &quic_bpf_refs = conn_handler_->get_quic_bpf_refs(); + + if (should_attach_bpf()) { + auto &bpfconf = config->quic.bpf; + + auto obj = bpf_object__open_file(bpfconf.prog_file.c_str(), nullptr); + if (!obj) { + auto error = errno; + LOG(FATAL) << "Failed to open bpf object file: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + rv = bpf_object__load(obj); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Failed to load bpf object file: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + auto prog = bpf_object__find_program_by_name(obj, "select_reuseport"); + if (!prog) { + auto error = errno; + LOG(FATAL) << "Failed to find sk_reuseport program: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + auto &ref = quic_bpf_refs[faddr.index]; + + ref.obj = obj; + + ref.reuseport_array = + bpf_object__find_map_by_name(obj, "reuseport_array"); + if (!ref.reuseport_array) { + auto error = errno; + LOG(FATAL) << "Failed to get reuseport_array: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + ref.cid_prefix_map = bpf_object__find_map_by_name(obj, "cid_prefix_map"); + if (!ref.cid_prefix_map) { + auto error = errno; + LOG(FATAL) << "Failed to get cid_prefix_map: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + auto sk_info = bpf_object__find_map_by_name(obj, "sk_info"); + if (!sk_info) { + auto error = errno; + LOG(FATAL) << "Failed to get sk_info: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + constexpr uint32_t zero = 0; + uint64_t num_socks = config->num_worker; + + rv = bpf_map__update_elem(sk_info, &zero, sizeof(zero), &num_socks, + sizeof(num_socks), BPF_ANY); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Failed to update sk_info: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + constexpr uint32_t key_high_idx = 1; + constexpr uint32_t key_low_idx = 2; + + auto &qkms = conn_handler_->get_quic_keying_materials(); + auto &qkm = qkms->keying_materials.front(); + + rv = bpf_map__update_elem(sk_info, &key_high_idx, sizeof(key_high_idx), + qkm.cid_encryption_key.data(), + qkm.cid_encryption_key.size() / 2, BPF_ANY); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Failed to update key_high_idx sk_info: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + rv = bpf_map__update_elem(sk_info, &key_low_idx, sizeof(key_low_idx), + qkm.cid_encryption_key.data() + + qkm.cid_encryption_key.size() / 2, + qkm.cid_encryption_key.size() / 2, BPF_ANY); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Failed to update key_low_idx sk_info: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + auto prog_fd = bpf_program__fd(prog); + + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &prog_fd, + static_cast(sizeof(prog_fd))) == -1) { + LOG(FATAL) << "Failed to attach bpf program: " + << xsi_strerror(errno, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + } + + if (should_update_bpf_map()) { + const auto &ref = quic_bpf_refs[faddr.index]; + auto sk_index = compute_sk_index(); + + rv = bpf_map__update_elem(ref.reuseport_array, &sk_index, + sizeof(sk_index), &fd, sizeof(fd), BPF_NOEXIST); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Failed to update reuseport_array: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + rv = bpf_map__update_elem(ref.cid_prefix_map, cid_prefix_.data(), + cid_prefix_.size(), &sk_index, sizeof(sk_index), + BPF_NOEXIST); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Failed to update cid_prefix_map: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + } +# endif // HAVE_LIBBPF + + break; + } + + if (!rp) { + LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6") + << " socket failed"; + + return -1; + } + + faddr.fd = fd; + faddr.hostport = util::make_http_hostport(mod_config()->balloc, + StringRef{host.data()}, faddr.port); + + LOG(NOTICE) << "Listening on " << faddr.hostport << ", quic"; + + return 0; +} + +const uint8_t *Worker::get_cid_prefix() const { return cid_prefix_.data(); } + +const UpstreamAddr *Worker::find_quic_upstream_addr(const Address &local_addr) { + std::array host; + + auto rv = getnameinfo(&local_addr.su.sa, local_addr.len, host.data(), + host.size(), nullptr, 0, NI_NUMERICHOST); + if (rv != 0) { + LOG(ERROR) << "getnameinfo: " << gai_strerror(rv); + + return nullptr; + } + + uint16_t port; + + switch (local_addr.su.sa.sa_family) { + case AF_INET: + port = htons(local_addr.su.in.sin_port); + + break; + case AF_INET6: + port = htons(local_addr.su.in6.sin6_port); + + break; + default: + assert(0); + abort(); + } + + std::array hostport_buf; + + auto hostport = util::make_http_hostport(std::begin(hostport_buf), + StringRef{host.data()}, port); + const UpstreamAddr *fallback_faddr = nullptr; + + for (auto &faddr : quic_upstream_addrs_) { + if (faddr.hostport == hostport) { + return &faddr; + } + + if (faddr.port != port || faddr.family != local_addr.su.sa.sa_family) { + continue; + } + + if (faddr.port == 443 || faddr.port == 80) { + switch (faddr.family) { + case AF_INET: + if (util::streq(faddr.hostport, StringRef::from_lit("0.0.0.0"))) { + fallback_faddr = &faddr; + } + + break; + case AF_INET6: + if (util::streq(faddr.hostport, StringRef::from_lit("[::]"))) { + fallback_faddr = &faddr; + } + + break; + default: + assert(0); + } + } else { + switch (faddr.family) { + case AF_INET: + if (util::starts_with(faddr.hostport, + StringRef::from_lit("0.0.0.0:"))) { + fallback_faddr = &faddr; + } + + break; + case AF_INET6: + if (util::starts_with(faddr.hostport, StringRef::from_lit("[::]:"))) { + fallback_faddr = &faddr; + } + + break; + default: + assert(0); + } + } + } + + return fallback_faddr; +} +#endif // ENABLE_HTTP3 + +namespace { +size_t match_downstream_addr_group_host( + const RouterConfig &routerconf, const StringRef &host, + const StringRef &path, + const std::vector> &groups, + size_t catch_all, BlockAllocator &balloc) { + + const auto &router = routerconf.router; + const auto &rev_wildcard_router = routerconf.rev_wildcard_router; + const auto &wildcard_patterns = routerconf.wildcard_patterns; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Perform mapping selection, using host=" << host + << ", path=" << path; + } + + auto group = router.match(host, path); + if (group != -1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Found pattern with query " << host << path + << ", matched pattern=" << groups[group]->pattern; + } + return group; + } + + if (!wildcard_patterns.empty() && !host.empty()) { + auto rev_host_src = make_byte_ref(balloc, host.size() - 1); + auto ep = + std::copy(std::begin(host) + 1, std::end(host), rev_host_src.base); + std::reverse(rev_host_src.base, ep); + auto rev_host = StringRef{rev_host_src.base, ep}; + + ssize_t best_group = -1; + const RNode *last_node = nullptr; + + for (;;) { + size_t nread = 0; + auto wcidx = + rev_wildcard_router.match_prefix(&nread, &last_node, rev_host); + if (wcidx == -1) { + break; + } + + rev_host = StringRef{std::begin(rev_host) + nread, std::end(rev_host)}; + + auto &wc = wildcard_patterns[wcidx]; + auto group = wc.router.match(StringRef{}, path); + if (group != -1) { + // We sorted wildcard_patterns in a way that first match is the + // longest host pattern. + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Found wildcard pattern with query " << host << path + << ", matched pattern=" << groups[group]->pattern; + } + + best_group = group; + } + } + + if (best_group != -1) { + return best_group; + } + } + + group = router.match(StringRef::from_lit(""), path); + if (group != -1) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Found pattern with query " << path + << ", matched pattern=" << groups[group]->pattern; + } + return group; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "None match. Use catch-all pattern"; + } + return catch_all; +} +} // namespace + +size_t match_downstream_addr_group( + const RouterConfig &routerconf, const StringRef &hostport, + const StringRef &raw_path, + const std::vector> &groups, + size_t catch_all, BlockAllocator &balloc) { + if (std::find(std::begin(hostport), std::end(hostport), '/') != + std::end(hostport)) { + // We use '/' specially, and if '/' is included in host, it breaks + // our code. Select catch-all case. + return catch_all; + } + + auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#'); + auto query = std::find(std::begin(raw_path), fragment, '?'); + auto path = StringRef{std::begin(raw_path), query}; + + if (path.empty() || path[0] != '/') { + path = StringRef::from_lit("/"); + } + + if (hostport.empty()) { + return match_downstream_addr_group_host(routerconf, hostport, path, groups, + catch_all, balloc); + } + + StringRef host; + if (hostport[0] == '[') { + // assume this is IPv6 numeric address + auto p = std::find(std::begin(hostport), std::end(hostport), ']'); + if (p == std::end(hostport)) { + return catch_all; + } + if (p + 1 < std::end(hostport) && *(p + 1) != ':') { + return catch_all; + } + host = StringRef{std::begin(hostport), p + 1}; + } else { + auto p = std::find(std::begin(hostport), std::end(hostport), ':'); + if (p == std::begin(hostport)) { + return catch_all; + } + host = StringRef{std::begin(hostport), p}; + } + + if (std::find_if(std::begin(host), std::end(host), [](char c) { + return 'A' <= c || c <= 'Z'; + }) != std::end(host)) { + auto low_host = make_byte_ref(balloc, host.size() + 1); + auto ep = std::copy(std::begin(host), std::end(host), low_host.base); + *ep = '\0'; + util::inp_strlower(low_host.base, ep); + host = StringRef{low_host.base, ep}; + } + return match_downstream_addr_group_host(routerconf, host, path, groups, + catch_all, balloc); +} + +void downstream_failure(DownstreamAddr *addr, const Address *raddr) { + const auto &connect_blocker = addr->connect_blocker; + + if (connect_blocker->in_offline()) { + return; + } + + connect_blocker->on_failure(); + + if (addr->fall == 0) { + return; + } + + auto fail_count = connect_blocker->get_fail_count(); + + if (fail_count >= addr->fall) { + if (raddr) { + LOG(WARN) << "Could not connect to " << util::to_numeric_addr(raddr) + << " " << fail_count + << " times in a row; considered as offline"; + } else { + LOG(WARN) << "Could not connect to " << addr->host << ":" << addr->port + << " " << fail_count + << " times in a row; considered as offline"; + } + + connect_blocker->offline(); + + if (addr->rise) { + addr->live_check->schedule(); + } + } +} + +#ifdef ENABLE_HTTP3 +int create_cid_prefix(uint8_t *cid_prefix, const uint8_t *server_id) { + auto p = std::copy_n(server_id, SHRPX_QUIC_SERVER_IDLEN, cid_prefix); + + if (RAND_bytes(p, SHRPX_QUIC_CID_PREFIXLEN - SHRPX_QUIC_SERVER_IDLEN) != 1) { + return -1; + } + + return 0; +} +#endif // ENABLE_HTTP3 + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_worker.h b/lib/nghttp2/src/shrpx_worker.h new file mode 100644 index 00000000000..3cc7b576235 --- /dev/null +++ b/lib/nghttp2/src/shrpx_worker.h @@ -0,0 +1,480 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 SHRPX_WORKER_H +#define SHRPX_WORKER_H + +#include "shrpx.h" + +#include +#include +#include +#include +#include +#include +#include +#ifndef NOTHREADS +# include +#endif // NOTHREADS + +#include +#include + +#include + +#include "shrpx_config.h" +#include "shrpx_downstream_connection_pool.h" +#include "memchunk.h" +#include "shrpx_tls.h" +#include "shrpx_live_check.h" +#include "shrpx_connect_blocker.h" +#include "shrpx_dns_tracker.h" +#ifdef ENABLE_HTTP3 +# include "shrpx_quic_connection_handler.h" +# include "shrpx_quic.h" +#endif // ENABLE_HTTP3 +#include "allocator.h" + +using namespace nghttp2; + +namespace shrpx { + +class Http2Session; +class ConnectBlocker; +class MemcachedDispatcher; +struct UpstreamAddr; +class ConnectionHandler; +#ifdef ENABLE_HTTP3 +class QUICListener; +#endif // ENABLE_HTTP3 + +#ifdef HAVE_MRUBY +namespace mruby { + +class MRubyContext; + +} // namespace mruby +#endif // HAVE_MRUBY + +namespace tls { +class CertLookupTree; +} // namespace tls + +struct WeightGroup; + +struct DownstreamAddr { + Address addr; + // backend address. If |host_unix| is true, this is UNIX domain + // socket path. + StringRef host; + StringRef hostport; + // backend port. 0 if |host_unix| is true. + uint16_t port; + // true if |host| contains UNIX domain socket path. + bool host_unix; + + // sni field to send remote server if TLS is enabled. + StringRef sni; + + std::unique_ptr connect_blocker; + std::unique_ptr live_check; + // Connection pool for this particular address if session affinity + // is enabled + std::unique_ptr dconn_pool; + size_t fall; + size_t rise; + // Client side TLS session cache + tls::TLSSessionCache tls_session_cache; + // List of Http2Session which is not fully utilized (i.e., the + // server advertised maximum concurrency is not reached). We will + // coalesce as much stream as possible in one Http2Session to fully + // utilize TCP connection. + DList http2_extra_freelist; + WeightGroup *wg; + // total number of streams created in HTTP/2 connections for this + // address. + size_t num_dconn; + // the sequence number of this address to randomize the order access + // threads. + size_t seq; + // Application protocol used in this backend + Proto proto; + // cycle is used to prioritize this address. Lower value takes + // higher priority. + uint32_t cycle; + // penalty which is applied to the next cycle calculation. + uint32_t pending_penalty; + // Weight of this address inside a weight group. Its range is [1, + // 256], inclusive. + uint32_t weight; + // name of group which this address belongs to. + StringRef group; + // Weight of the weight group which this address belongs to. Its + // range is [1, 256], inclusive. + uint32_t group_weight; + // affinity hash for this address. It is assigned when strict + // stickiness is enabled. + uint32_t affinity_hash; + // true if TLS is used in this backend + bool tls; + // true if dynamic DNS is enabled + bool dns; + // true if :scheme pseudo header field should be upgraded to secure + // variant (e.g., "https") when forwarding request to a backend + // connected by TLS connection. + bool upgrade_scheme; + // true if this address is queued. + bool queued; +}; + +constexpr uint32_t MAX_DOWNSTREAM_ADDR_WEIGHT = 256; + +struct DownstreamAddrEntry { + DownstreamAddr *addr; + size_t seq; + uint32_t cycle; +}; + +struct DownstreamAddrEntryGreater { + bool operator()(const DownstreamAddrEntry &lhs, + const DownstreamAddrEntry &rhs) const { + auto d = lhs.cycle - rhs.cycle; + if (d == 0) { + return rhs.seq < lhs.seq; + } + return d <= 2 * MAX_DOWNSTREAM_ADDR_WEIGHT - 1; + } +}; + +struct WeightGroup { + std::priority_queue, + DownstreamAddrEntryGreater> + pq; + size_t seq; + uint32_t weight; + uint32_t cycle; + uint32_t pending_penalty; + // true if this object is queued. + bool queued; +}; + +struct WeightGroupEntry { + WeightGroup *wg; + size_t seq; + uint32_t cycle; +}; + +struct WeightGroupEntryGreater { + bool operator()(const WeightGroupEntry &lhs, + const WeightGroupEntry &rhs) const { + auto d = lhs.cycle - rhs.cycle; + if (d == 0) { + return rhs.seq < lhs.seq; + } + return d <= 2 * MAX_DOWNSTREAM_ADDR_WEIGHT - 1; + } +}; + +struct SharedDownstreamAddr { + SharedDownstreamAddr() + : balloc(1024, 1024), + affinity{SessionAffinity::NONE}, + redirect_if_not_tls{false}, + dnf{false}, + timeout{} {} + + SharedDownstreamAddr(const SharedDownstreamAddr &) = delete; + SharedDownstreamAddr(SharedDownstreamAddr &&) = delete; + SharedDownstreamAddr &operator=(const SharedDownstreamAddr &) = delete; + SharedDownstreamAddr &operator=(SharedDownstreamAddr &&) = delete; + + BlockAllocator balloc; + std::vector addrs; + std::vector wgs; + std::priority_queue, + WeightGroupEntryGreater> + pq; + // Bunch of session affinity hash. Only used if affinity == + // SessionAffinity::IP. + std::vector affinity_hash; + // Maps affinity hash of each DownstreamAddr to its index in addrs. + // It is only assigned when strict stickiness is enabled. + std::unordered_map affinity_hash_map; +#ifdef HAVE_MRUBY + std::shared_ptr mruby_ctx; +#endif // HAVE_MRUBY + // Configuration for session affinity + AffinityConfig affinity; + // Session affinity + // true if this group requires that client connection must be TLS, + // and the request must be redirected to https URI. + bool redirect_if_not_tls; + // true if a request should not be forwarded to a backend. + bool dnf; + // Timeouts for backend connection. + struct { + ev_tstamp read; + ev_tstamp write; + } timeout; +}; + +struct DownstreamAddrGroup { + DownstreamAddrGroup(); + ~DownstreamAddrGroup(); + + DownstreamAddrGroup(const DownstreamAddrGroup &) = delete; + DownstreamAddrGroup(DownstreamAddrGroup &&) = delete; + DownstreamAddrGroup &operator=(const DownstreamAddrGroup &) = delete; + DownstreamAddrGroup &operator=(DownstreamAddrGroup &&) = delete; + + ImmutableString pattern; + std::shared_ptr shared_addr; + // true if this group is no longer used for new request. If this is + // true, the connection made using one of address in shared_addr + // must not be pooled. + bool retired; +}; + +struct WorkerStat { + size_t num_connections; + size_t num_close_waits; +}; + +#ifdef ENABLE_HTTP3 +struct QUICPacket { + QUICPacket(size_t upstream_addr_index, const Address &remote_addr, + const Address &local_addr, const ngtcp2_pkt_info &pi, + const uint8_t *data, size_t datalen) + : upstream_addr_index{upstream_addr_index}, + remote_addr{remote_addr}, + local_addr{local_addr}, + pi{pi}, + data{data, data + datalen} {} + QUICPacket() : upstream_addr_index{}, remote_addr{}, local_addr{}, pi{} {} + size_t upstream_addr_index; + Address remote_addr; + Address local_addr; + ngtcp2_pkt_info pi; + std::vector data; +}; +#endif // ENABLE_HTTP3 + +enum class WorkerEventType { + NEW_CONNECTION = 0x01, + REOPEN_LOG = 0x02, + GRACEFUL_SHUTDOWN = 0x03, + REPLACE_DOWNSTREAM = 0x04, +#ifdef ENABLE_HTTP3 + QUIC_PKT_FORWARD = 0x05, +#endif // ENABLE_HTTP3 +}; + +struct WorkerEvent { + WorkerEventType type; + struct { + sockaddr_union client_addr; + size_t client_addrlen; + int client_fd; + const UpstreamAddr *faddr; + }; + std::shared_ptr ticket_keys; + std::shared_ptr downstreamconf; +#ifdef ENABLE_HTTP3 + std::unique_ptr quic_pkt; +#endif // ENABLE_HTTP3 +}; + +class Worker { +public: + Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, + SSL_CTX *tls_session_cache_memcached_ssl_ctx, + tls::CertLookupTree *cert_tree, +#ifdef ENABLE_HTTP3 + SSL_CTX *quic_sv_ssl_ctx, tls::CertLookupTree *quic_cert_tree, + const uint8_t *cid_prefix, size_t cid_prefixlen, +# ifdef HAVE_LIBBPF + size_t index, +# endif // HAVE_LIBBPF +#endif // ENABLE_HTTP3 + const std::shared_ptr &ticket_keys, + ConnectionHandler *conn_handler, + std::shared_ptr downstreamconf); + ~Worker(); + void run_async(); + void wait(); + void process_events(); + void send(WorkerEvent event); + + tls::CertLookupTree *get_cert_lookup_tree() const; +#ifdef ENABLE_HTTP3 + tls::CertLookupTree *get_quic_cert_lookup_tree() const; +#endif // ENABLE_HTTP3 + + // These 2 functions make a lock m_ to get/set ticket keys + // atomically. + std::shared_ptr get_ticket_keys(); + void set_ticket_keys(std::shared_ptr ticket_keys); + + WorkerStat *get_worker_stat(); + struct ev_loop *get_loop() const; + SSL_CTX *get_sv_ssl_ctx() const; + SSL_CTX *get_cl_ssl_ctx() const; +#ifdef ENABLE_HTTP3 + SSL_CTX *get_quic_sv_ssl_ctx() const; +#endif // ENABLE_HTTP3 + + void set_graceful_shutdown(bool f); + bool get_graceful_shutdown() const; + + MemchunkPool *get_mcpool(); + void schedule_clear_mcpool(); + + MemcachedDispatcher *get_session_cache_memcached_dispatcher(); + + std::mt19937 &get_randgen(); + +#ifdef HAVE_MRUBY + int create_mruby_context(); + + mruby::MRubyContext *get_mruby_context() const; +#endif // HAVE_MRUBY + + std::vector> & + get_downstream_addr_groups(); + + ConnectBlocker *get_connect_blocker() const; + + const DownstreamConfig *get_downstream_config() const; + + void + replace_downstream_config(std::shared_ptr downstreamconf); + + ConnectionHandler *get_connection_handler() const; + +#ifdef ENABLE_HTTP3 + QUICConnectionHandler *get_quic_connection_handler(); + + int setup_quic_server_socket(); + + const uint8_t *get_cid_prefix() const; + +# ifdef HAVE_LIBBPF + bool should_attach_bpf() const; + + bool should_update_bpf_map() const; + + uint32_t compute_sk_index() const; +# endif // HAVE_LIBBPF + + int create_quic_server_socket(UpstreamAddr &addr); + + // Returns a pointer to UpstreamAddr which matches |local_addr|. + const UpstreamAddr *find_quic_upstream_addr(const Address &local_addr); +#endif // ENABLE_HTTP3 + + DNSTracker *get_dns_tracker(); + +private: +#ifndef NOTHREADS + std::future fut_; +#endif // NOTHREADS +#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF) + // Unique index of this worker. + size_t index_; +#endif // ENABLE_HTTP3 && HAVE_LIBBPF + std::mutex m_; + std::deque q_; + std::mt19937 randgen_; + ev_async w_; + ev_timer mcpool_clear_timer_; + ev_timer proc_wev_timer_; + MemchunkPool mcpool_; + WorkerStat worker_stat_; + DNSTracker dns_tracker_; + +#ifdef ENABLE_HTTP3 + std::array cid_prefix_; + std::vector quic_upstream_addrs_; + std::vector> quic_listeners_; +#endif // ENABLE_HTTP3 + + std::shared_ptr downstreamconf_; + std::unique_ptr session_cache_memcached_dispatcher_; +#ifdef HAVE_MRUBY + std::unique_ptr mruby_ctx_; +#endif // HAVE_MRUBY + struct ev_loop *loop_; + + // Following fields are shared across threads if + // get_config()->tls_ctx_per_worker == true. + SSL_CTX *sv_ssl_ctx_; + SSL_CTX *cl_ssl_ctx_; + tls::CertLookupTree *cert_tree_; + ConnectionHandler *conn_handler_; +#ifdef ENABLE_HTTP3 + SSL_CTX *quic_sv_ssl_ctx_; + tls::CertLookupTree *quic_cert_tree_; + + QUICConnectionHandler quic_conn_handler_; +#endif // ENABLE_HTTP3 + +#ifndef HAVE_ATOMIC_STD_SHARED_PTR + std::mutex ticket_keys_m_; +#endif // !HAVE_ATOMIC_STD_SHARED_PTR + std::shared_ptr ticket_keys_; + std::vector> downstream_addr_groups_; + // Worker level blocker for downstream connection. For example, + // this is used when file descriptor is exhausted. + std::unique_ptr connect_blocker_; + + bool graceful_shutdown_; +}; + +// Selects group based on request's |hostport| and |path|. |hostport| +// is the value taken from :authority or host header field, and may +// contain port. The |path| may contain query part. We require the +// catch-all pattern in place, so this function always selects one +// group. The catch-all group index is given in |catch_all|. All +// patterns are given in |groups|. +size_t match_downstream_addr_group( + const RouterConfig &routerconfig, const StringRef &hostport, + const StringRef &path, + const std::vector> &groups, + size_t catch_all, BlockAllocator &balloc); + +// Calls this function if connecting to backend failed. |raddr| is +// the actual address used to connect to backend, and it could be +// nullptr. This function may schedule live check. +void downstream_failure(DownstreamAddr *addr, const Address *raddr); + +#ifdef ENABLE_HTTP3 +// Creates unpredictable SHRPX_QUIC_CID_PREFIXLEN bytes sequence which +// is used as a prefix of QUIC Connection ID. This function returns +// -1 on failure. |server_id| must be 2 bytes long. +int create_cid_prefix(uint8_t *cid_prefix, const uint8_t *server_id); +#endif // ENABLE_HTTP3 + +} // namespace shrpx + +#endif // SHRPX_WORKER_H diff --git a/lib/nghttp2/src/shrpx_worker_process.cc b/lib/nghttp2/src/shrpx_worker_process.cc new file mode 100644 index 00000000000..33ef29c47ce --- /dev/null +++ b/lib/nghttp2/src/shrpx_worker_process.cc @@ -0,0 +1,701 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "shrpx_worker_process.h" + +#include +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#include "shrpx_config.h" +#include "shrpx_connection_handler.h" +#include "shrpx_log_config.h" +#include "shrpx_worker.h" +#include "shrpx_accept_handler.h" +#include "shrpx_http2_upstream.h" +#include "shrpx_http2_session.h" +#include "shrpx_memcached_dispatcher.h" +#include "shrpx_memcached_request.h" +#include "shrpx_process.h" +#include "shrpx_tls.h" +#include "shrpx_log.h" +#include "util.h" +#include "app_helper.h" +#include "template.h" +#include "xsi_strerror.h" + +using namespace nghttp2; + +namespace shrpx { + +namespace { +void drop_privileges( +#ifdef HAVE_NEVERBLEED + neverbleed_t *nb +#endif // HAVE_NEVERBLEED +) { + std::array errbuf; + auto config = get_config(); + + if (getuid() == 0 && config->uid != 0) { +#ifdef HAVE_NEVERBLEED + if (nb) { + neverbleed_setuidgid(nb, config->user.c_str(), 1); + } +#endif // HAVE_NEVERBLEED + + if (initgroups(config->user.c_str(), config->gid) != 0) { + auto error = errno; + LOG(FATAL) << "Could not change supplementary groups: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + exit(EXIT_FAILURE); + } + if (setgid(config->gid) != 0) { + auto error = errno; + LOG(FATAL) << "Could not change gid: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + exit(EXIT_FAILURE); + } + if (setuid(config->uid) != 0) { + auto error = errno; + LOG(FATAL) << "Could not change uid: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + exit(EXIT_FAILURE); + } + if (setuid(0) != -1) { + LOG(FATAL) << "Still have root privileges?"; + exit(EXIT_FAILURE); + } + } +} +} // namespace + +namespace { +void graceful_shutdown(ConnectionHandler *conn_handler) { + if (conn_handler->get_graceful_shutdown()) { + return; + } + + LOG(NOTICE) << "Graceful shutdown signal received"; + + conn_handler->set_graceful_shutdown(true); + + // TODO What happens for the connections not established in the + // kernel? + conn_handler->accept_pending_connection(); + conn_handler->delete_acceptor(); + + conn_handler->graceful_shutdown_worker(); + + auto single_worker = conn_handler->get_single_worker(); + if (single_worker) { + auto worker_stat = single_worker->get_worker_stat(); + if (worker_stat->num_connections == 0 && + worker_stat->num_close_waits == 0) { + ev_break(conn_handler->get_loop()); + } + + return; + } +} +} // namespace + +namespace { +void reopen_log(ConnectionHandler *conn_handler) { + LOG(NOTICE) << "Reopening log files: worker process (thread main)"; + + auto config = get_config(); + auto &loggingconf = config->logging; + + (void)reopen_log_files(loggingconf); + redirect_stderr_to_errorlog(loggingconf); + + conn_handler->worker_reopen_log_files(); +} +} // namespace + +namespace { +void ipc_readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn_handler = static_cast(w->data); + std::array buf; + ssize_t nread; + while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR) + ; + if (nread == -1) { + auto error = errno; + LOG(ERROR) << "Failed to read data from ipc channel: errno=" << error; + return; + } + + if (nread == 0) { + // IPC socket closed. Perform immediate shutdown. + LOG(FATAL) << "IPC socket is closed. Perform immediate shutdown."; + nghttp2_Exit(EXIT_FAILURE); + } + + for (ssize_t i = 0; i < nread; ++i) { + switch (buf[i]) { + case SHRPX_IPC_GRACEFUL_SHUTDOWN: + graceful_shutdown(conn_handler); + break; + case SHRPX_IPC_REOPEN_LOG: + reopen_log(conn_handler); + break; + } + } +} +} // namespace + +#ifdef ENABLE_HTTP3 +namespace { +void quic_ipc_readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto conn_handler = static_cast(w->data); + + if (conn_handler->quic_ipc_read() != 0) { + LOG(ERROR) << "Failed to read data from QUIC IPC channel"; + + return; + } +} +} // namespace +#endif // ENABLE_HTTP3 + +namespace { +int generate_ticket_key(TicketKey &ticket_key) { + ticket_key.cipher = get_config()->tls.ticket.cipher; + ticket_key.hmac = EVP_sha256(); + ticket_key.hmac_keylen = EVP_MD_size(ticket_key.hmac); + + assert(static_cast(EVP_CIPHER_key_length(ticket_key.cipher)) <= + ticket_key.data.enc_key.size()); + assert(ticket_key.hmac_keylen <= ticket_key.data.hmac_key.size()); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "enc_keylen=" << EVP_CIPHER_key_length(ticket_key.cipher) + << ", hmac_keylen=" << ticket_key.hmac_keylen; + } + + if (RAND_bytes(reinterpret_cast(&ticket_key.data), + sizeof(ticket_key.data)) == 0) { + return -1; + } + + return 0; +} +} // namespace + +namespace { +void renew_ticket_key_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto conn_handler = static_cast(w->data); + const auto &old_ticket_keys = conn_handler->get_ticket_keys(); + + auto ticket_keys = std::make_shared(); + LOG(NOTICE) << "Renew new ticket keys"; + + // If old_ticket_keys is not empty, it should contain at least 2 + // keys: one for encryption, and last one for the next encryption + // key but decryption only. The keys in between are old keys and + // decryption only. The next key is provided to ensure to mitigate + // possible problem when one worker encrypt new key, but one worker, + // which did not take the that key yet, and cannot decrypt it. + // + // We keep keys for get_config()->tls_session_timeout seconds. The + // default is 12 hours. Thus the maximum ticket vector size is 12. + if (old_ticket_keys) { + auto &old_keys = old_ticket_keys->keys; + auto &new_keys = ticket_keys->keys; + + assert(!old_keys.empty()); + + auto max_tickets = + static_cast(std::chrono::duration_cast( + get_config()->tls.session_timeout) + .count()); + + new_keys.resize(std::min(max_tickets, old_keys.size() + 1)); + std::copy_n(std::begin(old_keys), new_keys.size() - 1, + std::begin(new_keys) + 1); + } else { + ticket_keys->keys.resize(1); + } + + auto &new_key = ticket_keys->keys[0]; + + if (generate_ticket_key(new_key) != 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "failed to generate ticket key"; + } + conn_handler->set_ticket_keys(nullptr); + conn_handler->set_ticket_keys_to_worker(nullptr); + return; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "ticket keys generation done"; + assert(ticket_keys->keys.size() >= 1); + LOG(INFO) << 0 << " enc+dec: " + << util::format_hex(ticket_keys->keys[0].data.name); + for (size_t i = 1; i < ticket_keys->keys.size(); ++i) { + auto &key = ticket_keys->keys[i]; + LOG(INFO) << i << " dec: " << util::format_hex(key.data.name); + } + } + + conn_handler->set_ticket_keys(ticket_keys); + conn_handler->set_ticket_keys_to_worker(ticket_keys); +} +} // namespace + +namespace { +void memcached_get_ticket_key_cb(struct ev_loop *loop, ev_timer *w, + int revents) { + auto conn_handler = static_cast(w->data); + auto dispatcher = conn_handler->get_tls_ticket_key_memcached_dispatcher(); + + auto req = std::make_unique(); + req->key = "nghttpx:tls-ticket-key"; + req->op = MemcachedOp::GET; + req->cb = [conn_handler, w](MemcachedRequest *req, MemcachedResult res) { + switch (res.status_code) { + case MemcachedStatusCode::NO_ERROR: + break; + case MemcachedStatusCode::EXT_NETWORK_ERROR: + conn_handler->on_tls_ticket_key_network_error(w); + return; + default: + conn_handler->on_tls_ticket_key_not_found(w); + return; + } + + // |version (4bytes)|len (2bytes)|key (variable length)|... + // (len, key) pairs are repeated as necessary. + + auto &value = res.value; + if (value.size() < 4) { + LOG(WARN) << "Memcached: tls ticket key value is too small: got " + << value.size(); + conn_handler->on_tls_ticket_key_not_found(w); + return; + } + auto p = value.data(); + auto version = util::get_uint32(p); + // Currently supported version is 1. + if (version != 1) { + LOG(WARN) << "Memcached: tls ticket key version: want 1, got " << version; + conn_handler->on_tls_ticket_key_not_found(w); + return; + } + + auto end = p + value.size(); + p += 4; + + auto &ticketconf = get_config()->tls.ticket; + + size_t expectedlen; + size_t enc_keylen; + size_t hmac_keylen; + if (ticketconf.cipher == EVP_aes_128_cbc()) { + expectedlen = 48; + enc_keylen = 16; + hmac_keylen = 16; + } else if (ticketconf.cipher == EVP_aes_256_cbc()) { + expectedlen = 80; + enc_keylen = 32; + hmac_keylen = 32; + } else { + return; + } + + auto ticket_keys = std::make_shared(); + + for (; p != end;) { + if (end - p < 2) { + LOG(WARN) << "Memcached: tls ticket key data is too small"; + conn_handler->on_tls_ticket_key_not_found(w); + return; + } + auto len = util::get_uint16(p); + p += 2; + if (len != expectedlen) { + LOG(WARN) << "Memcached: wrong tls ticket key size: want " + << expectedlen << ", got " << len; + conn_handler->on_tls_ticket_key_not_found(w); + return; + } + if (p + len > end) { + LOG(WARN) << "Memcached: too short tls ticket key payload: want " << len + << ", got " << (end - p); + conn_handler->on_tls_ticket_key_not_found(w); + return; + } + auto key = TicketKey(); + key.cipher = ticketconf.cipher; + key.hmac = EVP_sha256(); + key.hmac_keylen = hmac_keylen; + + std::copy_n(p, key.data.name.size(), std::begin(key.data.name)); + p += key.data.name.size(); + + std::copy_n(p, enc_keylen, std::begin(key.data.enc_key)); + p += enc_keylen; + + std::copy_n(p, hmac_keylen, std::begin(key.data.hmac_key)); + p += hmac_keylen; + + ticket_keys->keys.push_back(std::move(key)); + } + + conn_handler->on_tls_ticket_key_get_success(ticket_keys, w); + }; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Memcached: tls ticket key get request sent"; + } + + dispatcher->add_request(std::move(req)); +} + +} // namespace + +#ifdef HAVE_NEVERBLEED +namespace { +void nb_child_cb(struct ev_loop *loop, ev_child *w, int revents) { + log_chld(w->rpid, w->rstatus, "neverbleed process"); + + ev_child_stop(loop, w); + + LOG(FATAL) << "neverbleed process exitted; aborting now"; + + nghttp2_Exit(EXIT_FAILURE); +} +} // namespace +#endif // HAVE_NEVERBLEED + +namespace { +int send_ready_event(int ready_ipc_fd) { + std::array errbuf; + auto pid = getpid(); + ssize_t nwrite; + + while ((nwrite = write(ready_ipc_fd, &pid, sizeof(pid))) == -1 && + errno == EINTR) + ; + + if (nwrite < 0) { + auto error = errno; + + LOG(ERROR) << "Writing PID to ready IPC channel failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + return -1; + } + + return 0; +} +} // namespace + +int worker_process_event_loop(WorkerProcessConfig *wpconf) { + int rv; + std::array errbuf; + (void)errbuf; + + auto config = get_config(); + + if (reopen_log_files(config->logging) != 0) { + LOG(FATAL) << "Failed to open log file"; + return -1; + } + + rv = ares_library_init(ARES_LIB_INIT_ALL); + if (rv != 0) { + LOG(FATAL) << "ares_library_init failed: " << ares_strerror(rv); + return -1; + } + + auto loop = EV_DEFAULT; + + auto gen = util::make_mt19937(); + +#ifdef HAVE_NEVERBLEED + std::array nb_errbuf; + auto nb = std::make_unique(); + if (neverbleed_init(nb.get(), nb_errbuf.data()) != 0) { + LOG(FATAL) << "neverbleed_init failed: " << nb_errbuf.data(); + return -1; + } + + LOG(NOTICE) << "neverbleed process [" << nb->daemon_pid << "] spawned"; + + ev_child nb_childev; + + ev_child_init(&nb_childev, nb_child_cb, nb->daemon_pid, 0); + nb_childev.data = nullptr; + ev_child_start(loop, &nb_childev); +#endif // HAVE_NEVERBLEED + + auto conn_handler = std::make_unique(loop, gen); + +#ifdef HAVE_NEVERBLEED + conn_handler->set_neverbleed(nb.get()); +#endif // HAVE_NEVERBLEED + +#ifdef ENABLE_HTTP3 + conn_handler->set_quic_ipc_fd(wpconf->quic_ipc_fd); + conn_handler->set_quic_lingering_worker_processes( + wpconf->quic_lingering_worker_processes); +#endif // ENABLE_HTTP3 + + for (auto &addr : config->conn.listener.addrs) { + conn_handler->add_acceptor( + std::make_unique(&addr, conn_handler.get())); + } + + MemchunkPool mcpool; + + ev_timer renew_ticket_key_timer; + if (tls::upstream_tls_enabled(config->conn)) { + auto &ticketconf = config->tls.ticket; + auto &memcachedconf = ticketconf.memcached; + + if (!memcachedconf.host.empty()) { + SSL_CTX *ssl_ctx = nullptr; + + if (memcachedconf.tls) { + ssl_ctx = conn_handler->create_tls_ticket_key_memcached_ssl_ctx(); + } + + conn_handler->set_tls_ticket_key_memcached_dispatcher( + std::make_unique( + &ticketconf.memcached.addr, loop, ssl_ctx, + StringRef{memcachedconf.host}, &mcpool, gen)); + + ev_timer_init(&renew_ticket_key_timer, memcached_get_ticket_key_cb, 0., + 0.); + renew_ticket_key_timer.data = conn_handler.get(); + // Get first ticket keys. + memcached_get_ticket_key_cb(loop, &renew_ticket_key_timer, 0); + } else { + bool auto_tls_ticket_key = true; + if (!ticketconf.files.empty()) { + if (!ticketconf.cipher_given) { + LOG(WARN) + << "It is strongly recommended to specify " + "--tls-ticket-key-cipher=aes-128-cbc (or " + "tls-ticket-key-cipher=aes-128-cbc in configuration file) " + "when --tls-ticket-key-file is used for the smooth " + "transition when the default value of --tls-ticket-key-cipher " + "becomes aes-256-cbc"; + } + auto ticket_keys = read_tls_ticket_key_file( + ticketconf.files, ticketconf.cipher, EVP_sha256()); + if (!ticket_keys) { + LOG(WARN) << "Use internal session ticket key generator"; + } else { + conn_handler->set_ticket_keys(std::move(ticket_keys)); + auto_tls_ticket_key = false; + } + } + if (auto_tls_ticket_key) { + // Generate new ticket key every 1hr. + ev_timer_init(&renew_ticket_key_timer, renew_ticket_key_cb, 0., 1_h); + renew_ticket_key_timer.data = conn_handler.get(); + ev_timer_again(loop, &renew_ticket_key_timer); + + // Generate first session ticket key before running workers. + renew_ticket_key_cb(loop, &renew_ticket_key_timer, 0); + } + } + } + +#ifdef ENABLE_HTTP3 + auto &quicconf = config->quic; + + std::shared_ptr qkms; + + if (!quicconf.upstream.secret_file.empty()) { + qkms = read_quic_secret_file(quicconf.upstream.secret_file); + if (!qkms) { + LOG(WARN) << "Use QUIC keying materials generated internally"; + } + } + + if (!qkms) { + qkms = std::make_shared(); + qkms->keying_materials.resize(1); + + auto &qkm = qkms->keying_materials.front(); + + if (RAND_bytes(qkm.reserved.data(), qkm.reserved.size()) != 1) { + LOG(ERROR) << "Failed to generate QUIC secret reserved data"; + return -1; + } + + if (RAND_bytes(qkm.secret.data(), qkm.secret.size()) != 1) { + LOG(ERROR) << "Failed to generate QUIC secret"; + return -1; + } + + if (RAND_bytes(qkm.salt.data(), qkm.salt.size()) != 1) { + LOG(ERROR) << "Failed to generate QUIC salt"; + return -1; + } + } + + for (auto &qkm : qkms->keying_materials) { + if (generate_quic_connection_id_encryption_key( + qkm.cid_encryption_key.data(), qkm.cid_encryption_key.size(), + qkm.secret.data(), qkm.secret.size(), qkm.salt.data(), + qkm.salt.size()) != 0) { + LOG(ERROR) << "Failed to generate QUIC Connection ID encryption key"; + return -1; + } + } + + conn_handler->set_quic_keying_materials(std::move(qkms)); + + conn_handler->set_cid_prefixes(wpconf->cid_prefixes); + conn_handler->set_quic_lingering_worker_processes( + wpconf->quic_lingering_worker_processes); +#endif // ENABLE_HTTP3 + + if (config->single_thread) { + rv = conn_handler->create_single_worker(); + if (rv != 0) { + return -1; + } + } else { +#ifndef NOTHREADS + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGCHLD); + + rv = pthread_sigmask(SIG_BLOCK, &set, nullptr); + if (rv != 0) { + LOG(ERROR) << "Blocking SIGCHLD failed: " + << xsi_strerror(rv, errbuf.data(), errbuf.size()); + return -1; + } +#endif // !NOTHREADS + + rv = conn_handler->create_worker_thread(config->num_worker); + if (rv != 0) { + return -1; + } + +#ifndef NOTHREADS + rv = pthread_sigmask(SIG_UNBLOCK, &set, nullptr); + if (rv != 0) { + LOG(ERROR) << "Unblocking SIGCHLD failed: " + << xsi_strerror(rv, errbuf.data(), errbuf.size()); + return -1; + } +#endif // !NOTHREADS + } + +#if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF) + conn_handler->unload_bpf_objects(); +#endif // defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF) + + drop_privileges( +#ifdef HAVE_NEVERBLEED + nb.get() +#endif // HAVE_NEVERBLEED + ); + + ev_io ipcev; + ev_io_init(&ipcev, ipc_readcb, wpconf->ipc_fd, EV_READ); + ipcev.data = conn_handler.get(); + ev_io_start(loop, &ipcev); + +#ifdef ENABLE_HTTP3 + ev_io quic_ipcev; + ev_io_init(&quic_ipcev, quic_ipc_readcb, wpconf->quic_ipc_fd, EV_READ); + quic_ipcev.data = conn_handler.get(); + ev_io_start(loop, &quic_ipcev); +#endif // ENABLE_HTTP3 + + if (tls::upstream_tls_enabled(config->conn) && !config->tls.ocsp.disabled) { + if (config->tls.ocsp.startup) { + conn_handler->set_enable_acceptor_on_ocsp_completion(true); + conn_handler->disable_acceptor(); + } + + conn_handler->proceed_next_cert_ocsp(); + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Entering event loop"; + } + + if (send_ready_event(wpconf->ready_ipc_fd) != 0) { + return -1; + } + + ev_run(loop, 0); + + conn_handler->cancel_ocsp_update(); + + // Destroy SSL_CTX held in conn_handler before killing neverbleed + // daemon. Otherwise priv_rsa_finish yields "write error" and + // worker process aborts. + conn_handler.reset(); + +#ifdef HAVE_NEVERBLEED + assert(nb->daemon_pid > 0); + + rv = kill(nb->daemon_pid, SIGTERM); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Could not send signal to neverbleed daemon: errno=" << error; + } + + while ((rv = waitpid(nb->daemon_pid, nullptr, 0)) == -1 && errno == EINTR) + ; + if (rv == -1) { + auto error = errno; + LOG(ERROR) << "Error occurred while we were waiting for the completion " + "of neverbleed process: errno=" + << error; + } +#endif // HAVE_NEVERBLEED + + ares_library_cleanup(); + + return 0; +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_worker_process.h b/lib/nghttp2/src/shrpx_worker_process.h new file mode 100644 index 00000000000..f4325031b41 --- /dev/null +++ b/lib/nghttp2/src/shrpx_worker_process.h @@ -0,0 +1,67 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 SHRPX_WORKER_PROCESS_H +#define SHRPX_WORKER_PROCESS_H + +#include "shrpx.h" + +#include +#include + +#include "shrpx_connection_handler.h" +#ifdef ENABLE_HTTP3 +# include "shrpx_quic.h" +#endif // ENABLE_HTTP3 + +namespace shrpx { + +class ConnectionHandler; + +struct WorkerProcessConfig { + // IPC socket to read event from main process + int ipc_fd; + // IPC socket to tell that a worker process is ready for service. + int ready_ipc_fd; + // IPv4 or UNIX domain socket, or -1 if not used + int server_fd; + // IPv6 socket, or -1 if not used + int server_fd6; +#ifdef ENABLE_HTTP3 + // CID prefixes for the new worker process. + std::vector> cid_prefixes; + // IPC socket to read forwarded QUIC UDP datagram from the current + // worker process. + int quic_ipc_fd; + // Lingering worker processes which were created before this worker + // process to forward QUIC UDP datagram during reload. + std::vector quic_lingering_worker_processes; +#endif // ENABLE_HTTP3 +}; + +int worker_process_event_loop(WorkerProcessConfig *wpconf); + +} // namespace shrpx + +#endif // SHRPX_WORKER_PROCESS_H diff --git a/lib/nghttp2/src/shrpx_worker_test.cc b/lib/nghttp2/src/shrpx_worker_test.cc new file mode 100644 index 00000000000..7c5c3299ec5 --- /dev/null +++ b/lib/nghttp2/src/shrpx_worker_test.cc @@ -0,0 +1,247 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "shrpx_worker_test.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H + +#include + +#include + +#include "shrpx_worker.h" +#include "shrpx_connect_blocker.h" +#include "shrpx_log.h" + +namespace shrpx { + +void test_shrpx_worker_match_downstream_addr_group(void) { + auto groups = std::vector>(); + for (auto &s : {"nghttp2.org/", "nghttp2.org/alpha/bravo/", + "nghttp2.org/alpha/charlie", "nghttp2.org/delta%3A", + "www.nghttp2.org/", "[::1]/", "nghttp2.org/alpha/bravo/delta", + // Check that match is done in the single node + "example.com/alpha/bravo", "192.168.0.1/alpha/", "/golf/"}) { + auto g = std::make_shared(); + g->pattern = ImmutableString(s); + groups.push_back(std::move(g)); + } + + BlockAllocator balloc(1024, 1024); + RouterConfig routerconf; + + auto &router = routerconf.router; + auto &wcrouter = routerconf.rev_wildcard_router; + auto &wp = routerconf.wildcard_patterns; + + for (size_t i = 0; i < groups.size(); ++i) { + auto &g = groups[i]; + router.add_route(StringRef{g->pattern}, i); + } + + CU_ASSERT(0 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/"), groups, 255, balloc)); + + // port is removed + CU_ASSERT(0 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org:8080"), + StringRef::from_lit("/"), groups, 255, balloc)); + + // host is case-insensitive + CU_ASSERT(4 == match_downstream_addr_group( + routerconf, StringRef::from_lit("WWW.nghttp2.org"), + StringRef::from_lit("/alpha"), groups, 255, balloc)); + + CU_ASSERT(1 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/bravo/"), groups, 255, + balloc)); + + // /alpha/bravo also matches /alpha/bravo/ + CU_ASSERT(1 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/bravo"), groups, 255, balloc)); + + // path part is case-sensitive + CU_ASSERT(0 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/Alpha/bravo"), groups, 255, balloc)); + + CU_ASSERT(1 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/bravo/charlie"), groups, 255, + balloc)); + + CU_ASSERT(2 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/charlie"), groups, 255, + balloc)); + + // pattern which does not end with '/' must match its entirely. So + // this matches to group 0, not group 2. + CU_ASSERT(0 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/charlie/"), groups, 255, + balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit("example.org"), + StringRef::from_lit("/"), groups, 255, balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit(""), + StringRef::from_lit("/"), groups, 255, balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit(""), + StringRef::from_lit("alpha"), groups, 255, balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit("foo/bar"), + StringRef::from_lit("/"), groups, 255, balloc)); + + // If path is StringRef::from_lit("*", only match with host + "/"). + CU_ASSERT(0 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("*"), groups, 255, balloc)); + + CU_ASSERT(5 == match_downstream_addr_group( + routerconf, StringRef::from_lit("[::1]"), + StringRef::from_lit("/"), groups, 255, balloc)); + CU_ASSERT(5 == match_downstream_addr_group( + routerconf, StringRef::from_lit("[::1]:8080"), + StringRef::from_lit("/"), groups, 255, balloc)); + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit("[::1"), + StringRef::from_lit("/"), groups, 255, balloc)); + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit("[::1]8000"), + StringRef::from_lit("/"), groups, 255, balloc)); + + // Check the case where adding route extends tree + CU_ASSERT(6 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/bravo/delta"), groups, 255, + balloc)); + + CU_ASSERT(1 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/alpha/bravo/delta/"), groups, 255, + balloc)); + + // Check the case where query is done in a single node + CU_ASSERT(7 == match_downstream_addr_group( + routerconf, StringRef::from_lit("example.com"), + StringRef::from_lit("/alpha/bravo"), groups, 255, balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit("example.com"), + StringRef::from_lit("/alpha/bravo/"), groups, 255, + balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit("example.com"), + StringRef::from_lit("/alpha"), groups, 255, balloc)); + + // Check the case where quey is done in a single node + CU_ASSERT(8 == match_downstream_addr_group( + routerconf, StringRef::from_lit("192.168.0.1"), + StringRef::from_lit("/alpha"), groups, 255, balloc)); + + CU_ASSERT(8 == match_downstream_addr_group( + routerconf, StringRef::from_lit("192.168.0.1"), + StringRef::from_lit("/alpha/"), groups, 255, balloc)); + + CU_ASSERT(8 == match_downstream_addr_group( + routerconf, StringRef::from_lit("192.168.0.1"), + StringRef::from_lit("/alpha/bravo"), groups, 255, balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit("192.168.0.1"), + StringRef::from_lit("/alph"), groups, 255, balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit("192.168.0.1"), + StringRef::from_lit("/"), groups, 255, balloc)); + + // Test for wildcard hosts + auto g1 = std::make_shared(); + g1->pattern = ImmutableString::from_lit("git.nghttp2.org"); + groups.push_back(std::move(g1)); + + auto g2 = std::make_shared(); + g2->pattern = ImmutableString::from_lit(".nghttp2.org"); + groups.push_back(std::move(g2)); + + auto g3 = std::make_shared(); + g3->pattern = ImmutableString::from_lit(".local"); + groups.push_back(std::move(g3)); + + wp.emplace_back(StringRef::from_lit("git.nghttp2.org")); + wcrouter.add_route(StringRef::from_lit("gro.2ptthgn.tig"), 0); + wp.back().router.add_route(StringRef::from_lit("/echo/"), 10); + + wp.emplace_back(StringRef::from_lit(".nghttp2.org")); + wcrouter.add_route(StringRef::from_lit("gro.2ptthgn."), 1); + wp.back().router.add_route(StringRef::from_lit("/echo/"), 11); + wp.back().router.add_route(StringRef::from_lit("/echo/foxtrot"), 12); + + wp.emplace_back(StringRef::from_lit(".local")); + wcrouter.add_route(StringRef::from_lit("lacol."), 2); + wp.back().router.add_route(StringRef::from_lit("/"), 13); + + CU_ASSERT(11 == match_downstream_addr_group( + routerconf, StringRef::from_lit("git.nghttp2.org"), + StringRef::from_lit("/echo"), groups, 255, balloc)); + + CU_ASSERT(10 == match_downstream_addr_group( + routerconf, StringRef::from_lit("0git.nghttp2.org"), + StringRef::from_lit("/echo"), groups, 255, balloc)); + + CU_ASSERT(11 == match_downstream_addr_group( + routerconf, StringRef::from_lit("it.nghttp2.org"), + StringRef::from_lit("/echo"), groups, 255, balloc)); + + CU_ASSERT(255 == match_downstream_addr_group( + routerconf, StringRef::from_lit(".nghttp2.org"), + StringRef::from_lit("/echo/foxtrot"), groups, 255, + balloc)); + + CU_ASSERT(9 == match_downstream_addr_group( + routerconf, StringRef::from_lit("alpha.nghttp2.org"), + StringRef::from_lit("/golf"), groups, 255, balloc)); + + CU_ASSERT(0 == match_downstream_addr_group( + routerconf, StringRef::from_lit("nghttp2.org"), + StringRef::from_lit("/echo"), groups, 255, balloc)); + + CU_ASSERT(13 == match_downstream_addr_group( + routerconf, StringRef::from_lit("test.local"), + StringRef{}, groups, 255, balloc)); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/shrpx_worker_test.h b/lib/nghttp2/src/shrpx_worker_test.h new file mode 100644 index 00000000000..8ffa2f12d03 --- /dev/null +++ b/lib/nghttp2/src/shrpx_worker_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 SHRPX_WORKER_TEST_H +#define SHRPX_WORKER_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_shrpx_worker_match_downstream_addr_group(void); + +} // namespace shrpx + +#endif // SHRPX_WORKER_TEST_H diff --git a/lib/nghttp2/src/ssl_compat.h b/lib/nghttp2/src/ssl_compat.h new file mode 100644 index 00000000000..ed3071973b8 --- /dev/null +++ b/lib/nghttp2/src/ssl_compat.h @@ -0,0 +1,51 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 OPENSSL_COMPAT_H + +# include + +# ifdef LIBRESSL_VERSION_NUMBER +# define OPENSSL_1_1_API 0 +# define OPENSSL_1_1_1_API 0 +# define OPENSSL_3_0_0_API 0 +# define LIBRESSL_IN_USE 1 +# define LIBRESSL_LEGACY_API (LIBRESSL_VERSION_NUMBER < 0x20700000L) +# define LIBRESSL_2_7_API (LIBRESSL_VERSION_NUMBER >= 0x20700000L) +# define LIBRESSL_3_5_API (LIBRESSL_VERSION_NUMBER >= 0x30500000L) +# else // !LIBRESSL_VERSION_NUMBER +# define OPENSSL_1_1_API (OPENSSL_VERSION_NUMBER >= 0x1010000fL) +# define OPENSSL_1_1_1_API (OPENSSL_VERSION_NUMBER >= 0x10101000L) +# define OPENSSL_3_0_0_API (OPENSSL_VERSION_NUMBER >= 0x30000000L) +# define LIBRESSL_IN_USE 0 +# define LIBRESSL_LEGACY_API 0 +# define LIBRESSL_2_7_API 0 +# define LIBRESSL_3_5_API 0 +# endif // !LIBRESSL_VERSION_NUMBER + +# if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +# define NGHTTP2_OPENSSL_IS_BORINGSSL +# endif // OPENSSL_IS_BORINGSSL || OPENSSL_IS_AWSLC + +#endif // OPENSSL_COMPAT_H diff --git a/lib/nghttp2/src/template.h b/lib/nghttp2/src/template.h new file mode 100644 index 00000000000..530a1d13334 --- /dev/null +++ b/lib/nghttp2/src/template.h @@ -0,0 +1,548 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nghttp2 { + +// std::forward is constexpr since C++14 +template +constexpr std::array< + typename std::decay::type>::type, + sizeof...(T)> +make_array(T &&...t) { + return std::array< + typename std::decay::type>::type, + sizeof...(T)>{{std::forward(t)...}}; +} + +template constexpr size_t array_size(T (&)[N]) { + return N; +} + +template constexpr size_t str_size(T (&)[N]) { + return N - 1; +} + +// inspired by , but our +// template can take functions returning other than void. +template struct Defer { + Defer(F &&f, T &&...t) + : f(std::bind(std::forward(f), std::forward(t)...)) {} + Defer(Defer &&o) noexcept : f(std::move(o.f)) {} + ~Defer() { f(); } + + using ResultType = typename std::result_of::type( + typename std::decay::type...)>::type; + std::function f; +}; + +template Defer defer(F &&f, T &&...t) { + return Defer(std::forward(f), std::forward(t)...); +} + +template bool test_flags(T t, F flags) { + return (t & flags) == flags; +} + +// doubly linked list of element T*. T must have field T *dlprev and +// T *dlnext, which point to previous element and next element in the +// list respectively. +template struct DList { + DList() : head(nullptr), tail(nullptr), len(0) {} + + DList(const DList &) = delete; + DList &operator=(const DList &) = delete; + + DList(DList &&other) noexcept + : head{std::exchange(other.head, nullptr)}, + tail{std::exchange(other.tail, nullptr)}, + len{std::exchange(other.len, 0)} {} + + DList &operator=(DList &&other) noexcept { + if (this == &other) { + return *this; + } + head = std::exchange(other.head, nullptr); + tail = std::exchange(other.tail, nullptr); + len = std::exchange(other.len, 0); + + return *this; + } + + void append(T *t) { + ++len; + if (tail) { + tail->dlnext = t; + t->dlprev = tail; + tail = t; + return; + } + head = tail = t; + } + + void remove(T *t) { + --len; + auto p = t->dlprev; + auto n = t->dlnext; + if (p) { + p->dlnext = n; + } + if (head == t) { + head = n; + } + if (n) { + n->dlprev = p; + } + if (tail == t) { + tail = p; + } + t->dlprev = t->dlnext = nullptr; + } + + bool empty() const { return head == nullptr; } + + size_t size() const { return len; } + + T *head, *tail; + size_t len; +}; + +template void dlist_delete_all(DList &dl) { + for (auto e = dl.head; e;) { + auto next = e->dlnext; + delete e; + e = next; + } +} + +// 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; +} + +// User-defined literals for time, converted into double in seconds + +// hours +constexpr double operator"" _h(unsigned long long h) { return h * 60 * 60; } + +// minutes +constexpr double operator"" _min(unsigned long long min) { return min * 60; } + +// seconds +constexpr double operator"" _s(unsigned long long s) { return s; } + +// milliseconds +constexpr double operator"" _ms(unsigned long long ms) { return ms / 1000.; } + +// Returns a copy of NULL-terminated string [first, last). +template +std::unique_ptr strcopy(InputIt first, InputIt last) { + auto res = std::make_unique(last - first + 1); + *std::copy(first, last, res.get()) = '\0'; + return res; +} + +// Returns a copy of NULL-terminated string |val|. +inline std::unique_ptr strcopy(const char *val) { + return strcopy(val, val + strlen(val)); +} + +inline std::unique_ptr strcopy(const char *val, size_t n) { + return strcopy(val, val + n); +} + +// Returns a copy of val.c_str(). +inline std::unique_ptr strcopy(const std::string &val) { + return strcopy(std::begin(val), std::end(val)); +} + +inline std::unique_ptr strcopy(const std::unique_ptr &val) { + if (!val) { + return nullptr; + } + return strcopy(val.get()); +} + +inline std::unique_ptr strcopy(const std::unique_ptr &val, + size_t n) { + if (!val) { + return nullptr; + } + return strcopy(val.get(), val.get() + n); +} + +// ImmutableString represents string that is immutable unlike +// std::string. It has c_str() and size() functions to mimic +// std::string. It manages buffer by itself. Just like std::string, +// c_str() returns NULL-terminated string, but NULL character may +// appear before the final terminal NULL. +class ImmutableString { +public: + using traits_type = std::char_traits; + using value_type = traits_type::char_type; + using allocator_type = std::allocator; + using size_type = std::allocator_traits::size_type; + using difference_type = + std::allocator_traits::difference_type; + using const_reference = const value_type &; + using const_pointer = const value_type *; + using const_iterator = const_pointer; + using const_reverse_iterator = std::reverse_iterator; + + ImmutableString() : len(0), base("") {} + ImmutableString(const char *s, size_t slen) + : len(slen), base(copystr(s, s + len)) {} + explicit ImmutableString(const char *s) + : len(strlen(s)), base(copystr(s, s + len)) {} + explicit ImmutableString(const std::string &s) + : len(s.size()), base(copystr(std::begin(s), std::end(s))) {} + template + ImmutableString(InputIt first, InputIt last) + : len(std::distance(first, last)), base(copystr(first, last)) {} + ImmutableString(const ImmutableString &other) + : len(other.len), base(copystr(std::begin(other), std::end(other))) {} + ImmutableString(ImmutableString &&other) noexcept + : len{std::exchange(other.len, 0)}, base{std::exchange(other.base, "")} {} + ~ImmutableString() { + if (len) { + delete[] base; + } + } + + ImmutableString &operator=(const ImmutableString &other) { + if (this == &other) { + return *this; + } + if (len) { + delete[] base; + } + len = other.len; + base = copystr(std::begin(other), std::end(other)); + return *this; + } + ImmutableString &operator=(ImmutableString &&other) noexcept { + if (this == &other) { + return *this; + } + if (len) { + delete[] base; + } + len = std::exchange(other.len, 0); + base = std::exchange(other.base, ""); + return *this; + } + + template static ImmutableString from_lit(const char (&s)[N]) { + return ImmutableString(s, N - 1); + } + + const_iterator begin() const { return base; }; + const_iterator cbegin() const { return base; }; + + const_iterator end() const { return base + len; }; + const_iterator cend() const { return base + len; }; + + const_reverse_iterator rbegin() const { + return const_reverse_iterator{base + len}; + } + const_reverse_iterator crbegin() const { + return const_reverse_iterator{base + len}; + } + + const_reverse_iterator rend() const { return const_reverse_iterator{base}; } + const_reverse_iterator crend() const { return const_reverse_iterator{base}; } + + const char *c_str() const { return base; } + size_type size() const { return len; } + bool empty() const { return len == 0; } + const_reference operator[](size_type pos) const { return *(base + pos); } + +private: + template const char *copystr(InputIt first, InputIt last) { + if (first == last) { + return ""; + } + auto res = new char[std::distance(first, last) + 1]; + *std::copy(first, last, res) = '\0'; + return res; + } + + size_type len; + const char *base; +}; + +inline bool operator==(const ImmutableString &lhs, const ImmutableString &rhs) { + return lhs.size() == rhs.size() && + std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); +} + +inline bool operator==(const ImmutableString &lhs, const std::string &rhs) { + return lhs.size() == rhs.size() && + std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); +} + +inline bool operator==(const std::string &lhs, const ImmutableString &rhs) { + return rhs == lhs; +} + +inline bool operator==(const ImmutableString &lhs, const char *rhs) { + return lhs.size() == strlen(rhs) && + std::equal(std::begin(lhs), std::end(lhs), rhs); +} + +inline bool operator==(const char *lhs, const ImmutableString &rhs) { + return rhs == lhs; +} + +inline bool operator!=(const ImmutableString &lhs, const ImmutableString &rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const ImmutableString &lhs, const std::string &rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const std::string &lhs, const ImmutableString &rhs) { + return !(rhs == lhs); +} + +inline bool operator!=(const ImmutableString &lhs, const char *rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const char *lhs, const ImmutableString &rhs) { + return !(rhs == lhs); +} + +inline std::ostream &operator<<(std::ostream &o, const ImmutableString &s) { + return o.write(s.c_str(), s.size()); +} + +inline std::string &operator+=(std::string &lhs, const ImmutableString &rhs) { + lhs.append(rhs.c_str(), rhs.size()); + return lhs; +} + +// StringRef is a reference to a string owned by something else. So +// it behaves like simple string, but it does not own pointer. When +// it is default constructed, it has empty string. You can freely +// copy or move around this struct, but never free its pointer. str() +// function can be used to export the content as std::string. +class StringRef { +public: + using traits_type = std::char_traits; + using value_type = traits_type::char_type; + using allocator_type = std::allocator; + using size_type = std::allocator_traits::size_type; + using difference_type = + std::allocator_traits::difference_type; + using const_reference = const value_type &; + using const_pointer = const value_type *; + using const_iterator = const_pointer; + using const_reverse_iterator = std::reverse_iterator; + + constexpr StringRef() : base(""), len(0) {} + explicit StringRef(const std::string &s) : base(s.c_str()), len(s.size()) {} + explicit StringRef(const ImmutableString &s) + : base(s.c_str()), len(s.size()) {} + explicit StringRef(const char *s) : base(s), len(strlen(s)) {} + constexpr StringRef(const char *s, size_t n) : base(s), len(n) {} + template + constexpr StringRef(const CharT *s, size_t n) + : base(reinterpret_cast(s)), len(n) {} + template + StringRef(InputIt first, InputIt last) + : base(reinterpret_cast(&*first)), + len(std::distance(first, last)) {} + template + StringRef(InputIt *first, InputIt *last) + : base(reinterpret_cast(first)), + len(std::distance(first, last)) {} + template + constexpr static StringRef from_lit(const CharT (&s)[N]) { + return StringRef{s, N - 1}; + } + static StringRef from_maybe_nullptr(const char *s) { + if (s == nullptr) { + return StringRef(); + } + + return StringRef(s); + } + + constexpr const_iterator begin() const { return base; }; + constexpr const_iterator cbegin() const { return base; }; + + constexpr const_iterator end() const { return base + len; }; + constexpr const_iterator cend() const { return base + len; }; + + const_reverse_iterator rbegin() const { + return const_reverse_iterator{base + len}; + } + const_reverse_iterator crbegin() const { + return const_reverse_iterator{base + len}; + } + + const_reverse_iterator rend() const { return const_reverse_iterator{base}; } + const_reverse_iterator crend() const { return const_reverse_iterator{base}; } + + constexpr const char *c_str() const { return base; } + constexpr size_type size() const { return len; } + constexpr bool empty() const { return len == 0; } + constexpr const_reference operator[](size_type pos) const { + return *(base + pos); + } + + std::string str() const { return std::string(base, len); } + const uint8_t *byte() const { + return reinterpret_cast(base); + } + +private: + const char *base; + size_type len; +}; + +inline bool operator==(const StringRef &lhs, const StringRef &rhs) { + return lhs.size() == rhs.size() && + std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); +} + +inline bool operator==(const StringRef &lhs, const std::string &rhs) { + return lhs.size() == rhs.size() && + std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); +} + +inline bool operator==(const std::string &lhs, const StringRef &rhs) { + return rhs == lhs; +} + +inline bool operator==(const StringRef &lhs, const char *rhs) { + return lhs.size() == strlen(rhs) && + std::equal(std::begin(lhs), std::end(lhs), rhs); +} + +inline bool operator==(const StringRef &lhs, const ImmutableString &rhs) { + return lhs.size() == rhs.size() && + std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); +} + +inline bool operator==(const ImmutableString &lhs, const StringRef &rhs) { + return rhs == lhs; +} + +inline bool operator==(const char *lhs, const StringRef &rhs) { + return rhs == lhs; +} + +inline bool operator!=(const StringRef &lhs, const StringRef &rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const StringRef &lhs, const std::string &rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const std::string &lhs, const StringRef &rhs) { + return !(rhs == lhs); +} + +inline bool operator!=(const StringRef &lhs, const char *rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const char *lhs, const StringRef &rhs) { + return !(rhs == lhs); +} + +inline bool operator<(const StringRef &lhs, const StringRef &rhs) { + return std::lexicographical_compare(std::begin(lhs), std::end(lhs), + std::begin(rhs), std::end(rhs)); +} + +inline std::ostream &operator<<(std::ostream &o, const StringRef &s) { + return o.write(s.c_str(), s.size()); +} + +inline std::string &operator+=(std::string &lhs, const StringRef &rhs) { + lhs.append(rhs.c_str(), rhs.size()); + return lhs; +} + +inline int run_app(std::function app, int argc, + char **argv) { + try { + return app(argc, argv); + } catch (const std::bad_alloc &) { + fputs("Out of memory\n", stderr); + } catch (const std::exception &x) { + fprintf(stderr, "Caught %s:\n%s\n", typeid(x).name(), x.what()); + } catch (...) { + fputs("Unknown exception caught\n", stderr); + } + return EXIT_FAILURE; +} + +} // namespace nghttp2 + +namespace std { +template <> struct hash { + std::size_t operator()(const nghttp2::StringRef &s) const noexcept { + // 32 bit FNV-1a: + // https://tools.ietf.org/html/draft-eastlake-fnv-16#section-6.1.1 + uint32_t h = 2166136261u; + for (auto c : s) { + h ^= static_cast(c); + h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); + } + return h; + } +}; +} // namespace std + +#endif // TEMPLATE_H diff --git a/lib/nghttp2/src/template_test.cc b/lib/nghttp2/src/template_test.cc new file mode 100644 index 00000000000..4a773158b54 --- /dev/null +++ b/lib/nghttp2/src/template_test.cc @@ -0,0 +1,204 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "template_test.h" + +#include +#include +#include + +#include + +#include "template.h" + +namespace nghttp2 { + +void test_template_immutable_string(void) { + ImmutableString null; + + CU_ASSERT("" == null); + CU_ASSERT(0 == null.size()); + CU_ASSERT(null.empty()); + + ImmutableString from_cstr("alpha"); + + CU_ASSERT(0 == strcmp("alpha", from_cstr.c_str())); + CU_ASSERT(5 == from_cstr.size()); + CU_ASSERT(!from_cstr.empty()); + CU_ASSERT("alpha" == from_cstr); + CU_ASSERT(from_cstr == "alpha"); + CU_ASSERT(std::string("alpha") == from_cstr); + CU_ASSERT(from_cstr == std::string("alpha")); + + // copy constructor + ImmutableString src("charlie"); + ImmutableString copy = src; + + CU_ASSERT("charlie" == copy); + CU_ASSERT(7 == copy.size()); + + // copy assignment + ImmutableString copy2; + copy2 = src; + + CU_ASSERT("charlie" == copy2); + CU_ASSERT(7 == copy2.size()); + + // move constructor + ImmutableString move = std::move(copy); + + CU_ASSERT("charlie" == move); + CU_ASSERT(7 == move.size()); + CU_ASSERT("" == copy); + CU_ASSERT(0 == copy.size()); + + // move assignment + move = std::move(from_cstr); + + CU_ASSERT("alpha" == move); + CU_ASSERT(5 == move.size()); + CU_ASSERT("" == from_cstr); + CU_ASSERT(0 == from_cstr.size()); + + // from string literal + auto from_lit = StringRef::from_lit("bravo"); + + CU_ASSERT("bravo" == from_lit); + CU_ASSERT(5 == from_lit.size()); + + // equality + ImmutableString eq("delta"); + + CU_ASSERT("delta1" != eq); + CU_ASSERT("delt" != eq); + CU_ASSERT(eq != "delta1"); + CU_ASSERT(eq != "delt"); + + // operator[] + ImmutableString br_op("foxtrot"); + + CU_ASSERT('f' == br_op[0]); + CU_ASSERT('o' == br_op[1]); + CU_ASSERT('t' == br_op[6]); + CU_ASSERT('\0' == br_op[7]); + + // operator==(const ImmutableString &, const ImmutableString &) + { + ImmutableString a("foo"); + ImmutableString b("foo"); + ImmutableString c("fo"); + + CU_ASSERT(a == b); + CU_ASSERT(a != c); + CU_ASSERT(c != b); + } + + // operator<< + { + ImmutableString a("foo"); + std::stringstream ss; + ss << a; + + CU_ASSERT("foo" == ss.str()); + } + + // operator +=(std::string &, const ImmutableString &) + { + std::string a = "alpha"; + a += ImmutableString("bravo"); + + CU_ASSERT("alphabravo" == a); + } +} + +void test_template_string_ref(void) { + StringRef empty; + + CU_ASSERT("" == empty); + CU_ASSERT(0 == empty.size()); + + // from std::string + std::string alpha = "alpha"; + + StringRef ref(alpha); + + CU_ASSERT("alpha" == ref); + CU_ASSERT(ref == "alpha"); + CU_ASSERT(alpha == ref); + CU_ASSERT(ref == alpha); + CU_ASSERT(5 == ref.size()); + + // from string literal + auto from_lit = StringRef::from_lit("alpha"); + + CU_ASSERT("alpha" == from_lit); + CU_ASSERT(5 == from_lit.size()); + + // from ImmutableString + auto im = ImmutableString::from_lit("bravo"); + + StringRef imref(im); + + CU_ASSERT("bravo" == imref); + CU_ASSERT(5 == imref.size()); + + // from C-string + StringRef cstrref("charlie"); + + CU_ASSERT("charlie" == cstrref); + CU_ASSERT(7 == cstrref.size()); + + // from C-string and its length + StringRef cstrnref("delta", 5); + + CU_ASSERT("delta" == cstrnref); + CU_ASSERT(5 == cstrnref.size()); + + // operator[] + StringRef br_op("foxtrot"); + + CU_ASSERT('f' == br_op[0]); + CU_ASSERT('o' == br_op[1]); + CU_ASSERT('t' == br_op[6]); + CU_ASSERT('\0' == br_op[7]); + + // operator<< + { + StringRef a("foo"); + std::stringstream ss; + ss << a; + + CU_ASSERT("foo" == ss.str()); + } + + // operator +=(std::string &, const StringRef &) + { + std::string a = "alpha"; + a += StringRef("bravo"); + + CU_ASSERT("alphabravo" == a); + } +} + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/template_test.h b/lib/nghttp2/src/template_test.h new file mode 100644 index 00000000000..2c1448f34df --- /dev/null +++ b/lib/nghttp2/src/template_test.h @@ -0,0 +1,39 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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_TEST_H +#define TEMPLATE_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif // HAVE_CONFIG_H + +namespace nghttp2 { + +void test_template_immutable_string(void); +void test_template_string_ref(void); + +} // namespace nghttp2 + +#endif // TEMPLATE_TEST_H diff --git a/lib/nghttp2/src/test.example.com-key.pem b/lib/nghttp2/src/test.example.com-key.pem new file mode 100644 index 00000000000..6d5515cfc98 --- /dev/null +++ b/lib/nghttp2/src/test.example.com-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2FroLDwqVUYRlxp +U/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpowUxvTDv/GNIHH +XK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmIsYsO0Y4iBtwB +M/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvcVzvSgZIzMLqS +zvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7G/ZbX/4kUU5b +PabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABAoIBAQCjUL69iFOs7muK +CZGFe/iU1uxQM6XuGPN7mso3z15W07UxdlS3o6ZUSoLTONWcBxP/N4knulHJjZSY +0LivbDYz3htic3t0kdGmxOxPVnLKRXN6ncbQTaeAE8tlBMAcWd/UH2Tlylz+Ac// +6cV3gNJMShwUmhb3l4v3Rml0nZ6PO1pFc/Chk5L9REAV8G6rNtc9bzgmgoFucRO/ +8ce/uJrENt1Pu3vBvmz42DTGfG48v5RZ0OY4qEPawZJ7p+QYiTf6h3Eilss/AllW +PPfQ0thdyB+yrZ3p6qb+ZUYphpGxgg6YlQxLfDKAikuo+EXwjPBPfeHhTO4kAj+h +opDCroZhAoGBANyVFbagCWqwguE6nVPmnCaiNQUIh8b7L2CnkkLfdbPQr/KvyIjg +Ua125bTJhe9Uk+ZBWsobQkjA0Baidiylx51pWYaxPVn5araVmkh2dqMluk2QE82X +AWemBgKhAqCLLLMVXbrRYlxpKUm1Fc/lJ8Ig2R/MJSntTMpQhJtIejUbAoGBAMnt +XMvlFABCoFbI9GMcteI/KkvNGQUy3OKEln/QCssnE4/XIu7LCxy6P+1lycbFy/mQ +0bnp525sPEIIkMpi6LeAbSzYN2O3BRjNrjPcbx6Khz9DweNhRIo5qTFRszZ+pHbV +N+9Oc9JVenwPw6EuW7uZRFKFhCHtsBFdUrWLJoSVAoGAQ3ytdwGBwA2fDW/UgL32 +mm9YT2DrwbpKJYU/X4xkw44ett6HOTGAa9ULtINPogi7c2AdeeZbIk0znSk5hLF3 +4DZCOM5zWdrQhmpBGNh9ta6uUFq7ZFRGDsMh5Z4DYsER/PyVf7neIS3ffviTYtbW +kjNgmrTnzesXanK2D5heI28CgYEAhl+qjRTYhoPP53C7EOmeL/0QzHij2c3LKAJL +lKqBREewwNvNp1L/BhL7T6OY7unZny48IpgBJn5oaxkAIW5IpzSTcnBAC99TSPo2 +ntRmLdDJx9PzRrkHv2Q3r1ZLCEymbV3eZyWx9ZpkdAKZkL0k1mZcDP5Eu79Ml4Ge +9Kiw7TECgYEAh+nTKwrCUFGbe4RIVCj/QG7FVPbq5PdxJ3gILZ3/1XkhPcNRFKJS +u5qPfA02tYEALz9KXATK1uRB/GlBM7Eap/g2GFiHpVxrw6wPpybLywJmNyNTwqiq +eJxQ0FRzW9Kwwn1ThPY38LdFe/wvXZFOcNvGD8hHCLQRdlBR4zuTsBk= +-----END RSA PRIVATE KEY----- diff --git a/lib/nghttp2/src/test.example.com.csr b/lib/nghttp2/src/test.example.com.csr new file mode 100644 index 00000000000..816393545dd --- /dev/null +++ b/lib/nghttp2/src/test.example.com.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpTCCAY0CAQAwYDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx +ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEZMBcGA1UEAxMQdGVz +dC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK39 +lAbBIf8cHf+FmX1RX+bDMlnJ7P9YnRdha6Cw8KlVGEZcaVPym6yBvF/L98OHFT/8 +Tl3dcvUPlvTiytmEnAHh3hPe5h2V23KaMFMb0w7/xjSBx1yv8r2DPEdhBJ2Ue9al +3aF7AmpL/2htnsJJLwNdPHhG5NLu0cTZiLGLDtGOIgbcATPzEpAGD31na3R4WPc9 +JeDPBgMlDrmXNLeo9M8N385I6plYBhCb3Fc70oGSMzC6ks7wvPb4l7Y9acs0IzC4 +Jrg27NJeO7kR6WV4DBlchd1LkIqT874Tuxv2W1/+JFFOWz2m3bbz2447u3ROT3xq +yjk4TOh0vd2O0L2E1LcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBMAUqwty7R +/YWRrC8NuvrbSsW0r7Z7FXWxny5w5ImONCgVffc2wVydtBVQ0rfd3pDZLyu0P4sx +4bJ/KBz67t2MsOKbCMDS7SJuFwHu9AUzaYNh455HeBOVwb6LemJDNnCtMG9DgcRv +2BpwKqekUVTGDuUQmLibjE8qwDHw/p9k4gjQBxlfJe2sIZGs6oA/JGFJUU6ZIn8Y +M6aazrbjWexbWCnjhiXkNa8kfKiSHzU+2ct+GY5QxI221+63bXRiAi2/LK0gaY+p ++3vYu75F7+8oPZOfsGmYEyPz7c1jPqcwPgVDk+sdvl1MO1TGFRaFNIlRP1DhpHkj +fuJ/id6oUHhj +-----END CERTIFICATE REQUEST----- diff --git a/lib/nghttp2/src/test.example.com.csr.json b/lib/nghttp2/src/test.example.com.csr.json new file mode 100644 index 00000000000..5cd3e1efd66 --- /dev/null +++ b/lib/nghttp2/src/test.example.com.csr.json @@ -0,0 +1,14 @@ +{ + "CN": "test.example.com", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "ST": "Some-State", + "O": "Internet Widgits Pty Ltd" + } + ] +} diff --git a/lib/nghttp2/src/test.example.com.pem b/lib/nghttp2/src/test.example.com.pem new file mode 100644 index 00000000000..1c7e71ef185 --- /dev/null +++ b/lib/nghttp2/src/test.example.com.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDwTCCAqmgAwIBAgIUDhKNhGRUq1TSHD6aG2k4TRR8iA0wDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v +cmcwHhcNMTYwNjI1MDkzNzAwWhcNMjYwNjIzMDkzNzAwWjBgMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRkwFwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2Fr +oLDwqVUYRlxpU/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpow +UxvTDv/GNIHHXK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmI +sYsO0Y4iBtwBM/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvc +VzvSgZIzMLqSzvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7 +G/ZbX/4kUU5bPabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABo3UwczAO +BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw +ADAdBgNVHQ4EFgQUm8jn1FICope9qUce6ORQ0CtbmhYwHwYDVR0jBBgwFoAU0DnF +VHVlxrFEkv1ULqnO4ua924YwDQYJKoZIhvcNAQELBQADggEBAD7RPz/5rAnS1MNP +JfAj1TXZSBwlYgtmJL65yaFB6a1SNSTo15deAm/1Vl10LbmYdV4sVnGKeZjhKNk+ +bvVzetUSUS7Rh1fHtxlivJFkG1VrvPu9b416l2aKftBiaNyAWXbyjqXwLYli6Ehk +uu6jZd0040Ggh7bY+KMSnDFDrp7Rar7OvGu9Iovs+sPdkc/iEbvwEiXdMjf3gwkT +Wqx6br1VDLzhD83HAsFA9tt5fv6KTf91UgJnCmOi81Uo6fSEJG84g32T25gwwmCK +q4U049aGF/f4u3QuWDsfYqNePycurAg3m5PC0wCoqvpY2u/q+PGbjWMi2PfZsF8U +imgl/L0= +-----END CERTIFICATE----- diff --git a/lib/nghttp2/src/test.nghttp2.org-key.pem b/lib/nghttp2/src/test.nghttp2.org-key.pem new file mode 100644 index 00000000000..253289577d0 --- /dev/null +++ b/lib/nghttp2/src/test.nghttp2.org-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA7p6KKa3ctS+Sr/nf2uTKNtTshuDVzTsBTbaGydj8q0YDmT3n +CnOPWXvvG1N+jJv5pcAXN2ZnV9UpGh3N5g/CaRcFTgQQ8o+NlCXYBdPIXAJ+Kkbx +limDw3n9xIXfeL6+V2QuPNrqh6n23xwDg5boKaNkpf7X5OrjT1Ph57SEfX1op3GX +bwkAP2+3WlxxYYs0htRq2gH97q9J4MlhHPDapi+uKGs+2b1y6Uxgf4nD5jEWdPmy +VqeKs+fT4ja2n+3gujpdOo2lg504p50gL4zP8zhAlcqlQCmeJGL1xFzCtm2wHQo7 +6XHSWca4pJ7rxf2oIdtE7ikvgFlTVXnG1T3TEQIDAQABAoIBAQDlX/UD96MPcDmb +e6EZ85AGgUsUpJAhBjVMlMagxTqtEVJoPj8XptoHdMD2DZ66XzztfedTU9bHcZpf +BoNkQYXqKzzoL7Ry1leML4ymnVweRi8tSKD2bdXBVEUCYoXctc6WhzCDQxTrcBBl +i7I9DhUB4ZTglEbIQJpdKQ8hAj/Rt55KWSxc+8X7ItSdtMrq+uz+pqg4PkysVAFS +3aDybOqiI/2hzOvwQU4HaB48uUQwpOU6EGidt0C5nAdWOQMbS8kkCJz6UODiUfdM +mLIyA4ygkQ45QthzrddKauUMhUd/y1SAFJOambR4ZiyA+bItomlbNq018sFx3FDr +Uvg7nz2ZAoGBAPC6aO4W0U7vsWyL/lgC7ybFbtPJ4emj4wK/W87qx3LW1/dRgl7Q +h6oblZTFK/oV6xA7J2/Foocz/s1ntnyIdxrtrZUYAIiBXlrWhDg9+MnnmErfXx7H +CkRyWH6i9JbTRUeiWRNBGQ9yMkwQPc2Ckytxrh7w9M+RVCpyzUh8lX+XAoGBAP3B +4V8cF3bVEUOk0tHshR5m2kcJ22qmUv8WUG+IdRUDb4tRpzVFC8HcKedEjk3jxkXR +UInRSD+hLhx0HIhjZKWqJffSZI/G3U8AqoKu9+eh/xHZCah/8KW1YWNsn4rROcyZ +5XFRiMn7psWTjLEZ17zQS4rk9g65SKc9u1wtTxeXAoGAIY3qOF2n2Tfh5D5zOnNW +QHI+q3i1a6qzZtujgWkKWgCGY+vRn0Oz1Us5A16kbZyGgmGscpD6wZvGxXzSW/Nt +nqxIiMKquFxH+aNzFJ/WwNXuTWlrSc/2p2nE2gn+y9MxEfYYMm3df2CskBuncbDk +sKaM3bU6eoBIWg5cfOEYuYsCgYACB2bR59uYK6PzsoGtBAMcdx4Pq1iBxcqsF3WV +LrYg8OIXbxOzLVYmuqfrHXU10jhnnoDSWUYGnDdOKu9/d6v6Vx3umVQMgj6Kvyqd +2OBKjdUIQ3/8ROmbqZOZw+iSp5GavTBEc65wTv7KXZ+mWtqKu++esK32+CxIignR +dttHCQKBgQDZUt94wj9s5p7H5LH6hxyqNr6P9JYEuYPao5l/3mOJ8N330wKuN2G+ +GUg7p/AhtQHwdoyErlsQavKZa791oCvfJiOURYa8gYU03sYsyI1tV45UexCwl40f +oS+VQYgU16UdYo9B2petecEPNpM+mgpne3qzVUwJ5NUNURgmWpyiQw== +-----END RSA PRIVATE KEY----- diff --git a/lib/nghttp2/src/test.nghttp2.org.csr b/lib/nghttp2/src/test.nghttp2.org.csr new file mode 100644 index 00000000000..dc4bb105dac --- /dev/null +++ b/lib/nghttp2/src/test.nghttp2.org.csr @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDATCCAekCAQAwZDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx +ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEdMBsGA1UEAxMUbm90 +LXVzZWQubmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDunooprdy1L5Kv+d/a5Mo21OyG4NXNOwFNtobJ2PyrRgOZPecKc49Ze+8bU36M +m/mlwBc3ZmdX1SkaHc3mD8JpFwVOBBDyj42UJdgF08hcAn4qRvGWKYPDef3Ehd94 +vr5XZC482uqHqfbfHAODlugpo2Sl/tfk6uNPU+HntIR9fWincZdvCQA/b7daXHFh +izSG1GraAf3ur0ngyWEc8NqmL64oaz7ZvXLpTGB/icPmMRZ0+bJWp4qz59PiNraf +7eC6Ol06jaWDnTinnSAvjM/zOECVyqVAKZ4kYvXEXMK2bbAdCjvpcdJZxriknuvF +/agh20TuKS+AWVNVecbVPdMRAgMBAAGgWDBWBgkqhkiG9w0BCQ4xSTBHMEUGA1Ud +EQQ+MDyCEFRFU1QuTkdIVFRQMi5PUkeCEioudGVzdC5uZ2h0dHAyLm9yZ4IUdyp3 +LnRlc3QubmdodHRwMi5vcmcwDQYJKoZIhvcNAQELBQADggEBAIAEwnoM5moRwO5U +eaeVCuzpxw1qQsB769GyQu+ey1aa+2BYflirv/FW+8x/uzQpCWGEgHqd5w+MXyXA +PsyucHgKh5Ia6MUW6xxlHkkOtVtmZiH7lXWv90RNtdfHHGWnBzw8iGsk5WfEaNho +NlPiuYLiFqA7W6jR/c4kOg3zziDlwTXaH6SWLCuDzLTb7E7nGcrWkN6moYj+QlSx +viA4GsqDBoFgXT7cSfUzS8ZwIjrqbx7C1xkzPEt5jAiCD/UBX9ot0G+lEgCv3UQj +Q1KkY+TO3bzMkt/kQSX2Q6plKj8D77tlDfFCjd77VC2lL3Qmzaz+M6T7uF+wyl9W +AQJvoUg= +-----END CERTIFICATE REQUEST----- diff --git a/lib/nghttp2/src/test.nghttp2.org.csr.json b/lib/nghttp2/src/test.nghttp2.org.csr.json new file mode 100644 index 00000000000..5ee306955f3 --- /dev/null +++ b/lib/nghttp2/src/test.nghttp2.org.csr.json @@ -0,0 +1,19 @@ +{ + "CN": "not-used.nghttp2.org", + "hosts": [ + "TEST.NGHTTP2.ORG", + "*.test.nghttp2.org", + "w*w.test.nghttp2.org" + ], + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "AU", + "ST": "Some-State", + "O": "Internet Widgits Pty Ltd" + } + ] +} diff --git a/lib/nghttp2/src/test.nghttp2.org.pem b/lib/nghttp2/src/test.nghttp2.org.pem new file mode 100644 index 00000000000..0c386fc0eec --- /dev/null +++ b/lib/nghttp2/src/test.nghttp2.org.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDjCCAvagAwIBAgIUQBCY8Nre85JT1c7P+HbXUF9yzg8wDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v +cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBkMQswCQYDVQQGEwJB +VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMR0wGwYDVQQDExRub3QtdXNlZC5uZ2h0dHAyLm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6eiimt3LUvkq/539rkyjbU7Ibg1c07 +AU22hsnY/KtGA5k95wpzj1l77xtTfoyb+aXAFzdmZ1fVKRodzeYPwmkXBU4EEPKP +jZQl2AXTyFwCfipG8ZYpg8N5/cSF33i+vldkLjza6oep9t8cA4OW6CmjZKX+1+Tq +409T4ee0hH19aKdxl28JAD9vt1pccWGLNIbUatoB/e6vSeDJYRzw2qYvrihrPtm9 +culMYH+Jw+YxFnT5slanirPn0+I2tp/t4Lo6XTqNpYOdOKedIC+Mz/M4QJXKpUAp +niRi9cRcwrZtsB0KO+lx0lnGuKSe68X9qCHbRO4pL4BZU1V5xtU90xECAwEAAaOB +vTCBujAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0T +AQH/BAIwADAdBgNVHQ4EFgQUGlxgxowH6jrQiyyFpCbwPkCXXIYwHwYDVR0jBBgw +FoAU0DnFVHVlxrFEkv1ULqnO4ua924YwRQYDVR0RBD4wPIIQVEVTVC5OR0hUVFAy +Lk9SR4ISKi50ZXN0Lm5naHR0cDIub3JnghR3KncudGVzdC5uZ2h0dHAyLm9yZzAN +BgkqhkiG9w0BAQsFAAOCAQEANCqM6ocfqOpgDEHYOOQTGFHJIptQhS3kRYAdTIo2 +G8XvGCoy+CDYe1GAUWbxE090+a1I1rsYMHcWKJnjKaCBZid7KMhyayIvrmgEsOCh +L8iLf3bxkCoyIAmCpxJwa3LMxm2QQLtRx8AoMXWf+N8are4HY6MLNn6aP4zaTrTZ +H+WkjKIh7WjSHtW/ro666PCXJDCCdRXljOf8v/fff3bYiLg8o70RBp7OFM0HaPtK +wCfcLLxBeoVIncWswB6GtVUFhLeGjepDzWpuDHOdw6DtpghwSXvWFu9bRtl+x02m +LAGfJ0kJrpYGfr9UB51NFX3aM/D3p2zxrjKwR2b59vJEcA== +-----END CERTIFICATE----- diff --git a/lib/nghttp2/src/testdata/Makefile.am b/lib/nghttp2/src/testdata/Makefile.am new file mode 100644 index 00000000000..b1cb575c012 --- /dev/null +++ b/lib/nghttp2/src/testdata/Makefile.am @@ -0,0 +1,27 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2023 Tatsuhiro Tsujikawa + +# 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 = \ + ipaddr.crt \ + nosan.crt \ + nosan_ip.crt \ + verify_hostname.crt diff --git a/lib/nghttp2/src/testdata/ipaddr.crt b/lib/nghttp2/src/testdata/ipaddr.crt new file mode 100644 index 00000000000..cdacdf03137 --- /dev/null +++ b/lib/nghttp2/src/testdata/ipaddr.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBfDCCASKgAwIBAgIBATAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwsxOTIuMTY4 +LjAuMTAeFw0yMzAzMTUxMjQ5MDBaFw0zMzAxMjExMjQ5MDBaMBYxFDASBgNVBAMT +CzE5Mi4xNjguMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDh3hNne6xGM +fOrf7ln5EFnlLpk98qadBx3MKjG5gAfMYHzf/S7v19G608sH1LtabubV+Tvjllon +K56G2Gk0+6NhMF8wDgYDVR0PAQH/BAQDAgeAME0GA1UdEQRGMESCE25naHR0cDIu +ZXhhbXBsZS5jb22CFSoubmdodHRwMi5leGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA +AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiEA3jZzO49MYccR5mYS08qVUCdh +HsEAC8GhRXFwL6zvf2ACIFAJrca2zTU4QRjV6V+LGRHc2ZocE2e7wFTLobblmDfB +-----END CERTIFICATE----- diff --git a/lib/nghttp2/src/testdata/nosan.crt b/lib/nghttp2/src/testdata/nosan.crt new file mode 100644 index 00000000000..ebbb5c04de2 --- /dev/null +++ b/lib/nghttp2/src/testdata/nosan.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBKDCBz6ADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCWxvY2FsaG9z +dDAeFw0yMzAzMTUxMjQzMzhaFw0zMzAxMjExMjQzMzhaMBQxEjAQBgNVBAMTCWxv +Y2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIEpWYgtXtcx0uJ2oFPK +RiII93iw5ITMrhMfBXQ0SzCfkUdvCJ0gNW+3isTBu4Jt0URpgP37eGwiJf2wPApq +KpajEjAQMA4GA1UdDwEB/wQEAwIHgDAKBggqhkjOPQQDAgNIADBFAiEA4IYil4G4 +cMxaVkcAnMGgiSdn7/qIgdhFB0Vx5AOd+EUCIGubRPhsXAJXvG//cK25mmxi3Wax +r7AgRKuDtWxn2bCO +-----END CERTIFICATE----- diff --git a/lib/nghttp2/src/testdata/nosan_ip.crt b/lib/nghttp2/src/testdata/nosan_ip.crt new file mode 100644 index 00000000000..717a1bd847a --- /dev/null +++ b/lib/nghttp2/src/testdata/nosan_ip.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBJzCBz6ADAgECAgEBMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMTCTEyNy4wLjAu +MTAeFw0yMzAzMTUxMjQ1MTVaFw0zMzAxMjExMjQ1MTVaMBQxEjAQBgNVBAMTCTEy +Ny4wLjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOXGPfSzXoeD7jszmAQO +qAhak5HQMTmj32Q/xqO9WmCnXRQ+T06701o6q1hjotrC/HdMk9kabsKHc9V7Bk4O +zkGjEjAQMA4GA1UdDwEB/wQEAwIHgDAKBggqhkjOPQQDAgNHADBEAiAI3fKrkNTN +IEo9qI8bd/pZ6on4d9vLcnHtqYhcuWZGTwIgW2zYMwASLUw4H1k/prBtTEEJOahJ +bvFs3oMbJEprQ+g= +-----END CERTIFICATE----- diff --git a/lib/nghttp2/src/testdata/verify_hostname.crt b/lib/nghttp2/src/testdata/verify_hostname.crt new file mode 100644 index 00000000000..a327616fde1 --- /dev/null +++ b/lib/nghttp2/src/testdata/verify_hostname.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBeTCCAR6gAwIBAgIBATAKBggqhkjOPQQDAjAUMRIwEAYDVQQDEwlsb2NhbGhv +c3QwHhcNMjMwMzE1MTIzNzU1WhcNMzMwMTIxMTIzNzU1WjAUMRIwEAYDVQQDEwls +b2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATMHcWmb55fi0KHNDwM +cYzVTAOfzJf44AqrqC+Pq2zW/ig8tPZbXf3eA/Vvp07Di+yWmuo3fGatUcY4nsx+ +Jd62o2EwXzAOBgNVHQ8BAf8EBAMCB4AwTQYDVR0RBEYwRIITbmdodHRwMi5leGFt +cGxlLmNvbYIVKi5uZ2h0dHAyLmV4YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAA +AAAAAAABMAoGCCqGSM49BAMCA0kAMEYCIQDQJFRJ3Ah4cGy7bwpkzVYeTgG+NhDa +55F4dPtJp9dS8wIhALQ9qf379lke1jVHg2t84iZLo3bL23RgICMezEYvqO3K +-----END CERTIFICATE----- diff --git a/lib/nghttp2/src/timegm.c b/lib/nghttp2/src/timegm.c new file mode 100644 index 00000000000..fc6df82494a --- /dev/null +++ b/lib/nghttp2/src/timegm.c @@ -0,0 +1,88 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "timegm.h" + +#include + +/* Counter the number of leap year in the range [0, y). The |y| is the + year, including century (e.g., 2012) */ +static int count_leap_year(int y) { + y -= 1; + return y / 4 - y / 100 + y / 400; +} + +/* Based on the algorithm of Python 2.7 calendar.timegm. */ +time_t nghttp2_timegm(struct tm *tm) { + int days; + int num_leap_year; + int64_t t; + if (tm->tm_mon > 11) { + return -1; + } + num_leap_year = count_leap_year(tm->tm_year + 1900) - count_leap_year(1970); + days = (tm->tm_year - 70) * 365 + num_leap_year + tm->tm_yday; + t = ((int64_t)days * 24 + tm->tm_hour) * 3600 + tm->tm_min * 60 + tm->tm_sec; + +#if SIZEOF_TIME_T == 4 + if (t < INT32_MIN || t > INT32_MAX) { + return -1; + } +#endif /* SIZEOF_TIME_T == 4 */ + + return (time_t)t; +} + +/* Returns nonzero if the |y| is the leap year. The |y| is the year, + including century (e.g., 2012) */ +static int is_leap_year(int y) { + return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); +} + +/* The number of days before ith month begins */ +static int daysum[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + +time_t nghttp2_timegm_without_yday(struct tm *tm) { + int days; + int num_leap_year; + int64_t t; + if (tm->tm_mon > 11) { + return -1; + } + num_leap_year = count_leap_year(tm->tm_year + 1900) - count_leap_year(1970); + days = (tm->tm_year - 70) * 365 + num_leap_year + daysum[tm->tm_mon] + + tm->tm_mday - 1; + if (tm->tm_mon >= 2 && is_leap_year(tm->tm_year + 1900)) { + ++days; + } + t = ((int64_t)days * 24 + tm->tm_hour) * 3600 + tm->tm_min * 60 + tm->tm_sec; + +#if SIZEOF_TIME_T == 4 + if (t < INT32_MIN || t > INT32_MAX) { + return -1; + } +#endif /* SIZEOF_TIME_T == 4 */ + + return (time_t)t; +} diff --git a/lib/nghttp2/src/timegm.h b/lib/nghttp2/src/timegm.h new file mode 100644 index 00000000000..56f9cc6c964 --- /dev/null +++ b/lib/nghttp2/src/timegm.h @@ -0,0 +1,51 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 TIMEGM_H +#define TIMEGM_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef HAVE_TIME_H +# include +#endif // HAVE_TIME_H + +time_t nghttp2_timegm(struct tm *tm); + +/* Just like nghttp2_timegm, but without using tm->tm_yday. This is + useful if we use tm from strptime, since some platforms do not + calculate tm_yday with that call. */ +time_t nghttp2_timegm_without_yday(struct tm *tm); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* TIMEGM_H */ diff --git a/lib/nghttp2/src/tls.cc b/lib/nghttp2/src/tls.cc new file mode 100644 index 00000000000..b0cadfd62f4 --- /dev/null +++ b/lib/nghttp2/src/tls.cc @@ -0,0 +1,201 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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.h" + +#include +#include +#include +#include + +#include +#include + +#include "ssl_compat.h" + +namespace nghttp2 { + +namespace tls { + +#if OPENSSL_1_1_API + +// CRYPTO_LOCK is deprecated as of OpenSSL 1.1.0 +LibsslGlobalLock::LibsslGlobalLock() {} + +#else // !OPENSSL_1_1_API + +namespace { +std::mutex *ssl_global_locks; +} // namespace + +namespace { +void ssl_locking_cb(int mode, int type, const char *file, int line) { + if (mode & CRYPTO_LOCK) { + ssl_global_locks[type].lock(); + } else { + ssl_global_locks[type].unlock(); + } +} +} // namespace + +LibsslGlobalLock::LibsslGlobalLock() { + if (ssl_global_locks) { + std::cerr << "OpenSSL global lock has been already set" << std::endl; + assert(0); + } + ssl_global_locks = new std::mutex[CRYPTO_num_locks()]; + // CRYPTO_set_id_callback(ssl_thread_id); OpenSSL manual says that + // if threadid_func is not specified using + // CRYPTO_THREADID_set_callback(), then default implementation is + // used. We use this default one. + CRYPTO_set_locking_callback(ssl_locking_cb); +} + +#endif // !OPENSSL_1_1_API + +const char *get_tls_protocol(SSL *ssl) { + switch (SSL_version(ssl)) { + case SSL2_VERSION: + return "SSLv2"; + case SSL3_VERSION: + return "SSLv3"; +#ifdef TLS1_3_VERSION + case TLS1_3_VERSION: + return "TLSv1.3"; +#endif // TLS1_3_VERSION + case TLS1_2_VERSION: + return "TLSv1.2"; + case TLS1_1_VERSION: + return "TLSv1.1"; + case TLS1_VERSION: + return "TLSv1"; + default: + return "unknown"; + } +} + +TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl) { + if (!ssl) { + return nullptr; + } + + auto session = SSL_get_session(ssl); + if (!session) { + return nullptr; + } + + tls_info->cipher = SSL_get_cipher_name(ssl); + tls_info->protocol = get_tls_protocol(ssl); + tls_info->session_reused = SSL_session_reused(ssl); + + unsigned int session_id_length; + tls_info->session_id = SSL_SESSION_get_id(session, &session_id_length); + tls_info->session_id_length = session_id_length; + + return tls_info; +} + +/* Conditional logic w/ lookup tables to check if id is one of the + the block listed cipher suites for HTTP/2 described in RFC 7540. + https://github.com/jay/http2_blacklisted_ciphers +*/ +#define IS_CIPHER_BANNED_METHOD2(id) \ + ((0x0000 <= id && id <= 0x00FF && \ + "\xFF\xFF\xFF\xCF\xFF\xFF\xFF\xFF\x7F\x00\x00\x00\x80\x3F\x00\x00" \ + "\xF0\xFF\xFF\x3F\xF3\xF3\xFF\xFF\x3F\x00\x00\x00\x00\x00\x00\x80" \ + [(id & 0xFF) / 8] & \ + (1 << (id % 8))) || \ + (0xC000 <= id && id <= 0xC0FF && \ + "\xFE\xFF\xFF\xFF\xFF\x67\xFE\xFF\xFF\xFF\x33\xCF\xFC\xCF\xFF\xCF" \ + "\x3C\xF3\xFC\x3F\x33\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + [(id & 0xFF) / 8] & \ + (1 << (id % 8)))) + +bool check_http2_cipher_block_list(SSL *ssl) { + int id = SSL_CIPHER_get_id(SSL_get_current_cipher(ssl)) & 0xFFFFFF; + + return IS_CIPHER_BANNED_METHOD2(id); +} + +bool check_http2_tls_version(SSL *ssl) { + auto tls_ver = SSL_version(ssl); + + return tls_ver >= TLS1_2_VERSION; +} + +bool check_http2_requirement(SSL *ssl) { + return check_http2_tls_version(ssl) && !check_http2_cipher_block_list(ssl); +} + +void libssl_init() { +#if OPENSSL_1_1_API +// No explicit initialization is required. +#elif defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + CRYPTO_library_init(); +#else // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + OPENSSL_config(nullptr); + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +#endif // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) +} + +int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max) { +#if OPENSSL_1_1_API || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + if (SSL_CTX_set_min_proto_version(ssl_ctx, min) != 1 || + SSL_CTX_set_max_proto_version(ssl_ctx, max) != 1) { + return -1; + } + return 0; +#else // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + long int opts = 0; + + // TODO We depends on the ordering of protocol version macro in + // OpenSSL. + if (min > TLS1_VERSION) { + opts |= SSL_OP_NO_TLSv1; + } + if (min > TLS1_1_VERSION) { + opts |= SSL_OP_NO_TLSv1_1; + } + if (min > TLS1_2_VERSION) { + opts |= SSL_OP_NO_TLSv1_2; + } + + if (max < TLS1_2_VERSION) { + opts |= SSL_OP_NO_TLSv1_2; + } + if (max < TLS1_1_VERSION) { + opts |= SSL_OP_NO_TLSv1_1; + } + + SSL_CTX_set_options(ssl_ctx, opts); + + return 0; +#endif // !OPENSSL_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) +} + +} // namespace tls + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/tls.h b/lib/nghttp2/src/tls.h new file mode 100644 index 00000000000..332ccb65310 --- /dev/null +++ b/lib/nghttp2/src/tls.h @@ -0,0 +1,116 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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_H +#define TLS_H + +#include "nghttp2_config.h" + +#include + +#include + +#include "ssl_compat.h" + +namespace nghttp2 { + +namespace tls { + +// Acquire OpenSSL global lock to share SSL_CTX across multiple +// threads. The constructor acquires lock and destructor unlocks. +class LibsslGlobalLock { +public: + LibsslGlobalLock(); + LibsslGlobalLock(const LibsslGlobalLock &) = delete; + LibsslGlobalLock &operator=(const LibsslGlobalLock &) = delete; +}; + +// Recommended general purpose "Intermediate compatibility" cipher +// suites for TLSv1.2 by mozilla. +// +// https://wiki.mozilla.org/Security/Server_Side_TLS +constexpr char DEFAULT_CIPHER_LIST[] = + "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-" + "AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-" + "POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-" + "AES256-GCM-SHA384"; + +// Recommended general purpose "Modern compatibility" cipher suites +// for TLSv1.3 by mozilla. +// +// https://wiki.mozilla.org/Security/Server_Side_TLS +constexpr char DEFAULT_TLS13_CIPHER_LIST[] = +#if OPENSSL_1_1_1_API && !defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" +#else + "" +#endif + ; + +constexpr auto NGHTTP2_TLS_MIN_VERSION = TLS1_VERSION; +#ifdef TLS1_3_VERSION +constexpr auto NGHTTP2_TLS_MAX_VERSION = TLS1_3_VERSION; +#else // !TLS1_3_VERSION +constexpr auto NGHTTP2_TLS_MAX_VERSION = TLS1_2_VERSION; +#endif // !TLS1_3_VERSION + +const char *get_tls_protocol(SSL *ssl); + +struct TLSSessionInfo { + const char *cipher; + const char *protocol; + const uint8_t *session_id; + bool session_reused; + size_t session_id_length; +}; + +TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl); + +// Returns true iff the negotiated protocol is TLSv1.2. +bool check_http2_tls_version(SSL *ssl); + +// Returns true iff the negotiated cipher suite is in HTTP/2 cipher +// block list. +bool check_http2_cipher_block_list(SSL *ssl); + +// Returns true if SSL/TLS requirement for HTTP/2 is fulfilled. +// To fulfill the requirement, the following 2 terms must be hold: +// +// 1. The negotiated protocol must be TLSv1.2. +// 2. The negotiated cipher cuite is not listed in the block list +// described in RFC 7540. +bool check_http2_requirement(SSL *ssl); + +// Initializes OpenSSL library +void libssl_init(); + +// Sets TLS min and max versions to |ssl_ctx|. This function returns +// 0 if it succeeds, or -1. +int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max); + +} // namespace tls + +} // namespace nghttp2 + +#endif // TLS_H diff --git a/lib/nghttp2/src/util.cc b/lib/nghttp2/src/util.cc new file mode 100644 index 00000000000..9d0c1bd7bd8 --- /dev/null +++ b/lib/nghttp2/src/util.cc @@ -0,0 +1,1808 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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_TIME_H +# include +#endif // HAVE_TIME_H +#include +#ifdef HAVE_SYS_SOCKET_H +# include +#endif // HAVE_SYS_SOCKET_H +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H +#include +#ifdef HAVE_FCNTL_H +# include +#endif // HAVE_FCNTL_H +#ifdef HAVE_NETINET_IN_H +# include +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_NETINET_IP_H +# include +#endif // HAVE_NETINET_IP_H +#include +#ifdef _WIN32 +# include +#else // !_WIN32 +# include +#endif // !_WIN32 +#ifdef HAVE_ARPA_INET_H +# include +#endif // HAVE_ARPA_INET_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "ssl_compat.h" +#include "timegm.h" + +namespace nghttp2 { + +namespace util { + +#ifndef _WIN32 +namespace { +int nghttp2_inet_pton(int af, const char *src, void *dst) { + return inet_pton(af, src, dst); +} +} // namespace +#else // _WIN32 +namespace { +// inet_pton-wrapper for Windows +int nghttp2_inet_pton(int af, const char *src, void *dst) { +# if _WIN32_WINNT >= 0x0600 + return InetPtonA(af, src, dst); +# else + // the function takes a 'char*', so we need to make a copy + char addr[INET6_ADDRSTRLEN + 1]; + strncpy(addr, src, sizeof(addr)); + addr[sizeof(addr) - 1] = 0; + + int size = sizeof(struct in6_addr); + + if (WSAStringToAddress(addr, af, nullptr, (LPSOCKADDR)dst, &size) == 0) + return 1; + return 0; +# endif +} +} // namespace +#endif // _WIN32 + +const char UPPER_XDIGITS[] = "0123456789ABCDEF"; + +bool in_rfc3986_unreserved_chars(const char c) { + switch (c) { + case '-': + case '.': + case '_': + case '~': + return true; + } + + return is_alpha(c) || is_digit(c); +} + +bool in_rfc3986_sub_delims(const char c) { + switch (c) { + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + return true; + } + + return false; +} + +std::string percent_encode(const unsigned char *target, size_t len) { + std::string dest; + for (size_t i = 0; i < len; ++i) { + unsigned char c = target[i]; + + if (in_rfc3986_unreserved_chars(c)) { + dest += c; + } else { + dest += '%'; + dest += UPPER_XDIGITS[c >> 4]; + dest += UPPER_XDIGITS[(c & 0x0f)]; + } + } + return dest; +} + +std::string percent_encode(const std::string &target) { + return percent_encode(reinterpret_cast(target.c_str()), + target.size()); +} + +bool in_token(char c) { + switch (c) { + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '*': + case '+': + case '-': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + return true; + } + + return is_alpha(c) || is_digit(c); +} + +bool in_attr_char(char c) { + switch (c) { + case '*': + case '\'': + case '%': + return false; + } + + return util::in_token(c); +} + +StringRef percent_encode_token(BlockAllocator &balloc, + const StringRef &target) { + auto iov = make_byte_ref(balloc, target.size() * 3 + 1); + auto p = percent_encode_token(iov.base, target); + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +size_t percent_encode_tokenlen(const StringRef &target) { + size_t n = 0; + + for (auto first = std::begin(target); first != std::end(target); ++first) { + uint8_t c = *first; + + if (c != '%' && in_token(c)) { + ++n; + continue; + } + + // percent-encoded character '%ff' + n += 3; + } + + return n; +} + +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; +} + +StringRef quote_string(BlockAllocator &balloc, const StringRef &target) { + auto cnt = std::count(std::begin(target), std::end(target), '"'); + + if (cnt == 0) { + return make_string_ref(balloc, target); + } + + auto iov = make_byte_ref(balloc, target.size() + cnt + 1); + auto p = quote_string(iov.base, target); + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +size_t quote_stringlen(const StringRef &target) { + size_t n = 0; + + for (auto c : target) { + if (c == '"') { + n += 2; + } else { + ++n; + } + } + + return n; +} + +namespace { +template +Iterator cpydig(Iterator d, uint32_t n, size_t len) { + auto p = d + len - 1; + + do { + *p-- = (n % 10) + '0'; + n /= 10; + } while (p >= d); + + return d + len; +} +} // namespace + +namespace { +constexpr const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +constexpr const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; +} // namespace + +std::string http_date(time_t t) { + /* Sat, 27 Sep 2014 06:31:15 GMT */ + std::string res(29, 0); + http_date(&res[0], t); + return res; +} + +char *http_date(char *res, time_t t) { + struct tm tms; + + if (gmtime_r(&t, &tms) == nullptr) { + return res; + } + + auto p = res; + + auto s = DAY_OF_WEEK[tms.tm_wday]; + p = std::copy_n(s, 3, p); + *p++ = ','; + *p++ = ' '; + p = cpydig(p, tms.tm_mday, 2); + *p++ = ' '; + s = MONTH[tms.tm_mon]; + p = std::copy_n(s, 3, p); + *p++ = ' '; + p = cpydig(p, tms.tm_year + 1900, 4); + *p++ = ' '; + p = cpydig(p, tms.tm_hour, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_min, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_sec, 2); + s = " GMT"; + p = std::copy_n(s, 4, p); + + return p; +} + +std::string common_log_date(time_t t) { + // 03/Jul/2014:00:19:38 +0900 + std::string res(26, 0); + common_log_date(&res[0], t); + return res; +} + +char *common_log_date(char *res, time_t t) { + struct tm tms; + + if (localtime_r(&t, &tms) == nullptr) { + return res; + } + + auto p = res; + + p = cpydig(p, tms.tm_mday, 2); + *p++ = '/'; + auto s = MONTH[tms.tm_mon]; + p = std::copy_n(s, 3, p); + *p++ = '/'; + p = cpydig(p, tms.tm_year + 1900, 4); + *p++ = ':'; + p = cpydig(p, tms.tm_hour, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_min, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_sec, 2); + *p++ = ' '; + +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = tms.tm_gmtoff; +#else // !HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = nghttp2_timegm(&tms) - t; +#endif // !HAVE_STRUCT_TM_TM_GMTOFF + if (gmtoff >= 0) { + *p++ = '+'; + } else { + *p++ = '-'; + gmtoff = -gmtoff; + } + + p = cpydig(p, gmtoff / 3600, 2); + p = cpydig(p, (gmtoff % 3600) / 60, 2); + + return p; +} + +std::string iso8601_date(int64_t ms) { + // 2014-11-15T12:58:24.741Z + // 2014-11-15T12:58:24.741+09:00 + std::string res(29, 0); + auto p = iso8601_date(&res[0], ms); + res.resize(p - &res[0]); + return res; +} + +char *iso8601_date(char *res, int64_t ms) { + time_t sec = ms / 1000; + + tm tms; + if (localtime_r(&sec, &tms) == nullptr) { + return res; + } + + auto p = res; + + p = cpydig(p, tms.tm_year + 1900, 4); + *p++ = '-'; + p = cpydig(p, tms.tm_mon + 1, 2); + *p++ = '-'; + p = cpydig(p, tms.tm_mday, 2); + *p++ = 'T'; + p = cpydig(p, tms.tm_hour, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_min, 2); + *p++ = ':'; + p = cpydig(p, tms.tm_sec, 2); + *p++ = '.'; + p = cpydig(p, ms % 1000, 3); + +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = tms.tm_gmtoff; +#else // !HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = nghttp2_timegm(&tms) - sec; +#endif // !HAVE_STRUCT_TM_TM_GMTOFF + if (gmtoff == 0) { + *p++ = 'Z'; + } else { + if (gmtoff > 0) { + *p++ = '+'; + } else { + *p++ = '-'; + gmtoff = -gmtoff; + } + p = cpydig(p, gmtoff / 3600, 2); + *p++ = ':'; + p = cpydig(p, (gmtoff % 3600) / 60, 2); + } + + return p; +} + +char *iso8601_basic_date(char *res, int64_t ms) { + time_t sec = ms / 1000; + + tm tms; + if (localtime_r(&sec, &tms) == nullptr) { + return res; + } + + auto p = res; + + p = cpydig(p, tms.tm_year + 1900, 4); + p = cpydig(p, tms.tm_mon + 1, 2); + p = cpydig(p, tms.tm_mday, 2); + *p++ = 'T'; + p = cpydig(p, tms.tm_hour, 2); + p = cpydig(p, tms.tm_min, 2); + p = cpydig(p, tms.tm_sec, 2); + *p++ = '.'; + p = cpydig(p, ms % 1000, 3); + +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = tms.tm_gmtoff; +#else // !HAVE_STRUCT_TM_TM_GMTOFF + auto gmtoff = nghttp2_timegm(&tms) - sec; +#endif // !HAVE_STRUCT_TM_TM_GMTOFF + if (gmtoff == 0) { + *p++ = 'Z'; + } else { + if (gmtoff > 0) { + *p++ = '+'; + } else { + *p++ = '-'; + gmtoff = -gmtoff; + } + p = cpydig(p, gmtoff / 3600, 2); + p = cpydig(p, (gmtoff % 3600) / 60, 2); + } + + return p; +} + +time_t parse_http_date(const StringRef &s) { + tm tm{}; +#ifdef _WIN32 + // there is no strptime - use std::get_time + std::stringstream sstr(s.str()); + sstr >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S GMT"); + if (sstr.fail()) { + return 0; + } +#else // !_WIN32 + char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm); + if (r == 0) { + return 0; + } +#endif // !_WIN32 + return nghttp2_timegm_without_yday(&tm); +} + +time_t parse_openssl_asn1_time_print(const StringRef &s) { + tm tm{}; + auto r = strptime(s.c_str(), "%b %d %H:%M:%S %Y GMT", &tm); + if (r == nullptr) { + return 0; + } + return nghttp2_timegm_without_yday(&tm); +} + +char upcase(char c) { + if ('a' <= c && c <= 'z') { + return c - 'a' + 'A'; + } else { + return c; + } +} + +std::string format_hex(const unsigned char *s, size_t len) { + std::string res; + res.resize(len * 2); + + for (size_t i = 0; i < len; ++i) { + unsigned char c = s[i]; + + res[i * 2] = LOWER_XDIGITS[c >> 4]; + res[i * 2 + 1] = LOWER_XDIGITS[c & 0x0f]; + } + return res; +} + +StringRef format_hex(BlockAllocator &balloc, const StringRef &s) { + auto iov = make_byte_ref(balloc, s.size() * 2 + 1); + auto p = iov.base; + + for (auto cc : s) { + uint8_t c = cc; + *p++ = LOWER_XDIGITS[c >> 4]; + *p++ = LOWER_XDIGITS[c & 0xf]; + } + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +void to_token68(std::string &base64str) { + std::transform(std::begin(base64str), std::end(base64str), + std::begin(base64str), [](char c) { + switch (c) { + case '+': + return '-'; + case '/': + return '_'; + default: + return c; + } + }); + base64str.erase(std::find(std::begin(base64str), std::end(base64str), '='), + std::end(base64str)); +} + +StringRef to_base64(BlockAllocator &balloc, const StringRef &token68str) { + // At most 3 padding '=' + auto len = token68str.size() + 3; + auto iov = make_byte_ref(balloc, len + 1); + auto p = iov.base; + + p = std::transform(std::begin(token68str), std::end(token68str), p, + [](char c) { + switch (c) { + case '-': + return '+'; + case '_': + return '/'; + default: + return c; + } + }); + + auto rem = token68str.size() & 0x3; + if (rem) { + p = std::fill_n(p, 4 - rem, '='); + } + + *p = '\0'; + + return StringRef{iov.base, p}; +} + +namespace { +// Calculates Damerau–Levenshtein distance between c-string a and b +// with given costs. swapcost, subcost, addcost and delcost are cost +// to swap 2 adjacent characters, substitute characters, add character +// and delete character respectively. +int levenshtein(const char *a, int alen, const char *b, int blen, int swapcost, + int subcost, int addcost, int delcost) { + auto dp = std::vector>(3, std::vector(blen + 1)); + for (int i = 0; i <= blen; ++i) { + dp[1][i] = i; + } + for (int i = 1; i <= alen; ++i) { + dp[0][0] = i; + for (int j = 1; j <= blen; ++j) { + dp[0][j] = dp[1][j - 1] + (a[i - 1] == b[j - 1] ? 0 : subcost); + if (i >= 2 && j >= 2 && a[i - 1] != b[j - 1] && a[i - 2] == b[j - 1] && + a[i - 1] == b[j - 2]) { + dp[0][j] = std::min(dp[0][j], dp[2][j - 2] + swapcost); + } + dp[0][j] = std::min(dp[0][j], + std::min(dp[1][j] + delcost, dp[0][j - 1] + addcost)); + } + std::rotate(std::begin(dp), std::begin(dp) + 2, std::end(dp)); + } + return dp[1][blen]; +} +} // namespace + +void show_candidates(const char *unkopt, const option *options) { + for (; *unkopt == '-'; ++unkopt) + ; + if (*unkopt == '\0') { + return; + } + auto unkoptend = unkopt; + for (; *unkoptend && *unkoptend != '='; ++unkoptend) + ; + auto unkoptlen = unkoptend - unkopt; + if (unkoptlen == 0) { + return; + } + int prefix_match = 0; + auto cands = std::vector>(); + for (size_t i = 0; options[i].name != nullptr; ++i) { + auto optnamelen = strlen(options[i].name); + // Use cost 0 for prefix match + if (istarts_with(options[i].name, options[i].name + optnamelen, unkopt, + unkopt + unkoptlen)) { + if (optnamelen == static_cast(unkoptlen)) { + // Exact match, then we don't show any condidates. + return; + } + ++prefix_match; + cands.emplace_back(0, options[i].name); + continue; + } + // Use cost 0 for suffix match, but match at least 3 characters + if (unkoptlen >= 3 && + iends_with(options[i].name, options[i].name + optnamelen, unkopt, + unkopt + unkoptlen)) { + cands.emplace_back(0, options[i].name); + continue; + } + // cost values are borrowed from git, help.c. + int sim = + levenshtein(unkopt, unkoptlen, options[i].name, optnamelen, 0, 2, 1, 3); + cands.emplace_back(sim, options[i].name); + } + if (prefix_match == 1 || cands.empty()) { + return; + } + std::sort(std::begin(cands), std::end(cands)); + int threshold = cands[0].first; + // threshold value is a magic value. + if (threshold > 6) { + return; + } + std::cerr << "\nDid you mean:\n"; + for (auto &item : cands) { + if (item.first > threshold) { + break; + } + std::cerr << "\t--" << item.second << "\n"; + } +} + +bool has_uri_field(const http_parser_url &u, http_parser_url_fields field) { + return u.field_set & (1 << field); +} + +bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2, + const http_parser_url &u2, http_parser_url_fields field) { + if (!has_uri_field(u1, field)) { + if (!has_uri_field(u2, field)) { + return true; + } else { + return false; + } + } else if (!has_uri_field(u2, field)) { + return false; + } + if (u1.field_data[field].len != u2.field_data[field].len) { + return false; + } + return memcmp(uri1 + u1.field_data[field].off, + uri2 + u2.field_data[field].off, u1.field_data[field].len) == 0; +} + +bool fieldeq(const char *uri, const http_parser_url &u, + http_parser_url_fields field, const char *t) { + return fieldeq(uri, u, field, StringRef{t}); +} + +bool fieldeq(const char *uri, const http_parser_url &u, + http_parser_url_fields field, const StringRef &t) { + if (!has_uri_field(u, field)) { + return t.empty(); + } + auto &f = u.field_data[field]; + return StringRef{uri + f.off, f.len} == t; +} + +StringRef get_uri_field(const char *uri, const http_parser_url &u, + http_parser_url_fields field) { + if (!util::has_uri_field(u, field)) { + return StringRef{}; + } + + return StringRef{uri + u.field_data[field].off, u.field_data[field].len}; +} + +uint16_t get_default_port(const char *uri, const http_parser_url &u) { + if (util::fieldeq(uri, u, UF_SCHEMA, "https")) { + return 443; + } else if (util::fieldeq(uri, u, UF_SCHEMA, "http")) { + return 80; + } else { + return 443; + } +} + +bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2, + const http_parser_url &u2) { + uint16_t port1, port2; + port1 = + util::has_uri_field(u1, UF_PORT) ? u1.port : get_default_port(uri1, u1); + port2 = + util::has_uri_field(u2, UF_PORT) ? u2.port : get_default_port(uri2, u2); + return port1 == port2; +} + +void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u, + http_parser_url_fields field) { + if (util::has_uri_field(u, field)) { + o.write(uri + u.field_data[field].off, u.field_data[field].len); + } +} + +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 dst; + + rv = nghttp2_inet_pton(family, hostname, dst.data()); + + return rv == 1; +} + +std::string numeric_name(const struct sockaddr *sa, socklen_t salen) { + std::array host; + auto rv = getnameinfo(sa, salen, host.data(), host.size(), nullptr, 0, + NI_NUMERICHOST); + if (rv != 0) { + return "unknown"; + } + return host.data(); +} + +std::string to_numeric_addr(const Address *addr) { + return to_numeric_addr(&addr->su.sa, addr->len); +} + +std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen) { + auto family = sa->sa_family; +#ifndef _WIN32 + if (family == AF_UNIX) { + return reinterpret_cast(sa)->sun_path; + } +#endif // !_WIN32 + + std::array host; + std::array serv; + auto rv = getnameinfo(sa, salen, host.data(), host.size(), serv.data(), + serv.size(), NI_NUMERICHOST | NI_NUMERICSERV); + if (rv != 0) { + return "unknown"; + } + + auto hostlen = strlen(host.data()); + auto servlen = strlen(serv.data()); + + std::string s; + char *p; + if (family == AF_INET6) { + s.resize(hostlen + servlen + 2 + 1); + p = &s[0]; + *p++ = '['; + p = std::copy_n(host.data(), hostlen, p); + *p++ = ']'; + } else { + s.resize(hostlen + servlen + 1); + p = &s[0]; + p = std::copy_n(host.data(), hostlen, p); + } + *p++ = ':'; + std::copy_n(serv.data(), servlen, p); + + return s; +} + +void set_port(Address &addr, uint16_t port) { + switch (addr.su.storage.ss_family) { + case AF_INET: + addr.su.in.sin_port = htons(port); + break; + case AF_INET6: + addr.su.in6.sin6_port = htons(port); + break; + } +} + +std::string ascii_dump(const uint8_t *data, size_t len) { + std::string res; + + for (size_t i = 0; i < len; ++i) { + auto c = data[i]; + + if (c >= 0x20 && c < 0x7f) { + res += c; + } else { + res += '.'; + } + } + + return res; +} + +char *get_exec_path(int argc, char **const argv, const char *cwd) { + if (argc == 0 || cwd == nullptr) { + return nullptr; + } + + auto argv0 = argv[0]; + auto len = strlen(argv0); + + char *path; + + if (argv0[0] == '/') { + path = static_cast(malloc(len + 1)); + if (path == nullptr) { + return nullptr; + } + memcpy(path, argv0, len + 1); + } else { + auto cwdlen = strlen(cwd); + path = static_cast(malloc(len + 1 + cwdlen + 1)); + if (path == nullptr) { + return nullptr; + } + memcpy(path, cwd, cwdlen); + path[cwdlen] = '/'; + memcpy(path + cwdlen + 1, argv0, len + 1); + } + + return path; +} + +bool check_path(const std::string &path) { + // We don't like '\' in path. + return !path.empty() && path[0] == '/' && + path.find('\\') == std::string::npos && + path.find("/../") == std::string::npos && + path.find("/./") == std::string::npos && + !util::ends_with_l(path, "/..") && !util::ends_with_l(path, "/."); +} + +int64_t to_time64(const timeval &tv) { + return tv.tv_sec * 1000000 + tv.tv_usec; +} + +bool check_h2_is_selected(const StringRef &proto) { + return streq(NGHTTP2_H2, proto) || streq(NGHTTP2_H2_16, proto) || + streq(NGHTTP2_H2_14, proto); +} + +namespace { +bool select_proto(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const StringRef &key) { + for (auto p = in, end = in + inlen; p + key.size() <= end; p += *p + 1) { + if (std::equal(std::begin(key), std::end(key), p)) { + *out = p + 1; + *outlen = *p; + return true; + } + } + return false; +} +} // namespace + +bool select_h2(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen) { + return select_proto(out, outlen, in, inlen, NGHTTP2_H2_ALPN) || + select_proto(out, outlen, in, inlen, NGHTTP2_H2_16_ALPN) || + select_proto(out, outlen, in, inlen, NGHTTP2_H2_14_ALPN); +} + +bool select_protocol(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + std::vector proto_list) { + for (const auto &proto : proto_list) { + if (select_proto(out, outlen, in, inlen, StringRef{proto})) { + return true; + } + } + + return false; +} + +std::vector get_default_alpn() { + auto res = std::vector(NGHTTP2_H2_ALPN.size() + + NGHTTP2_H2_16_ALPN.size() + + NGHTTP2_H2_14_ALPN.size()); + auto p = std::begin(res); + + p = std::copy_n(std::begin(NGHTTP2_H2_ALPN), NGHTTP2_H2_ALPN.size(), p); + p = std::copy_n(std::begin(NGHTTP2_H2_16_ALPN), NGHTTP2_H2_16_ALPN.size(), p); + p = std::copy_n(std::begin(NGHTTP2_H2_14_ALPN), NGHTTP2_H2_14_ALPN.size(), p); + + return res; +} + +std::vector split_str(const StringRef &s, char delim) { + size_t len = 1; + auto last = std::end(s); + StringRef::const_iterator d; + for (auto first = std::begin(s); (d = std::find(first, last, delim)) != last; + ++len, first = d + 1) + ; + + auto list = std::vector(len); + + len = 0; + for (auto first = std::begin(s);; ++len) { + auto stop = std::find(first, last, delim); + list[len] = StringRef{first, stop}; + if (stop == last) { + break; + } + first = stop + 1; + } + return list; +} + +std::vector split_str(const StringRef &s, char delim, size_t n) { + if (n == 0) { + return split_str(s, delim); + } + + if (n == 1) { + return {s}; + } + + size_t len = 1; + auto last = std::end(s); + StringRef::const_iterator d; + for (auto first = std::begin(s); + len < n && (d = std::find(first, last, delim)) != last; + ++len, first = d + 1) + ; + + auto list = std::vector(len); + + len = 0; + for (auto first = std::begin(s);; ++len) { + if (len == n - 1) { + list[len] = StringRef{first, last}; + break; + } + + auto stop = std::find(first, last, delim); + list[len] = StringRef{first, stop}; + if (stop == last) { + break; + } + first = stop + 1; + } + return list; +} + +std::vector parse_config_str_list(const StringRef &s, char delim) { + auto sublist = split_str(s, delim); + auto res = std::vector(); + res.reserve(sublist.size()); + for (const auto &s : sublist) { + res.emplace_back(std::begin(s), std::end(s)); + } + return res; +} + +int make_socket_closeonexec(int fd) { +#ifdef _WIN32 + (void)fd; + return 0; +#else // !_WIN32 + int flags; + int rv; + while ((flags = fcntl(fd, F_GETFD)) == -1 && errno == EINTR) + ; + while ((rv = fcntl(fd, F_SETFD, flags | FD_CLOEXEC)) == -1 && errno == EINTR) + ; + return rv; +#endif // !_WIN32 +} + +int make_socket_nonblocking(int fd) { + int rv; + +#ifdef _WIN32 + u_long mode = 1; + + rv = ioctlsocket(fd, FIONBIO, &mode); +#else // !_WIN32 + int flags; + while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR) + ; + while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR) + ; +#endif // !_WIN32 + + return rv; +} + +int make_socket_nodelay(int fd) { + int val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&val), + sizeof(val)) == -1) { + return -1; + } + return 0; +} + +int create_nonblock_socket(int family) { +#ifdef SOCK_NONBLOCK + auto fd = socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + + if (fd == -1) { + return -1; + } +#else // !SOCK_NONBLOCK + auto fd = socket(family, SOCK_STREAM, 0); + + if (fd == -1) { + return -1; + } + + make_socket_nonblocking(fd); + make_socket_closeonexec(fd); +#endif // !SOCK_NONBLOCK + + if (family == AF_INET || family == AF_INET6) { + make_socket_nodelay(fd); + } + + return fd; +} + +int create_nonblock_udp_socket(int family) { +#ifdef SOCK_NONBLOCK + auto fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + + if (fd == -1) { + return -1; + } +#else // !SOCK_NONBLOCK + auto fd = socket(family, SOCK_DGRAM, 0); + + if (fd == -1) { + return -1; + } + + make_socket_nonblocking(fd); + make_socket_closeonexec(fd); +#endif // !SOCK_NONBLOCK + + return fd; +} + +int bind_any_addr_udp(int fd, int family) { + addrinfo hints{}; + addrinfo *res, *rp; + int rv; + + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + rv = getaddrinfo(nullptr, "0", &hints, &res); + if (rv != 0) { + return -1; + } + + for (rp = res; rp; rp = rp->ai_next) { + if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + } + + freeaddrinfo(res); + + if (!rp) { + return -1; + } + + return 0; +} + +bool check_socket_connected(int fd) { + int error; + socklen_t len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) != 0) { + return false; + } + + return error == 0; +} + +int get_socket_error(int fd) { + int error; + socklen_t len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len) != 0) { + return -1; + } + + return error; +} + +bool ipv6_numeric_addr(const char *host) { + uint8_t dst[16]; + return nghttp2_inet_pton(AF_INET6, host, dst) == 1; +} + +namespace { +std::pair parse_uint_digits(const void *ss, size_t len) { + const uint8_t *s = static_cast(ss); + int64_t n = 0; + size_t i; + if (len == 0) { + return {-1, 0}; + } + constexpr int64_t max = std::numeric_limits::max(); + for (i = 0; i < len; ++i) { + if ('0' <= s[i] && s[i] <= '9') { + if (n > max / 10) { + return {-1, 0}; + } + n *= 10; + if (n > max - (s[i] - '0')) { + return {-1, 0}; + } + n += s[i] - '0'; + continue; + } + break; + } + if (i == 0) { + return {-1, 0}; + } + return {n, i}; +} +} // namespace + +int64_t parse_uint_with_unit(const char *s) { + return parse_uint_with_unit(reinterpret_cast(s), strlen(s)); +} + +int64_t parse_uint_with_unit(const StringRef &s) { + return parse_uint_with_unit(s.byte(), s.size()); +} + +int64_t parse_uint_with_unit(const uint8_t *s, size_t len) { + int64_t n; + size_t i; + std::tie(n, i) = parse_uint_digits(s, len); + if (n == -1) { + return -1; + } + if (i == len) { + return n; + } + if (i + 1 != len) { + return -1; + } + int mul = 1; + switch (s[i]) { + case 'K': + case 'k': + mul = 1 << 10; + break; + case 'M': + case 'm': + mul = 1 << 20; + break; + case 'G': + case 'g': + mul = 1 << 30; + break; + default: + return -1; + } + constexpr int64_t max = std::numeric_limits::max(); + if (n > max / mul) { + return -1; + } + return n * mul; +} + +int64_t parse_uint(const char *s) { + return parse_uint(reinterpret_cast(s), strlen(s)); +} + +int64_t parse_uint(const std::string &s) { + return parse_uint(reinterpret_cast(s.c_str()), s.size()); +} + +int64_t parse_uint(const StringRef &s) { + return parse_uint(s.byte(), s.size()); +} + +int64_t parse_uint(const uint8_t *s, size_t len) { + int64_t n; + size_t i; + std::tie(n, i) = parse_uint_digits(s, len); + if (n == -1 || i != len) { + return -1; + } + return n; +} + +double parse_duration_with_unit(const char *s) { + return parse_duration_with_unit(reinterpret_cast(s), + strlen(s)); +} + +double parse_duration_with_unit(const StringRef &s) { + return parse_duration_with_unit(s.byte(), s.size()); +} + +double parse_duration_with_unit(const uint8_t *s, size_t len) { + constexpr auto max = std::numeric_limits::max(); + int64_t n; + size_t i; + + std::tie(n, i) = parse_uint_digits(s, len); + if (n == -1) { + goto fail; + } + if (i == len) { + return static_cast(n); + } + switch (s[i]) { + case 'S': + case 's': + // seconds + if (i + 1 != len) { + goto fail; + } + return static_cast(n); + case 'M': + case 'm': + if (i + 1 == len) { + // minutes + if (n > max / 60) { + goto fail; + } + return static_cast(n) * 60; + } + + if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) { + goto fail; + } + // milliseconds + return static_cast(n) / 1000.; + case 'H': + case 'h': + // hours + if (i + 1 != len) { + goto fail; + } + if (n > max / 3600) { + goto fail; + } + return static_cast(n) * 3600; + } +fail: + return std::numeric_limits::infinity(); +} + +std::string duration_str(double t) { + if (t == 0.) { + return "0"; + } + auto frac = static_cast(t * 1000) % 1000; + if (frac > 0) { + return utos(static_cast(t * 1000)) + "ms"; + } + auto v = static_cast(t); + if (v % 60) { + return utos(v) + "s"; + } + v /= 60; + if (v % 60) { + return utos(v) + "m"; + } + v /= 60; + return utos(v) + "h"; +} + +std::string format_duration(const std::chrono::microseconds &u) { + const char *unit = "us"; + int d = 0; + auto t = u.count(); + if (t >= 1000000) { + d = 1000000; + unit = "s"; + } else if (t >= 1000) { + d = 1000; + unit = "ms"; + } else { + return utos(t) + unit; + } + return dtos(static_cast(t) / d) + unit; +} + +std::string format_duration(double t) { + const char *unit = "us"; + if (t >= 1.) { + unit = "s"; + } else if (t >= 0.001) { + t *= 1000.; + unit = "ms"; + } else { + t *= 1000000.; + return utos(static_cast(t)) + unit; + } + return dtos(t) + unit; +} + +std::string dtos(double n) { + auto m = llround(100. * n); + auto f = utos(m % 100); + return utos(m / 100) + "." + (f.size() == 1 ? "0" : "") + f; +} + +StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host, + uint16_t port) { + auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1); + return make_http_hostport(iov.base, host, port); +} + +StringRef make_hostport(BlockAllocator &balloc, const StringRef &host, + uint16_t port) { + auto iov = make_byte_ref(balloc, host.size() + 2 + 1 + 5 + 1); + return make_hostport(iov.base, host, port); +} + +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 buf{}; + auto end = src + len; + auto i = src; + for (;;) { + auto nextlen = + std::min(static_cast(16), static_cast(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(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); + } +} + +void put_uint16be(uint8_t *buf, uint16_t n) { + uint16_t x = htons(n); + memcpy(buf, &x, sizeof(uint16_t)); +} + +void put_uint32be(uint8_t *buf, uint32_t n) { + uint32_t x = htonl(n); + memcpy(buf, &x, sizeof(uint32_t)); +} + +uint16_t get_uint16(const uint8_t *data) { + uint16_t n; + memcpy(&n, data, sizeof(uint16_t)); + return ntohs(n); +} + +uint32_t get_uint32(const uint8_t *data) { + uint32_t n; + memcpy(&n, data, sizeof(uint32_t)); + return ntohl(n); +} + +uint64_t get_uint64(const uint8_t *data) { + uint64_t n = 0; + n += static_cast(data[0]) << 56; + n += static_cast(data[1]) << 48; + n += static_cast(data[2]) << 40; + n += static_cast(data[3]) << 32; + n += static_cast(data[4]) << 24; + n += data[5] << 16; + n += data[6] << 8; + n += data[7]; + return n; +} + +int read_mime_types(std::map &res, + const char *filename) { + std::ifstream infile(filename); + if (!infile) { + return -1; + } + + auto delim_pred = [](char c) { return c == ' ' || c == '\t'; }; + + std::string line; + while (std::getline(infile, line)) { + if (line.empty() || line[0] == '#') { + continue; + } + + auto type_end = std::find_if(std::begin(line), std::end(line), delim_pred); + if (type_end == std::begin(line)) { + continue; + } + + auto ext_end = type_end; + for (;;) { + auto ext_start = std::find_if_not(ext_end, std::end(line), delim_pred); + if (ext_start == std::end(line)) { + break; + } + ext_end = std::find_if(ext_start, std::end(line), delim_pred); +#ifdef HAVE_STD_MAP_EMPLACE + res.emplace(std::string(ext_start, ext_end), + std::string(std::begin(line), type_end)); +#else // !HAVE_STD_MAP_EMPLACE + res.insert(std::make_pair(std::string(ext_start, ext_end), + std::string(std::begin(line), type_end))); +#endif // !HAVE_STD_MAP_EMPLACE + } + } + + return 0; +} + +StringRef percent_decode(BlockAllocator &balloc, const StringRef &src) { + auto iov = make_byte_ref(balloc, src.size() * 3 + 1); + auto p = iov.base; + for (auto first = std::begin(src); first != std::end(src); ++first) { + if (*first != '%') { + *p++ = *first; + continue; + } + + if (first + 1 != std::end(src) && first + 2 != std::end(src) && + 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; + } + *p = '\0'; + return StringRef{iov.base, p}; +} + +// Returns x**y +double int_pow(double x, size_t y) { + auto res = 1.; + for (; y; --y) { + res *= x; + } + return res; +} + +uint32_t hash32(const StringRef &s) { + /* 32 bit FNV-1a: http://isthe.com/chongo/tech/comp/fnv/ */ + uint32_t h = 2166136261u; + size_t i; + + for (i = 0; i < s.size(); ++i) { + h ^= s[i]; + h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24); + } + + return h; +} + +#if !OPENSSL_1_1_API +namespace { +EVP_MD_CTX *EVP_MD_CTX_new(void) { return EVP_MD_CTX_create(); } +} // namespace + +namespace { +void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); } +} // namespace +#endif // !OPENSSL_1_1_API + +namespace { +int message_digest(uint8_t *res, const EVP_MD *meth, const StringRef &s) { + int rv; + + auto ctx = EVP_MD_CTX_new(); + if (ctx == nullptr) { + return -1; + } + + auto ctx_deleter = defer(EVP_MD_CTX_free, ctx); + + rv = EVP_DigestInit_ex(ctx, meth, nullptr); + if (rv != 1) { + return -1; + } + + rv = EVP_DigestUpdate(ctx, s.c_str(), s.size()); + if (rv != 1) { + return -1; + } + + unsigned int mdlen = EVP_MD_size(meth); + + rv = EVP_DigestFinal_ex(ctx, res, &mdlen); + if (rv != 1) { + return -1; + } + + return 0; +} +} // namespace + +int sha256(uint8_t *res, const StringRef &s) { + return message_digest(res, EVP_sha256(), s); +} + +int sha1(uint8_t *res, const StringRef &s) { + return message_digest(res, EVP_sha1(), s); +} + +bool is_hex_string(const StringRef &s) { + if (s.size() % 2) { + return false; + } + + for (auto c : s) { + if (!is_hex_digit(c)) { + return false; + } + } + + return true; +} + +StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) { + auto iov = make_byte_ref(balloc, s.size() + 1); + auto p = decode_hex(iov.base, s); + *p = '\0'; + return StringRef{iov.base, p}; +} + +StringRef extract_host(const StringRef &hostport) { + if (hostport[0] == '[') { + // assume this is IPv6 numeric address + auto p = std::find(std::begin(hostport), std::end(hostport), ']'); + if (p == std::end(hostport)) { + return StringRef{}; + } + if (p + 1 < std::end(hostport) && *(p + 1) != ':') { + return StringRef{}; + } + return StringRef{std::begin(hostport), p + 1}; + } + + auto p = std::find(std::begin(hostport), std::end(hostport), ':'); + if (p == std::begin(hostport)) { + return StringRef{}; + } + return StringRef{std::begin(hostport), p}; +} + +std::pair split_hostport(const StringRef &hostport) { + if (hostport.empty()) { + return {}; + } + if (hostport[0] == '[') { + // assume this is IPv6 numeric address + auto p = std::find(std::begin(hostport), std::end(hostport), ']'); + if (p == std::end(hostport)) { + return {}; + } + if (p + 1 == std::end(hostport)) { + return {StringRef{std::begin(hostport) + 1, p}, {}}; + } + if (*(p + 1) != ':' || p + 2 == std::end(hostport)) { + return {}; + } + return {StringRef{std::begin(hostport) + 1, p}, + StringRef{p + 2, std::end(hostport)}}; + } + + auto p = std::find(std::begin(hostport), std::end(hostport), ':'); + if (p == std::begin(hostport)) { + return {}; + } + if (p == std::end(hostport)) { + return {StringRef{std::begin(hostport), p}, {}}; + } + if (p + 1 == std::end(hostport)) { + return {}; + } + + return {StringRef{std::begin(hostport), p}, + StringRef{p + 1, std::end(hostport)}}; +} + +std::mt19937 make_mt19937() { + std::random_device rd; + return std::mt19937(rd()); +} + +int daemonize(int nochdir, int noclose) { +#ifdef __APPLE__ + pid_t pid; + pid = fork(); + if (pid == -1) { + return -1; + } else if (pid > 0) { + _exit(EXIT_SUCCESS); + } + if (setsid() == -1) { + return -1; + } + pid = fork(); + if (pid == -1) { + return -1; + } else if (pid > 0) { + _exit(EXIT_SUCCESS); + } + if (nochdir == 0) { + if (chdir("/") == -1) { + return -1; + } + } + if (noclose == 0) { + if (freopen("/dev/null", "r", stdin) == nullptr) { + return -1; + } + if (freopen("/dev/null", "w", stdout) == nullptr) { + return -1; + } + if (freopen("/dev/null", "w", stderr) == nullptr) { + return -1; + } + } + return 0; +#else // !__APPLE__ + return daemon(nochdir, noclose); +#endif // !__APPLE__ +} + +StringRef rstrip(BlockAllocator &balloc, const StringRef &s) { + auto it = std::rbegin(s); + for (; it != std::rend(s) && (*it == ' ' || *it == '\t'); ++it) + ; + + auto len = it - std::rbegin(s); + if (len == 0) { + return s; + } + + return make_string_ref(balloc, StringRef{s.c_str(), s.size() - len}); +} + +#ifdef ENABLE_HTTP3 +int msghdr_get_local_addr(Address &dest, 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) { + in_pktinfo pktinfo; + memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo)); + dest.len = sizeof(dest.su.in); + auto &sa = dest.su.in; + sa.sin_family = AF_INET; + sa.sin_addr = pktinfo.ipi_addr; + + return 0; + } + } + + return -1; + 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) { + in6_pktinfo pktinfo; + memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo)); + dest.len = sizeof(dest.su.in6); + auto &sa = dest.su.in6; + sa.sin6_family = AF_INET6; + sa.sin6_addr = pktinfo.ipi6_addr; + return 0; + } + } + + return -1; + } + + return -1; +} + +uint8_t 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 && +# ifdef __APPLE__ + cmsg->cmsg_type == IP_RECVTOS +# else // !__APPLE__ + cmsg->cmsg_type == IP_TOS +# endif // !__APPLE__ + && cmsg->cmsg_len) { + return *reinterpret_cast(CMSG_DATA(cmsg)) & IPTOS_ECN_MASK; + } + } + + return 0; + 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) { + unsigned int tos; + + memcpy(&tos, CMSG_DATA(cmsg), sizeof(tos)); + + return tos & IPTOS_ECN_MASK; + } + } + + return 0; + } + + return 0; +} + +size_t msghdr_get_udp_gro(msghdr *msg) { + uint16_t gso_size = 0; + +# ifdef UDP_GRO + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) { + memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size)); + + break; + } + } +# endif // UDP_GRO + + return gso_size; +} +#endif // ENABLE_HTTP3 + +} // namespace util + +} // namespace nghttp2 diff --git a/lib/nghttp2/src/util.h b/lib/nghttp2/src/util.h new file mode 100644 index 00000000000..d818bf2fd3b --- /dev/null +++ b/lib/nghttp2/src/util.h @@ -0,0 +1,971 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 + +#include "nghttp2_config.h" + +#ifdef HAVE_UNISTD_H +# include +#endif // HAVE_UNISTD_H +#include +#ifdef HAVE_NETDB_H +# include +#endif // HAVE_NETDB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBEV +# include +#endif // HAVE_LIBEV + +#include "url-parser/url_parser.h" + +#include "template.h" +#include "network.h" +#include "allocator.h" + +namespace nghttp2 { + +constexpr auto NGHTTP2_H2_ALPN = StringRef::from_lit("\x2h2"); +constexpr auto NGHTTP2_H2 = StringRef::from_lit("h2"); + +// The additional HTTP/2 protocol ALPN protocol identifier we also +// supports for our applications to make smooth migration into final +// h2 ALPN ID. +constexpr auto NGHTTP2_H2_16_ALPN = StringRef::from_lit("\x5h2-16"); +constexpr auto NGHTTP2_H2_16 = StringRef::from_lit("h2-16"); + +constexpr auto NGHTTP2_H2_14_ALPN = StringRef::from_lit("\x5h2-14"); +constexpr auto NGHTTP2_H2_14 = StringRef::from_lit("h2-14"); + +constexpr auto NGHTTP2_H1_1_ALPN = StringRef::from_lit("\x8http/1.1"); +constexpr auto NGHTTP2_H1_1 = StringRef::from_lit("http/1.1"); + +constexpr size_t NGHTTP2_MAX_UINT64_DIGITS = str_size("18446744073709551615"); + +namespace util { + +extern const char UPPER_XDIGITS[]; + +inline bool is_alpha(const char c) { + return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); +} + +inline bool is_digit(const char c) { return '0' <= c && c <= '9'; } + +inline bool is_hex_digit(const char c) { + return is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'); +} + +// Returns true if |s| is hex string. +bool is_hex_string(const StringRef &s); + +bool in_rfc3986_unreserved_chars(const char c); + +bool in_rfc3986_sub_delims(const char c); + +// Returns true if |c| is in token (HTTP-p1, Section 3.2.6) +bool in_token(char c); + +bool in_attr_char(char c); + +// Returns integer corresponding to hex notation |c|. If +// is_hex_digit(c) is false, it returns 256. +uint32_t hex_to_uint(char c); + +std::string percent_encode(const unsigned char *target, size_t len); + +std::string percent_encode(const std::string &target); + +template +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; +} + +StringRef percent_decode(BlockAllocator &balloc, const StringRef &src); + +// Percent encode |target| if character is not in token or '%'. +StringRef percent_encode_token(BlockAllocator &balloc, const StringRef &target); + +template +OutputIt percent_encode_token(OutputIt it, const StringRef &target) { + for (auto first = std::begin(target); first != std::end(target); ++first) { + uint8_t c = *first; + + if (c != '%' && in_token(c)) { + *it++ = c; + continue; + } + + *it++ = '%'; + *it++ = UPPER_XDIGITS[c >> 4]; + *it++ = UPPER_XDIGITS[(c & 0x0f)]; + } + + return it; +} + +// Returns the number of bytes written by percent_encode_token with +// the same |target| parameter. The return value does not include a +// terminal NUL byte. +size_t percent_encode_tokenlen(const StringRef &target); + +// Returns quotedString version of |target|. Currently, this function +// just replace '"' with '\"'. +StringRef quote_string(BlockAllocator &balloc, const StringRef &target); + +template +OutputIt quote_string(OutputIt it, const StringRef &target) { + for (auto c : target) { + if (c == '"') { + *it++ = '\\'; + *it++ = '"'; + } else { + *it++ = c; + } + } + + return it; +} + +// Returns the number of bytes written by quote_string with the same +// |target| parameter. The return value does not include a terminal +// NUL byte. +size_t quote_stringlen(const StringRef &target); + +std::string format_hex(const unsigned char *s, size_t len); + +template std::string format_hex(const unsigned char (&s)[N]) { + return format_hex(s, N); +} + +template std::string format_hex(const std::array &s) { + return format_hex(s.data(), s.size()); +} + +StringRef format_hex(BlockAllocator &balloc, const StringRef &s); + +static constexpr char LOWER_XDIGITS[] = "0123456789abcdef"; + +template +OutputIt format_hex(OutputIt it, const StringRef &s) { + for (auto cc : s) { + uint8_t c = cc; + *it++ = LOWER_XDIGITS[c >> 4]; + *it++ = LOWER_XDIGITS[c & 0xf]; + } + + return it; +} + +// decode_hex decodes hex string |s|, returns the decoded byte string. +// This function assumes |s| is hex string, that is is_hex_string(s) +// == true. +StringRef decode_hex(BlockAllocator &balloc, const StringRef &s); + +template +OutputIt decode_hex(OutputIt d_first, const StringRef &s) { + for (auto it = std::begin(s); it != std::end(s); it += 2) { + *d_first++ = (hex_to_uint(*it) << 4) | hex_to_uint(*(it + 1)); + } + + return d_first; +} + +// Returns given time |t| from epoch in HTTP Date format (e.g., Mon, +// 10 Oct 2016 10:25:58 GMT). +std::string http_date(time_t t); +// Writes given time |t| from epoch in HTTP Date format into the +// buffer pointed by |res|. The buffer must be at least 29 bytes +// long. This function returns the one beyond the last position. +char *http_date(char *res, time_t t); + +// Returns given time |t| from epoch in Common Log format (e.g., +// 03/Jul/2014:00:19:38 +0900) +std::string common_log_date(time_t t); +// Writes given time |t| from epoch in Common Log format into the +// buffer pointed by |res|. The buffer must be at least 26 bytes +// long. This function returns the one beyond the last position. +char *common_log_date(char *res, time_t t); + +// Returns given millisecond |ms| from epoch in ISO 8601 format (e.g., +// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00) +std::string iso8601_date(int64_t ms); +// Writes given time |t| from epoch in ISO 8601 format into the buffer +// pointed by |res|. The buffer must be at least 29 bytes long. This +// function returns the one beyond the last position. +char *iso8601_date(char *res, int64_t ms); + +// Writes given time |t| from epoch in ISO 8601 basic format into the +// buffer pointed by |res|. The buffer must be at least 24 bytes +// long. This function returns the one beyond the last position. +char *iso8601_basic_date(char *res, int64_t ms); + +time_t parse_http_date(const StringRef &s); + +// Parses time formatted as "MMM DD HH:MM:SS YYYY [GMT]" (e.g., Feb 3 +// 00:55:52 2015 GMT), which is specifically used by OpenSSL +// ASN1_TIME_print(). +time_t parse_openssl_asn1_time_print(const StringRef &s); + +char upcase(char c); + +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(c)]; +} + +template +bool starts_with(InputIterator1 first1, InputIterator1 last1, + InputIterator2 first2, InputIterator2 last2) { + if (last1 - first1 < last2 - first2) { + return false; + } + return std::equal(first2, last2, first1); +} + +template bool starts_with(const S &a, const T &b) { + return starts_with(a.begin(), a.end(), b.begin(), b.end()); +} + +struct CaseCmp { + bool operator()(char lhs, char rhs) const { + return lowcase(lhs) == lowcase(rhs); + } +}; + +template +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 bool istarts_with(const S &a, const T &b) { + return istarts_with(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool istarts_with_l(const T &a, const CharT (&b)[N]) { + return istarts_with(a.begin(), a.end(), b, b + N - 1); +} + +template +bool ends_with(InputIterator1 first1, InputIterator1 last1, + InputIterator2 first2, InputIterator2 last2) { + if (last1 - first1 < last2 - first2) { + return false; + } + return std::equal(first2, last2, last1 - (last2 - first2)); +} + +template bool ends_with(const T &a, const S &b) { + return ends_with(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool ends_with_l(const T &a, const CharT (&b)[N]) { + return ends_with(a.begin(), a.end(), b, b + N - 1); +} + +template +bool iends_with(InputIterator1 first1, InputIterator1 last1, + InputIterator2 first2, InputIterator2 last2) { + if (last1 - first1 < last2 - first2) { + return false; + } + return std::equal(first2, last2, last1 - (last2 - first2), CaseCmp()); +} + +template bool iends_with(const T &a, const S &b) { + return iends_with(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool iends_with_l(const T &a, const CharT (&b)[N]) { + return iends_with(a.begin(), a.end(), b, b + N - 1); +} + +template +bool strieq(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) { + if (std::distance(first1, last1) != std::distance(first2, last2)) { + return false; + } + + return std::equal(first1, last1, first2, CaseCmp()); +} + +template bool strieq(const T &a, const S &b) { + return strieq(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool strieq_l(const CharT (&a)[N], InputIt b, size_t blen) { + return strieq(a, a + (N - 1), b, b + blen); +} + +template +bool strieq_l(const CharT (&a)[N], const T &b) { + return strieq(a, a + (N - 1), b.begin(), b.end()); +} + +template +bool streq(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) { + if (std::distance(first1, last1) != std::distance(first2, last2)) { + return false; + } + return std::equal(first1, last1, first2); +} + +template bool streq(const T &a, const S &b) { + return streq(a.begin(), a.end(), b.begin(), b.end()); +} + +template +bool streq_l(const CharT (&a)[N], InputIt b, size_t blen) { + return streq(a, a + (N - 1), b, b + blen); +} + +template +bool streq_l(const CharT (&a)[N], const T &b) { + return streq(a, a + (N - 1), b.begin(), b.end()); +} + +// Returns true if |a| contains |b|. If both |a| and |b| are empty, +// this function returns false. +template bool strifind(const S &a, const T &b) { + return std::search(a.begin(), a.end(), b.begin(), b.end(), CaseCmp()) != + a.end(); +} + +template void inp_strlower(InputIt first, InputIt last) { + std::transform(first, last, first, lowcase); +} + +// Lowercase |s| in place. +inline void inp_strlower(std::string &s) { + inp_strlower(std::begin(s), std::end(s)); +} + +// Returns string representation of |n| with 2 fractional digits. +std::string dtos(double n); + +template std::string utos(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; +} + +template OutputIt utos(OutputIt dst, T n) { + if (n == 0) { + *dst++ = '0'; + return dst; + } + size_t nlen = 0; + for (auto t = n; t; t /= 10, ++nlen) + ; + auto p = dst + nlen; + auto res = p; + for (; n; n /= 10) { + *--p = (n % 10) + '0'; + } + return res; +} + +template +StringRef make_string_ref_uint(BlockAllocator &balloc, T n) { + auto iov = make_byte_ref(balloc, NGHTTP2_MAX_UINT64_DIGITS + 1); + auto p = iov.base; + p = util::utos(p, n); + *p = '\0'; + return StringRef{iov.base, p}; +} + +template std::string utos_unit(T n) { + char u = 0; + if (n >= (1 << 30)) { + u = 'G'; + n /= (1 << 30); + } else if (n >= (1 << 20)) { + u = 'M'; + n /= (1 << 20); + } else if (n >= (1 << 10)) { + u = 'K'; + n /= (1 << 10); + } + if (u == 0) { + return utos(n); + } + return utos(n) + u; +} + +// Like utos_unit(), but 2 digits fraction part is followed. +template std::string utos_funit(T n) { + char u = 0; + int b = 0; + if (n >= (1 << 30)) { + u = 'G'; + b = 30; + } else if (n >= (1 << 20)) { + u = 'M'; + b = 20; + } else if (n >= (1 << 10)) { + u = 'K'; + b = 10; + } + if (b == 0) { + return utos(n); + } + return dtos(static_cast(n) / (1 << b)) + u; +} + +template std::string utox(T n) { + std::string res; + if (n == 0) { + res = "0"; + return res; + } + int i = 0; + T t = n; + for (; t; t /= 16, ++i) + ; + res.resize(i); + --i; + for (; n; --i, n /= 16) { + res[i] = UPPER_XDIGITS[(n & 0x0f)]; + } + return res; +} + +void to_token68(std::string &base64str); + +StringRef to_base64(BlockAllocator &balloc, const StringRef &token68str); + +void show_candidates(const char *unkopt, const option *options); + +bool has_uri_field(const http_parser_url &u, http_parser_url_fields field); + +bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2, + const http_parser_url &u2, http_parser_url_fields field); + +bool fieldeq(const char *uri, const http_parser_url &u, + http_parser_url_fields field, const char *t); + +bool fieldeq(const char *uri, const http_parser_url &u, + http_parser_url_fields field, const StringRef &t); + +StringRef get_uri_field(const char *uri, const http_parser_url &u, + http_parser_url_fields field); + +uint16_t get_default_port(const char *uri, const http_parser_url &u); + +bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2, + const http_parser_url &u2); + +void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u, + http_parser_url_fields field); + +bool numeric_host(const char *hostname); + +bool numeric_host(const char *hostname, int family); + +// Returns numeric address string of |addr|. If getnameinfo() is +// failed, "unknown" is returned. +std::string numeric_name(const struct sockaddr *sa, socklen_t salen); + +// Returns string representation of numeric address and port of +// |addr|. If address family is AF_UNIX, this return path to UNIX +// domain socket. Otherwise, the format is like :. For +// IPv6 address, address is enclosed by square brackets ([]). +std::string to_numeric_addr(const Address *addr); + +std::string to_numeric_addr(const struct sockaddr *sa, socklen_t salen); + +// Sets |port| to |addr|. +void set_port(Address &addr, uint16_t port); + +// Returns ASCII dump of |data| of length |len|. Only ASCII printable +// characters are preserved. Other characters are replaced with ".". +std::string ascii_dump(const uint8_t *data, size_t len); + +// Returns absolute path of executable path. If argc == 0 or |cwd| is +// nullptr, this function returns nullptr. If argv[0] starts with +// '/', this function returns argv[0]. Otherwise return cwd + "/" + +// argv[0]. If non-null is returned, it is NULL-terminated string and +// dynamically allocated by malloc. The caller is responsible to free +// it. +char *get_exec_path(int argc, char **const argv, const char *cwd); + +// Validates path so that it does not contain directory traversal +// vector. Returns true if path is safe. The |path| must start with +// "/" otherwise returns false. This function should be called after +// percent-decode was performed. +bool check_path(const std::string &path); + +// Returns the |tv| value as 64 bit integer using a microsecond as an +// unit. +int64_t to_time64(const timeval &tv); + +// Returns true if ALPN ID |proto| is supported HTTP/2 protocol +// identifier. +bool check_h2_is_selected(const StringRef &proto); + +// Selects h2 protocol ALPN ID if one of supported h2 versions are +// present in |in| of length inlen. Returns true if h2 version is +// selected. +bool select_h2(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen); + +// Selects protocol ALPN ID if one of identifiers contained in |protolist| is +// present in |in| of length inlen. Returns true if identifier is +// selected. +bool select_protocol(const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + std::vector proto_list); + +// Returns default ALPN protocol list, which only contains supported +// HTTP/2 protocol identifier. +std::vector get_default_alpn(); + +// Parses delimited strings in |s| and returns the array of substring, +// delimited by |delim|. The any white spaces around substring are +// treated as a part of substring. +std::vector parse_config_str_list(const StringRef &s, + char delim = ','); + +// Parses delimited strings in |s| and returns Substrings in |s| +// delimited by |delim|. The any white spaces around substring are +// treated as a part of substring. +std::vector split_str(const StringRef &s, char delim); + +// Behaves like split_str, but this variant splits at most |n| - 1 +// times and returns at most |n| sub-strings. If |n| is zero, it +// falls back to split_str. +std::vector split_str(const StringRef &s, char delim, size_t n); + +// Writes given time |tp| in Common Log format (e.g., +// 03/Jul/2014:00:19:38 +0900) in buffer pointed by |out|. The buffer +// must be at least 27 bytes, including terminal NULL byte. Expected +// type of |tp| is std::chrono::time_point. This function returns +// StringRef wrapping the buffer pointed by |out|, and this string is +// terminated by NULL. +template StringRef format_common_log(char *out, const T &tp) { + auto t = + std::chrono::duration_cast(tp.time_since_epoch()); + auto p = common_log_date(out, t.count()); + *p = '\0'; + return StringRef{out, p}; +} + +// Returns given time |tp| in ISO 8601 format (e.g., +// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00). +// Expected type of |tp| is std::chrono::time_point +template std::string format_iso8601(const T &tp) { + auto t = std::chrono::duration_cast( + tp.time_since_epoch()); + return iso8601_date(t.count()); +} + +// Writes given time |tp| in ISO 8601 format (e.g., +// 2014-11-15T12:58:24.741Z or 2014-11-15T12:58:24.741+09:00) in +// buffer pointed by |out|. The buffer must be at least 30 bytes, +// including terminal NULL byte. Expected type of |tp| is +// std::chrono::time_point. This function returns StringRef wrapping +// the buffer pointed by |out|, and this string is terminated by NULL. +template StringRef format_iso8601(char *out, const T &tp) { + auto t = std::chrono::duration_cast( + tp.time_since_epoch()); + auto p = iso8601_date(out, t.count()); + *p = '\0'; + return StringRef{out, p}; +} + +// Writes given time |tp| in ISO 8601 basic format (e.g., +// 20141115T125824.741Z or 20141115T125824.741+0900) in buffer pointed +// by |out|. The buffer must be at least 25 bytes, including terminal +// NULL byte. Expected type of |tp| is std::chrono::time_point. This +// function returns StringRef wrapping the buffer pointed by |out|, +// and this string is terminated by NULL. +template StringRef format_iso8601_basic(char *out, const T &tp) { + auto t = std::chrono::duration_cast( + tp.time_since_epoch()); + auto p = iso8601_basic_date(out, t.count()); + *p = '\0'; + return StringRef{out, p}; +} + +// Writes given time |tp| in HTTP Date format (e.g., Mon, 10 Oct 2016 +// 10:25:58 GMT) in buffer pointed by |out|. The buffer must be at +// least 30 bytes, including terminal NULL byte. Expected type of +// |tp| is std::chrono::time_point. This function returns StringRef +// wrapping the buffer pointed by |out|, and this string is terminated +// by NULL. +template StringRef format_http_date(char *out, const T &tp) { + auto t = + std::chrono::duration_cast(tp.time_since_epoch()); + auto p = http_date(out, t.count()); + *p = '\0'; + return StringRef{out, p}; +} + +// Return the system precision of the template parameter |Clock| as +// a nanosecond value of type |Rep| +template Rep clock_precision() { + std::chrono::duration duration = typename Clock::duration(1); + + return duration.count(); +} + +#ifdef HAVE_LIBEV +template +Duration duration_from(ev_tstamp d) { + return std::chrono::duration_cast(std::chrono::duration(d)); +} + +template ev_tstamp ev_tstamp_from(const Duration &d) { + return std::chrono::duration(d).count(); +} +#endif // HAVE_LIBEV + +int make_socket_closeonexec(int fd); +int make_socket_nonblocking(int fd); +int make_socket_nodelay(int fd); + +int create_nonblock_socket(int family); +int create_nonblock_udp_socket(int family); + +int bind_any_addr_udp(int fd, int family); + +bool check_socket_connected(int fd); + +// Returns the error code (errno) by inspecting SO_ERROR of given +// |fd|. This function returns the error code if it succeeds, or -1. +// Returning 0 means no error. +int get_socket_error(int fd); + +// Returns true if |host| is IPv6 numeric address (e.g., ::1) +bool ipv6_numeric_addr(const char *host); + +// Parses NULL terminated string |s| as unsigned integer and returns +// the parsed integer. Additionally, if |s| ends with 'k', 'm', 'g' +// and its upper case characters, multiply the integer by 1024, 1024 * +// 1024 and 1024 * 1024 respectively. If there is an error, returns +// -1. +int64_t parse_uint_with_unit(const char *s); +// The following overload does not require |s| is NULL terminated. +int64_t parse_uint_with_unit(const uint8_t *s, size_t len); +int64_t parse_uint_with_unit(const StringRef &s); + +// Parses NULL terminated string |s| as unsigned integer and returns +// the parsed integer. If there is an error, returns -1. +int64_t parse_uint(const char *s); +// The following overload does not require |s| is NULL terminated. +int64_t parse_uint(const uint8_t *s, size_t len); +int64_t parse_uint(const std::string &s); +int64_t parse_uint(const StringRef &s); + +// Parses NULL terminated string |s| as unsigned integer and returns +// the parsed integer casted to double. If |s| ends with "s", the +// parsed value's unit is a second. If |s| ends with "ms", the unit +// is millisecond. Similarly, it also supports 'm' and 'h' for +// minutes and hours respectively. If none of them are given, the +// unit is second. This function returns +// std::numeric_limits::infinity() if error occurs. +double parse_duration_with_unit(const char *s); +// The following overload does not require |s| is NULL terminated. +double parse_duration_with_unit(const uint8_t *s, size_t len); +double parse_duration_with_unit(const StringRef &s); + +// Returns string representation of time duration |t|. If t has +// fractional part (at least more than or equal to 1e-3), |t| is +// multiplied by 1000 and the unit "ms" is appended. Otherwise, |t| +// is left as is and "s" is appended. +std::string duration_str(double t); + +// Returns string representation of time duration |t|. It appends +// unit after the formatting. The available units are s, ms and us. +// The unit which is equal to or less than |t| is used and 2 +// fractional digits follow. +std::string format_duration(const std::chrono::microseconds &u); + +// Just like above, but this takes |t| as seconds. +std::string format_duration(double t); + +// The maximum buffer size including terminal NULL to store the result +// of make_hostport. +constexpr size_t max_hostport = NI_MAXHOST + /* [] for IPv6 */ 2 + /* : */ 1 + + /* port */ 5 + /* terminal NULL */ 1; + +// Just like make_http_hostport(), but doesn't treat 80 and 443 +// specially. +StringRef make_hostport(BlockAllocator &balloc, const StringRef &host, + uint16_t port); + +template +StringRef make_hostport(OutputIt first, const StringRef &host, uint16_t port) { + auto ipv6 = ipv6_numeric_addr(host.c_str()); + auto serv = utos(port); + auto p = first; + + if (ipv6) { + *p++ = '['; + } + + p = std::copy(std::begin(host), std::end(host), p); + + if (ipv6) { + *p++ = ']'; + } + + *p++ = ':'; + + p = std::copy(std::begin(serv), std::end(serv), p); + + *p = '\0'; + + return StringRef{first, p}; +} + +// Creates "host:port" string using given |host| and |port|. If +// |host| is numeric IPv6 address (e.g., ::1), it is enclosed by "[" +// and "]". If |port| is 80 or 443, port part is omitted. +StringRef make_http_hostport(BlockAllocator &balloc, const StringRef &host, + uint16_t port); + +template +StringRef make_http_hostport(OutputIt first, const StringRef &host, + uint16_t port) { + if (port != 80 && port != 443) { + return make_hostport(first, host, port); + } + + auto ipv6 = ipv6_numeric_addr(host.c_str()); + auto p = first; + + if (ipv6) { + *p++ = '['; + } + + p = std::copy(std::begin(host), std::end(host), p); + + if (ipv6) { + *p++ = ']'; + } + + *p = '\0'; + + return StringRef{first, p}; +} + +// Dumps |src| of length |len| in the format similar to `hexdump -C`. +void hexdump(FILE *out, const uint8_t *src, size_t len); + +// Copies 2 byte unsigned integer |n| in host byte order to |buf| in +// network byte order. +void put_uint16be(uint8_t *buf, uint16_t n); + +// Copies 4 byte unsigned integer |n| in host byte order to |buf| in +// network byte order. +void put_uint32be(uint8_t *buf, uint32_t n); + +// Retrieves 2 byte unsigned integer stored in |data| in network byte +// order and returns it in host byte order. +uint16_t get_uint16(const uint8_t *data); + +// Retrieves 4 byte unsigned integer stored in |data| in network byte +// order and returns it in host byte order. +uint32_t get_uint32(const uint8_t *data); + +// Retrieves 8 byte unsigned integer stored in |data| in network byte +// order and returns it in host byte order. +uint64_t get_uint64(const uint8_t *data); + +// Reads mime types file (see /etc/mime.types), and stores extension +// -> MIME type map in |res|. This function returns 0 if it succeeds, +// or -1. +int read_mime_types(std::map &res, + const char *filename); + +// Fills random alpha and digit byte to the range [|first|, |last|). +// Returns the one beyond the |last|. +template +OutputIt random_alpha_digit(OutputIt first, OutputIt last, Generator &gen) { + // If we use uint8_t instead char, gcc 6.2.0 complains by shouting + // char-array initialized from wide string. + static constexpr char s[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + std::uniform_int_distribution<> dis(0, 26 * 2 + 10 - 1); + for (; first != last; ++first) { + *first = s[dis(gen)]; + } + return first; +} + +// Fills random bytes to the range [|first|, |last|). +template +void random_bytes(OutputIt first, OutputIt last, Generator &gen) { + std::uniform_int_distribution dis; + std::generate(first, last, [&dis, &gen]() { return dis(gen); }); +} + +// Shuffles the range [|first|, |last|] by calling swap function |fun| +// for each pair. |fun| takes 2 RandomIt iterators. If |fun| is +// noop, no modification is made. +template +void shuffle(RandomIt first, RandomIt last, Generator &&gen, SwapFun fun) { + auto len = std::distance(first, last); + if (len < 2) { + return; + } + + for (unsigned int i = 0; i < static_cast(len - 1); ++i) { + auto dis = std::uniform_int_distribution(i, len - 1); + auto j = dis(gen); + if (i == j) { + continue; + } + fun(first + i, first + j); + } +} + +template +OutputIterator copy_lit(OutputIterator it, CharT (&s)[N]) { + return std::copy_n(s, N - 1, it); +} + +// Returns x**y +double int_pow(double x, size_t y); + +uint32_t hash32(const StringRef &s); + +// Computes SHA-256 of |s|, and stores it in |buf|. This function +// returns 0 if it succeeds, or -1. +int sha256(uint8_t *buf, const StringRef &s); + +// Computes SHA-1 of |s|, and stores it in |buf|. This function +// returns 0 if it succeeds, or -1. +int sha1(uint8_t *buf, const StringRef &s); + +// Returns host from |hostport|. If host cannot be found in +// |hostport|, returns empty string. The returned string might not be +// NULL-terminated. +StringRef extract_host(const StringRef &hostport); + +// split_hostport splits host and port in |hostport|. Unlike +// extract_host, square brackets enclosing host name is stripped. If +// port is not available, it returns empty string in the second +// string. The returned string might not be NULL-terminated. On any +// error, it returns a pair which has empty strings. +std::pair split_hostport(const StringRef &hostport); + +// Returns new std::mt19937 object. +std::mt19937 make_mt19937(); + +// daemonize calls daemon(3). If __APPLE__ is defined, it implements +// daemon() using fork(). +int daemonize(int nochdir, int noclose); + +// Returns |s| from which trailing white spaces (SPC or HTAB) are +// removed. If any white spaces are removed, new string is allocated +// by |balloc| and returned. Otherwise, the copy of |s| is returned +// without allocation. +StringRef rstrip(BlockAllocator &balloc, const StringRef &s); + +#ifdef ENABLE_HTTP3 +int msghdr_get_local_addr(Address &dest, msghdr *msg, int family); + +uint8_t msghdr_get_ecn(msghdr *msg, int family); + +// msghdr_get_udp_gro returns UDP_GRO value from |msg|. If UDP_GRO is +// not found, or UDP_GRO is not supported, this function returns 0. +size_t msghdr_get_udp_gro(msghdr *msg); +#endif // ENABLE_HTTP3 + +} // namespace util + +} // namespace nghttp2 + +#endif // UTIL_H diff --git a/lib/nghttp2/src/util_test.cc b/lib/nghttp2/src/util_test.cc new file mode 100644 index 00000000000..0ac0f1de61d --- /dev/null +++ b/lib/nghttp2/src/util_test.cc @@ -0,0 +1,707 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 +#include +#include + +#include + +#include + +#include "util.h" +#include "template.h" + +using namespace nghttp2; + +namespace shrpx { + +void test_util_streq(void) { + CU_ASSERT( + util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alpha"))); + CU_ASSERT(!util::streq(StringRef::from_lit("alpha"), + StringRef::from_lit("alphabravo"))); + CU_ASSERT(!util::streq(StringRef::from_lit("alphabravo"), + StringRef::from_lit("alpha"))); + CU_ASSERT( + !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alphA"))); + CU_ASSERT(!util::streq(StringRef{}, StringRef::from_lit("a"))); + CU_ASSERT(util::streq(StringRef{}, StringRef{})); + CU_ASSERT(!util::streq(StringRef::from_lit("alpha"), StringRef{})); + + CU_ASSERT( + !util::streq(StringRef::from_lit("alph"), StringRef::from_lit("alpha"))); + CU_ASSERT( + !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alph"))); + CU_ASSERT( + !util::streq(StringRef::from_lit("alpha"), StringRef::from_lit("alphA"))); + + CU_ASSERT(util::streq_l("alpha", "alpha", 5)); + CU_ASSERT(util::streq_l("alpha", "alphabravo", 5)); + CU_ASSERT(!util::streq_l("alpha", "alphabravo", 6)); + CU_ASSERT(!util::streq_l("alphabravo", "alpha", 5)); + CU_ASSERT(!util::streq_l("alpha", "alphA", 5)); + CU_ASSERT(!util::streq_l("", "a", 1)); + CU_ASSERT(util::streq_l("", "", 0)); + CU_ASSERT(!util::streq_l("alpha", "", 0)); +} + +void test_util_strieq(void) { + CU_ASSERT(util::strieq(std::string("alpha"), std::string("alpha"))); + CU_ASSERT(util::strieq(std::string("alpha"), std::string("AlPhA"))); + CU_ASSERT(util::strieq(std::string(), std::string())); + CU_ASSERT(!util::strieq(std::string("alpha"), std::string("AlPhA "))); + CU_ASSERT(!util::strieq(std::string(), std::string("AlPhA "))); + + CU_ASSERT( + util::strieq(StringRef::from_lit("alpha"), StringRef::from_lit("alpha"))); + CU_ASSERT( + util::strieq(StringRef::from_lit("alpha"), StringRef::from_lit("AlPhA"))); + CU_ASSERT(util::strieq(StringRef{}, StringRef{})); + CU_ASSERT(!util::strieq(StringRef::from_lit("alpha"), + StringRef::from_lit("AlPhA "))); + CU_ASSERT( + !util::strieq(StringRef::from_lit(""), StringRef::from_lit("AlPhA "))); + + CU_ASSERT(util::strieq_l("alpha", "alpha", 5)); + CU_ASSERT(util::strieq_l("alpha", "AlPhA", 5)); + CU_ASSERT(util::strieq_l("", static_cast(nullptr), 0)); + CU_ASSERT(!util::strieq_l("alpha", "AlPhA ", 6)); + CU_ASSERT(!util::strieq_l("", "AlPhA ", 6)); + + CU_ASSERT(util::strieq_l("alpha", StringRef::from_lit("alpha"))); + CU_ASSERT(util::strieq_l("alpha", StringRef::from_lit("AlPhA"))); + CU_ASSERT(util::strieq_l("", StringRef{})); + CU_ASSERT(!util::strieq_l("alpha", StringRef::from_lit("AlPhA "))); + CU_ASSERT(!util::strieq_l("", StringRef::from_lit("AlPhA "))); +} + +void test_util_inp_strlower(void) { + std::string a("alPha"); + util::inp_strlower(a); + CU_ASSERT("alpha" == a); + + a = "ALPHA123BRAVO"; + util::inp_strlower(a); + CU_ASSERT("alpha123bravo" == a); + + a = ""; + util::inp_strlower(a); + CU_ASSERT("" == a); +} + +void test_util_to_base64(void) { + BlockAllocator balloc(4096, 4096); + + CU_ASSERT("AAA++B/=" == + util::to_base64(balloc, StringRef::from_lit("AAA--B_"))); + CU_ASSERT("AAA++B/B" == + util::to_base64(balloc, StringRef::from_lit("AAA--B_B"))); +} + +void test_util_to_token68(void) { + std::string x = "AAA++B/="; + util::to_token68(x); + CU_ASSERT("AAA--B_" == x); + + x = "AAA++B/B"; + util::to_token68(x); + CU_ASSERT("AAA--B_B" == x); +} + +void test_util_percent_encode_token(void) { + BlockAllocator balloc(4096, 4096); + CU_ASSERT("h2" == + util::percent_encode_token(balloc, StringRef::from_lit("h2"))); + CU_ASSERT("h3~" == + util::percent_encode_token(balloc, StringRef::from_lit("h3~"))); + CU_ASSERT("100%25" == + util::percent_encode_token(balloc, StringRef::from_lit("100%"))); + CU_ASSERT("http%202" == + util::percent_encode_token(balloc, StringRef::from_lit("http 2"))); +} + +void test_util_percent_decode(void) { + { + std::string s = "%66%6F%6f%62%61%72"; + CU_ASSERT("foobar" == util::percent_decode(std::begin(s), std::end(s))); + } + { + std::string s = "%66%6"; + CU_ASSERT("f%6" == util::percent_decode(std::begin(s), std::end(s))); + } + { + std::string s = "%66%"; + CU_ASSERT("f%" == util::percent_decode(std::begin(s), std::end(s))); + } + BlockAllocator balloc(1024, 1024); + + CU_ASSERT("foobar" == util::percent_decode( + balloc, StringRef::from_lit("%66%6F%6f%62%61%72"))); + + CU_ASSERT("f%6" == + util::percent_decode(balloc, StringRef::from_lit("%66%6"))); + + CU_ASSERT("f%" == util::percent_decode(balloc, StringRef::from_lit("%66%"))); +} + +void test_util_quote_string(void) { + BlockAllocator balloc(4096, 4096); + CU_ASSERT("alpha" == + util::quote_string(balloc, StringRef::from_lit("alpha"))); + CU_ASSERT("" == util::quote_string(balloc, StringRef::from_lit(""))); + CU_ASSERT("\\\"alpha\\\"" == + util::quote_string(balloc, StringRef::from_lit("\"alpha\""))); +} + +void test_util_utox(void) { + CU_ASSERT("0" == util::utox(0)); + CU_ASSERT("1" == util::utox(1)); + CU_ASSERT("F" == util::utox(15)); + CU_ASSERT("10" == util::utox(16)); + CU_ASSERT("3B9ACA07" == util::utox(1000000007)); + CU_ASSERT("100000000" == util::utox(1LL << 32)); +} + +void test_util_http_date(void) { + CU_ASSERT("Thu, 01 Jan 1970 00:00:00 GMT" == util::http_date(0)); + CU_ASSERT("Wed, 29 Feb 2012 09:15:16 GMT" == util::http_date(1330506916)); + + std::array http_buf; + + CU_ASSERT("Thu, 01 Jan 1970 00:00:00 GMT" == + util::format_http_date(http_buf.data(), + std::chrono::system_clock::time_point())); + CU_ASSERT("Wed, 29 Feb 2012 09:15:16 GMT" == + util::format_http_date(http_buf.data(), + std::chrono::system_clock::time_point( + std::chrono::seconds(1330506916)))); +} + +void test_util_select_h2(void) { + const unsigned char *out = nullptr; + unsigned char outlen = 0; + + // Check single entry and select it. + const unsigned char t1[] = "\x2h2"; + CU_ASSERT(util::select_h2(&out, &outlen, t1, sizeof(t1) - 1)); + CU_ASSERT( + memcmp(NGHTTP2_PROTO_VERSION_ID, out, NGHTTP2_PROTO_VERSION_ID_LEN) == 0); + CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen); + + out = nullptr; + outlen = 0; + + // Check the case where id is correct but length is invalid and too + // long. + const unsigned char t2[] = "\x6h2-14"; + CU_ASSERT(!util::select_h2(&out, &outlen, t2, sizeof(t2) - 1)); + + // Check the case where h2 is located after bogus ID. + const unsigned char t3[] = "\x2h3\x2h2"; + CU_ASSERT(util::select_h2(&out, &outlen, t3, sizeof(t3) - 1)); + + CU_ASSERT( + memcmp(NGHTTP2_PROTO_VERSION_ID, out, NGHTTP2_PROTO_VERSION_ID_LEN) == 0); + CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen); + + out = nullptr; + outlen = 0; + + // Check the case that last entry's length is invalid and too long. + const unsigned char t4[] = "\x2h3\x6h2-14"; + CU_ASSERT(!util::select_h2(&out, &outlen, t4, sizeof(t4) - 1)); + + // Check the case that all entries are not supported. + const unsigned char t5[] = "\x2h3\x2h4"; + CU_ASSERT(!util::select_h2(&out, &outlen, t5, sizeof(t5) - 1)); + + // Check the case where 2 values are eligible, but last one is + // picked up because it has precedence over the other. + const unsigned char t6[] = "\x5h2-14\x5h2-16"; + CU_ASSERT(util::select_h2(&out, &outlen, t6, sizeof(t6) - 1)); + CU_ASSERT(util::streq(NGHTTP2_H2_16, StringRef{out, outlen})); +} + +void test_util_ipv6_numeric_addr(void) { + CU_ASSERT(util::ipv6_numeric_addr("::1")); + CU_ASSERT(util::ipv6_numeric_addr("2001:0db8:85a3:0042:1000:8a2e:0370:7334")); + // IPv4 + CU_ASSERT(!util::ipv6_numeric_addr("127.0.0.1")); + // not numeric address + CU_ASSERT(!util::ipv6_numeric_addr("localhost")); +} + +void test_util_utos(void) { + uint8_t buf[32]; + + CU_ASSERT(("0" == StringRef{buf, util::utos(buf, 0)})); + CU_ASSERT(("123" == StringRef{buf, util::utos(buf, 123)})); + CU_ASSERT(("18446744073709551615" == + StringRef{buf, util::utos(buf, 18446744073709551615ULL)})); +} + +void test_util_make_string_ref_uint(void) { + BlockAllocator balloc(1024, 1024); + + CU_ASSERT("0" == util::make_string_ref_uint(balloc, 0)); + CU_ASSERT("123" == util::make_string_ref_uint(balloc, 123)); + CU_ASSERT("18446744073709551615" == + util::make_string_ref_uint(balloc, 18446744073709551615ULL)); +} + +void test_util_utos_unit(void) { + CU_ASSERT("0" == util::utos_unit(0)); + CU_ASSERT("1023" == util::utos_unit(1023)); + CU_ASSERT("1K" == util::utos_unit(1024)); + CU_ASSERT("1K" == util::utos_unit(1025)); + CU_ASSERT("1M" == util::utos_unit(1 << 20)); + CU_ASSERT("1G" == util::utos_unit(1 << 30)); + CU_ASSERT("1024G" == util::utos_unit(1LL << 40)); +} + +void test_util_utos_funit(void) { + CU_ASSERT("0" == util::utos_funit(0)); + CU_ASSERT("1023" == util::utos_funit(1023)); + CU_ASSERT("1.00K" == util::utos_funit(1024)); + CU_ASSERT("1.00K" == util::utos_funit(1025)); + CU_ASSERT("1.09K" == util::utos_funit(1119)); + CU_ASSERT("1.27K" == util::utos_funit(1300)); + CU_ASSERT("1.00M" == util::utos_funit(1 << 20)); + CU_ASSERT("1.18M" == util::utos_funit(1234567)); + CU_ASSERT("1.00G" == util::utos_funit(1 << 30)); + CU_ASSERT("4492450797.23G" == util::utos_funit(4823732313248234343LL)); + CU_ASSERT("1024.00G" == util::utos_funit(1LL << 40)); +} + +void test_util_parse_uint_with_unit(void) { + CU_ASSERT(0 == util::parse_uint_with_unit("0")); + CU_ASSERT(1023 == util::parse_uint_with_unit("1023")); + CU_ASSERT(1024 == util::parse_uint_with_unit("1k")); + CU_ASSERT(2048 == util::parse_uint_with_unit("2K")); + CU_ASSERT(1 << 20 == util::parse_uint_with_unit("1m")); + CU_ASSERT(1 << 21 == util::parse_uint_with_unit("2M")); + CU_ASSERT(1 << 30 == util::parse_uint_with_unit("1g")); + CU_ASSERT(1LL << 31 == util::parse_uint_with_unit("2G")); + CU_ASSERT(9223372036854775807LL == + util::parse_uint_with_unit("9223372036854775807")); + // check overflow case + CU_ASSERT(-1 == util::parse_uint_with_unit("9223372036854775808")); + CU_ASSERT(-1 == util::parse_uint_with_unit("10000000000000000000")); + CU_ASSERT(-1 == util::parse_uint_with_unit("9223372036854775807G")); + // bad characters + CU_ASSERT(-1 == util::parse_uint_with_unit("1.1")); + CU_ASSERT(-1 == util::parse_uint_with_unit("1a")); + CU_ASSERT(-1 == util::parse_uint_with_unit("a1")); + CU_ASSERT(-1 == util::parse_uint_with_unit("1T")); + CU_ASSERT(-1 == util::parse_uint_with_unit("")); +} + +void test_util_parse_uint(void) { + CU_ASSERT(0 == util::parse_uint("0")); + CU_ASSERT(1023 == util::parse_uint("1023")); + CU_ASSERT(-1 == util::parse_uint("1k")); + CU_ASSERT(9223372036854775807LL == util::parse_uint("9223372036854775807")); + // check overflow case + CU_ASSERT(-1 == util::parse_uint("9223372036854775808")); + CU_ASSERT(-1 == util::parse_uint("10000000000000000000")); + // bad characters + CU_ASSERT(-1 == util::parse_uint("1.1")); + CU_ASSERT(-1 == util::parse_uint("1a")); + CU_ASSERT(-1 == util::parse_uint("a1")); + CU_ASSERT(-1 == util::parse_uint("1T")); + CU_ASSERT(-1 == util::parse_uint("")); +} + +void test_util_parse_duration_with_unit(void) { + CU_ASSERT(0. == util::parse_duration_with_unit("0")); + CU_ASSERT(123. == util::parse_duration_with_unit("123")); + CU_ASSERT(123. == util::parse_duration_with_unit("123s")); + CU_ASSERT(0.500 == util::parse_duration_with_unit("500ms")); + CU_ASSERT(123. == util::parse_duration_with_unit("123S")); + CU_ASSERT(0.500 == util::parse_duration_with_unit("500MS")); + CU_ASSERT(180 == util::parse_duration_with_unit("3m")); + CU_ASSERT(3600 * 5 == util::parse_duration_with_unit("5h")); + + auto err = std::numeric_limits::infinity(); + // check overflow case + CU_ASSERT(err == util::parse_duration_with_unit("9223372036854775808")); + // bad characters + CU_ASSERT(err == util::parse_duration_with_unit("0u")); + CU_ASSERT(err == util::parse_duration_with_unit("0xs")); + CU_ASSERT(err == util::parse_duration_with_unit("0mt")); + CU_ASSERT(err == util::parse_duration_with_unit("0mss")); + CU_ASSERT(err == util::parse_duration_with_unit("s")); + CU_ASSERT(err == util::parse_duration_with_unit("ms")); +} + +void test_util_duration_str(void) { + CU_ASSERT("0" == util::duration_str(0.)); + CU_ASSERT("1s" == util::duration_str(1.)); + CU_ASSERT("500ms" == util::duration_str(0.5)); + CU_ASSERT("1500ms" == util::duration_str(1.5)); + CU_ASSERT("2m" == util::duration_str(120.)); + CU_ASSERT("121s" == util::duration_str(121.)); + CU_ASSERT("1h" == util::duration_str(3600.)); +} + +void test_util_format_duration(void) { + CU_ASSERT("0us" == util::format_duration(std::chrono::microseconds(0))); + CU_ASSERT("999us" == util::format_duration(std::chrono::microseconds(999))); + CU_ASSERT("1.00ms" == util::format_duration(std::chrono::microseconds(1000))); + CU_ASSERT("1.09ms" == util::format_duration(std::chrono::microseconds(1090))); + CU_ASSERT("1.01ms" == util::format_duration(std::chrono::microseconds(1009))); + CU_ASSERT("999.99ms" == + util::format_duration(std::chrono::microseconds(999990))); + CU_ASSERT("1.00s" == + util::format_duration(std::chrono::microseconds(1000000))); + CU_ASSERT("1.05s" == + util::format_duration(std::chrono::microseconds(1050000))); + + CU_ASSERT("0us" == util::format_duration(0.)); + CU_ASSERT("999us" == util::format_duration(0.000999)); + CU_ASSERT("1.00ms" == util::format_duration(0.001)); + CU_ASSERT("1.09ms" == util::format_duration(0.00109)); + CU_ASSERT("1.01ms" == util::format_duration(0.001009)); + CU_ASSERT("999.99ms" == util::format_duration(0.99999)); + CU_ASSERT("1.00s" == util::format_duration(1.)); + CU_ASSERT("1.05s" == util::format_duration(1.05)); +} + +void test_util_starts_with(void) { + CU_ASSERT(util::starts_with(StringRef::from_lit("foo"), + StringRef::from_lit("foo"))); + CU_ASSERT(util::starts_with(StringRef::from_lit("fooo"), + StringRef::from_lit("foo"))); + CU_ASSERT(util::starts_with(StringRef::from_lit("ofoo"), StringRef{})); + CU_ASSERT(!util::starts_with(StringRef::from_lit("ofoo"), + StringRef::from_lit("foo"))); + + CU_ASSERT(util::istarts_with(StringRef::from_lit("FOO"), + StringRef::from_lit("fOO"))); + CU_ASSERT(util::istarts_with(StringRef::from_lit("ofoo"), StringRef{})); + CU_ASSERT(util::istarts_with(StringRef::from_lit("fOOo"), + StringRef::from_lit("Foo"))); + CU_ASSERT(!util::istarts_with(StringRef::from_lit("ofoo"), + StringRef::from_lit("foo"))); + + CU_ASSERT(util::istarts_with_l(StringRef::from_lit("fOOo"), "Foo")); + CU_ASSERT(!util::istarts_with_l(StringRef::from_lit("ofoo"), "foo")); +} + +void test_util_ends_with(void) { + CU_ASSERT( + util::ends_with(StringRef::from_lit("foo"), StringRef::from_lit("foo"))); + CU_ASSERT(util::ends_with(StringRef::from_lit("foo"), StringRef{})); + CU_ASSERT( + util::ends_with(StringRef::from_lit("ofoo"), StringRef::from_lit("foo"))); + CU_ASSERT( + !util::ends_with(StringRef::from_lit("ofoo"), StringRef::from_lit("fo"))); + + CU_ASSERT( + util::iends_with(StringRef::from_lit("fOo"), StringRef::from_lit("Foo"))); + CU_ASSERT(util::iends_with(StringRef::from_lit("foo"), StringRef{})); + CU_ASSERT(util::iends_with(StringRef::from_lit("oFoo"), + StringRef::from_lit("fOO"))); + CU_ASSERT(!util::iends_with(StringRef::from_lit("ofoo"), + StringRef::from_lit("fo"))); + + CU_ASSERT(util::iends_with_l(StringRef::from_lit("oFoo"), "fOO")); + CU_ASSERT(!util::iends_with_l(StringRef::from_lit("ofoo"), "fo")); +} + +void test_util_parse_http_date(void) { + CU_ASSERT(1001939696 == util::parse_http_date(StringRef::from_lit( + "Mon, 1 Oct 2001 12:34:56 GMT"))); +} + +void test_util_localtime_date(void) { + auto tz = getenv("TZ"); + if (tz) { + tz = strdup(tz); + } +#ifdef __linux__ + setenv("TZ", "NZST-12:00:00:00", 1); +#else // !__linux__ + setenv("TZ", ":Pacific/Auckland", 1); +#endif // !__linux__ + tzset(); + + CU_ASSERT_STRING_EQUAL("02/Oct/2001:00:34:56 +1200", + util::common_log_date(1001939696).c_str()); + CU_ASSERT_STRING_EQUAL("2001-10-02T00:34:56.123+12:00", + util::iso8601_date(1001939696000LL + 123).c_str()); + + std::array common_buf; + + CU_ASSERT("02/Oct/2001:00:34:56 +1200" == + util::format_common_log(common_buf.data(), + std::chrono::system_clock::time_point( + std::chrono::seconds(1001939696)))); + + std::array iso8601_buf; + + CU_ASSERT( + "2001-10-02T00:34:56.123+12:00" == + util::format_iso8601(iso8601_buf.data(), + std::chrono::system_clock::time_point( + std::chrono::milliseconds(1001939696123LL)))); + + if (tz) { + setenv("TZ", tz, 1); + free(tz); + } else { + unsetenv("TZ"); + } + tzset(); +} + +void test_util_get_uint64(void) { + { + auto v = std::array{ + {0x01, 0x12, 0x34, 0x56, 0xff, 0x9a, 0xab, 0xbc}}; + + auto n = util::get_uint64(v.data()); + + CU_ASSERT(0x01123456ff9aabbcULL == n); + } + { + auto v = std::array{ + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; + + auto n = util::get_uint64(v.data()); + + CU_ASSERT(0xffffffffffffffffULL == n); + } +} + +void test_util_parse_config_str_list(void) { + auto res = util::parse_config_str_list(StringRef::from_lit("a")); + CU_ASSERT(1 == res.size()); + CU_ASSERT("a" == res[0]); + + res = util::parse_config_str_list(StringRef::from_lit("a,")); + CU_ASSERT(2 == res.size()); + CU_ASSERT("a" == res[0]); + CU_ASSERT("" == res[1]); + + res = util::parse_config_str_list(StringRef::from_lit(":a::"), ':'); + CU_ASSERT(4 == res.size()); + CU_ASSERT("" == res[0]); + CU_ASSERT("a" == res[1]); + CU_ASSERT("" == res[2]); + CU_ASSERT("" == res[3]); + + res = util::parse_config_str_list(StringRef{}); + CU_ASSERT(1 == res.size()); + CU_ASSERT("" == res[0]); + + res = util::parse_config_str_list(StringRef::from_lit("alpha,bravo,charlie")); + CU_ASSERT(3 == res.size()); + CU_ASSERT("alpha" == res[0]); + CU_ASSERT("bravo" == res[1]); + CU_ASSERT("charlie" == res[2]); +} + +void test_util_make_http_hostport(void) { + BlockAllocator balloc(4096, 4096); + + CU_ASSERT("localhost" == util::make_http_hostport( + balloc, StringRef::from_lit("localhost"), 80)); + CU_ASSERT("[::1]" == + util::make_http_hostport(balloc, StringRef::from_lit("::1"), 443)); + CU_ASSERT( + "localhost:3000" == + util::make_http_hostport(balloc, StringRef::from_lit("localhost"), 3000)); +} + +void test_util_make_hostport(void) { + std::array hostport_buf; + CU_ASSERT("localhost:80" == + util::make_hostport(std::begin(hostport_buf), + StringRef::from_lit("localhost"), 80)); + CU_ASSERT("[::1]:443" == util::make_hostport(std::begin(hostport_buf), + StringRef::from_lit("::1"), + 443)); + + BlockAllocator balloc(4096, 4096); + CU_ASSERT("localhost:80" == + util::make_hostport(balloc, StringRef::from_lit("localhost"), 80)); + CU_ASSERT("[::1]:443" == + util::make_hostport(balloc, StringRef::from_lit("::1"), 443)); +} + +void test_util_strifind(void) { + CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"), + StringRef::from_lit("gzip"))); + + CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"), + StringRef::from_lit("dEflate"))); + + CU_ASSERT(util::strifind(StringRef::from_lit("gzip, deflate, bzip2"), + StringRef::from_lit("BZIP2"))); + + CU_ASSERT(util::strifind(StringRef::from_lit("nghttp2"), StringRef{})); + + // Be aware this fact + CU_ASSERT(!util::strifind(StringRef{}, StringRef{})); + + CU_ASSERT(!util::strifind(StringRef::from_lit("nghttp2"), + StringRef::from_lit("http1"))); +} + +void test_util_random_alpha_digit(void) { + std::random_device rd; + std::mt19937 gen(rd()); + std::array data; + + auto p = util::random_alpha_digit(std::begin(data), std::end(data), gen); + + CU_ASSERT(std::end(data) == p); + + for (auto b : data) { + CU_ASSERT(('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') || + ('0' <= b && b <= '9')); + } +} + +void test_util_format_hex(void) { + BlockAllocator balloc(4096, 4096); + + CU_ASSERT("0ff0" == + util::format_hex(balloc, StringRef::from_lit("\x0f\xf0"))); + CU_ASSERT("" == util::format_hex(balloc, StringRef::from_lit(""))); +} + +void test_util_is_hex_string(void) { + CU_ASSERT(util::is_hex_string(StringRef{})); + CU_ASSERT(util::is_hex_string(StringRef::from_lit("0123456789abcdef"))); + CU_ASSERT(util::is_hex_string(StringRef::from_lit("0123456789ABCDEF"))); + CU_ASSERT(!util::is_hex_string(StringRef::from_lit("000"))); + CU_ASSERT(!util::is_hex_string(StringRef::from_lit("XX"))); +} + +void test_util_decode_hex(void) { + BlockAllocator balloc(4096, 4096); + + CU_ASSERT("\x0f\xf0" == + util::decode_hex(balloc, StringRef::from_lit("0ff0"))); + CU_ASSERT("" == util::decode_hex(balloc, StringRef{})); +} + +void test_util_extract_host(void) { + CU_ASSERT(StringRef::from_lit("foo") == + util::extract_host(StringRef::from_lit("foo"))); + CU_ASSERT(StringRef::from_lit("foo") == + util::extract_host(StringRef::from_lit("foo:"))); + CU_ASSERT(StringRef::from_lit("foo") == + util::extract_host(StringRef::from_lit("foo:0"))); + CU_ASSERT(StringRef::from_lit("[::1]") == + util::extract_host(StringRef::from_lit("[::1]"))); + CU_ASSERT(StringRef::from_lit("[::1]") == + util::extract_host(StringRef::from_lit("[::1]:"))); + + CU_ASSERT(util::extract_host(StringRef::from_lit(":foo")).empty()); + CU_ASSERT(util::extract_host(StringRef::from_lit("[::1")).empty()); + CU_ASSERT(util::extract_host(StringRef::from_lit("[::1]0")).empty()); + CU_ASSERT(util::extract_host(StringRef{}).empty()); +} + +void test_util_split_hostport(void) { + CU_ASSERT(std::make_pair(StringRef::from_lit("foo"), StringRef{}) == + util::split_hostport(StringRef::from_lit("foo"))); + CU_ASSERT( + std::make_pair(StringRef::from_lit("foo"), StringRef::from_lit("80")) == + util::split_hostport(StringRef::from_lit("foo:80"))); + CU_ASSERT( + std::make_pair(StringRef::from_lit("::1"), StringRef::from_lit("80")) == + util::split_hostport(StringRef::from_lit("[::1]:80"))); + CU_ASSERT(std::make_pair(StringRef::from_lit("::1"), StringRef{}) == + util::split_hostport(StringRef::from_lit("[::1]"))); + + CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) == + util::split_hostport(StringRef{})); + CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) == + util::split_hostport(StringRef::from_lit("[::1]:"))); + CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) == + util::split_hostport(StringRef::from_lit("foo:"))); + CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) == + util::split_hostport(StringRef::from_lit("[::1:"))); + CU_ASSERT(std::make_pair(StringRef{}, StringRef{}) == + util::split_hostport(StringRef::from_lit("[::1]80"))); +} + +void test_util_split_str(void) { + CU_ASSERT(std::vector{StringRef::from_lit("")} == + util::split_str(StringRef::from_lit(""), ',')); + CU_ASSERT(std::vector{StringRef::from_lit("alpha")} == + util::split_str(StringRef::from_lit("alpha"), ',')); + CU_ASSERT((std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("")}) == + util::split_str(StringRef::from_lit("alpha,"), ',')); + CU_ASSERT((std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("bravo")}) == + util::split_str(StringRef::from_lit("alpha,bravo"), ',')); + CU_ASSERT((std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("bravo"), + StringRef::from_lit("charlie")}) == + util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',')); + CU_ASSERT( + (std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("bravo"), + StringRef::from_lit("charlie")}) == + util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 0)); + CU_ASSERT(std::vector{StringRef::from_lit("")} == + util::split_str(StringRef::from_lit(""), ',', 1)); + CU_ASSERT(std::vector{StringRef::from_lit("")} == + util::split_str(StringRef::from_lit(""), ',', 2)); + CU_ASSERT( + (std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("bravo,charlie")}) == + util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 2)); + CU_ASSERT(std::vector{StringRef::from_lit("alpha")} == + util::split_str(StringRef::from_lit("alpha"), ',', 2)); + CU_ASSERT((std::vector{StringRef::from_lit("alpha"), + StringRef::from_lit("")}) == + util::split_str(StringRef::from_lit("alpha,"), ',', 2)); + CU_ASSERT(std::vector{StringRef::from_lit("alpha")} == + util::split_str(StringRef::from_lit("alpha"), ',', 0)); + CU_ASSERT( + std::vector{StringRef::from_lit("alpha,bravo,charlie")} == + util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 1)); +} + +void test_util_rstrip(void) { + BlockAllocator balloc(4096, 4096); + + CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha"))); + CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha "))); + CU_ASSERT("alpha" == util::rstrip(balloc, StringRef::from_lit("alpha \t"))); + CU_ASSERT("" == util::rstrip(balloc, StringRef::from_lit(""))); + CU_ASSERT("" == util::rstrip(balloc, StringRef::from_lit("\t\t\t "))); +} + +} // namespace shrpx diff --git a/lib/nghttp2/src/util_test.h b/lib/nghttp2/src/util_test.h new file mode 100644 index 00000000000..48925ab2b1e --- /dev/null +++ b/lib/nghttp2/src/util_test.h @@ -0,0 +1,75 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 +#endif // HAVE_CONFIG_H + +namespace shrpx { + +void test_util_streq(void); +void test_util_strieq(void); +void test_util_inp_strlower(void); +void test_util_to_base64(void); +void test_util_to_token68(void); +void test_util_percent_encode_token(void); +void test_util_percent_decode(void); +void test_util_quote_string(void); +void test_util_utox(void); +void test_util_http_date(void); +void test_util_select_h2(void); +void test_util_ipv6_numeric_addr(void); +void test_util_utos(void); +void test_util_make_string_ref_uint(void); +void test_util_utos_unit(void); +void test_util_utos_funit(void); +void test_util_parse_uint_with_unit(void); +void test_util_parse_uint(void); +void test_util_parse_duration_with_unit(void); +void test_util_duration_str(void); +void test_util_format_duration(void); +void test_util_starts_with(void); +void test_util_ends_with(void); +void test_util_parse_http_date(void); +void test_util_localtime_date(void); +void test_util_get_uint64(void); +void test_util_parse_config_str_list(void); +void test_util_make_http_hostport(void); +void test_util_make_hostport(void); +void test_util_strifind(void); +void test_util_random_alpha_digit(void); +void test_util_format_hex(void); +void test_util_is_hex_string(void); +void test_util_decode_hex(void); +void test_util_extract_host(void); +void test_util_split_hostport(void); +void test_util_split_str(void); +void test_util_rstrip(void); + +} // namespace shrpx + +#endif // UTIL_TEST_H diff --git a/lib/nghttp2/src/xsi_strerror.c b/lib/nghttp2/src/xsi_strerror.c new file mode 100644 index 00000000000..d008d4e26e4 --- /dev/null +++ b/lib/nghttp2/src/xsi_strerror.c @@ -0,0 +1,50 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 "xsi_strerror.h" + +/* Make sure that we get XSI-compliant version of strerror_r */ +#ifdef _POSIX_C_SOURCE +# undef _POSIX_C_SOURCE +#endif /* _POSIX_C_SOURCE */ + +#ifdef _GNU_SOURCE +# undef _GNU_SOURCE +#endif /* _GNU_SOURCE */ + +#include + +char *xsi_strerror(int errnum, char *buf, size_t buflen) { + int rv; + + rv = strerror_r(errnum, buf, buflen); + + if (rv != 0) { + if (buflen > 0) { + buf[0] = '\0'; + } + } + + return buf; +} diff --git a/lib/nghttp2/src/xsi_strerror.h b/lib/nghttp2/src/xsi_strerror.h new file mode 100644 index 00000000000..32cadc34c44 --- /dev/null +++ b/lib/nghttp2/src/xsi_strerror.h @@ -0,0 +1,55 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2016 Tatsuhiro Tsujikawa + * + * 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 XSI_STRERROR_H +#define XSI_STRERROR_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Looks like error message is quite small, but we really don't know + how much longer they become. */ +#define STRERROR_BUFSIZE 256 + +/* + * Returns description of error denoted by |errnum|. The description + * is written in |buf| of length |buflen| including terminal NULL. If + * there is an error, including the case that buffer space is not + * sufficient to include error message, and |buflen| > 0, empty string + * is written to |buf|. This function returns |buf|. + */ +char *xsi_strerror(int errnum, char *buf, size_t buflen); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* XSI_STRERROR_H */ diff --git a/lib/nghttp2/tests/.gitignore b/lib/nghttp2/tests/.gitignore new file mode 100644 index 00000000000..42dfe637767 --- /dev/null +++ b/lib/nghttp2/tests/.gitignore @@ -0,0 +1,3 @@ +# tests +failmalloc +main diff --git a/lib/nghttp2/tests/CMakeLists.txt b/lib/nghttp2/tests/CMakeLists.txt new file mode 100644 index 00000000000..0e05235e187 --- /dev/null +++ b/lib/nghttp2/tests/CMakeLists.txt @@ -0,0 +1,60 @@ +# XXX testdata/: EXTRA_DIST = cacert.pem index.html privkey.pem +if(HAVE_CUNIT) + string(REPLACE " " ";" c_flags "${WARNCFLAGS}") + add_compile_options(${c_flags}) + + include_directories( + "${CMAKE_SOURCE_DIR}/lib/includes" + "${CMAKE_SOURCE_DIR}/lib" + "${CMAKE_BINARY_DIR}/lib/includes" + ${CUNIT_INCLUDE_DIRS} + ) + + set(MAIN_SOURCES + main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c + nghttp2_test_helper.c + nghttp2_frame_test.c + nghttp2_stream_test.c + nghttp2_session_test.c + nghttp2_hd_test.c + nghttp2_npn_test.c + nghttp2_helper_test.c + nghttp2_buf_test.c + nghttp2_http_test.c + nghttp2_extpri_test.c + nghttp2_ratelim_test.c + ) + + add_executable(main EXCLUDE_FROM_ALL + ${MAIN_SOURCES} + ) + target_include_directories(main PRIVATE ${CUNIT_INCLUDE_DIRS}) + target_link_libraries(main + nghttp2_static + ${CUNIT_LIBRARIES} + ) + add_test(main main) + add_dependencies(check main) + + if(ENABLE_FAILMALLOC) + set(FAILMALLOC_SOURCES + failmalloc.c failmalloc_test.c + malloc_wrapper.c + nghttp2_test_helper.c + ) + add_executable(failmalloc EXCLUDE_FROM_ALL + ${FAILMALLOC_SOURCES} + ) + target_link_libraries(failmalloc + nghttp2_static + ${CUNIT_LIBRARIES} + ) + add_test(failmalloc failmalloc) + add_dependencies(check failmalloc) + endif() + + if(ENABLE_APP) + # EXTRA_DIST = end_to_end.py + # TESTS += end_to_end.py + endif() +endif() diff --git a/lib/nghttp2/tests/Makefile.am b/lib/nghttp2/tests/Makefile.am new file mode 100644 index 00000000000..e6cdde111cb --- /dev/null +++ b/lib/nghttp2/tests/Makefile.am @@ -0,0 +1,100 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = testdata + +EXTRA_DIST = CMakeLists.txt + +if HAVE_CUNIT + +check_PROGRAMS = main + +if ENABLE_FAILMALLOC +check_PROGRAMS += failmalloc +endif # ENABLE_FAILMALLOC + +OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \ + nghttp2_test_helper.c \ + nghttp2_frame_test.c \ + nghttp2_stream_test.c \ + nghttp2_session_test.c \ + nghttp2_hd_test.c \ + nghttp2_npn_test.c \ + nghttp2_helper_test.c \ + nghttp2_buf_test.c \ + nghttp2_http_test.c \ + nghttp2_extpri_test.c \ + nghttp2_ratelim_test.c + +HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \ + nghttp2_session_test.h \ + nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_hd_test.h \ + nghttp2_npn_test.h nghttp2_helper_test.h \ + nghttp2_test_helper.h \ + nghttp2_buf_test.h \ + nghttp2_http_test.h \ + nghttp2_extpri_test.h \ + nghttp2_ratelim_test.h + +main_SOURCES = $(HFILES) $(OBJECTS) + +if ENABLE_STATIC +main_LDADD = ${top_builddir}/lib/libnghttp2.la +else +# With static lib disabled and symbol hiding enabled, we have to link object +# files directly because the tests use symbols not included in public API. +main_LDADD = ${top_builddir}/lib/.libs/*.o +endif + +main_LDADD += @CUNIT_LIBS@ @TESTLDADD@ +main_LDFLAGS = -static + +if ENABLE_FAILMALLOC +failmalloc_SOURCES = failmalloc.c failmalloc_test.c failmalloc_test.h \ + malloc_wrapper.c malloc_wrapper.h \ + nghttp2_test_helper.c nghttp2_test_helper.h +failmalloc_LDADD = $(main_LDADD) +failmalloc_LDFLAGS = $(main_LDFLAGS) +endif # ENABLE_FAILMALLOC + +AM_CFLAGS = $(WARNCFLAGS) \ + -I${top_srcdir}/lib \ + -I${top_srcdir}/lib/includes \ + -I${top_builddir}/lib/includes \ + -DBUILDING_NGHTTP2 \ + -DNGHTTP2_STATICLIB \ + @CUNIT_CFLAGS@ @DEFS@ + +TESTS = main + +if ENABLE_FAILMALLOC +TESTS += failmalloc +endif # ENABLE_FAILMALLOC + +if ENABLE_APP + +# EXTRA_DIST = end_to_end.py +# TESTS += end_to_end.py + +endif # ENABLE_APP + +endif # HAVE_CUNIT diff --git a/lib/nghttp2/tests/end_to_end.py b/lib/nghttp2/tests/end_to_end.py new file mode 100755 index 00000000000..cfb3cdd7dce --- /dev/null +++ b/lib/nghttp2/tests/end_to_end.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +"""End to end tests for the utility programs. + +This test assumes the utilities inside src directory have already been +built. + +At the moment top_buiddir is not in the environment, but top_builddir would be +more reliable than '..', so it's worth trying to pull it from the environment. +""" + +__author__ = 'Jim Morrison ' + + +import os +import subprocess +import time +import unittest + + +_PORT = 9893 + + +def _run_server(port, args): + srcdir = os.environ.get('srcdir', '.') + testdata = '%s/testdata' % srcdir + top_builddir = os.environ.get('top_builddir', '..') + base_args = ['%s/src/spdyd' % top_builddir, '-d', testdata] + if args: + base_args.extend(args) + base_args.extend([str(port), '%s/privkey.pem' % testdata, + '%s/cacert.pem' % testdata]) + return subprocess.Popen(base_args) + +def _check_server_up(port): + # Check this check for now. + time.sleep(1) + +def _kill_server(server): + while server.returncode is None: + server.terminate() + time.sleep(1) + server.poll() + + +class EndToEndSpdyTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.setUpServer([]) + + @classmethod + def setUpServer(cls, args): + cls.server = _run_server(_PORT, args) + _check_server_up(_PORT) + + @classmethod + def tearDownClass(cls): + _kill_server(cls.server) + + def setUp(self): + build_dir = os.environ.get('top_builddir', '..') + self.client = '%s/src/spdycat' % build_dir + self.stdout = 'No output' + + def call(self, path, args): + full_args = [self.client,'http://localhost:%d%s' % (_PORT, path)] + args + p = subprocess.Popen(full_args, stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + self.stdout, self.stderr = p.communicate() + return p.returncode + + +class EndToEndSpdy2Tests(EndToEndSpdyTests): + def testSimpleRequest(self): + self.assertEquals(0, self.call('/', [])) + + def testSimpleRequestSpdy3(self): + self.assertEquals(0, self.call('/', ['-v', '-3'])) + self.assertIn('NPN selected the protocol: spdy/3', self.stdout) + + def testFailedRequests(self): + self.assertEquals( + 2, self.call('/', ['https://localhost:25/', 'http://localhost:79'])) + + def testOneFailedRequest(self): + self.assertEquals(1, subprocess.call([self.client, 'http://localhost:2/'])) + + def testOneTimedOutRequest(self): + self.assertEquals(1, self.call('/?spdyd_do_not_respond_to_req=yes', + ['--timeout=2'])) + self.assertEquals(0, self.call('/', ['--timeout=20'])) + + +class EndToEndSpdy3Tests(EndToEndSpdyTests): + @classmethod + def setUpClass(cls): + cls.setUpServer(['-3']) + + def testSimpleRequest(self): + self.assertEquals(0, self.call('/', ['-v'])) + self.assertIn('NPN selected the protocol: spdy/3', self.stdout) + + +if __name__ == '__main__': + unittest.main() diff --git a/lib/nghttp2/tests/failmalloc.c b/lib/nghttp2/tests/failmalloc.c new file mode 100644 index 00000000000..6294cffff88 --- /dev/null +++ b/lib/nghttp2/tests/failmalloc.c @@ -0,0 +1,79 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +/* include test cases' include files here */ + +#include "failmalloc_test.h" + +static int init_suite1(void) { return 0; } + +static int clean_suite1(void) { return 0; } + +int main(void) { + CU_pSuite pSuite = NULL; + unsigned int num_tests_failed; + + /* initialize the CUnit test registry */ + if (CUE_SUCCESS != CU_initialize_registry()) + return (int)CU_get_error(); + + /* add a suite to the registry */ + pSuite = CU_add_suite("libnghttp2_TestSuite", init_suite1, clean_suite1); + if (NULL == pSuite) { + CU_cleanup_registry(); + return (int)CU_get_error(); + } + + /* add the tests to the suite */ + if (!CU_add_test(pSuite, "failmalloc_session_send", + test_nghttp2_session_send) || + !CU_add_test(pSuite, "failmalloc_session_send_server", + test_nghttp2_session_send_server) || + !CU_add_test(pSuite, "failmalloc_session_recv", + test_nghttp2_session_recv) || + !CU_add_test(pSuite, "failmalloc_frame", test_nghttp2_frame) || + !CU_add_test(pSuite, "failmalloc_hd", test_nghttp2_hd)) { + CU_cleanup_registry(); + return (int)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 (int)num_tests_failed; + } else { + printf("CUnit Error: %s\n", CU_get_error_msg()); + return (int)CU_get_error(); + } +} diff --git a/lib/nghttp2/tests/failmalloc_test.c b/lib/nghttp2/tests/failmalloc_test.c new file mode 100644 index 00000000000..594bc727e3f --- /dev/null +++ b/lib/nghttp2/tests/failmalloc_test.c @@ -0,0 +1,576 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2014 Tatsuhiro Tsujikawa + * + * 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 "failmalloc_test.h" + +#include +#include + +#include + +#include "nghttp2_session.h" +#include "nghttp2_stream.h" +#include "nghttp2_frame.h" +#include "nghttp2_helper.h" +#include "malloc_wrapper.h" +#include "nghttp2_test_helper.h" + +typedef struct { + uint8_t data[8192]; + uint8_t *datamark, *datalimit; +} data_feed; + +typedef struct { + data_feed *df; + size_t data_source_length; +} my_user_data; + +static void data_feed_init(data_feed *df, nghttp2_bufs *bufs) { + nghttp2_buf *buf; + size_t data_length; + + buf = &bufs->head->buf; + data_length = nghttp2_buf_len(buf); + + assert(data_length <= sizeof(df->data)); + memcpy(df->data, buf->pos, data_length); + df->datamark = df->data; + df->datalimit = df->data + data_length; +} + +static ssize_t null_send_callback(nghttp2_session *session, const uint8_t *data, + size_t len, int flags, void *user_data) { + (void)session; + (void)data; + (void)flags; + (void)user_data; + + return (ssize_t)len; +} + +static ssize_t data_feed_recv_callback(nghttp2_session *session, uint8_t *data, + size_t len, int flags, void *user_data) { + data_feed *df = ((my_user_data *)user_data)->df; + size_t avail = (size_t)(df->datalimit - df->datamark); + size_t wlen = nghttp2_min(avail, len); + (void)session; + (void)flags; + + memcpy(data, df->datamark, wlen); + df->datamark += wlen; + return (ssize_t)wlen; +} + +static ssize_t fixed_length_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + size_t wlen; + (void)session; + (void)stream_id; + (void)buf; + (void)source; + + if (len < ud->data_source_length) { + wlen = len; + } else { + wlen = ud->data_source_length; + } + ud->data_source_length -= wlen; + if (ud->data_source_length == 0) { + *data_flags = NGHTTP2_DATA_FLAG_EOF; + } + return (ssize_t)wlen; +} + +#define TEST_FAILMALLOC_RUN(FUN) \ + do { \ + int nmalloc, i; \ + \ + nghttp2_failmalloc = 0; \ + nghttp2_nmalloc = 0; \ + FUN(); \ + nmalloc = nghttp2_nmalloc; \ + \ + nghttp2_failmalloc = 1; \ + for (i = 0; i < nmalloc; ++i) { \ + nghttp2_nmalloc = 0; \ + nghttp2_failstart = i; \ + /* printf("i=%zu\n", i); */ \ + FUN(); \ + /* printf("nmalloc=%d\n", nghttp2_nmalloc); */ \ + } \ + nghttp2_failmalloc = 0; \ + } while (0) + +static void run_nghttp2_session_send(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv nv[] = {MAKE_NV(":host", "example.org"), + MAKE_NV(":scheme", "https")}; + nghttp2_data_provider data_prd; + nghttp2_settings_entry iv[2]; + my_user_data ud; + int rv; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = 64 * 1024; + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 4096; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[1].value = 100; + + rv = nghttp2_session_client_new3(&session, &callbacks, &ud, NULL, + nghttp2_mem_fm()); + if (rv != 0) { + goto client_new_fail; + } + rv = nghttp2_submit_request(session, NULL, nv, ARRLEN(nv), &data_prd, NULL); + if (rv < 0) { + goto fail; + } + rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, nv, + ARRLEN(nv), NULL); + if (rv < 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + /* The HEADERS submitted by the previous nghttp2_submit_headers will + have stream ID 3. Send HEADERS to that stream. */ + rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, NULL, nv, + ARRLEN(nv), NULL); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 3, &data_prd); + if (rv != 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 3, NGHTTP2_CANCEL); + if (rv != 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2); + if (rv != 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + rv = nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 100, NGHTTP2_NO_ERROR, + NULL, 0); + if (rv != 0) { + goto fail; + } + rv = nghttp2_session_send(session); + if (rv != 0) { + goto fail; + } + +fail: + nghttp2_session_del(session); +client_new_fail:; +} + +void test_nghttp2_session_send(void) { + TEST_FAILMALLOC_RUN(run_nghttp2_session_send); +} + +static void run_nghttp2_session_send_server(void) { + nghttp2_session *session; + nghttp2_session_callbacks *callbacks; + int rv; + const uint8_t *txdata; + ssize_t txdatalen; + const uint8_t origin[] = "nghttp2.org"; + const uint8_t altsvc_field_value[] = "h2=\":443\""; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + static const nghttp2_origin_entry ov = { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }; + + rv = nghttp2_session_callbacks_new(&callbacks); + if (rv != 0) { + return; + } + + rv = nghttp2_session_server_new3(&session, callbacks, NULL, NULL, + nghttp2_mem_fm()); + + nghttp2_session_callbacks_del(callbacks); + + if (rv != 0) { + return; + } + + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin, + sizeof(origin) - 1, altsvc_field_value, + sizeof(altsvc_field_value) - 1); + if (rv != 0) { + goto fail; + } + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, &ov, 1); + if (rv != 0) { + goto fail; + } + + txdatalen = nghttp2_session_mem_send(session, &txdata); + + if (txdatalen < 0) { + goto fail; + } + +fail: + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_server(void) { + TEST_FAILMALLOC_RUN(run_nghttp2_session_send_server); +} + +static void run_nghttp2_session_recv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_nv nv[] = { + MAKE_NV(":method", "GET"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "example.org"), + MAKE_NV(":path", "/"), + }; + nghttp2_settings_entry iv[2]; + my_user_data ud; + data_feed df; + int rv; + nghttp2_nv *nva; + size_t nvlen; + + rv = frame_pack_bufs_init(&bufs); + + if (rv != 0) { + return; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.recv_callback = data_feed_recv_callback; + ud.df = &df; + + nghttp2_failmalloc_pause(); + nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm()); + nghttp2_session_server_new3(&session, &callbacks, &ud, NULL, + nghttp2_mem_fm()); + + /* Client preface */ + nghttp2_bufs_add(&bufs, NGHTTP2_CLIENT_MAGIC, NGHTTP2_CLIENT_MAGIC_LEN); + data_feed_init(&df, &bufs); + nghttp2_bufs_reset(&bufs); + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + + nghttp2_failmalloc_pause(); + /* SETTINGS */ + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 4096; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[1].value = 100; + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + nghttp2_frame_iv_copy(iv, 2, nghttp2_mem_fm()), + 2); + nghttp2_frame_pack_settings(&bufs, &frame.settings); + nghttp2_frame_settings_free(&frame.settings, nghttp2_mem_fm()); + data_feed_init(&df, &bufs); + nghttp2_bufs_reset(&bufs); + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + + nghttp2_failmalloc_pause(); + /* HEADERS */ + nvlen = ARRLEN(nv); + nghttp2_nv_array_copy(&nva, nv, nvlen, nghttp2_mem_fm()); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1, + NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + nghttp2_frame_headers_free(&frame.headers, nghttp2_mem_fm()); + data_feed_init(&df, &bufs); + nghttp2_bufs_reset(&bufs); + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + + /* PING */ + nghttp2_failmalloc_pause(); + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + nghttp2_frame_pack_ping(&bufs, &frame.ping); + nghttp2_frame_ping_free(&frame.ping); + data_feed_init(&df, &bufs); + nghttp2_bufs_reset(&bufs); + + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + + /* RST_STREAM */ + nghttp2_failmalloc_pause(); + nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_PROTOCOL_ERROR); + nghttp2_frame_pack_rst_stream(&bufs, &frame.rst_stream); + nghttp2_frame_rst_stream_free(&frame.rst_stream); + nghttp2_bufs_reset(&bufs); + + nghttp2_failmalloc_unpause(); + + rv = nghttp2_session_recv(session); + if (rv != 0) { + goto fail; + } + +fail: + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_session_recv(void) { + TEST_FAILMALLOC_RUN(run_nghttp2_session_recv); +} + +static void run_nghttp2_frame_pack_headers(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_frame frame, oframe; + nghttp2_bufs bufs; + nghttp2_nv nv[] = {MAKE_NV(":host", "example.org"), + MAKE_NV(":scheme", "https")}; + int rv; + nghttp2_nv *nva; + size_t nvlen; + + rv = frame_pack_bufs_init(&bufs); + + if (rv != 0) { + return; + } + + rv = nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm()); + if (rv != 0) { + goto deflate_init_fail; + } + rv = nghttp2_hd_inflate_init(&inflater, nghttp2_mem_fm()); + if (rv != 0) { + goto inflate_init_fail; + } + nvlen = ARRLEN(nv); + rv = nghttp2_nv_array_copy(&nva, nv, nvlen, nghttp2_mem_fm()); + if (rv < 0) { + goto nv_copy_fail; + } + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1, + NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + if (rv != 0) { + goto fail; + } + rv = unpack_framebuf(&oframe, &bufs); + if (rv != 0) { + goto fail; + } + nghttp2_frame_headers_free(&oframe.headers, nghttp2_mem_fm()); + +fail: + nghttp2_frame_headers_free(&frame.headers, nghttp2_mem_fm()); +nv_copy_fail: + nghttp2_hd_inflate_free(&inflater); +inflate_init_fail: + nghttp2_hd_deflate_free(&deflater); +deflate_init_fail: + nghttp2_bufs_free(&bufs); +} + +static void run_nghttp2_frame_pack_settings(void) { + nghttp2_frame frame, oframe; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_settings_entry iv[2], *iv_copy; + int rv; + + rv = frame_pack_bufs_init(&bufs); + + if (rv != 0) { + return; + } + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 4096; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[1].value = 100; + + iv_copy = nghttp2_frame_iv_copy(iv, 2, nghttp2_mem_fm()); + + if (iv_copy == NULL) { + goto iv_copy_fail; + } + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, iv_copy, 2); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + if (rv != 0) { + goto fail; + } + + buf = &bufs.head->buf; + + rv = nghttp2_frame_unpack_settings_payload2( + &oframe.settings.iv, &oframe.settings.niv, buf->pos + NGHTTP2_FRAME_HDLEN, + nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN, nghttp2_mem_fm()); + + if (rv != 0) { + goto fail; + } + nghttp2_frame_settings_free(&oframe.settings, nghttp2_mem_fm()); + +fail: + nghttp2_frame_settings_free(&frame.settings, nghttp2_mem_fm()); +iv_copy_fail: + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame(void) { + TEST_FAILMALLOC_RUN(run_nghttp2_frame_pack_headers); + TEST_FAILMALLOC_RUN(run_nghttp2_frame_pack_settings); +} + +static int deflate_inflate(nghttp2_hd_deflater *deflater, + nghttp2_hd_inflater *inflater, nghttp2_bufs *bufs, + nghttp2_nv *nva, size_t nvlen, nghttp2_mem *mem) { + int rv; + + rv = nghttp2_hd_deflate_hd_bufs(deflater, bufs, nva, nvlen); + + if (rv != 0) { + return rv; + } + + rv = (int)inflate_hd(inflater, NULL, bufs, 0, mem); + + if (rv < 0) { + return rv; + } + + nghttp2_bufs_reset(bufs); + + return 0; +} + +static void run_nghttp2_hd(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + int rv; + nghttp2_nv nva1[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"), + MAKE_NV(":path", "/slashdot"), + MAKE_NV("accept-encoding", "gzip, deflate"), MAKE_NV("foo", "bar")}; + nghttp2_nv nva2[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"), + MAKE_NV(":path", "/style.css"), MAKE_NV("cookie", "nghttp2=FTW"), + MAKE_NV("foo", "bar2")}; + + rv = frame_pack_bufs_init(&bufs); + + if (rv != 0) { + return; + } + + rv = nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm()); + + if (rv != 0) { + goto deflate_init_fail; + } + + rv = nghttp2_hd_inflate_init(&inflater, nghttp2_mem_fm()); + + if (rv != 0) { + goto inflate_init_fail; + } + + rv = deflate_inflate(&deflater, &inflater, &bufs, nva1, ARRLEN(nva1), + nghttp2_mem_fm()); + + if (rv != 0) { + goto deflate_hd_fail; + } + + rv = deflate_inflate(&deflater, &inflater, &bufs, nva2, ARRLEN(nva2), + nghttp2_mem_fm()); + + if (rv != 0) { + goto deflate_hd_fail; + } + +deflate_hd_fail: + nghttp2_hd_inflate_free(&inflater); +inflate_init_fail: + nghttp2_hd_deflate_free(&deflater); +deflate_init_fail: + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_hd(void) { TEST_FAILMALLOC_RUN(run_nghttp2_hd); } diff --git a/lib/nghttp2/tests/failmalloc_test.h b/lib/nghttp2/tests/failmalloc_test.h new file mode 100644 index 00000000000..576932a7a93 --- /dev/null +++ b/lib/nghttp2/tests/failmalloc_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 FAILMALLOC_TEST_H +#define FAILMALLOC_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_session_send(void); +void test_nghttp2_session_send_server(void); +void test_nghttp2_session_recv(void); +void test_nghttp2_frame(void); +void test_nghttp2_hd(void); + +#endif /* FAILMALLOC_TEST_H */ diff --git a/lib/nghttp2/tests/main.c b/lib/nghttp2/tests/main.c new file mode 100644 index 00000000000..fc3c28d17a5 --- /dev/null +++ b/lib/nghttp2/tests/main.c @@ -0,0 +1,473 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +/* include test cases' include files here */ +#include "nghttp2_pq_test.h" +#include "nghttp2_map_test.h" +#include "nghttp2_queue_test.h" +#include "nghttp2_session_test.h" +#include "nghttp2_frame_test.h" +#include "nghttp2_stream_test.h" +#include "nghttp2_hd_test.h" +#include "nghttp2_npn_test.h" +#include "nghttp2_helper_test.h" +#include "nghttp2_buf_test.h" +#include "nghttp2_http_test.h" +#include "nghttp2_extpri_test.h" +#include "nghttp2_ratelim_test.h" + +extern int nghttp2_enable_strict_preface; + +static int init_suite1(void) { return 0; } + +static int clean_suite1(void) { return 0; } + +int main(void) { + CU_pSuite pSuite = NULL; + unsigned int num_tests_failed; + + nghttp2_enable_strict_preface = 0; + + /* initialize the CUnit test registry */ + if (CUE_SUCCESS != CU_initialize_registry()) + return (int)CU_get_error(); + + /* add a suite to the registry */ + pSuite = CU_add_suite("libnghttp2_TestSuite", init_suite1, clean_suite1); + if (NULL == pSuite) { + CU_cleanup_registry(); + return (int)CU_get_error(); + } + + /* add the tests to the suite */ + if (!CU_add_test(pSuite, "pq", test_nghttp2_pq) || + !CU_add_test(pSuite, "pq_update", test_nghttp2_pq_update) || + !CU_add_test(pSuite, "pq_remove", test_nghttp2_pq_remove) || + !CU_add_test(pSuite, "map", test_nghttp2_map) || + !CU_add_test(pSuite, "map_functional", test_nghttp2_map_functional) || + !CU_add_test(pSuite, "map_each_free", test_nghttp2_map_each_free) || + !CU_add_test(pSuite, "queue", test_nghttp2_queue) || + !CU_add_test(pSuite, "npn", test_nghttp2_npn) || + !CU_add_test(pSuite, "session_recv", test_nghttp2_session_recv) || + !CU_add_test(pSuite, "session_recv_invalid_stream_id", + test_nghttp2_session_recv_invalid_stream_id) || + !CU_add_test(pSuite, "session_recv_invalid_frame", + test_nghttp2_session_recv_invalid_frame) || + !CU_add_test(pSuite, "session_recv_eof", test_nghttp2_session_recv_eof) || + !CU_add_test(pSuite, "session_recv_data", + test_nghttp2_session_recv_data) || + !CU_add_test(pSuite, "session_recv_data_no_auto_flow_control", + test_nghttp2_session_recv_data_no_auto_flow_control) || + !CU_add_test(pSuite, "session_recv_continuation", + test_nghttp2_session_recv_continuation) || + !CU_add_test(pSuite, "session_recv_headers_with_priority", + test_nghttp2_session_recv_headers_with_priority) || + !CU_add_test(pSuite, "session_recv_headers_with_padding", + test_nghttp2_session_recv_headers_with_padding) || + !CU_add_test(pSuite, "session_recv_headers_early_response", + test_nghttp2_session_recv_headers_early_response) || + !CU_add_test(pSuite, "session_recv_headers_for_closed_stream", + test_nghttp2_session_recv_headers_for_closed_stream) || + !CU_add_test(pSuite, "session_recv_headers_with_extpri", + test_nghttp2_session_recv_headers_with_extpri) || + !CU_add_test(pSuite, "session_server_recv_push_response", + test_nghttp2_session_server_recv_push_response) || + !CU_add_test(pSuite, "session_recv_premature_headers", + test_nghttp2_session_recv_premature_headers) || + !CU_add_test(pSuite, "session_recv_unknown_frame", + test_nghttp2_session_recv_unknown_frame) || + !CU_add_test(pSuite, "session_recv_unexpected_continuation", + test_nghttp2_session_recv_unexpected_continuation) || + !CU_add_test(pSuite, "session_recv_settings_header_table_size", + test_nghttp2_session_recv_settings_header_table_size) || + !CU_add_test(pSuite, "session_recv_too_large_frame_length", + test_nghttp2_session_recv_too_large_frame_length) || + !CU_add_test(pSuite, "session_recv_extension", + test_nghttp2_session_recv_extension) || + !CU_add_test(pSuite, "session_recv_altsvc", + test_nghttp2_session_recv_altsvc) || + !CU_add_test(pSuite, "session_recv_origin", + test_nghttp2_session_recv_origin) || + !CU_add_test(pSuite, "session_recv_priority_update", + test_nghttp2_session_recv_priority_update) || + !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || + !CU_add_test(pSuite, "session_add_frame", + test_nghttp2_session_add_frame) || + !CU_add_test(pSuite, "session_on_request_headers_received", + test_nghttp2_session_on_request_headers_received) || + !CU_add_test(pSuite, "session_on_response_headers_received", + test_nghttp2_session_on_response_headers_received) || + !CU_add_test(pSuite, "session_on_headers_received", + test_nghttp2_session_on_headers_received) || + !CU_add_test(pSuite, "session_on_push_response_headers_received", + test_nghttp2_session_on_push_response_headers_received) || + !CU_add_test(pSuite, "session_on_priority_received", + test_nghttp2_session_on_priority_received) || + !CU_add_test(pSuite, "session_on_rst_stream_received", + test_nghttp2_session_on_rst_stream_received) || + !CU_add_test(pSuite, "session_on_settings_received", + test_nghttp2_session_on_settings_received) || + !CU_add_test(pSuite, "session_on_push_promise_received", + test_nghttp2_session_on_push_promise_received) || + !CU_add_test(pSuite, "session_on_ping_received", + test_nghttp2_session_on_ping_received) || + !CU_add_test(pSuite, "session_on_goaway_received", + test_nghttp2_session_on_goaway_received) || + !CU_add_test(pSuite, "session_on_window_update_received", + test_nghttp2_session_on_window_update_received) || + !CU_add_test(pSuite, "session_on_data_received", + test_nghttp2_session_on_data_received) || + !CU_add_test(pSuite, "session_on_data_received_fail_fast", + test_nghttp2_session_on_data_received_fail_fast) || + !CU_add_test(pSuite, "session_on_altsvc_received", + test_nghttp2_session_on_altsvc_received) || + !CU_add_test(pSuite, "session_send_headers_start_stream", + test_nghttp2_session_send_headers_start_stream) || + !CU_add_test(pSuite, "session_send_headers_reply", + test_nghttp2_session_send_headers_reply) || + !CU_add_test(pSuite, "session_send_headers_frame_size_error", + test_nghttp2_session_send_headers_frame_size_error) || + !CU_add_test(pSuite, "session_send_headers_push_reply", + test_nghttp2_session_send_headers_push_reply) || + !CU_add_test(pSuite, "session_send_rst_stream", + test_nghttp2_session_send_rst_stream) || + !CU_add_test(pSuite, "session_send_push_promise", + test_nghttp2_session_send_push_promise) || + !CU_add_test(pSuite, "session_is_my_stream_id", + test_nghttp2_session_is_my_stream_id) || + !CU_add_test(pSuite, "session_upgrade2", test_nghttp2_session_upgrade2) || + !CU_add_test(pSuite, "session_reprioritize_stream", + test_nghttp2_session_reprioritize_stream) || + !CU_add_test( + pSuite, "session_reprioritize_stream_with_idle_stream_dep", + test_nghttp2_session_reprioritize_stream_with_idle_stream_dep) || + !CU_add_test(pSuite, "submit_data", test_nghttp2_submit_data) || + !CU_add_test(pSuite, "submit_data_read_length_too_large", + test_nghttp2_submit_data_read_length_too_large) || + !CU_add_test(pSuite, "submit_data_read_length_smallest", + test_nghttp2_submit_data_read_length_smallest) || + !CU_add_test(pSuite, "submit_data_twice", + test_nghttp2_submit_data_twice) || + !CU_add_test(pSuite, "submit_request_with_data", + test_nghttp2_submit_request_with_data) || + !CU_add_test(pSuite, "submit_request_without_data", + test_nghttp2_submit_request_without_data) || + !CU_add_test(pSuite, "submit_response_with_data", + test_nghttp2_submit_response_with_data) || + !CU_add_test(pSuite, "submit_response_without_data", + test_nghttp2_submit_response_without_data) || + !CU_add_test(pSuite, "Submit_response_push_response", + test_nghttp2_submit_response_push_response) || + !CU_add_test(pSuite, "submit_trailer", test_nghttp2_submit_trailer) || + !CU_add_test(pSuite, "submit_headers_start_stream", + test_nghttp2_submit_headers_start_stream) || + !CU_add_test(pSuite, "submit_headers_reply", + test_nghttp2_submit_headers_reply) || + !CU_add_test(pSuite, "submit_headers_push_reply", + test_nghttp2_submit_headers_push_reply) || + !CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) || + !CU_add_test(pSuite, "submit_headers_continuation", + test_nghttp2_submit_headers_continuation) || + !CU_add_test(pSuite, "submit_headers_continuation_extra_large", + test_nghttp2_submit_headers_continuation_extra_large) || + !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) || + !CU_add_test(pSuite, "session_submit_settings", + test_nghttp2_submit_settings) || + !CU_add_test(pSuite, "session_submit_settings_update_local_window_size", + test_nghttp2_submit_settings_update_local_window_size) || + !CU_add_test(pSuite, "session_submit_settings_multiple_times", + test_nghttp2_submit_settings_multiple_times) || + !CU_add_test(pSuite, "session_submit_push_promise", + test_nghttp2_submit_push_promise) || + !CU_add_test(pSuite, "submit_window_update", + test_nghttp2_submit_window_update) || + !CU_add_test(pSuite, "submit_window_update_local_window_size", + test_nghttp2_submit_window_update_local_window_size) || + !CU_add_test(pSuite, "submit_shutdown_notice", + test_nghttp2_submit_shutdown_notice) || + !CU_add_test(pSuite, "submit_invalid_nv", + test_nghttp2_submit_invalid_nv) || + !CU_add_test(pSuite, "submit_extension", test_nghttp2_submit_extension) || + !CU_add_test(pSuite, "submit_altsvc", test_nghttp2_submit_altsvc) || + !CU_add_test(pSuite, "submit_origin", test_nghttp2_submit_origin) || + !CU_add_test(pSuite, "submit_priority_update", + test_nghttp2_submit_priority_update) || + !CU_add_test(pSuite, "submit_rst_stream", + test_nghttp2_submit_rst_stream) || + !CU_add_test(pSuite, "session_open_stream", + test_nghttp2_session_open_stream) || + !CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep", + test_nghttp2_session_open_stream_with_idle_stream_dep) || + !CU_add_test(pSuite, "session_get_next_ob_item", + test_nghttp2_session_get_next_ob_item) || + !CU_add_test(pSuite, "session_pop_next_ob_item", + test_nghttp2_session_pop_next_ob_item) || + !CU_add_test(pSuite, "session_reply_fail", + test_nghttp2_session_reply_fail) || + !CU_add_test(pSuite, "session_max_concurrent_streams", + test_nghttp2_session_max_concurrent_streams) || + !CU_add_test(pSuite, "session_stop_data_with_rst_stream", + test_nghttp2_session_stop_data_with_rst_stream) || + !CU_add_test(pSuite, "session_defer_data", + test_nghttp2_session_defer_data) || + !CU_add_test(pSuite, "session_flow_control", + test_nghttp2_session_flow_control) || + !CU_add_test(pSuite, "session_flow_control_data_recv", + test_nghttp2_session_flow_control_data_recv) || + !CU_add_test(pSuite, "session_flow_control_data_with_padding_recv", + test_nghttp2_session_flow_control_data_with_padding_recv) || + !CU_add_test(pSuite, "session_data_read_temporal_failure", + test_nghttp2_session_data_read_temporal_failure) || + !CU_add_test(pSuite, "session_on_stream_close", + test_nghttp2_session_on_stream_close) || + !CU_add_test(pSuite, "session_on_ctrl_not_send", + test_nghttp2_session_on_ctrl_not_send) || + !CU_add_test(pSuite, "session_get_outbound_queue_size", + test_nghttp2_session_get_outbound_queue_size) || + !CU_add_test(pSuite, "session_get_effective_local_window_size", + test_nghttp2_session_get_effective_local_window_size) || + !CU_add_test(pSuite, "session_set_option", + test_nghttp2_session_set_option) || + !CU_add_test(pSuite, "session_data_backoff_by_high_pri_frame", + test_nghttp2_session_data_backoff_by_high_pri_frame) || + !CU_add_test(pSuite, "session_pack_data_with_padding", + test_nghttp2_session_pack_data_with_padding) || + !CU_add_test(pSuite, "session_pack_headers_with_padding", + test_nghttp2_session_pack_headers_with_padding) || + !CU_add_test(pSuite, "pack_settings_payload", + test_nghttp2_pack_settings_payload) || + !CU_add_test(pSuite, "session_stream_dep_add", + test_nghttp2_session_stream_dep_add) || + !CU_add_test(pSuite, "session_stream_dep_remove", + test_nghttp2_session_stream_dep_remove) || + !CU_add_test(pSuite, "session_stream_dep_add_subtree", + test_nghttp2_session_stream_dep_add_subtree) || + !CU_add_test(pSuite, "session_stream_dep_remove_subtree", + test_nghttp2_session_stream_dep_remove_subtree) || + !CU_add_test( + pSuite, "session_stream_dep_all_your_stream_are_belong_to_us", + test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us) || + !CU_add_test(pSuite, "session_stream_attach_item", + test_nghttp2_session_stream_attach_item) || + !CU_add_test(pSuite, "session_stream_attach_item_subtree", + test_nghttp2_session_stream_attach_item_subtree) || + !CU_add_test(pSuite, "session_stream_get_state", + test_nghttp2_session_stream_get_state) || + !CU_add_test(pSuite, "session_stream_get_something", + test_nghttp2_session_stream_get_something) || + !CU_add_test(pSuite, "session_find_stream", + test_nghttp2_session_find_stream) || + !CU_add_test(pSuite, "session_keep_closed_stream", + test_nghttp2_session_keep_closed_stream) || + !CU_add_test(pSuite, "session_keep_idle_stream", + test_nghttp2_session_keep_idle_stream) || + !CU_add_test(pSuite, "session_detach_idle_stream", + test_nghttp2_session_detach_idle_stream) || + !CU_add_test(pSuite, "session_large_dep_tree", + test_nghttp2_session_large_dep_tree) || + !CU_add_test(pSuite, "session_graceful_shutdown", + test_nghttp2_session_graceful_shutdown) || + !CU_add_test(pSuite, "session_on_header_temporal_failure", + test_nghttp2_session_on_header_temporal_failure) || + !CU_add_test(pSuite, "session_recv_client_magic", + test_nghttp2_session_recv_client_magic) || + !CU_add_test(pSuite, "session_delete_data_item", + test_nghttp2_session_delete_data_item) || + !CU_add_test(pSuite, "session_open_idle_stream", + test_nghttp2_session_open_idle_stream) || + !CU_add_test(pSuite, "session_cancel_reserved_remote", + test_nghttp2_session_cancel_reserved_remote) || + !CU_add_test(pSuite, "session_reset_pending_headers", + test_nghttp2_session_reset_pending_headers) || + !CU_add_test(pSuite, "session_send_data_callback", + test_nghttp2_session_send_data_callback) || + !CU_add_test(pSuite, "session_on_begin_headers_temporal_failure", + test_nghttp2_session_on_begin_headers_temporal_failure) || + !CU_add_test(pSuite, "session_defer_then_close", + test_nghttp2_session_defer_then_close) || + !CU_add_test(pSuite, "session_detach_item_from_closed_stream", + test_nghttp2_session_detach_item_from_closed_stream) || + !CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) || + !CU_add_test(pSuite, "session_change_stream_priority", + test_nghttp2_session_change_stream_priority) || + !CU_add_test(pSuite, "session_change_extpri_stream_priority", + test_nghttp2_session_change_extpri_stream_priority) || + !CU_add_test(pSuite, "session_create_idle_stream", + test_nghttp2_session_create_idle_stream) || + !CU_add_test(pSuite, "session_repeated_priority_change", + test_nghttp2_session_repeated_priority_change) || + !CU_add_test(pSuite, "session_repeated_priority_submission", + test_nghttp2_session_repeated_priority_submission) || + !CU_add_test(pSuite, "session_set_local_window_size", + test_nghttp2_session_set_local_window_size) || + !CU_add_test(pSuite, "session_cancel_from_before_frame_send", + test_nghttp2_session_cancel_from_before_frame_send) || + !CU_add_test(pSuite, "session_too_many_settings", + test_nghttp2_session_too_many_settings) || + !CU_add_test(pSuite, "session_removed_closed_stream", + test_nghttp2_session_removed_closed_stream) || + !CU_add_test(pSuite, "session_pause_data", + test_nghttp2_session_pause_data) || + !CU_add_test(pSuite, "session_no_closed_streams", + test_nghttp2_session_no_closed_streams) || + !CU_add_test(pSuite, "session_set_stream_user_data", + test_nghttp2_session_set_stream_user_data) || + !CU_add_test(pSuite, "session_no_rfc7540_priorities", + test_nghttp2_session_no_rfc7540_priorities) || + !CU_add_test(pSuite, "session_server_fallback_rfc7540_priorities", + test_nghttp2_session_server_fallback_rfc7540_priorities) || + !CU_add_test(pSuite, "session_stream_reset_ratelim", + test_nghttp2_session_stream_reset_ratelim) || + !CU_add_test(pSuite, "http_mandatory_headers", + test_nghttp2_http_mandatory_headers) || + !CU_add_test(pSuite, "http_content_length", + test_nghttp2_http_content_length) || + !CU_add_test(pSuite, "http_content_length_mismatch", + test_nghttp2_http_content_length_mismatch) || + !CU_add_test(pSuite, "http_non_final_response", + test_nghttp2_http_non_final_response) || + !CU_add_test(pSuite, "http_trailer_headers", + test_nghttp2_http_trailer_headers) || + !CU_add_test(pSuite, "http_ignore_regular_header", + test_nghttp2_http_ignore_regular_header) || + !CU_add_test(pSuite, "http_ignore_content_length", + test_nghttp2_http_ignore_content_length) || + !CU_add_test(pSuite, "http_record_request_method", + test_nghttp2_http_record_request_method) || + !CU_add_test(pSuite, "http_push_promise", + test_nghttp2_http_push_promise) || + !CU_add_test(pSuite, "http_head_method_upgrade_workaround", + test_nghttp2_http_head_method_upgrade_workaround) || + !CU_add_test( + pSuite, "http_no_rfc9113_leading_and_trailing_ws_validation", + test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation) || + !CU_add_test(pSuite, "frame_pack_headers", + test_nghttp2_frame_pack_headers) || + !CU_add_test(pSuite, "frame_pack_headers_frame_too_large", + test_nghttp2_frame_pack_headers_frame_too_large) || + !CU_add_test(pSuite, "frame_pack_priority", + test_nghttp2_frame_pack_priority) || + !CU_add_test(pSuite, "frame_pack_rst_stream", + test_nghttp2_frame_pack_rst_stream) || + !CU_add_test(pSuite, "frame_pack_settings", + test_nghttp2_frame_pack_settings) || + !CU_add_test(pSuite, "frame_pack_push_promise", + test_nghttp2_frame_pack_push_promise) || + !CU_add_test(pSuite, "frame_pack_ping", test_nghttp2_frame_pack_ping) || + !CU_add_test(pSuite, "frame_pack_goaway", + test_nghttp2_frame_pack_goaway) || + !CU_add_test(pSuite, "frame_pack_window_update", + test_nghttp2_frame_pack_window_update) || + !CU_add_test(pSuite, "frame_pack_altsvc", + test_nghttp2_frame_pack_altsvc) || + !CU_add_test(pSuite, "frame_pack_origin", + test_nghttp2_frame_pack_origin) || + !CU_add_test(pSuite, "frame_pack_priority_update", + test_nghttp2_frame_pack_priority_update) || + !CU_add_test(pSuite, "nv_array_copy", test_nghttp2_nv_array_copy) || + !CU_add_test(pSuite, "iv_check", test_nghttp2_iv_check) || + !CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) || + !CU_add_test(pSuite, "hd_deflate_same_indexed_repr", + test_nghttp2_hd_deflate_same_indexed_repr) || + !CU_add_test(pSuite, "hd_inflate_indexed", + test_nghttp2_hd_inflate_indexed) || + !CU_add_test(pSuite, "hd_inflate_indname_noinc", + test_nghttp2_hd_inflate_indname_noinc) || + !CU_add_test(pSuite, "hd_inflate_indname_inc", + test_nghttp2_hd_inflate_indname_inc) || + !CU_add_test(pSuite, "hd_inflate_indname_inc_eviction", + test_nghttp2_hd_inflate_indname_inc_eviction) || + !CU_add_test(pSuite, "hd_inflate_newname_noinc", + test_nghttp2_hd_inflate_newname_noinc) || + !CU_add_test(pSuite, "hd_inflate_newname_inc", + test_nghttp2_hd_inflate_newname_inc) || + !CU_add_test(pSuite, "hd_inflate_clearall_inc", + test_nghttp2_hd_inflate_clearall_inc) || + !CU_add_test(pSuite, "hd_inflate_zero_length_huffman", + test_nghttp2_hd_inflate_zero_length_huffman) || + !CU_add_test(pSuite, "hd_inflate_expect_table_size_update", + test_nghttp2_hd_inflate_expect_table_size_update) || + !CU_add_test(pSuite, "hd_inflate_unexpected_table_size_update", + test_nghttp2_hd_inflate_unexpected_table_size_update) || + !CU_add_test(pSuite, "hd_ringbuf_reserve", + test_nghttp2_hd_ringbuf_reserve) || + !CU_add_test(pSuite, "hd_change_table_size", + test_nghttp2_hd_change_table_size) || + !CU_add_test(pSuite, "hd_deflate_inflate", + test_nghttp2_hd_deflate_inflate) || + !CU_add_test(pSuite, "hd_no_index", test_nghttp2_hd_no_index) || + !CU_add_test(pSuite, "hd_deflate_bound", test_nghttp2_hd_deflate_bound) || + !CU_add_test(pSuite, "hd_public_api", test_nghttp2_hd_public_api) || + !CU_add_test(pSuite, "hd_deflate_hd_vec", + test_nghttp2_hd_deflate_hd_vec) || + !CU_add_test(pSuite, "hd_decode_length", test_nghttp2_hd_decode_length) || + !CU_add_test(pSuite, "hd_huff_encode", test_nghttp2_hd_huff_encode) || + !CU_add_test(pSuite, "hd_huff_decode", test_nghttp2_hd_huff_decode) || + !CU_add_test(pSuite, "adjust_local_window_size", + test_nghttp2_adjust_local_window_size) || + !CU_add_test(pSuite, "check_header_name", + test_nghttp2_check_header_name) || + !CU_add_test(pSuite, "check_header_value", + test_nghttp2_check_header_value) || + !CU_add_test(pSuite, "check_header_value_rfc9113", + test_nghttp2_check_header_value_rfc9113) || + !CU_add_test(pSuite, "bufs_add", test_nghttp2_bufs_add) || + !CU_add_test(pSuite, "bufs_add_stack_buffer_overflow_bug", + test_nghttp2_bufs_add_stack_buffer_overflow_bug) || + !CU_add_test(pSuite, "bufs_addb", test_nghttp2_bufs_addb) || + !CU_add_test(pSuite, "bufs_orb", test_nghttp2_bufs_orb) || + !CU_add_test(pSuite, "bufs_remove", test_nghttp2_bufs_remove) || + !CU_add_test(pSuite, "bufs_reset", test_nghttp2_bufs_reset) || + !CU_add_test(pSuite, "bufs_advance", test_nghttp2_bufs_advance) || + !CU_add_test(pSuite, "bufs_next_present", + test_nghttp2_bufs_next_present) || + !CU_add_test(pSuite, "bufs_realloc", test_nghttp2_bufs_realloc) || + !CU_add_test(pSuite, "http_parse_priority", + test_nghttp2_http_parse_priority) || + !CU_add_test(pSuite, "extpri_to_uint8", test_nghttp2_extpri_to_uint8) || + !CU_add_test(pSuite, "ratelim_update", test_nghttp2_ratelim_update) || + !CU_add_test(pSuite, "ratelim_drain", test_nghttp2_ratelim_drain)) { + CU_cleanup_registry(); + return (int)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 (int)num_tests_failed; + } else { + printf("CUnit Error: %s\n", CU_get_error_msg()); + return (int)CU_get_error(); + } +} diff --git a/lib/nghttp2/tests/malloc_wrapper.c b/lib/nghttp2/tests/malloc_wrapper.c new file mode 100644 index 00000000000..f814c3dd9c1 --- /dev/null +++ b/lib/nghttp2/tests/malloc_wrapper.c @@ -0,0 +1,85 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "malloc_wrapper.h" + +int nghttp2_failmalloc = 0; +int nghttp2_failstart = 0; +int nghttp2_countmalloc = 1; +int nghttp2_nmalloc = 0; + +#define CHECK_PREREQ \ + do { \ + if (nghttp2_failmalloc && nghttp2_nmalloc >= nghttp2_failstart) { \ + return NULL; \ + } \ + if (nghttp2_countmalloc) { \ + ++nghttp2_nmalloc; \ + } \ + } while (0) + +static void *my_malloc(size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return malloc(size); +} + +static void my_free(void *ptr, void *mud) { + (void)mud; + + free(ptr); +} + +static void *my_calloc(size_t nmemb, size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return calloc(nmemb, size); +} + +static void *my_realloc(void *ptr, size_t size, void *mud) { + (void)mud; + + CHECK_PREREQ; + return realloc(ptr, size); +} + +static nghttp2_mem mem = {NULL, my_malloc, my_free, my_calloc, my_realloc}; + +nghttp2_mem *nghttp2_mem_fm(void) { return &mem; } + +static int failmalloc_bk, countmalloc_bk; + +void nghttp2_failmalloc_pause(void) { + failmalloc_bk = nghttp2_failmalloc; + countmalloc_bk = nghttp2_countmalloc; + nghttp2_failmalloc = 0; + nghttp2_countmalloc = 0; +} + +void nghttp2_failmalloc_unpause(void) { + nghttp2_failmalloc = failmalloc_bk; + nghttp2_countmalloc = countmalloc_bk; +} diff --git a/lib/nghttp2/tests/malloc_wrapper.h b/lib/nghttp2/tests/malloc_wrapper.h new file mode 100644 index 00000000000..a3a3dd79f73 --- /dev/null +++ b/lib/nghttp2/tests/malloc_wrapper.h @@ -0,0 +1,65 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 MALLOC_WRAPPER_H +#define MALLOC_WRAPPER_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include + +#include "nghttp2_mem.h" + +/* Global variables to control the behavior of malloc() */ + +/* If nonzero, malloc failure mode is on */ +extern int nghttp2_failmalloc; +/* If nghttp2_failstart <= nghttp2_nmalloc and nghttp2_failmalloc is + nonzero, malloc() fails. */ +extern int nghttp2_failstart; +/* If nonzero, nghttp2_nmalloc is incremented if malloc() succeeds. */ +extern int nghttp2_countmalloc; +/* The number of successful invocation of malloc(). This value is only + incremented if nghttp2_nmalloc is nonzero. */ +extern int nghttp2_nmalloc; + +/* Returns pointer to nghttp2_mem, which, when dereferenced, contains + specifically instrumented memory allocators for failmalloc + tests. */ +nghttp2_mem *nghttp2_mem_fm(void); + +/* Copies nghttp2_failmalloc and nghttp2_countmalloc to statically + allocated space and sets 0 to them. This will effectively make + malloc() work like normal malloc(). This is useful when you want to + disable malloc() failure mode temporarily. */ +void nghttp2_failmalloc_pause(void); + +/* Restores the values of nghttp2_failmalloc and nghttp2_countmalloc + with the values saved by the previous + nghttp2_failmalloc_pause(). */ +void nghttp2_failmalloc_unpause(void); + +#endif /* MALLOC_WRAPPER_H */ diff --git a/lib/nghttp2/tests/nghttp2_buf_test.c b/lib/nghttp2/tests/nghttp2_buf_test.c new file mode 100644 index 00000000000..e3e8a14a5b6 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_buf_test.c @@ -0,0 +1,344 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_buf_test.h" + +#include + +#include + +#include "nghttp2_buf.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_bufs_add(void) { + int rv; + nghttp2_bufs bufs; + uint8_t data[2048]; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 1000, 3, mem); + CU_ASSERT(0 == rv); + + CU_ASSERT(bufs.cur->buf.pos == bufs.cur->buf.last); + + rv = nghttp2_bufs_add(&bufs, data, 493); + CU_ASSERT(0 == rv); + CU_ASSERT(493 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(493 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(507 == nghttp2_bufs_cur_avail(&bufs)); + + rv = nghttp2_bufs_add(&bufs, data, 507); + CU_ASSERT(0 == rv); + CU_ASSERT(1000 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1000 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(bufs.cur == bufs.head); + + rv = nghttp2_bufs_add(&bufs, data, 1); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1001 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(bufs.cur == bufs.head->next); + + nghttp2_bufs_free(&bufs); +} + +/* Test for GH-232, stack-buffer-overflow */ +void test_nghttp2_bufs_add_stack_buffer_overflow_bug(void) { + int rv; + nghttp2_bufs bufs; + uint8_t data[1024]; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 100, 200, mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_add(&bufs, data, sizeof(data)); + + CU_ASSERT(0 == rv); + CU_ASSERT(sizeof(data) == nghttp2_bufs_len(&bufs)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_addb(void) { + int rv; + nghttp2_bufs bufs; + ssize_t i; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 1000, 3, mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_addb(&bufs, 14); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(14 == *bufs.cur->buf.pos); + + for (i = 0; i < 999; ++i) { + rv = nghttp2_bufs_addb(&bufs, 254); + + CU_ASSERT(0 == rv); + CU_ASSERT((size_t)(i + 2) == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT((size_t)(i + 2) == nghttp2_bufs_len(&bufs)); + CU_ASSERT(254 == *(bufs.cur->buf.last - 1)); + CU_ASSERT(bufs.cur == bufs.head); + } + + rv = nghttp2_bufs_addb(&bufs, 253); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1001 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(253 == *(bufs.cur->buf.last - 1)); + CU_ASSERT(bufs.cur == bufs.head->next); + + rv = nghttp2_bufs_addb_hold(&bufs, 15); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1001 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(15 == *(bufs.cur->buf.last)); + + /* test fast version */ + + nghttp2_bufs_fast_addb(&bufs, 240); + + CU_ASSERT(2 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1002 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(240 == *(bufs.cur->buf.last - 1)); + + nghttp2_bufs_fast_addb_hold(&bufs, 113); + + CU_ASSERT(2 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1002 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(113 == *(bufs.cur->buf.last)); + + /* addb_hold when last == end */ + bufs.cur->buf.last = bufs.cur->buf.end; + + rv = nghttp2_bufs_addb_hold(&bufs, 19); + CU_ASSERT(0 == rv); + CU_ASSERT(0 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(2000 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(19 == *(bufs.cur->buf.last)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_orb(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 1000, 3, mem); + CU_ASSERT(0 == rv); + + *(bufs.cur->buf.last) = 0; + + rv = nghttp2_bufs_orb_hold(&bufs, 15); + CU_ASSERT(0 == rv); + CU_ASSERT(0 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(0 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(15 == *(bufs.cur->buf.last)); + + rv = nghttp2_bufs_orb(&bufs, 240); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf)); + CU_ASSERT(1 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(255 == *(bufs.cur->buf.last - 1)); + + *(bufs.cur->buf.last) = 0; + nghttp2_bufs_fast_orb_hold(&bufs, 240); + CU_ASSERT(240 == *(bufs.cur->buf.last)); + + nghttp2_bufs_fast_orb(&bufs, 15); + CU_ASSERT(255 == *(bufs.cur->buf.last - 1)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_remove(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_buf_chain *chain; + int i; + uint8_t *out; + ssize_t outlen; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 1000, 3, mem); + CU_ASSERT(0 == rv); + + nghttp2_buf_shift_right(&bufs.cur->buf, 10); + + rv = nghttp2_bufs_add(&bufs, "hello ", 6); + CU_ASSERT(0 == rv); + + for (i = 0; i < 2; ++i) { + chain = bufs.cur; + + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + + CU_ASSERT(chain->next == bufs.cur); + } + + rv = nghttp2_bufs_add(&bufs, "world", 5); + CU_ASSERT(0 == rv); + + outlen = nghttp2_bufs_remove(&bufs, &out); + CU_ASSERT(11 == outlen); + + CU_ASSERT(0 == memcmp("hello world", out, (size_t)outlen)); + CU_ASSERT(11 == nghttp2_bufs_len(&bufs)); + + mem->free(out, NULL); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_reset(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_buf_chain *ci; + size_t offset = 9; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init3(&bufs, 250, 3, 1, offset, mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_add(&bufs, "foo", 3); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_add(&bufs, "bar", 3); + CU_ASSERT(0 == rv); + + CU_ASSERT(6 == nghttp2_bufs_len(&bufs)); + + nghttp2_bufs_reset(&bufs); + + CU_ASSERT(0 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(bufs.cur == bufs.head); + + for (ci = bufs.head; ci; ci = ci->next) { + CU_ASSERT((ssize_t)offset == ci->buf.pos - ci->buf.begin); + CU_ASSERT(ci->buf.pos == ci->buf.last); + } + + CU_ASSERT(bufs.head->next == NULL); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_advance(void) { + int rv; + nghttp2_bufs bufs; + int i; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 250, 3, mem); + CU_ASSERT(0 == rv); + + for (i = 0; i < 2; ++i) { + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + } + + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(NGHTTP2_ERR_BUFFER_ERROR == rv); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_next_present(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init(&bufs, 250, 3, mem); + CU_ASSERT(0 == rv); + + CU_ASSERT(0 == nghttp2_bufs_next_present(&bufs)); + + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + + nghttp2_bufs_rewind(&bufs); + + CU_ASSERT(0 == nghttp2_bufs_next_present(&bufs)); + + bufs.cur = bufs.head->next; + + rv = nghttp2_bufs_addb(&bufs, 1); + CU_ASSERT(0 == rv); + + nghttp2_bufs_rewind(&bufs); + + CU_ASSERT(0 != nghttp2_bufs_next_present(&bufs)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_bufs_realloc(void) { + int rv; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + rv = nghttp2_bufs_init3(&bufs, 266, 3, 1, 10, mem); + CU_ASSERT(0 == rv); + + /* Create new buffer to see that these buffers are deallocated on + realloc */ + rv = nghttp2_bufs_advance(&bufs); + CU_ASSERT(0 == rv); + + rv = nghttp2_bufs_realloc(&bufs, 522); + CU_ASSERT(0 == rv); + + CU_ASSERT(512 == nghttp2_bufs_cur_avail(&bufs)); + + rv = nghttp2_bufs_realloc(&bufs, 9); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_bufs_free(&bufs); +} diff --git a/lib/nghttp2/tests/nghttp2_buf_test.h b/lib/nghttp2/tests/nghttp2_buf_test.h new file mode 100644 index 00000000000..714b89fde6f --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_buf_test.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_BUF_TEST_H +#define NGHTTP2_BUF_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_bufs_add(void); +void test_nghttp2_bufs_add_stack_buffer_overflow_bug(void); +void test_nghttp2_bufs_addb(void); +void test_nghttp2_bufs_orb(void); +void test_nghttp2_bufs_remove(void); +void test_nghttp2_bufs_reset(void); +void test_nghttp2_bufs_advance(void); +void test_nghttp2_bufs_next_present(void); +void test_nghttp2_bufs_realloc(void); + +#endif /* NGHTTP2_BUF_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_extpri_test.c b/lib/nghttp2/tests/nghttp2_extpri_test.c new file mode 100644 index 00000000000..0ef59b7428b --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_extpri_test.c @@ -0,0 +1,52 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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 "nghttp2_extpri_test.h" + +#include + +#include + +#include "nghttp2_extpri.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_extpri_to_uint8(void) { + { + nghttp2_extpri pri = {1, 0}; + CU_ASSERT(1 == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {1, 1}; + CU_ASSERT((0x80 | 1) == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {7, 1}; + CU_ASSERT((0x80 | 7) == nghttp2_extpri_to_uint8(&pri)); + } + { + nghttp2_extpri pri = {7, 0}; + CU_ASSERT(7 == nghttp2_extpri_to_uint8(&pri)); + } +} diff --git a/lib/nghttp2/tests/nghttp2_extpri_test.h b/lib/nghttp2/tests/nghttp2_extpri_test.h new file mode 100644 index 00000000000..a8a93b92d98 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_extpri_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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 NGHTTP2_EXTPRI_TEST_H +#define NGHTTP2_EXTPRI_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_extpri_to_uint8(void); + +#endif /* NGHTTP2_EXTPRI_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_frame_test.c b/lib/nghttp2/tests/nghttp2_frame_test.c new file mode 100644 index 00000000000..7ce98dd045c --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_frame_test.c @@ -0,0 +1,735 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_frame_test.h" + +#include +#include + +#include + +#include "nghttp2_frame.h" +#include "nghttp2_helper.h" +#include "nghttp2_test_helper.h" +#include "nghttp2_priority_spec.h" + +static nghttp2_nv make_nv(const char *name, const char *value) { + nghttp2_nv nv; + nv.name = (uint8_t *)name; + nv.value = (uint8_t *)value; + nv.namelen = strlen(name); + nv.valuelen = strlen(value); + nv.flags = NGHTTP2_NV_FLAG_NONE; + + return nv; +} + +#define HEADERS_LENGTH 7 + +static nghttp2_nv *headers(nghttp2_mem *mem) { + nghttp2_nv *nva = mem->malloc(sizeof(nghttp2_nv) * HEADERS_LENGTH, NULL); + nva[0] = make_nv("method", "GET"); + nva[1] = make_nv("scheme", "https"); + nva[2] = make_nv("url", "/"); + nva[3] = make_nv("x-head", "foo"); + nva[4] = make_nv("x-head", "bar"); + nva[5] = make_nv("version", "HTTP/1.1"); + nva[6] = make_nv("x-empty", ""); + return nva; +} + +static void check_frame_header(size_t length, uint8_t type, uint8_t flags, + int32_t stream_id, nghttp2_frame_hd *hd) { + CU_ASSERT(length == hd->length); + CU_ASSERT(type == hd->type); + CU_ASSERT(flags == hd->flags); + CU_ASSERT(stream_id == hd->stream_id); + CU_ASSERT(0 == hd->reserved); +} + +void test_nghttp2_frame_pack_headers(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_headers frame, oframe; + nghttp2_bufs bufs; + nghttp2_nv *nva; + nghttp2_priority_spec pri_spec; + size_t nvlen; + nva_out out; + size_t hdblocklen; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + nva = headers(mem); + nvlen = HEADERS_LENGTH; + + nghttp2_priority_spec_default_init(&pri_spec); + + nghttp2_frame_headers_init( + &frame, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, 1000000007, + NGHTTP2_HCAT_REQUEST, &pri_spec, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); + + nghttp2_bufs_rewind(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + + check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN, + NGHTTP2_HEADERS, + NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, + 1000000007, &oframe.hd); + /* We did not include PRIORITY flag */ + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == oframe.pri_spec.weight); + + hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN; + CU_ASSERT((ssize_t)hdblocklen == + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem)); + + CU_ASSERT(7 == out.nvlen); + CU_ASSERT(nvnameeq("method", &out.nva[0])); + CU_ASSERT(nvvalueeq("GET", &out.nva[0])); + + nghttp2_frame_headers_free(&oframe, mem); + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + memset(&oframe, 0, sizeof(oframe)); + /* Next, include NGHTTP2_FLAG_PRIORITY */ + nghttp2_priority_spec_init(&frame.pri_spec, 1000000009, 12, 1); + frame.hd.flags |= NGHTTP2_FLAG_PRIORITY; + + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + + check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN, + NGHTTP2_HEADERS, + NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS | + NGHTTP2_FLAG_PRIORITY, + 1000000007, &oframe.hd); + + CU_ASSERT(1000000009 == oframe.pri_spec.stream_id); + CU_ASSERT(12 == oframe.pri_spec.weight); + CU_ASSERT(1 == oframe.pri_spec.exclusive); + + hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN - + nghttp2_frame_priority_len(oframe.hd.flags); + CU_ASSERT((ssize_t)hdblocklen == + inflate_hd(&inflater, &out, &bufs, + NGHTTP2_FRAME_HDLEN + + nghttp2_frame_priority_len(oframe.hd.flags), + mem)); + + nghttp2_nv_array_sort(out.nva, out.nvlen); + CU_ASSERT(nvnameeq("method", &out.nva[0])); + + nghttp2_frame_headers_free(&oframe, mem); + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_frame_pack_headers_frame_too_large(void) { + nghttp2_hd_deflater deflater; + nghttp2_headers frame; + nghttp2_bufs bufs; + nghttp2_nv *nva; + size_t big_vallen = NGHTTP2_HD_MAX_NV; + nghttp2_nv big_hds[16]; + size_t big_hdslen = ARRLEN(big_hds); + size_t i; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + for (i = 0; i < big_hdslen; ++i) { + big_hds[i].name = (uint8_t *)"header"; + big_hds[i].value = mem->malloc(big_vallen + 1, NULL); + memset(big_hds[i].value, '0' + (int)i, big_vallen); + big_hds[i].value[big_vallen] = '\0'; + big_hds[i].namelen = strlen((char *)big_hds[i].name); + big_hds[i].valuelen = big_vallen; + big_hds[i].flags = NGHTTP2_NV_FLAG_NONE; + } + + nghttp2_nv_array_copy(&nva, big_hds, big_hdslen, mem); + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_frame_headers_init( + &frame, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, 1000000007, + NGHTTP2_HCAT_REQUEST, NULL, nva, big_hdslen); + rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == rv); + + nghttp2_frame_headers_free(&frame, mem); + nghttp2_bufs_free(&bufs); + for (i = 0; i < big_hdslen; ++i) { + mem->free(big_hds[i].value, NULL); + } + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_frame_pack_priority(void) { + nghttp2_priority frame, oframe; + nghttp2_bufs bufs; + nghttp2_priority_spec pri_spec; + + frame_pack_bufs_init(&bufs); + + /* First, pack priority with priority group and weight */ + nghttp2_priority_spec_init(&pri_spec, 1000000009, 12, 1); + + nghttp2_frame_priority_init(&frame, 1000000007, &pri_spec); + nghttp2_frame_pack_priority(&bufs, &frame); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + + CU_ASSERT(1000000009 == oframe.pri_spec.stream_id); + CU_ASSERT(12 == oframe.pri_spec.weight); + CU_ASSERT(1 == oframe.pri_spec.exclusive); + + nghttp2_frame_priority_free(&oframe); + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_priority_free(&frame); +} + +void test_nghttp2_frame_pack_rst_stream(void) { + nghttp2_rst_stream frame, oframe; + nghttp2_bufs bufs; + + frame_pack_bufs_init(&bufs); + + nghttp2_frame_rst_stream_init(&frame, 1000000007, NGHTTP2_PROTOCOL_ERROR); + nghttp2_frame_pack_rst_stream(&bufs, &frame); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == oframe.error_code); + + nghttp2_frame_rst_stream_free(&oframe); + nghttp2_bufs_reset(&bufs); + + /* Unknown error code is passed to callback as is */ + frame.error_code = 1000000009; + nghttp2_frame_pack_rst_stream(&bufs, &frame); + + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + + check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + + CU_ASSERT(1000000009 == oframe.error_code); + + nghttp2_frame_rst_stream_free(&oframe); + + nghttp2_frame_rst_stream_free(&frame); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame_pack_settings(void) { + nghttp2_settings frame, oframe; + nghttp2_bufs bufs; + int i; + int rv; + nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 256}, + {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 16384}, + {NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, 4096}}; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nghttp2_frame_settings_init(&frame, NGHTTP2_FLAG_NONE, + nghttp2_frame_iv_copy(iv, 3, mem), 3); + rv = nghttp2_frame_pack_settings(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 3 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH == + nghttp2_bufs_len(&bufs)); + + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(3 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH, NGHTTP2_SETTINGS, + NGHTTP2_FLAG_NONE, 0, &oframe.hd); + CU_ASSERT(3 == oframe.niv); + for (i = 0; i < 3; ++i) { + CU_ASSERT(iv[i].settings_id == oframe.iv[i].settings_id); + CU_ASSERT(iv[i].value == oframe.iv[i].value); + } + + nghttp2_bufs_free(&bufs); + nghttp2_frame_settings_free(&frame, mem); + nghttp2_frame_settings_free(&oframe, mem); +} + +void test_nghttp2_frame_pack_push_promise(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_push_promise frame, oframe; + nghttp2_bufs bufs; + nghttp2_nv *nva; + size_t nvlen; + nva_out out; + size_t hdblocklen; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + nva = headers(mem); + nvlen = HEADERS_LENGTH; + nghttp2_frame_push_promise_init(&frame, NGHTTP2_FLAG_END_HEADERS, 1000000007, + (1U << 31) - 1, nva, nvlen); + rv = nghttp2_frame_pack_push_promise(&bufs, &frame, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + + check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN, + NGHTTP2_PUSH_PROMISE, NGHTTP2_FLAG_END_HEADERS, 1000000007, + &oframe.hd); + CU_ASSERT((1U << 31) - 1 == oframe.promised_stream_id); + + hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN - 4; + CU_ASSERT((ssize_t)hdblocklen == + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN + 4, mem)); + + CU_ASSERT(7 == out.nvlen); + CU_ASSERT(nvnameeq("method", &out.nva[0])); + CU_ASSERT(nvvalueeq("GET", &out.nva[0])); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_push_promise_free(&oframe, mem); + nghttp2_frame_push_promise_free(&frame, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_frame_pack_ping(void) { + nghttp2_ping frame, oframe; + nghttp2_bufs bufs; + const uint8_t opaque_data[] = "01234567"; + + frame_pack_bufs_init(&bufs); + + nghttp2_frame_ping_init(&frame, NGHTTP2_FLAG_ACK, opaque_data); + nghttp2_frame_pack_ping(&bufs, &frame); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 8 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd); + CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1) == + 0); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_ping_free(&oframe); + nghttp2_frame_ping_free(&frame); +} + +void test_nghttp2_frame_pack_goaway(void) { + nghttp2_goaway frame, oframe; + nghttp2_bufs bufs; + size_t opaque_data_len = 16; + uint8_t *opaque_data; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + opaque_data = mem->malloc(opaque_data_len, NULL); + memcpy(opaque_data, "0123456789abcdef", opaque_data_len); + nghttp2_frame_goaway_init(&frame, 1000000007, NGHTTP2_PROTOCOL_ERROR, + opaque_data, opaque_data_len); + rv = nghttp2_frame_pack_goaway(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 8 + opaque_data_len == + nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd); + CU_ASSERT(1000000007 == oframe.last_stream_id); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == oframe.error_code); + + CU_ASSERT(opaque_data_len == oframe.opaque_data_len); + CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, opaque_data_len) == 0); + + nghttp2_frame_goaway_free(&oframe, mem); + nghttp2_bufs_reset(&bufs); + + /* Unknown error code is passed to callback as is */ + frame.error_code = 1000000009; + + rv = nghttp2_frame_pack_goaway(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd); + CU_ASSERT(1000000009 == oframe.error_code); + + nghttp2_frame_goaway_free(&oframe, mem); + + nghttp2_frame_goaway_free(&frame, mem); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame_pack_window_update(void) { + nghttp2_window_update frame, oframe; + nghttp2_bufs bufs; + + frame_pack_bufs_init(&bufs); + + nghttp2_frame_window_update_init(&frame, NGHTTP2_FLAG_NONE, 1000000007, 4096); + nghttp2_frame_pack_window_update(&bufs, &frame); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs)); + CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs)); + check_frame_header(4, NGHTTP2_WINDOW_UPDATE, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + CU_ASSERT(4096 == oframe.window_size_increment); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_window_update_free(&oframe); + nghttp2_frame_window_update_free(&frame); +} + +void test_nghttp2_frame_pack_altsvc(void) { + nghttp2_extension frame, oframe; + nghttp2_ext_altsvc altsvc, oaltsvc; + nghttp2_bufs bufs; + int rv; + size_t payloadlen; + static const uint8_t origin[] = "nghttp2.org"; + static const uint8_t field_value[] = "h2=\":443\""; + nghttp2_buf buf; + uint8_t *rawbuf; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + frame.payload = &altsvc; + oframe.payload = &oaltsvc; + + rawbuf = nghttp2_mem_malloc(mem, 32); + nghttp2_buf_wrap_init(&buf, rawbuf, 32); + + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + nghttp2_frame_altsvc_init(&frame, 1000000007, buf.pos, sizeof(origin) - 1, + buf.pos + sizeof(origin) - 1, + sizeof(field_value) - 1); + + payloadlen = 2 + sizeof(origin) - 1 + sizeof(field_value) - 1; + + nghttp2_frame_pack_altsvc(&bufs, &frame); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(payloadlen, NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 1000000007, + &oframe.hd); + + CU_ASSERT(sizeof(origin) - 1 == oaltsvc.origin_len); + CU_ASSERT(0 == memcmp(origin, oaltsvc.origin, sizeof(origin) - 1)); + CU_ASSERT(sizeof(field_value) - 1 == oaltsvc.field_value_len); + CU_ASSERT(0 == + memcmp(field_value, oaltsvc.field_value, sizeof(field_value) - 1)); + + nghttp2_frame_altsvc_free(&oframe, mem); + nghttp2_frame_altsvc_free(&frame, mem); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame_pack_origin(void) { + nghttp2_extension frame, oframe; + nghttp2_ext_origin origin, oorigin; + nghttp2_bufs bufs; + nghttp2_buf *buf; + int rv; + size_t payloadlen; + static const uint8_t example[] = "https://example.com"; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + nghttp2_origin_entry ov[] = { + { + (uint8_t *)example, + sizeof(example) - 1, + }, + { + NULL, + 0, + }, + { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }, + }; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + frame.payload = &origin; + oframe.payload = &oorigin; + + nghttp2_frame_origin_init(&frame, ov, 3); + + payloadlen = 2 + sizeof(example) - 1 + 2 + 2 + sizeof(nghttp2) - 1; + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(payloadlen, NGHTTP2_ORIGIN, NGHTTP2_FLAG_NONE, 0, + &oframe.hd); + + CU_ASSERT(2 == oorigin.nov); + CU_ASSERT(sizeof(example) - 1 == oorigin.ov[0].origin_len); + CU_ASSERT(0 == memcmp(example, oorigin.ov[0].origin, sizeof(example) - 1)); + CU_ASSERT(sizeof(nghttp2) - 1 == oorigin.ov[1].origin_len); + CU_ASSERT(0 == memcmp(nghttp2, oorigin.ov[1].origin, sizeof(nghttp2) - 1)); + + nghttp2_frame_origin_free(&oframe, mem); + + /* Check the case where origin length is too large */ + buf = &bufs.head->buf; + nghttp2_put_uint16be(buf->pos + NGHTTP2_FRAME_HDLEN, + (uint16_t)(payloadlen - 1)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == rv); + + nghttp2_bufs_reset(&bufs); + memset(&oframe, 0, sizeof(oframe)); + memset(&oorigin, 0, sizeof(oorigin)); + oframe.payload = &oorigin; + + /* Empty ORIGIN frame */ + nghttp2_frame_origin_init(&frame, NULL, 0); + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(0, NGHTTP2_ORIGIN, NGHTTP2_FLAG_NONE, 0, &oframe.hd); + + CU_ASSERT(0 == oorigin.nov); + CU_ASSERT(NULL == oorigin.ov); + + nghttp2_frame_origin_free(&oframe, mem); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_frame_pack_priority_update(void) { + nghttp2_extension frame, oframe; + nghttp2_ext_priority_update priority_update, opriority_update; + nghttp2_bufs bufs; + int rv; + size_t payloadlen; + static const uint8_t field_value[] = "i,u=0"; + + frame_pack_bufs_init(&bufs); + + frame.payload = &priority_update; + oframe.payload = &opriority_update; + + nghttp2_frame_priority_update_init(&frame, 1000000007, (uint8_t *)field_value, + sizeof(field_value) - 1); + + payloadlen = 4 + sizeof(field_value) - 1; + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + payloadlen == nghttp2_bufs_len(&bufs)); + + rv = unpack_framebuf((nghttp2_frame *)&oframe, &bufs); + + CU_ASSERT(0 == rv); + + check_frame_header(payloadlen, NGHTTP2_PRIORITY_UPDATE, NGHTTP2_FLAG_NONE, 0, + &oframe.hd); + + CU_ASSERT(sizeof(field_value) - 1 == opriority_update.field_value_len); + CU_ASSERT(0 == memcmp(field_value, opriority_update.field_value, + sizeof(field_value) - 1)); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_nv_array_copy(void) { + nghttp2_nv *nva; + ssize_t rv; + nghttp2_nv emptynv[] = {MAKE_NV("", ""), MAKE_NV("", "")}; + nghttp2_nv nv[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")}; + nghttp2_nv bignv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + bignv.name = (uint8_t *)"echo"; + bignv.namelen = strlen("echo"); + bignv.valuelen = (1 << 14) - 1; + bignv.value = mem->malloc(bignv.valuelen, NULL); + bignv.flags = NGHTTP2_NV_FLAG_NONE; + memset(bignv.value, '0', bignv.valuelen); + + rv = nghttp2_nv_array_copy(&nva, NULL, 0, mem); + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == nva); + + rv = nghttp2_nv_array_copy(&nva, emptynv, ARRLEN(emptynv), mem); + CU_ASSERT(0 == rv); + CU_ASSERT(nva[0].namelen == 0); + CU_ASSERT(nva[0].valuelen == 0); + CU_ASSERT(nva[1].namelen == 0); + CU_ASSERT(nva[1].valuelen == 0); + + nghttp2_nv_array_del(nva, mem); + + rv = nghttp2_nv_array_copy(&nva, nv, ARRLEN(nv), mem); + CU_ASSERT(0 == rv); + CU_ASSERT(nva[0].namelen == 5); + CU_ASSERT(0 == memcmp("alpha", nva[0].name, 5)); + CU_ASSERT(nva[0].valuelen == 5); + CU_ASSERT(0 == memcmp("bravo", nva[0].value, 5)); + CU_ASSERT(nva[1].namelen == 7); + CU_ASSERT(0 == memcmp("charlie", nva[1].name, 7)); + CU_ASSERT(nva[1].valuelen == 5); + CU_ASSERT(0 == memcmp("delta", nva[1].value, 5)); + + nghttp2_nv_array_del(nva, mem); + + /* Large header field is acceptable */ + rv = nghttp2_nv_array_copy(&nva, &bignv, 1, mem); + CU_ASSERT(0 == rv); + + nghttp2_nv_array_del(nva, mem); + + mem->free(bignv.value, NULL); +} + +void test_nghttp2_iv_check(void) { + nghttp2_settings_entry iv[5]; + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 100; + iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[1].value = 1024; + + CU_ASSERT(nghttp2_iv_check(iv, 2)); + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = NGHTTP2_MAX_WINDOW_SIZE; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + + /* Too large window size */ + iv[1].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1; + CU_ASSERT(0 == nghttp2_iv_check(iv, 2)); + + /* ENABLE_PUSH only allows 0 or 1 */ + iv[1].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[1].value = 0; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + iv[1].value = 1; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + iv[1].value = 3; + CU_ASSERT(!nghttp2_iv_check(iv, 2)); + + /* Undefined SETTINGS ID is allowed */ + iv[1].settings_id = 1000000009; + iv[1].value = 0; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + + /* Full size SETTINGS_HEADER_TABLE_SIZE (UINT32_MAX) must be + accepted */ + iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[1].value = UINT32_MAX; + CU_ASSERT(nghttp2_iv_check(iv, 2)); + + /* Too small SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN - 1; + CU_ASSERT(!nghttp2_iv_check(iv, 1)); + + /* Too large SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1; + CU_ASSERT(!nghttp2_iv_check(iv, 1)); + + /* Max and min SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[1].value = NGHTTP2_MAX_FRAME_SIZE_MAX; + CU_ASSERT(nghttp2_iv_check(iv, 2)); +} diff --git a/lib/nghttp2/tests/nghttp2_frame_test.h b/lib/nghttp2/tests/nghttp2_frame_test.h new file mode 100644 index 00000000000..dc0762573b2 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_frame_test.h @@ -0,0 +1,47 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_FRAME_TEST_H +#define NGHTTP2_FRAME_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_frame_pack_headers(void); +void test_nghttp2_frame_pack_headers_frame_too_large(void); +void test_nghttp2_frame_pack_priority(void); +void test_nghttp2_frame_pack_rst_stream(void); +void test_nghttp2_frame_pack_settings(void); +void test_nghttp2_frame_pack_push_promise(void); +void test_nghttp2_frame_pack_ping(void); +void test_nghttp2_frame_pack_goaway(void); +void test_nghttp2_frame_pack_window_update(void); +void test_nghttp2_frame_pack_altsvc(void); +void test_nghttp2_frame_pack_origin(void); +void test_nghttp2_frame_pack_priority_update(void); +void test_nghttp2_nv_array_copy(void); +void test_nghttp2_iv_check(void); + +#endif /* NGHTTP2_FRAME_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_hd_test.c b/lib/nghttp2/tests/nghttp2_hd_test.c new file mode 100644 index 00000000000..657d895faba --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_hd_test.c @@ -0,0 +1,1577 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_hd_test.h" + +#include +#include + +#include + +#include "nghttp2_hd.h" +#include "nghttp2_frame.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_hd_deflate(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nva1[] = {MAKE_NV(":path", "/my-example/index.html"), + MAKE_NV(":scheme", "https"), MAKE_NV("hello", "world")}; + nghttp2_nv nva2[] = {MAKE_NV(":path", "/script.js"), + MAKE_NV(":scheme", "https")}; + nghttp2_nv nva3[] = {MAKE_NV("cookie", "k1=v1"), MAKE_NV("cookie", "k2=v2"), + MAKE_NV("via", "proxy")}; + nghttp2_nv nva4[] = {MAKE_NV(":path", "/style.css"), + MAKE_NV("cookie", "k1=v1"), MAKE_NV("cookie", "k1=v1")}; + nghttp2_nv nva5[] = {MAKE_NV(":path", "/style.css"), + MAKE_NV("x-nghttp2", "")}; + nghttp2_bufs bufs; + ssize_t blocklen; + nva_out out; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + CU_ASSERT(0 == nghttp2_hd_deflate_init(&deflater, mem)); + CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater, mem)); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(3 == out.nvlen); + assert_nv_equal(nva1, out.nva, 3, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Second headers */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(2 == out.nvlen); + assert_nv_equal(nva2, out.nva, 2, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Third headers, including same header field name, but value is not + the same. */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva3, ARRLEN(nva3)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(3 == out.nvlen); + assert_nv_equal(nva3, out.nva, 3, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Fourth headers, including duplicate header fields. */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva4, ARRLEN(nva4)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(3 == out.nvlen); + assert_nv_equal(nva4, out.nva, 3, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Fifth headers includes empty value */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva5, ARRLEN(nva5)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(2 == out.nvlen); + assert_nv_equal(nva5, out.nva, 2, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Cleanup */ + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_deflate_same_indexed_repr(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nva1[] = {MAKE_NV("host", "alpha"), MAKE_NV("host", "alpha")}; + nghttp2_nv nva2[] = {MAKE_NV("host", "alpha"), MAKE_NV("host", "alpha"), + MAKE_NV("host", "alpha")}; + nghttp2_bufs bufs; + ssize_t blocklen; + nva_out out; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + CU_ASSERT(0 == nghttp2_hd_deflate_init(&deflater, mem)); + CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater, mem)); + + /* Encode 2 same headers. Emit 1 literal reprs and 1 index repr. */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(2 == out.nvlen); + assert_nv_equal(nva1, out.nva, 2, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Encode 3 same headers. This time, emits 3 index reprs. */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen == 3); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(3 == out.nvlen); + assert_nv_equal(nva2, out.nva, 3, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Cleanup */ + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_inflate_indexed(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv = MAKE_NV(":path", "/"); + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + nghttp2_bufs_addb(&bufs, (1 << 7) | 4); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(1 == blocklen); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + + assert_nv_equal(&nv, out.nva, 1, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* index = 0 is error */ + nghttp2_bufs_addb(&bufs, 1 << 7); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(1 == blocklen); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_noinc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv[] = {/* Huffman */ + MAKE_NV("user-agent", "nghttp2"), + /* Expecting no huffman */ + MAKE_NV("user-agent", "x")}; + size_t i; + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + for (i = 0; i < ARRLEN(nv); ++i) { + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv[i], + NGHTTP2_HD_WITHOUT_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv[i], out.nva, 1, mem); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + } + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_inc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv = MAKE_NV("user-agent", "nghttp2"); + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv, + NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(1 == inflater.ctx.hd_table.len); + CU_ASSERT(62 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + assert_nv_equal( + &nv, + nghttp2_hd_inflate_get_table_entry( + &inflater, NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len), + 1, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_indname_inc_eviction(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + uint8_t value[1025]; + nva_out out; + nghttp2_nv nv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + memset(value, '0', sizeof(value)); + value[sizeof(value) - 1] = '\0'; + nv.value = value; + nv.valuelen = sizeof(value) - 1; + + nv.flags = NGHTTP2_NV_FLAG_NONE; + + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 14, &nv, + NGHTTP2_HD_WITH_INDEXING)); + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 15, &nv, + NGHTTP2_HD_WITH_INDEXING)); + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 16, &nv, + NGHTTP2_HD_WITH_INDEXING)); + CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 17, &nv, + NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(4 == out.nvlen); + CU_ASSERT(14 == out.nva[0].namelen); + CU_ASSERT(0 == memcmp("accept-charset", out.nva[0].name, out.nva[0].namelen)); + CU_ASSERT(sizeof(value) - 1 == out.nva[0].valuelen); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + CU_ASSERT(3 == inflater.ctx.hd_table.len); + CU_ASSERT(64 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_newname_noinc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv[] = {/* Expecting huffman for both */ + MAKE_NV("my-long-content-length", "nghttp2"), + /* Expecting no huffman for both */ + MAKE_NV("x", "y"), + /* Huffman for key only */ + MAKE_NV("my-long-content-length", "y"), + /* Huffman for value only */ + MAKE_NV("x", "nghttp2")}; + size_t i; + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + for (i = 0; i < ARRLEN(nv); ++i) { + CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv[i], + NGHTTP2_HD_WITHOUT_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv[i], out.nva, 1, mem); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + } + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_newname_inc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv = MAKE_NV("x-rel", "nghttp2"); + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT( + 0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(1 == inflater.ctx.hd_table.len); + assert_nv_equal( + &nv, + nghttp2_hd_inflate_get_table_entry( + &inflater, NGHTTP2_STATIC_TABLE_LENGTH + inflater.ctx.hd_table.len), + 1, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_clearall_inc(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nv; + uint8_t value[4061]; + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + bufs_large_init(&bufs, 8192); + + nva_out_init(&out); + /* Total 4097 bytes space required to hold this entry */ + nv.name = (uint8_t *)"alpha"; + nv.namelen = strlen((char *)nv.name); + memset(value, '0', sizeof(value)); + value[sizeof(value) - 1] = '\0'; + nv.value = value; + nv.valuelen = sizeof(value) - 1; + + nv.flags = NGHTTP2_NV_FLAG_NONE; + + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT( + 0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + + nva_out_reset(&out, mem); + + /* Do it again */ + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* This time, 4096 bytes space required, which is just fits in the + header table */ + nv.valuelen = sizeof(value) - 2; + + CU_ASSERT( + 0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING)); + + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + CU_ASSERT(1 == inflater.ctx.hd_table.len); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_zero_length_huffman(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + /* Literal header without indexing - new name */ + uint8_t data[] = {0x40, 0x01, 0x78 /* 'x' */, 0x80}; + nva_out out; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + + nghttp2_bufs_add(&bufs, data, sizeof(data)); + + /* /\* Literal header without indexing - new name *\/ */ + /* ptr[0] = 0x40; */ + /* ptr[1] = 1; */ + /* ptr[2] = 'x'; */ + /* ptr[3] = 0x80; */ + + nghttp2_hd_inflate_init(&inflater, mem); + CU_ASSERT(4 == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + CU_ASSERT(1 == out.nva[0].namelen); + CU_ASSERT('x' == out.nva[0].name[0]); + CU_ASSERT(NULL == out.nva[0].value); + CU_ASSERT(0 == out.nva[0].valuelen); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_inflate_expect_table_size_update(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + nghttp2_mem *mem; + /* Indexed Header: :method: GET */ + uint8_t data[] = {0x82}; + nva_out out; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nva_out_init(&out); + + nghttp2_bufs_add(&bufs, data, sizeof(data)); + nghttp2_hd_inflate_init(&inflater, mem); + /* This will make inflater require table size update in the next + inflation. */ + nghttp2_hd_inflate_change_table_size(&inflater, 4095); + nghttp2_hd_inflate_change_table_size(&inflater, 4096); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* This does not require for encoder to emit table size update since + * size is not changed. */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 4096); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* This does not require for encodre to emit table size update since + new size is larger than current size. */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 4097); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* Received table size is strictly larger than minimum table size */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 111); + nghttp2_hd_inflate_change_table_size(&inflater, 4096); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_emit_table_size(&bufs, 112); + + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* Receiving 2 table size updates, min and last value */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 111); + nghttp2_hd_inflate_change_table_size(&inflater, 4096); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_emit_table_size(&bufs, 111); + nghttp2_hd_emit_table_size(&bufs, 4096); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + /* 2nd update is larger than last value */ + nghttp2_hd_inflate_init(&inflater, mem); + nghttp2_hd_inflate_change_table_size(&inflater, 111); + nghttp2_hd_inflate_change_table_size(&inflater, 4095); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_emit_table_size(&bufs, 111); + nghttp2_hd_emit_table_size(&bufs, 4096); + + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_hd_inflate_free(&inflater); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_hd_inflate_unexpected_table_size_update(void) { + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + nghttp2_mem *mem; + /* Indexed Header: :method: GET, followed by table size update. + This violates RFC 7541. */ + uint8_t data[] = {0x82, 0x20}; + nva_out out; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nva_out_init(&out); + + nghttp2_bufs_add(&bufs, data, sizeof(data)); + nghttp2_hd_inflate_init(&inflater, mem); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); +} + +void test_nghttp2_hd_ringbuf_reserve(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nv; + nghttp2_bufs bufs; + nva_out out; + int i; + ssize_t rv; + ssize_t blocklen; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nva_out_init(&out); + + nv.flags = NGHTTP2_NV_FLAG_NONE; + nv.name = (uint8_t *)"a"; + nv.namelen = strlen((const char *)nv.name); + nv.valuelen = 4; + nv.value = mem->malloc(nv.valuelen + 1, NULL); + memset(nv.value, 0, nv.valuelen); + + nghttp2_hd_deflate_init2(&deflater, 8000, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + nghttp2_hd_inflate_change_table_size(&inflater, 8000); + nghttp2_hd_deflate_change_table_size(&deflater, 8000); + + for (i = 0; i < 150; ++i) { + memcpy(nv.value, &i, sizeof(i)); + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, &nv, 1); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(1 == out.nvlen); + assert_nv_equal(&nv, out.nva, 1, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + } + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + mem->free(nv.value, NULL); +} + +void test_nghttp2_hd_change_table_size(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nva[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")}; + nghttp2_nv nva2[] = {MAKE_NV(":path", "/")}; + nghttp2_bufs bufs; + ssize_t rv; + nva_out out; + ssize_t blocklen; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + /* inflater changes notifies 8000 max header table size */ + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 8000)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 8000)); + + CU_ASSERT(4096 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max); + + /* This will emit encoding context update with header table size 4096 */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(63 == nghttp2_hd_deflate_get_num_table_entries(&deflater)); + CU_ASSERT(4096 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(63 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* inflater changes header table size to 1024 */ + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 1024)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 1024)); + + CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(1024 == inflater.settings_hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(63 == nghttp2_hd_deflate_get_num_table_entries(&deflater)); + CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(63 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(1024 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* inflater changes header table size to 0 */ + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 0)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 0)); + + CU_ASSERT(0 == deflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_deflate_get_num_table_entries(&deflater)); + CU_ASSERT(0 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(0 == inflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + CU_ASSERT(0 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(0 == inflater.settings_hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(0 == deflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_deflate_get_num_table_entries(&deflater)); + CU_ASSERT(0 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(0 == inflater.ctx.hd_table.len); + CU_ASSERT(61 == nghttp2_hd_inflate_get_num_table_entries(&inflater)); + CU_ASSERT(0 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(0 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + /* Check table buffer is expanded */ + frame_pack_bufs_init(&bufs); + + nghttp2_hd_deflate_init2(&deflater, 8192, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + /* First inflater changes header table size to 8000 */ + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 8000)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 8000)); + + CU_ASSERT(8000 == deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == nghttp2_hd_deflate_get_max_dynamic_table_size(&deflater)); + CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(4096 == nghttp2_hd_inflate_get_max_dynamic_table_size(&inflater)); + CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(8000 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(8000 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 16383)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 16383)); + + CU_ASSERT(8192 == deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8192 == nghttp2_hd_deflate_get_max_dynamic_table_size(&deflater)); + + CU_ASSERT(8000 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(8000 == nghttp2_hd_inflate_get_max_dynamic_table_size(&inflater)); + CU_ASSERT(16383 == inflater.settings_hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(8192 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(8192 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(16383 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + /* Lastly, check the error condition */ + + rv = nghttp2_hd_emit_table_size(&bufs, 25600); + CU_ASSERT(rv == 0); + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == + inflate_hd(&inflater, &out, &bufs, 0, mem)); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + /* Check that encoder can handle the case where its allowable buffer + size is less than default size, 4096 */ + nghttp2_hd_deflate_init2(&deflater, 1024, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max); + + /* This emits context update with buffer size 1024 */ + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(2 == deflater.ctx.hd_table.len); + CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(2 == inflater.ctx.hd_table.len); + CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(4096 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + /* Check that table size UINT32_MAX can be received */ + nghttp2_hd_deflate_init2(&deflater, UINT32_MAX, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, UINT32_MAX)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, UINT32_MAX)); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(UINT32_MAX == deflater.ctx.hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(UINT32_MAX == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(UINT32_MAX == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + /* Check that context update emitted twice */ + nghttp2_hd_deflate_init2(&deflater, 4096, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 0)); + CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 3000)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 0)); + CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 3000)); + + CU_ASSERT(0 == deflater.min_hd_table_bufsize_max); + CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, 1); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(3 < blocklen); + CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(UINT32_MAX == deflater.min_hd_table_bufsize_max); + + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + CU_ASSERT(3000 == inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(3000 == inflater.settings_hd_table_bufsize_max); + + nva_out_reset(&out, mem); + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); + + nghttp2_bufs_free(&bufs); +} + +static void check_deflate_inflate(nghttp2_hd_deflater *deflater, + nghttp2_hd_inflater *inflater, + nghttp2_nv *nva, size_t nvlen, + nghttp2_mem *mem) { + nghttp2_bufs bufs; + ssize_t blocklen; + nva_out out; + int rv; + + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nva, nvlen); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen >= 0); + + CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(nvlen == out.nvlen); + assert_nv_equal(nva, out.nva, nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_hd_deflate_inflate(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_nv nv1[] = { + MAKE_NV(":status", "200 OK"), + MAKE_NV("access-control-allow-origin", "*"), + MAKE_NV("cache-control", "private, max-age=0, must-revalidate"), + MAKE_NV("content-length", "76073"), + MAKE_NV("content-type", "text/html"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("server", "Apache"), + MAKE_NV("vary", "foobar"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "MISS from alphabravo"), + MAKE_NV("x-cache-action", "MISS"), + MAKE_NV("x-cache-age", "0"), + MAKE_NV("x-cache-lookup", "MISS from alphabravo:3128"), + MAKE_NV("x-lb-nocache", "true"), + }; + nghttp2_nv nv2[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=56682045"), + MAKE_NV("content-type", "text/css"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Thu, 14 May 2015 07:22:57 GMT"), + MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:15 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128")}; + nghttp2_nv nv3[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=56682072"), + MAKE_NV("content-type", "text/css"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Thu, 14 May 2015 07:23:24 GMT"), + MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:13 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv4[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=56682022"), + MAKE_NV("content-type", "text/css"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Thu, 14 May 2015 07:22:34 GMT"), + MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:14 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv5[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=4461139"), + MAKE_NV("content-type", "application/x-javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Mon, 16 Sep 2013 21:34:31 GMT"), + MAKE_NV("last-modified", "Thu, 05 May 2011 09:15:59 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv6[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=18645951"), + MAKE_NV("content-type", "application/x-javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Fri, 28 Feb 2014 01:48:03 GMT"), + MAKE_NV("last-modified", "Tue, 12 Jul 2011 16:02:59 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv7[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=31536000"), + MAKE_NV("content-type", "application/javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("etag", "\"6807-4dc5b54e0dcc0\""), + MAKE_NV("expires", "Wed, 21 May 2014 08:32:17 GMT"), + MAKE_NV("last-modified", "Fri, 10 May 2013 11:18:51 GMT"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv8[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=31536000"), + MAKE_NV("content-type", "application/javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("etag", "\"41c6-4de7d28585b00\""), + MAKE_NV("expires", "Thu, 12 Jun 2014 10:00:58 GMT"), + MAKE_NV("last-modified", "Thu, 06 Jun 2013 14:30:36 GMT"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv9[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=31536000"), + MAKE_NV("content-type", "application/javascript"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("etag", "\"19d6e-4dc5b35a541c0\""), + MAKE_NV("expires", "Wed, 21 May 2014 08:32:18 GMT"), + MAKE_NV("last-modified", "Fri, 10 May 2013 11:10:07 GMT"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_nv nv10[] = { + MAKE_NV(":status", "304 Not Modified"), + MAKE_NV("age", "0"), + MAKE_NV("cache-control", "max-age=56682045"), + MAKE_NV("content-type", "text/css"), + MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"), + MAKE_NV("expires", "Thu, 14 May 2015 07:22:57 GMT"), + MAKE_NV("last-modified", "Tue, 14 May 2013 07:21:53 GMT"), + MAKE_NV("vary", "Accept-Encoding"), + MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"), + MAKE_NV("x-cache", "HIT from alphabravo"), + MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"), + }; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + check_deflate_inflate(&deflater, &inflater, nv1, ARRLEN(nv1), mem); + check_deflate_inflate(&deflater, &inflater, nv2, ARRLEN(nv2), mem); + check_deflate_inflate(&deflater, &inflater, nv3, ARRLEN(nv3), mem); + check_deflate_inflate(&deflater, &inflater, nv4, ARRLEN(nv4), mem); + check_deflate_inflate(&deflater, &inflater, nv5, ARRLEN(nv5), mem); + check_deflate_inflate(&deflater, &inflater, nv6, ARRLEN(nv6), mem); + check_deflate_inflate(&deflater, &inflater, nv7, ARRLEN(nv7), mem); + check_deflate_inflate(&deflater, &inflater, nv8, ARRLEN(nv8), mem); + check_deflate_inflate(&deflater, &inflater, nv9, ARRLEN(nv9), mem); + check_deflate_inflate(&deflater, &inflater, nv10, ARRLEN(nv10), mem); + + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_no_index(void) { + nghttp2_hd_deflater deflater; + nghttp2_hd_inflater inflater; + nghttp2_bufs bufs; + ssize_t blocklen; + nghttp2_nv nva[] = { + MAKE_NV(":method", "GET"), MAKE_NV(":method", "POST"), + MAKE_NV(":path", "/foo"), MAKE_NV("version", "HTTP/1.1"), + MAKE_NV(":method", "GET"), + }; + size_t i; + nva_out out; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + /* 1st :method: GET can be indexable, last one is not */ + for (i = 1; i < ARRLEN(nva); ++i) { + nva[i].flags = NGHTTP2_NV_FLAG_NO_INDEX; + } + + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_hd_inflate_init(&inflater, mem); + + rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, ARRLEN(nva)); + blocklen = (ssize_t)nghttp2_bufs_len(&bufs); + + CU_ASSERT(0 == rv); + CU_ASSERT(blocklen > 0); + CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(ARRLEN(nva) == out.nvlen); + assert_nv_equal(nva, out.nva, ARRLEN(nva), mem); + + CU_ASSERT(out.nva[0].flags == NGHTTP2_NV_FLAG_NONE); + for (i = 1; i < ARRLEN(nva); ++i) { + CU_ASSERT(out.nva[i].flags == NGHTTP2_NV_FLAG_NO_INDEX); + } + + nva_out_reset(&out, mem); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_deflate_bound(void) { + nghttp2_hd_deflater deflater; + nghttp2_nv nva[] = {MAKE_NV(":method", "GET"), MAKE_NV("alpha", "bravo")}; + nghttp2_bufs bufs; + size_t bound, bound2; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nghttp2_hd_deflate_init(&deflater, mem); + + bound = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva)); + + CU_ASSERT(12 + 6 * 2 * 2 + nva[0].namelen + nva[0].valuelen + nva[1].namelen + + nva[1].valuelen == + bound); + + nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, ARRLEN(nva)); + + CU_ASSERT(bound > (size_t)nghttp2_bufs_len(&bufs)); + + bound2 = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva)); + + CU_ASSERT(bound == bound2); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); +} + +void test_nghttp2_hd_public_api(void) { + nghttp2_hd_deflater *deflater; + nghttp2_hd_inflater *inflater; + nghttp2_nv nva[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")}; + uint8_t buf[4096]; + size_t buflen; + ssize_t blocklen; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + CU_ASSERT(0 == nghttp2_hd_deflate_new(&deflater, 4096)); + CU_ASSERT(0 == nghttp2_hd_inflate_new(&inflater)); + + buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva)); + + blocklen = nghttp2_hd_deflate_hd(deflater, buf, buflen, nva, ARRLEN(nva)); + + CU_ASSERT(blocklen > 0); + + nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem); + bufs.head->buf.last += blocklen; + + CU_ASSERT(blocklen == inflate_hd(inflater, NULL, &bufs, 0, mem)); + + nghttp2_bufs_wrap_free(&bufs); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + + /* See NGHTTP2_ERR_INSUFF_BUFSIZE */ + CU_ASSERT(0 == nghttp2_hd_deflate_new(&deflater, 4096)); + + blocklen = nghttp2_hd_deflate_hd(deflater, buf, (size_t)(blocklen - 1), nva, + ARRLEN(nva)); + + CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen); + + nghttp2_hd_deflate_del(deflater); +} + +void test_nghttp2_hd_deflate_hd_vec(void) { + nghttp2_hd_deflater *deflater; + nghttp2_hd_inflater *inflater; + nghttp2_nv nva[] = { + MAKE_NV(":method", "PUT"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost:3000"), + MAKE_NV(":path", "/usr/foo/alpha/bravo"), + MAKE_NV("content-type", "image/png"), + MAKE_NV("content-length", "1000000007"), + }; + uint8_t buf[4096]; + ssize_t blocklen; + nghttp2_mem *mem; + nghttp2_vec vec[256]; + size_t buflen; + nghttp2_bufs bufs; + nva_out out; + size_t i; + + mem = nghttp2_mem_default(); + + nva_out_init(&out); + + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva)); + + vec[0].base = &buf[0]; + vec[0].len = buflen / 2; + vec[1].base = &buf[buflen / 2]; + vec[1].len = buflen / 2; + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva)); + + CU_ASSERT(blocklen > 0); + + nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem); + bufs.head->buf.last += blocklen; + + CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem)); + + CU_ASSERT(ARRLEN(nva) == out.nvlen); + assert_nv_equal(nva, out.nva, ARRLEN(nva), mem); + + nghttp2_bufs_wrap_free(&bufs); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + nva_out_reset(&out, mem); + + /* check the case when veclen is 0 */ + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, NULL, 0, nva, ARRLEN(nva)); + + CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + + /* check the case when chunk length is 0 */ + vec[0].base = NULL; + vec[0].len = 0; + vec[1].base = NULL; + vec[1].len = 0; + + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva)); + + CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + + /* check the case where chunk size differs in each chunk */ + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva)); + + vec[0].base = &buf[0]; + vec[0].len = buflen / 2; + vec[1].base = &buf[buflen / 2]; + vec[1].len = (buflen / 2) + 1; + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, 2, nva, ARRLEN(nva)); + + CU_ASSERT(blocklen > 0); + + nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem); + bufs.head->buf.last += blocklen; + + CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem)); + CU_ASSERT(ARRLEN(nva) == out.nvlen); + assert_nv_equal(nva, out.nva, ARRLEN(nva), mem); + + nghttp2_bufs_wrap_free(&bufs); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + nva_out_reset(&out, mem); + + /* check the case where chunk size is 1 */ + nghttp2_hd_deflate_new(&deflater, 4096); + nghttp2_hd_inflate_new(&inflater); + + buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva)); + + assert(buflen <= ARRLEN(vec)); + + for (i = 0; i < buflen; ++i) { + vec[i].base = &buf[i]; + vec[i].len = 1; + } + + blocklen = nghttp2_hd_deflate_hd_vec(deflater, vec, buflen, nva, ARRLEN(nva)); + + CU_ASSERT(blocklen > 0); + + nghttp2_bufs_wrap_init(&bufs, buf, (size_t)blocklen, mem); + bufs.head->buf.last += blocklen; + + CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0, mem)); + CU_ASSERT(ARRLEN(nva) == out.nvlen); + assert_nv_equal(nva, out.nva, ARRLEN(nva), mem); + + nghttp2_bufs_wrap_free(&bufs); + + nghttp2_hd_inflate_del(inflater); + nghttp2_hd_deflate_del(deflater); + nva_out_reset(&out, mem); +} + +static size_t encode_length(uint8_t *buf, uint64_t n, size_t prefix) { + size_t k = (size_t)((1 << prefix) - 1); + size_t len = 0; + *buf = (uint8_t)(*buf & ~k); + if (n >= k) { + *buf = (uint8_t)(*buf | k); + ++buf; + n -= k; + ++len; + } else { + *buf = (uint8_t)(*buf | n); + ++buf; + return 1; + } + do { + ++len; + if (n >= 128) { + *buf = (uint8_t)((1 << 7) | (n & 0x7f)); + ++buf; + n >>= 7; + } else { + *buf++ = (uint8_t)n; + break; + } + } while (n); + return len; +} + +void test_nghttp2_hd_decode_length(void) { + uint32_t out; + size_t shift; + int fin; + uint8_t buf[16]; + uint8_t *bufp; + size_t len; + ssize_t rv; + size_t i; + + memset(buf, 0, sizeof(buf)); + len = encode_length(buf, UINT32_MAX, 7); + + rv = nghttp2_hd_decode_length(&out, &shift, &fin, 0, 0, buf, buf + len, 7); + + CU_ASSERT((ssize_t)len == rv); + CU_ASSERT(0 != fin); + CU_ASSERT(UINT32_MAX == out); + + /* Make sure that we can decode integer if we feed 1 byte at a + time */ + out = 0; + shift = 0; + fin = 0; + bufp = buf; + + for (i = 0; i < len; ++i, ++bufp) { + rv = nghttp2_hd_decode_length(&out, &shift, &fin, out, shift, bufp, + bufp + 1, 7); + + CU_ASSERT(rv == 1); + + if (fin) { + break; + } + } + + CU_ASSERT(i == len - 1); + CU_ASSERT(0 != fin); + CU_ASSERT(UINT32_MAX == out); + + /* Check overflow case */ + memset(buf, 0, sizeof(buf)); + len = encode_length(buf, 1ll << 32, 7); + + rv = nghttp2_hd_decode_length(&out, &shift, &fin, 0, 0, buf, buf + len, 7); + + CU_ASSERT(-1 == rv); + + /* Check the case that shift goes beyond 32 bits */ + buf[0] = 255; + buf[1] = 128; + buf[2] = 128; + buf[3] = 128; + buf[4] = 128; + buf[5] = 128; + buf[6] = 1; + + rv = nghttp2_hd_decode_length(&out, &shift, &fin, 0, 0, buf, buf + 7, 8); + + CU_ASSERT(-1 == rv); +} + +void test_nghttp2_hd_huff_encode(void) { + int rv; + ssize_t len; + nghttp2_buf outbuf; + nghttp2_bufs bufs; + nghttp2_hd_huff_decode_context ctx; + const uint8_t t1[] = {22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, + 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + uint8_t b[256]; + + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + frame_pack_bufs_init(&bufs); + + rv = nghttp2_hd_huff_encode(&bufs, t1, sizeof(t1)); + + CU_ASSERT(rv == 0); + + nghttp2_hd_huff_decode_context_init(&ctx); + + len = nghttp2_hd_huff_decode(&ctx, &outbuf, bufs.cur->buf.pos, + nghttp2_bufs_len(&bufs), 1); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == len); + CU_ASSERT((ssize_t)sizeof(t1) == nghttp2_buf_len(&outbuf)); + + CU_ASSERT(0 == memcmp(t1, outbuf.pos, sizeof(t1))); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_hd_huff_decode(void) { + const uint8_t e[] = {0x1f, 0xff, 0xff, 0xff, 0xff, 0xff}; + nghttp2_hd_huff_decode_context ctx; + nghttp2_buf outbuf; + uint8_t b[256]; + ssize_t len; + + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + nghttp2_hd_huff_decode_context_init(&ctx); + len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 1, 1); + + CU_ASSERT(1 == len); + CU_ASSERT(0 == memcmp("a", outbuf.pos, 1)); + + /* Premature sequence must elicit decoding error */ + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + nghttp2_hd_huff_decode_context_init(&ctx); + len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 2, 1); + + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == len); + + /* Fully decoding EOS is error */ + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + nghttp2_hd_huff_decode_context_init(&ctx); + len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 2, 6); + + CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == len); + + /* Check failure state */ + nghttp2_buf_wrap_init(&outbuf, b, sizeof(b)); + nghttp2_hd_huff_decode_context_init(&ctx); + len = nghttp2_hd_huff_decode(&ctx, &outbuf, e, 5, 0); + + CU_ASSERT(5 == len); + CU_ASSERT(nghttp2_hd_huff_decode_failure_state(&ctx)); +} diff --git a/lib/nghttp2/tests/nghttp2_hd_test.h b/lib/nghttp2/tests/nghttp2_hd_test.h new file mode 100644 index 00000000000..ab0117cb9cc --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_hd_test.h @@ -0,0 +1,55 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_HD_TEST_H +#define NGHTTP2_HD_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_hd_deflate(void); +void test_nghttp2_hd_deflate_same_indexed_repr(void); +void test_nghttp2_hd_inflate_indexed(void); +void test_nghttp2_hd_inflate_indname_noinc(void); +void test_nghttp2_hd_inflate_indname_inc(void); +void test_nghttp2_hd_inflate_indname_inc_eviction(void); +void test_nghttp2_hd_inflate_newname_noinc(void); +void test_nghttp2_hd_inflate_newname_inc(void); +void test_nghttp2_hd_inflate_clearall_inc(void); +void test_nghttp2_hd_inflate_zero_length_huffman(void); +void test_nghttp2_hd_inflate_expect_table_size_update(void); +void test_nghttp2_hd_inflate_unexpected_table_size_update(void); +void test_nghttp2_hd_ringbuf_reserve(void); +void test_nghttp2_hd_change_table_size(void); +void test_nghttp2_hd_deflate_inflate(void); +void test_nghttp2_hd_no_index(void); +void test_nghttp2_hd_deflate_bound(void); +void test_nghttp2_hd_public_api(void); +void test_nghttp2_hd_deflate_hd_vec(void); +void test_nghttp2_hd_decode_length(void); +void test_nghttp2_hd_huff_encode(void); +void test_nghttp2_hd_huff_decode(void); + +#endif /* NGHTTP2_HD_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_helper_test.c b/lib/nghttp2/tests/nghttp2_helper_test.c new file mode 100644 index 00000000000..377f49d7666 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_helper_test.c @@ -0,0 +1,195 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_helper_test.h" + +#include + +#include + +#include "nghttp2_helper.h" + +void test_nghttp2_adjust_local_window_size(void) { + int32_t local_window_size = 100; + int32_t recv_window_size = 50; + int32_t recv_reduction = 0; + int32_t delta; + + delta = 0; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(0 == delta); + + delta = 49; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(1 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(49 == delta); + + delta = 1; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(0 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(1 == delta); + + delta = 1; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(101 == local_window_size); + CU_ASSERT(0 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(1 == delta); + + delta = -1; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(-1 == recv_window_size); + CU_ASSERT(1 == recv_reduction); + CU_ASSERT(0 == delta); + + delta = 1; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(101 == local_window_size); + CU_ASSERT(0 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(0 == delta); + + delta = 100; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(201 == local_window_size); + CU_ASSERT(0 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(100 == delta); + + delta = -3; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(198 == local_window_size); + CU_ASSERT(-3 == recv_window_size); + CU_ASSERT(3 == recv_reduction); + CU_ASSERT(0 == delta); + + recv_window_size += 3; + + delta = 3; + CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, + &recv_reduction, &delta)); + CU_ASSERT(201 == local_window_size); + CU_ASSERT(3 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(0 == delta); + + local_window_size = 100; + recv_window_size = 50; + recv_reduction = 0; + delta = INT32_MAX; + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, &recv_reduction, + &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(INT32_MAX == delta); + + delta = INT32_MIN; + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_adjust_local_window_size(&local_window_size, + &recv_window_size, &recv_reduction, + &delta)); + CU_ASSERT(100 == local_window_size); + CU_ASSERT(50 == recv_window_size); + CU_ASSERT(0 == recv_reduction); + CU_ASSERT(INT32_MIN == delta); +} + +#define check_header_name(S) \ + nghttp2_check_header_name((const uint8_t *)S, sizeof(S) - 1) + +void test_nghttp2_check_header_name(void) { + CU_ASSERT(check_header_name(":path")); + CU_ASSERT(check_header_name("path")); + CU_ASSERT(check_header_name("!#$%&'*+-.^_`|~")); + CU_ASSERT(!check_header_name(":PATH")); + CU_ASSERT(!check_header_name("path:")); + CU_ASSERT(!check_header_name("")); + CU_ASSERT(!check_header_name(":")); +} + +#define check_header_value(S) \ + nghttp2_check_header_value((const uint8_t *)S, sizeof(S) - 1) + +void test_nghttp2_check_header_value(void) { + uint8_t goodval[] = {'a', 'b', 0x80u, 'c', 0xffu, 'd', '\t', ' '}; + uint8_t badval1[] = {'a', 0x1fu, 'b'}; + uint8_t badval2[] = {'a', 0x7fu, 'b'}; + + CU_ASSERT(check_header_value(" !|}~")); + CU_ASSERT(check_header_value(goodval)); + CU_ASSERT(!check_header_value(badval1)); + CU_ASSERT(!check_header_value(badval2)); + CU_ASSERT(check_header_value("")); + CU_ASSERT(check_header_value(" ")); + CU_ASSERT(check_header_value("\t")); +} + +#define check_header_value_rfc9113(S) \ + nghttp2_check_header_value_rfc9113((const uint8_t *)S, sizeof(S) - 1) + +void test_nghttp2_check_header_value_rfc9113(void) { + uint8_t goodval[] = {'a', 'b', 0x80u, 'c', 0xffu, 'd'}; + uint8_t badval1[] = {'a', 0x1fu, 'b'}; + uint8_t badval2[] = {'a', 0x7fu, 'b'}; + + CU_ASSERT(check_header_value_rfc9113("!|}~")); + CU_ASSERT(!check_header_value_rfc9113(" !|}~")); + CU_ASSERT(!check_header_value_rfc9113("!|}~ ")); + CU_ASSERT(!check_header_value_rfc9113("\t!|}~")); + CU_ASSERT(!check_header_value_rfc9113("!|}~\t")); + CU_ASSERT(check_header_value_rfc9113(goodval)); + CU_ASSERT(!check_header_value_rfc9113(badval1)); + CU_ASSERT(!check_header_value_rfc9113(badval2)); + CU_ASSERT(check_header_value_rfc9113("")); + CU_ASSERT(!check_header_value_rfc9113(" ")); + CU_ASSERT(!check_header_value_rfc9113("\t")); +} diff --git a/lib/nghttp2/tests/nghttp2_helper_test.h b/lib/nghttp2/tests/nghttp2_helper_test.h new file mode 100644 index 00000000000..8790dcf6d5e --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_helper_test.h @@ -0,0 +1,37 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_HELPER_TEST_H +#define NGHTTP2_HELPER_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_adjust_local_window_size(void); +void test_nghttp2_check_header_name(void); +void test_nghttp2_check_header_value(void); +void test_nghttp2_check_header_value_rfc9113(void); + +#endif /* NGHTTP2_HELPER_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_http_test.c b/lib/nghttp2/tests/nghttp2_http_test.c new file mode 100644 index 00000000000..19f345bae79 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_http_test.c @@ -0,0 +1,206 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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 "nghttp2_http_test.h" + +#include +#include + +#include + +#include "nghttp2_http.h" +#include "nghttp2_test_helper.h" + +void test_nghttp2_http_parse_priority(void) { + int rv; + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = ""; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)-1 == pri.urgency); + CU_ASSERT(-1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=7,i"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)7 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0,i=?0"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)0 == pri.urgency); + CU_ASSERT(0 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=3, i"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)3 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0, i, i=?0, u=6"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)6 == pri.urgency); + CU_ASSERT(0 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0,"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0, "; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u="; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?1"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)-1 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?2"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i="; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=-1"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=8"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = + "i=?0, u=1, a=(x y z), u=2; i=?0;foo=\",,,\", i=?1;i=?0; u=6"; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)2 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp2_extpri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = {'u', '='}; + + rv = nghttp2_http_parse_priority(&pri, v, sizeof(v)); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + } +} diff --git a/lib/nghttp2/tests/nghttp2_http_test.h b/lib/nghttp2/tests/nghttp2_http_test.h new file mode 100644 index 00000000000..e616cdcf492 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_http_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 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 NGHTTP2_HTTP_TEST_H +#define NGHTTP2_HTTP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_http_parse_priority(void); + +#endif /* NGHTTP2_HTTP_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_map_test.c b/lib/nghttp2/tests/nghttp2_map_test.c new file mode 100644 index 00000000000..7ba9bd6b669 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_map_test.c @@ -0,0 +1,208 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * 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 "nghttp2_map_test.h" + +#include + +#include + +#include "nghttp2_map.h" + +typedef struct strentry { + nghttp2_map_key_type key; + const char *str; +} strentry; + +static void strentry_init(strentry *entry, nghttp2_map_key_type key, + const char *str) { + entry->key = key; + entry->str = str; +} + +void test_nghttp2_map(void) { + strentry foo, FOO, bar, baz, shrubbery; + nghttp2_map map; + nghttp2_map_init(&map, nghttp2_mem_default()); + + strentry_init(&foo, 1, "foo"); + strentry_init(&FOO, 1, "FOO"); + strentry_init(&bar, 2, "bar"); + strentry_init(&baz, 3, "baz"); + strentry_init(&shrubbery, 4, "shrubbery"); + + CU_ASSERT(0 == nghttp2_map_insert(&map, foo.key, &foo)); + CU_ASSERT(strcmp("foo", ((strentry *)nghttp2_map_find(&map, 1))->str) == 0); + CU_ASSERT(1 == nghttp2_map_size(&map)); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_map_insert(&map, FOO.key, &FOO)); + + CU_ASSERT(1 == nghttp2_map_size(&map)); + CU_ASSERT(strcmp("foo", ((strentry *)nghttp2_map_find(&map, 1))->str) == 0); + + CU_ASSERT(0 == nghttp2_map_insert(&map, bar.key, &bar)); + CU_ASSERT(2 == nghttp2_map_size(&map)); + + CU_ASSERT(0 == nghttp2_map_insert(&map, baz.key, &baz)); + CU_ASSERT(3 == nghttp2_map_size(&map)); + + CU_ASSERT(0 == nghttp2_map_insert(&map, shrubbery.key, &shrubbery)); + CU_ASSERT(4 == nghttp2_map_size(&map)); + + CU_ASSERT(strcmp("baz", ((strentry *)nghttp2_map_find(&map, 3))->str) == 0); + + nghttp2_map_remove(&map, 3); + CU_ASSERT(3 == nghttp2_map_size(&map)); + CU_ASSERT(NULL == nghttp2_map_find(&map, 3)); + + nghttp2_map_remove(&map, 1); + CU_ASSERT(2 == nghttp2_map_size(&map)); + CU_ASSERT(NULL == nghttp2_map_find(&map, 1)); + + /* Erasing non-existent entry */ + nghttp2_map_remove(&map, 1); + CU_ASSERT(2 == nghttp2_map_size(&map)); + CU_ASSERT(NULL == nghttp2_map_find(&map, 1)); + + CU_ASSERT(strcmp("bar", ((strentry *)nghttp2_map_find(&map, 2))->str) == 0); + CU_ASSERT(strcmp("shrubbery", ((strentry *)nghttp2_map_find(&map, 4))->str) == + 0); + + nghttp2_map_free(&map); +} + +static void shuffle(int *a, int n) { + int i; + for (i = n - 1; i >= 1; --i) { + size_t j = (size_t)((double)(i + 1) * rand() / (RAND_MAX + 1.0)); + int t = a[j]; + a[j] = a[i]; + a[i] = t; + } +} + +static int eachfun(void *data, void *ptr) { + (void)data; + (void)ptr; + + return 0; +} + +#define NUM_ENT 6000 +static strentry arr[NUM_ENT]; +static int order[NUM_ENT]; + +void test_nghttp2_map_functional(void) { + nghttp2_map map; + int i; + strentry *ent; + + nghttp2_map_init(&map, nghttp2_mem_default()); + for (i = 0; i < NUM_ENT; ++i) { + strentry_init(&arr[i], (nghttp2_map_key_type)(i + 1), "foo"); + order[i] = i + 1; + } + /* insertion */ + shuffle(order, NUM_ENT); + for (i = 0; i < NUM_ENT; ++i) { + ent = &arr[order[i] - 1]; + CU_ASSERT(0 == nghttp2_map_insert(&map, ent->key, ent)); + } + + CU_ASSERT(NUM_ENT == nghttp2_map_size(&map)); + + /* traverse */ + nghttp2_map_each(&map, eachfun, NULL); + /* find */ + shuffle(order, NUM_ENT); + for (i = 0; i < NUM_ENT; ++i) { + CU_ASSERT(NULL != nghttp2_map_find(&map, (nghttp2_map_key_type)order[i])); + } + /* remove */ + for (i = 0; i < NUM_ENT; ++i) { + CU_ASSERT(0 == nghttp2_map_remove(&map, (nghttp2_map_key_type)order[i])); + } + + /* each_free (but no op function for testing purpose) */ + for (i = 0; i < NUM_ENT; ++i) { + strentry_init(&arr[i], (nghttp2_map_key_type)(i + 1), "foo"); + } + /* insert once again */ + for (i = 0; i < NUM_ENT; ++i) { + ent = &arr[i]; + CU_ASSERT(0 == nghttp2_map_insert(&map, ent->key, ent)); + } + nghttp2_map_each_free(&map, eachfun, NULL); + nghttp2_map_free(&map); +} + +static int entry_free(void *data, void *ptr) { + const nghttp2_mem *mem = ptr; + + mem->free(data, NULL); + return 0; +} + +void test_nghttp2_map_each_free(void) { + const nghttp2_mem *mem = nghttp2_mem_default(); + strentry *foo = mem->malloc(sizeof(strentry), NULL), + *bar = mem->malloc(sizeof(strentry), NULL), + *baz = mem->malloc(sizeof(strentry), NULL), + *shrubbery = mem->malloc(sizeof(strentry), NULL); + nghttp2_map map; + nghttp2_map_init(&map, nghttp2_mem_default()); + + strentry_init(foo, 1, "foo"); + strentry_init(bar, 2, "bar"); + strentry_init(baz, 3, "baz"); + strentry_init(shrubbery, 4, "shrubbery"); + + nghttp2_map_insert(&map, foo->key, foo); + nghttp2_map_insert(&map, bar->key, bar); + nghttp2_map_insert(&map, baz->key, baz); + nghttp2_map_insert(&map, shrubbery->key, shrubbery); + + nghttp2_map_each_free(&map, entry_free, (void *)mem); + nghttp2_map_free(&map); +} + +void test_nghttp2_map_clear(void) { + nghttp2_mem *mem = nghttp2_mem_default(); + nghttp2_map map; + strentry foo; + + strentry_init(&foo, 1, "foo"); + + nghttp2_map_init(&map, mem); + + CU_ASSERT(0 == nghttp2_map_insert(&map, foo.key, &foo)); + + nghttp2_map_clear(&map); + + CU_ASSERT(0 == nghttp2_map_size(&map)); + + nghttp2_map_free(&map); +} diff --git a/lib/nghttp2/tests/nghttp2_map_test.h b/lib/nghttp2/tests/nghttp2_map_test.h new file mode 100644 index 00000000000..235624de182 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_map_test.h @@ -0,0 +1,38 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * 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 NGHTTP2_MAP_TEST_H +#define NGHTTP2_MAP_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_map(void); +void test_nghttp2_map_functional(void); +void test_nghttp2_map_each_free(void); +void test_nghttp2_map_clear(void); + +#endif /* NGHTTP2_MAP_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_npn_test.c b/lib/nghttp2/tests/nghttp2_npn_test.c new file mode 100644 index 00000000000..99cb751a485 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_npn_test.c @@ -0,0 +1,73 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Twist Inc. + * + * 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 "nghttp2_npn_test.h" + +#include +#include + +#include +#include + +static void http2(void) { + const unsigned char p[] = {8, 'h', 't', 't', 'p', '/', '1', '.', '1', 2, + 'h', '2', 6, 's', 'p', 'd', 'y', '/', '3'}; + unsigned char outlen; + unsigned char *out; + CU_ASSERT(1 == nghttp2_select_next_protocol(&out, &outlen, p, sizeof(p))); + CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen); + CU_ASSERT(memcmp(NGHTTP2_PROTO_VERSION_ID, out, outlen) == 0); +} + +static void http11(void) { + const unsigned char spdy[] = { + 6, 's', 'p', 'd', 'y', '/', '4', 8, 's', 'p', 'd', 'y', '/', + '2', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '1', + }; + unsigned char outlen; + unsigned char *out; + CU_ASSERT(0 == + nghttp2_select_next_protocol(&out, &outlen, spdy, sizeof(spdy))); + CU_ASSERT(8 == outlen); + CU_ASSERT(memcmp("http/1.1", out, outlen) == 0); +} + +static void no_overlap(void) { + const unsigned char spdy[] = { + 6, 's', 'p', 'd', 'y', '/', '4', 8, 's', 'p', 'd', 'y', '/', + '2', '.', '1', 8, 'h', 't', 't', 'p', '/', '1', '.', '0', + }; + unsigned char outlen = 0; + unsigned char *out = NULL; + CU_ASSERT(-1 == + nghttp2_select_next_protocol(&out, &outlen, spdy, sizeof(spdy))); + CU_ASSERT(0 == outlen); + CU_ASSERT(NULL == out); +} + +void test_nghttp2_npn(void) { + http2(); + http11(); + no_overlap(); +} diff --git a/lib/nghttp2/tests/nghttp2_npn_test.h b/lib/nghttp2/tests/nghttp2_npn_test.h new file mode 100644 index 00000000000..f1c97631f1e --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_npn_test.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Twist Inc. + * + * 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 NGHTTP2_NPN_TEST_H +#define NGHTTP2_NPN_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_npn(void); + +#endif /* NGHTTP2_NPN_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_pq_test.c b/lib/nghttp2/tests/nghttp2_pq_test.c new file mode 100644 index 00000000000..90db26de39d --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_pq_test.c @@ -0,0 +1,228 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_pq_test.h" + +#include + +#include + +#include "nghttp2_pq.h" + +typedef struct { + nghttp2_pq_entry ent; + const char *s; +} string_entry; + +static string_entry *string_entry_new(const char *s) { + nghttp2_mem *mem; + string_entry *ent; + + mem = nghttp2_mem_default(); + + ent = nghttp2_mem_malloc(mem, sizeof(string_entry)); + ent->s = s; + + return ent; +} + +static void string_entry_del(string_entry *ent) { free(ent); } + +static int pq_less(const void *lhs, const void *rhs) { + return strcmp(((string_entry *)lhs)->s, ((string_entry *)rhs)->s) < 0; +} + +void test_nghttp2_pq(void) { + int i; + nghttp2_pq pq; + string_entry *top; + + nghttp2_pq_init(&pq, pq_less, nghttp2_mem_default()); + CU_ASSERT(nghttp2_pq_empty(&pq)); + CU_ASSERT(0 == nghttp2_pq_size(&pq)); + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("foo")->ent)); + CU_ASSERT(0 == nghttp2_pq_empty(&pq)); + CU_ASSERT(1 == nghttp2_pq_size(&pq)); + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("foo", top->s) == 0); + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("bar")->ent)); + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("bar", top->s) == 0); + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("baz")->ent)); + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("bar", top->s) == 0); + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("C")->ent)); + CU_ASSERT(4 == nghttp2_pq_size(&pq)); + + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("C", top->s) == 0); + string_entry_del(top); + nghttp2_pq_pop(&pq); + + CU_ASSERT(3 == nghttp2_pq_size(&pq)); + + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("bar", top->s) == 0); + nghttp2_pq_pop(&pq); + string_entry_del(top); + + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("baz", top->s) == 0); + nghttp2_pq_pop(&pq); + string_entry_del(top); + + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(strcmp("foo", top->s) == 0); + nghttp2_pq_pop(&pq); + string_entry_del(top); + + CU_ASSERT(nghttp2_pq_empty(&pq)); + CU_ASSERT(0 == nghttp2_pq_size(&pq)); + CU_ASSERT(NULL == nghttp2_pq_top(&pq)); + + /* Add bunch of entry to see realloc works */ + for (i = 0; i < 10000; ++i) { + CU_ASSERT(0 == nghttp2_pq_push(&pq, &string_entry_new("foo")->ent)); + CU_ASSERT((size_t)(i + 1) == nghttp2_pq_size(&pq)); + } + for (i = 10000; i > 0; --i) { + top = (string_entry *)nghttp2_pq_top(&pq); + CU_ASSERT(NULL != top); + nghttp2_pq_pop(&pq); + string_entry_del(top); + CU_ASSERT((size_t)(i - 1) == nghttp2_pq_size(&pq)); + } + + nghttp2_pq_free(&pq); +} + +typedef struct { + nghttp2_pq_entry ent; + int key; + int val; +} node; + +static int node_less(const void *lhs, const void *rhs) { + node *ln = (node *)lhs; + node *rn = (node *)rhs; + return ln->key < rn->key; +} + +static int node_update(nghttp2_pq_entry *item, void *arg) { + node *nd = (node *)item; + (void)arg; + + if ((nd->key % 2) == 0) { + nd->key *= -1; + return 1; + } else { + return 0; + } +} + +void test_nghttp2_pq_update(void) { + nghttp2_pq pq; + node nodes[10]; + int i; + node *nd; + int ans[] = {-8, -6, -4, -2, 0, 1, 3, 5, 7, 9}; + + nghttp2_pq_init(&pq, node_less, nghttp2_mem_default()); + + for (i = 0; i < (int)(sizeof(nodes) / sizeof(nodes[0])); ++i) { + nodes[i].key = i; + nodes[i].val = i; + nghttp2_pq_push(&pq, &nodes[i].ent); + } + + nghttp2_pq_update(&pq, node_update, NULL); + + for (i = 0; i < (int)(sizeof(nodes) / sizeof(nodes[0])); ++i) { + nd = (node *)nghttp2_pq_top(&pq); + CU_ASSERT(ans[i] == nd->key); + nghttp2_pq_pop(&pq); + } + + nghttp2_pq_free(&pq); +} + +static void push_nodes(nghttp2_pq *pq, node *dest, size_t n) { + size_t i; + for (i = 0; i < n; ++i) { + dest[i].key = (int)i; + dest[i].val = (int)i; + nghttp2_pq_push(pq, &dest[i].ent); + } +} + +static void check_nodes(nghttp2_pq *pq, size_t n, int *ans_key, int *ans_val) { + size_t i; + for (i = 0; i < n; ++i) { + node *nd = (node *)nghttp2_pq_top(pq); + CU_ASSERT(ans_key[i] == nd->key); + CU_ASSERT(ans_val[i] == nd->val); + nghttp2_pq_pop(pq); + } +} + +void test_nghttp2_pq_remove(void) { + nghttp2_pq pq; + node nodes[10]; + int ans_key1[] = {1, 2, 3, 4, 5}; + int ans_val1[] = {1, 2, 3, 4, 5}; + int ans_key2[] = {0, 1, 2, 4, 5}; + int ans_val2[] = {0, 1, 2, 4, 5}; + int ans_key3[] = {0, 1, 2, 3, 4}; + int ans_val3[] = {0, 1, 2, 3, 4}; + + nghttp2_pq_init(&pq, node_less, nghttp2_mem_default()); + + push_nodes(&pq, nodes, 6); + + nghttp2_pq_remove(&pq, &nodes[0].ent); + + check_nodes(&pq, 5, ans_key1, ans_val1); + + nghttp2_pq_free(&pq); + + nghttp2_pq_init(&pq, node_less, nghttp2_mem_default()); + + push_nodes(&pq, nodes, 6); + + nghttp2_pq_remove(&pq, &nodes[3].ent); + + check_nodes(&pq, 5, ans_key2, ans_val2); + + nghttp2_pq_free(&pq); + + nghttp2_pq_init(&pq, node_less, nghttp2_mem_default()); + + push_nodes(&pq, nodes, 6); + + nghttp2_pq_remove(&pq, &nodes[5].ent); + + check_nodes(&pq, 5, ans_key3, ans_val3); + + nghttp2_pq_free(&pq); +} diff --git a/lib/nghttp2/tests/nghttp2_pq_test.h b/lib/nghttp2/tests/nghttp2_pq_test.h new file mode 100644 index 00000000000..969662a954e --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_pq_test.h @@ -0,0 +1,36 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_PQ_TEST_H +#define NGHTTP2_PQ_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_pq(void); +void test_nghttp2_pq_update(void); +void test_nghttp2_pq_remove(void); + +#endif /* NGHTTP2_PQ_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_queue_test.c b/lib/nghttp2/tests/nghttp2_queue_test.c new file mode 100644 index 00000000000..cb993a81b03 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_queue_test.c @@ -0,0 +1,50 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_queue_test.h" + +#include + +#include + +#include "nghttp2_queue.h" + +void test_nghttp2_queue(void) { + int ints[] = {1, 2, 3, 4, 5}; + int i; + nghttp2_queue queue; + nghttp2_queue_init(&queue); + CU_ASSERT(nghttp2_queue_empty(&queue)); + for (i = 0; i < 5; ++i) { + nghttp2_queue_push(&queue, &ints[i]); + CU_ASSERT_EQUAL(ints[0], *(int *)(nghttp2_queue_front(&queue))); + CU_ASSERT(!nghttp2_queue_empty(&queue)); + } + for (i = 0; i < 5; ++i) { + CU_ASSERT_EQUAL(ints[i], *(int *)(nghttp2_queue_front(&queue))); + nghttp2_queue_pop(&queue); + } + CU_ASSERT(nghttp2_queue_empty(&queue)); + nghttp2_queue_free(&queue); +} diff --git a/lib/nghttp2/tests/nghttp2_queue_test.h b/lib/nghttp2/tests/nghttp2_queue_test.h new file mode 100644 index 00000000000..64f8ce85a74 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_queue_test.h @@ -0,0 +1,34 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_QUEUE_TEST_H +#define NGHTTP2_QUEUE_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_queue(void); + +#endif /* NGHTTP2_QUEUE_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_ratelim_test.c b/lib/nghttp2/tests/nghttp2_ratelim_test.c new file mode 100644 index 00000000000..6abece95291 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_ratelim_test.c @@ -0,0 +1,101 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 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 "nghttp2_ratelim_test.h" + +#include + +#include + +#include "nghttp2_ratelim.h" + +void test_nghttp2_ratelim_update(void) { + nghttp2_ratelim rl; + + nghttp2_ratelim_init(&rl, 1000, 21); + + CU_ASSERT(1000 == rl.val); + CU_ASSERT(1000 == rl.burst); + CU_ASSERT(21 == rl.rate); + CU_ASSERT(0 == rl.tstamp); + + nghttp2_ratelim_update(&rl, 999); + + CU_ASSERT(1000 == rl.val); + CU_ASSERT(999 == rl.tstamp); + + nghttp2_ratelim_drain(&rl, 100); + + CU_ASSERT(900 == rl.val); + + nghttp2_ratelim_update(&rl, 1000); + + CU_ASSERT(921 == rl.val); + + nghttp2_ratelim_update(&rl, 1002); + + CU_ASSERT(963 == rl.val); + + nghttp2_ratelim_update(&rl, 1004); + + CU_ASSERT(1000 == rl.val); + CU_ASSERT(1004 == rl.tstamp); + + /* timer skew */ + nghttp2_ratelim_init(&rl, 1000, 21); + nghttp2_ratelim_update(&rl, 1); + + CU_ASSERT(1000 == rl.val); + + nghttp2_ratelim_update(&rl, 0); + + CU_ASSERT(1000 == rl.val); + + /* rate * duration overflow */ + nghttp2_ratelim_init(&rl, 1000, 100); + nghttp2_ratelim_drain(&rl, 999); + + CU_ASSERT(1 == rl.val); + + nghttp2_ratelim_update(&rl, UINT64_MAX); + + CU_ASSERT(1000 == rl.val); + + /* val + rate * duration overflow */ + nghttp2_ratelim_init(&rl, UINT64_MAX - 1, 2); + nghttp2_ratelim_update(&rl, 1); + + CU_ASSERT(UINT64_MAX - 1 == rl.val); +} + +void test_nghttp2_ratelim_drain(void) { + nghttp2_ratelim rl; + + nghttp2_ratelim_init(&rl, 100, 7); + + CU_ASSERT(-1 == nghttp2_ratelim_drain(&rl, 101)); + CU_ASSERT(0 == nghttp2_ratelim_drain(&rl, 51)); + CU_ASSERT(0 == nghttp2_ratelim_drain(&rl, 49)); + CU_ASSERT(-1 == nghttp2_ratelim_drain(&rl, 1)); +} diff --git a/lib/nghttp2/tests/nghttp2_ratelim_test.h b/lib/nghttp2/tests/nghttp2_ratelim_test.h new file mode 100644 index 00000000000..02b2f2b207c --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_ratelim_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2023 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 NGHTTP2_RATELIM_TEST_H +#define NGHTTP2_RATELIM_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_ratelim_update(void); +void test_nghttp2_ratelim_drain(void); + +#endif /* NGHTTP2_RATELIM_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_session_test.c b/lib/nghttp2/tests/nghttp2_session_test.c new file mode 100644 index 00000000000..9f6a6673474 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_session_test.c @@ -0,0 +1,13438 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_session_test.h" + +#include +#include + +#include + +#include "nghttp2_session.h" +#include "nghttp2_stream.h" +#include "nghttp2_net.h" +#include "nghttp2_helper.h" +#include "nghttp2_test_helper.h" +#include "nghttp2_priority_spec.h" +#include "nghttp2_extpri.h" + +typedef struct { + uint8_t buf[65535]; + size_t length; +} accumulator; + +typedef struct { + uint8_t data[8192]; + uint8_t *datamark; + uint8_t *datalimit; + size_t feedseq[8192]; + size_t seqidx; +} scripted_data_feed; + +typedef struct { + accumulator *acc; + scripted_data_feed *df; + int frame_recv_cb_called, invalid_frame_recv_cb_called; + uint8_t recv_frame_type; + nghttp2_frame_hd recv_frame_hd; + int frame_send_cb_called; + uint8_t sent_frame_type; + int before_frame_send_cb_called; + int frame_not_send_cb_called; + uint8_t not_sent_frame_type; + int not_sent_error; + int stream_close_cb_called; + uint32_t stream_close_error_code; + size_t data_source_length; + int32_t stream_id; + size_t block_count; + int data_chunk_recv_cb_called; + const nghttp2_frame *frame; + size_t fixed_sendlen; + int header_cb_called; + int invalid_header_cb_called; + int begin_headers_cb_called; + nghttp2_nv nv; + size_t data_chunk_len; + size_t padlen; + int begin_frame_cb_called; + nghttp2_buf scratchbuf; + size_t data_source_read_cb_paused; +} my_user_data; + +static const nghttp2_nv reqnv[] = { + MAKE_NV(":method", "GET"), + MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), +}; + +static const nghttp2_nv resnv[] = { + MAKE_NV(":status", "200"), +}; + +static const nghttp2_nv trailernv[] = { + // from http://tools.ietf.org/html/rfc6249#section-7 + MAKE_NV("digest", "SHA-256=" + "MWVkMWQxYTRiMzk5MDQ0MzI3NGU5NDEyZTk5OWY1ZGFmNzgyZTJlODYz" + "YjRjYzFhOTlmNTQwYzI2M2QwM2U2MQ=="), +}; + +static void scripted_data_feed_init2(scripted_data_feed *df, + nghttp2_bufs *bufs) { + nghttp2_buf_chain *ci; + nghttp2_buf *buf; + uint8_t *ptr; + size_t len; + + memset(df, 0, sizeof(scripted_data_feed)); + ptr = df->data; + len = 0; + + for (ci = bufs->head; ci; ci = ci->next) { + buf = &ci->buf; + ptr = nghttp2_cpymem(ptr, buf->pos, nghttp2_buf_len(buf)); + len += nghttp2_buf_len(buf); + } + + df->datamark = df->data; + df->datalimit = df->data + len; + df->feedseq[0] = len; +} + +static ssize_t null_send_callback(nghttp2_session *session, const uint8_t *data, + size_t len, int flags, void *user_data) { + (void)session; + (void)data; + (void)flags; + (void)user_data; + + return (ssize_t)len; +} + +static ssize_t fail_send_callback(nghttp2_session *session, const uint8_t *data, + size_t len, int flags, void *user_data) { + (void)session; + (void)data; + (void)len; + (void)flags; + (void)user_data; + + return NGHTTP2_ERR_CALLBACK_FAILURE; +} + +static ssize_t fixed_bytes_send_callback(nghttp2_session *session, + const uint8_t *data, size_t len, + int flags, void *user_data) { + size_t fixed_sendlen = ((my_user_data *)user_data)->fixed_sendlen; + (void)session; + (void)data; + (void)flags; + + return (ssize_t)(fixed_sendlen < len ? fixed_sendlen : len); +} + +static ssize_t scripted_recv_callback(nghttp2_session *session, uint8_t *data, + size_t len, int flags, void *user_data) { + scripted_data_feed *df = ((my_user_data *)user_data)->df; + size_t wlen = df->feedseq[df->seqidx] > len ? len : df->feedseq[df->seqidx]; + (void)session; + (void)flags; + + memcpy(data, df->datamark, wlen); + df->datamark += wlen; + df->feedseq[df->seqidx] -= wlen; + if (df->feedseq[df->seqidx] == 0) { + ++df->seqidx; + } + return (ssize_t)wlen; +} + +static ssize_t eof_recv_callback(nghttp2_session *session, uint8_t *data, + size_t len, int flags, void *user_data) { + (void)session; + (void)data; + (void)len; + (void)flags; + (void)user_data; + + return NGHTTP2_ERR_EOF; +} + +static ssize_t accumulator_send_callback(nghttp2_session *session, + const uint8_t *buf, size_t len, + int flags, void *user_data) { + accumulator *acc = ((my_user_data *)user_data)->acc; + (void)session; + (void)flags; + + assert(acc->length + len < sizeof(acc->buf)); + memcpy(acc->buf + acc->length, buf, len); + acc->length += len; + return (ssize_t)len; +} + +static int on_begin_frame_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)hd; + + ++ud->begin_frame_cb_called; + return 0; +} + +static int on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + + ++ud->frame_recv_cb_called; + ud->recv_frame_type = frame->hd.type; + ud->recv_frame_hd = frame->hd; + + return 0; +} + +static int on_invalid_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)frame; + (void)lib_error_code; + + ++ud->invalid_frame_recv_cb_called; + return 0; +} + +static int on_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + + ++ud->frame_send_cb_called; + ud->sent_frame_type = frame->hd.type; + return 0; +} + +static int on_frame_not_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, int lib_error, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + + ++ud->frame_not_send_cb_called; + ud->not_sent_frame_type = frame->hd.type; + ud->not_sent_error = lib_error; + return 0; +} + +static int cancel_before_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)frame; + + ++ud->before_frame_send_cb_called; + return NGHTTP2_ERR_CANCEL; +} + +static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)flags; + (void)stream_id; + (void)data; + + ++ud->data_chunk_recv_cb_called; + ud->data_chunk_len = len; + return 0; +} + +static int pause_on_data_chunk_recv_callback(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + const uint8_t *data, size_t len, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)flags; + (void)stream_id; + (void)data; + (void)len; + + ++ud->data_chunk_recv_cb_called; + return NGHTTP2_ERR_PAUSE; +} + +static ssize_t select_padding_callback(nghttp2_session *session, + const nghttp2_frame *frame, + size_t max_payloadlen, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + + return (ssize_t)nghttp2_min(max_payloadlen, frame->hd.length + ud->padlen); +} + +static ssize_t too_large_data_source_length_callback( + nghttp2_session *session, uint8_t frame_type, int32_t stream_id, + int32_t session_remote_window_size, int32_t stream_remote_window_size, + uint32_t remote_max_frame_size, void *user_data) { + (void)session; + (void)frame_type; + (void)stream_id; + (void)session_remote_window_size; + (void)stream_remote_window_size; + (void)remote_max_frame_size; + (void)user_data; + + return NGHTTP2_MAX_FRAME_SIZE_MAX + 1; +} + +static ssize_t smallest_length_data_source_length_callback( + nghttp2_session *session, uint8_t frame_type, int32_t stream_id, + int32_t session_remote_window_size, int32_t stream_remote_window_size, + uint32_t remote_max_frame_size, void *user_data) { + (void)session; + (void)frame_type; + (void)stream_id; + (void)session_remote_window_size; + (void)stream_remote_window_size; + (void)remote_max_frame_size; + (void)user_data; + + return 1; +} + +static ssize_t fixed_length_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + size_t wlen; + (void)session; + (void)stream_id; + (void)buf; + (void)source; + + if (len < ud->data_source_length) { + wlen = len; + } else { + wlen = ud->data_source_length; + } + ud->data_source_length -= wlen; + if (ud->data_source_length == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return (ssize_t)wlen; +} + +static ssize_t temporal_failure_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)len; + (void)data_flags; + (void)source; + (void)user_data; + + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static ssize_t fail_data_source_read_callback(nghttp2_session *session, + int32_t stream_id, uint8_t *buf, + size_t len, uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)len; + (void)data_flags; + (void)source; + (void)user_data; + + return NGHTTP2_ERR_CALLBACK_FAILURE; +} + +static ssize_t no_end_stream_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)len; + (void)source; + (void)user_data; + + *data_flags |= NGHTTP2_DATA_FLAG_EOF | NGHTTP2_DATA_FLAG_NO_END_STREAM; + return 0; +} + +static ssize_t no_copy_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + size_t wlen; + (void)session; + (void)stream_id; + (void)buf; + (void)source; + + if (len < ud->data_source_length) { + wlen = len; + } else { + wlen = ud->data_source_length; + } + + ud->data_source_length -= wlen; + + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + + if (ud->data_source_length == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return (ssize_t)wlen; +} + +static int send_data_callback(nghttp2_session *session, nghttp2_frame *frame, + const uint8_t *framehd, size_t length, + nghttp2_data_source *source, void *user_data) { + accumulator *acc = ((my_user_data *)user_data)->acc; + (void)session; + (void)source; + + memcpy(acc->buf + acc->length, framehd, NGHTTP2_FRAME_HDLEN); + acc->length += NGHTTP2_FRAME_HDLEN; + + if (frame->data.padlen) { + *(acc->buf + acc->length++) = (uint8_t)(frame->data.padlen - 1); + } + + acc->length += length; + + if (frame->data.padlen) { + acc->length += frame->data.padlen - 1; + } + + return 0; +} + +static ssize_t block_count_send_callback(nghttp2_session *session, + const uint8_t *data, size_t len, + int flags, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)data; + (void)flags; + + if (ud->block_count == 0) { + return NGHTTP2_ERR_WOULDBLOCK; + } + + --ud->block_count; + return (ssize_t)len; +} + +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)flags; + + ++ud->header_cb_called; + ud->nv.name = (uint8_t *)name; + ud->nv.namelen = namelen; + ud->nv.value = (uint8_t *)value; + ud->nv.valuelen = valuelen; + + ud->frame = frame; + return 0; +} + +static int pause_on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data) { + on_header_callback(session, frame, name, namelen, value, valuelen, flags, + user_data); + return NGHTTP2_ERR_PAUSE; +} + +static int temporal_failure_on_header_callback( + nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + on_header_callback(session, frame, name, namelen, value, valuelen, flags, + user_data); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static int on_invalid_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)flags; + + ++ud->invalid_header_cb_called; + ud->nv.name = (uint8_t *)name; + ud->nv.namelen = namelen; + ud->nv.value = (uint8_t *)value; + ud->nv.valuelen = valuelen; + + ud->frame = frame; + return 0; +} + +static int pause_on_invalid_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, + size_t valuelen, uint8_t flags, + void *user_data) { + on_invalid_header_callback(session, frame, name, namelen, value, valuelen, + flags, user_data); + return NGHTTP2_ERR_PAUSE; +} + +static int reset_on_invalid_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, + size_t valuelen, uint8_t flags, + void *user_data) { + on_invalid_header_callback(session, frame, name, namelen, value, valuelen, + flags, user_data); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + my_user_data *ud = (my_user_data *)user_data; + (void)session; + (void)frame; + + ++ud->begin_headers_cb_called; + return 0; +} + +static int temporal_failure_on_begin_headers_callback( + nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { + on_begin_headers_callback(session, frame, user_data); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static ssize_t defer_data_source_read_callback(nghttp2_session *session, + int32_t stream_id, uint8_t *buf, + size_t len, uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)len; + (void)data_flags; + (void)source; + (void)user_data; + + return NGHTTP2_ERR_DEFERRED; +} + +static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + my_user_data *my_data = (my_user_data *)user_data; + (void)session; + (void)stream_id; + (void)error_code; + + ++my_data->stream_close_cb_called; + my_data->stream_close_error_code = error_code; + + return 0; +} + +static int fatal_error_on_stream_close_callback(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data) { + on_stream_close_callback(session, stream_id, error_code, user_data); + + return NGHTTP2_ERR_CALLBACK_FAILURE; +} + +static ssize_t pack_extension_callback(nghttp2_session *session, uint8_t *buf, + size_t len, const nghttp2_frame *frame, + void *user_data) { + nghttp2_buf *p = frame->ext.payload; + (void)session; + (void)len; + (void)user_data; + + memcpy(buf, p->pos, nghttp2_buf_len(p)); + + return (ssize_t)nghttp2_buf_len(p); +} + +static int on_extension_chunk_recv_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + const uint8_t *data, size_t len, + void *user_data) { + my_user_data *my_data = (my_user_data *)user_data; + nghttp2_buf *buf = &my_data->scratchbuf; + (void)session; + (void)hd; + + buf->last = nghttp2_cpymem(buf->last, data, len); + + return 0; +} + +static int cancel_on_extension_chunk_recv_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + const uint8_t *data, + size_t len, + void *user_data) { + (void)session; + (void)hd; + (void)data; + (void)len; + (void)user_data; + + return NGHTTP2_ERR_CANCEL; +} + +static int unpack_extension_callback(nghttp2_session *session, void **payload, + const nghttp2_frame_hd *hd, + void *user_data) { + my_user_data *my_data = (my_user_data *)user_data; + nghttp2_buf *buf = &my_data->scratchbuf; + (void)session; + (void)hd; + + *payload = buf; + + return 0; +} + +static int cancel_unpack_extension_callback(nghttp2_session *session, + void **payload, + const nghttp2_frame_hd *hd, + void *user_data) { + (void)session; + (void)payload; + (void)hd; + (void)user_data; + + return NGHTTP2_ERR_CANCEL; +} + +static nghttp2_settings_entry *dup_iv(const nghttp2_settings_entry *iv, + size_t niv) { + return nghttp2_frame_iv_copy(iv, niv, nghttp2_mem_default()); +} + +static nghttp2_priority_spec pri_spec_default = {0, NGHTTP2_DEFAULT_WEIGHT, 0}; + +void test_nghttp2_session_recv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + scripted_data_feed df; + my_user_data user_data; + nghttp2_bufs bufs; + size_t framelen; + nghttp2_frame frame; + size_t i; + nghttp2_outbound_item *item; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_hd_deflater deflater; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.recv_callback = scripted_recv_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_begin_frame_callback = on_begin_frame_callback; + + user_data.df = &df; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + scripted_data_feed_init2(&df, &bufs); + + framelen = nghttp2_bufs_len(&bufs); + + /* Send 1 byte per each read */ + for (i = 0; i < framelen; ++i) { + df.feedseq[i] = 1; + } + + nghttp2_frame_headers_free(&frame.headers, mem); + + user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + + while (df.seqidx < framelen) { + CU_ASSERT(0 == nghttp2_session_recv(session)); + } + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(1 == user_data.begin_frame_cb_called); + + nghttp2_bufs_reset(&bufs); + + /* Receive PRIORITY */ + nghttp2_frame_priority_init(&frame.priority, 5, &pri_spec_default); + + nghttp2_frame_pack_priority(&bufs, &frame.priority); + + nghttp2_frame_priority_free(&frame.priority); + + scripted_data_feed_init2(&df, &bufs); + + user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(1 == user_data.begin_frame_cb_called); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Some tests for frame too large */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + + /* Receive PING with too large payload */ + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + + nghttp2_frame_pack_ping(&bufs, &frame.ping); + + /* Add extra 16 bytes */ + nghttp2_bufs_seek_last_present(&bufs); + assert(nghttp2_buf_len(&bufs.cur->buf) >= 16); + + bufs.cur->buf.last += 16; + nghttp2_put_uint32be( + bufs.cur->buf.pos, + (uint32_t)(((frame.hd.length + 16) << 8) + bufs.cur->buf.pos[3])); + + nghttp2_frame_ping_free(&frame.ping); + + scripted_data_feed_init2(&df, &bufs); + user_data.frame_recv_cb_called = 0; + user_data.begin_frame_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(0 == user_data.frame_recv_cb_called); + CU_ASSERT(0 == user_data.begin_frame_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == item->frame.goaway.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_invalid_stream_id(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + scripted_data_feed df; + my_user_data user_data; + nghttp2_bufs bufs; + nghttp2_frame frame; + nghttp2_hd_deflater deflater; + int rv; + nghttp2_mem *mem; + nghttp2_nv *nva; + size_t nvlen; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.recv_callback = scripted_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + user_data.df = &df; + user_data.invalid_frame_recv_cb_called = 0; + nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + scripted_data_feed_init2(&df, &bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_invalid_frame(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + scripted_data_feed df; + my_user_data user_data; + nghttp2_bufs bufs; + nghttp2_frame frame; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_hd_deflater deflater; + int rv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.recv_callback = scripted_recv_callback; + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + user_data.df = &df; + user_data.frame_send_cb_called = 0; + nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_hd_deflate_init(&deflater, mem); + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + scripted_data_feed_init2(&df, &bufs); + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == user_data.frame_send_cb_called); + + /* Receive exactly same bytes of HEADERS is treated as error, because it has + * pseudo headers and without END_STREAM flag set */ + scripted_data_feed_init2(&df, &bufs); + + CU_ASSERT(0 == nghttp2_session_recv(session)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_send_cb_called); + CU_ASSERT(NGHTTP2_RST_STREAM == user_data.sent_frame_type); + + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_eof(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.recv_callback = eof_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + CU_ASSERT(NGHTTP2_ERR_EOF == nghttp2_session_recv(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + uint8_t data[8092]; + ssize_t rv; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + nghttp2_frame_hd hd; + int i; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + /* Create DATA frame with length 4KiB */ + memset(data, 0, sizeof(data)); + hd.length = 4096; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + + /* stream 1 is not opened, so it must be responded with connection + error. This is not mandated by the spec */ + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + CU_ASSERT(0 == ud.data_chunk_recv_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &ud); + + /* Create stream 1 with CLOSING state. DATA is ignored. */ + stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_CLOSING); + + /* Set initial window size 16383 to check stream flow control, + isolating it from the connection flow control */ + stream->local_window_size = 16383; + + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + CU_ASSERT(0 == ud.data_chunk_recv_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NULL == item); + + /* This is normal case. DATA is acceptable. */ + stream->state = NGHTTP2_STREAM_OPENED; + + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + CU_ASSERT(1 == ud.data_chunk_recv_cb_called); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + /* Now we got data more than initial-window-size / 2, WINDOW_UPDATE + must be queued */ + CU_ASSERT(1 == ud.data_chunk_recv_cb_called); + CU_ASSERT(1 == ud.frame_recv_cb_called); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1 == item->frame.window_update.hd.stream_id); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Set initial window size to 1MiB, so that we can check connection + flow control individually */ + stream->local_window_size = 1 << 20; + /* Connection flow control takes into account DATA which is received + in the error condition. We have received 4096 * 4 bytes of + DATA. Additional 4 DATA frames, connection flow control will kick + in. */ + for (i = 0; i < 5; ++i) { + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + } + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.window_update.hd.stream_id); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Reception of DATA with stream ID = 0 causes connection error */ + hd.length = 4096; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 0; + nghttp2_frame_pack_frame_hd(data, &hd); + + ud.data_chunk_recv_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + + CU_ASSERT(0 == ud.data_chunk_recv_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_session_del(session); + + /* Check window_update_queued flag in both session and stream */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + hd.length = 4096; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + + stream = open_recv_stream(session, 1); + + /* Send 32767 bytes of DATA. In our current flow control algorithm, + it triggers first WINDOW_UPDATE of window_size_increment + 32767. */ + for (i = 0; i < 7; ++i) { + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + } + + hd.length = 4095; + nghttp2_frame_pack_frame_hd(data, &hd); + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4095); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4095 == rv); + + /* Now 2 WINDOW_UPDATEs for session and stream should be queued. */ + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(1 == stream->window_update_queued); + CU_ASSERT(1 == session->window_update_queued); + + /* Then send 32768 bytes of DATA. Since we have not sent queued + WINDOW_UDPATE frame, recv_window_size should not be decreased */ + hd.length = 4096; + nghttp2_frame_pack_frame_hd(data, &hd); + + for (i = 0; i < 8; ++i) { + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + } + + /* WINDOW_UPDATE is blocked for session and stream, so + recv_window_size must not be decreased. */ + CU_ASSERT(32768 == stream->recv_window_size); + CU_ASSERT(32768 == session->recv_window_size); + CU_ASSERT(1 == stream->window_update_queued); + CU_ASSERT(1 == session->window_update_queued); + + ud.frame_send_cb_called = 0; + + /* This sends queued WINDOW_UPDATES. And then check + recv_window_size, and queue WINDOW_UPDATEs for both session and + stream, and send them at once. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(4 == ud.frame_send_cb_called); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(0 == stream->window_update_queued); + CU_ASSERT(0 == session->window_update_queued); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_data_no_auto_flow_control(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_option *option; + nghttp2_frame_hd hd; + size_t padlen; + uint8_t data[8192]; + ssize_t rv; + size_t sendlen; + nghttp2_stream *stream; + size_t i; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + /* Create DATA frame with length 4KiB + 11 bytes padding*/ + padlen = 11; + memset(data, 0, sizeof(data)); + hd.length = 4096 + 1 + padlen; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_PADDED; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + data[NGHTTP2_FRAME_HDLEN] = (uint8_t)padlen; + + /* First create stream 1, then close it. Check that data is + consumed for connection in this situation */ + open_recv_stream(session, 1); + + /* Receive first 100 bytes */ + sendlen = 100; + rv = nghttp2_session_mem_recv(session, data, sendlen); + CU_ASSERT((ssize_t)sendlen == rv); + + /* We consumed pad length field (1 byte) */ + CU_ASSERT(1 == session->consumed_size); + + /* close stream here */ + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, NGHTTP2_NO_ERROR); + nghttp2_session_send(session); + + /* stream 1 has been closed, and we disabled auto flow-control, so + data must be immediately consumed for connection. */ + rv = nghttp2_session_mem_recv(session, data + sendlen, + NGHTTP2_FRAME_HDLEN + hd.length - sendlen); + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length - sendlen) == rv); + + /* We already consumed pad length field (1 byte), so do +1 here */ + CU_ASSERT((int32_t)(NGHTTP2_FRAME_HDLEN + hd.length - sendlen + 1) == + session->consumed_size); + + nghttp2_session_del(session); + + /* Reuse DATA created previously. */ + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + /* Now we are expecting final response header, which means receiving + DATA for that stream is illegal. */ + stream = open_recv_stream(session, 1); + stream->http_flags |= NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE; + + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length); + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) == rv); + + /* Whole payload must be consumed now because HTTP messaging rule + was not honored. */ + CU_ASSERT((int32_t)hd.length == session->consumed_size); + + nghttp2_session_del(session); + + /* Check window_update_queued flag in both session and stream */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + stream = open_recv_stream(session, 1); + + hd.length = 4096; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + + /* Receive up to 65535 bytes of DATA */ + for (i = 0; i < 15; ++i) { + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv); + } + + hd.length = 4095; + nghttp2_frame_pack_frame_hd(data, &hd); + + rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4095); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4095 == rv); + + CU_ASSERT(65535 == session->recv_window_size); + CU_ASSERT(65535 == stream->recv_window_size); + + /* The first call of nghttp2_session_consume_connection() will queue + WINDOW_UPDATE. Next call does not. */ + nghttp2_session_consume_connection(session, 32767); + nghttp2_session_consume_connection(session, 32768); + + CU_ASSERT(32768 == session->recv_window_size); + CU_ASSERT(65535 == stream->recv_window_size); + CU_ASSERT(1 == session->window_update_queued); + CU_ASSERT(0 == stream->window_update_queued); + + ud.frame_send_cb_called = 0; + + /* This will send WINDOW_UPDATE, and check whether we should send + WINDOW_UPDATE, and queue and send it at once. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(65535 == stream->recv_window_size); + CU_ASSERT(0 == session->window_update_queued); + CU_ASSERT(0 == stream->window_update_queued); + CU_ASSERT(2 == ud.frame_send_cb_called); + + /* Do the same for stream */ + nghttp2_session_consume_stream(session, 1, 32767); + nghttp2_session_consume_stream(session, 1, 32768); + + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(32768 == stream->recv_window_size); + CU_ASSERT(0 == session->window_update_queued); + CU_ASSERT(1 == stream->window_update_queued); + + ud.frame_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == session->window_update_queued); + CU_ASSERT(0 == stream->window_update_queued); + CU_ASSERT(2 == ud.frame_send_cb_called); + + nghttp2_session_del(session); + nghttp2_option_del(option); +} + +void test_nghttp2_session_recv_continuation(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + uint8_t data[1024]; + size_t datalen; + nghttp2_frame_hd cont_hd; + nghttp2_priority_spec pri_spec; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_header_callback = on_header_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_begin_frame_callback = on_begin_frame_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* Make 1 HEADERS and insert CONTINUATION header */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + /* make sure that all data is in the first buf */ + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* HEADERS's payload is 1 byte */ + memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN + 1); + datalen = NGHTTP2_FRAME_HDLEN + 1; + buf->pos += NGHTTP2_FRAME_HDLEN + 1; + + nghttp2_put_uint32be(data, (uint32_t)((1 << 8) + data[3])); + + /* First CONTINUATION, 2 bytes */ + nghttp2_frame_hd_init(&cont_hd, 2, NGHTTP2_CONTINUATION, NGHTTP2_FLAG_NONE, + 1); + + nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd); + datalen += NGHTTP2_FRAME_HDLEN; + + memcpy(data + datalen, buf->pos, cont_hd.length); + datalen += cont_hd.length; + buf->pos += cont_hd.length; + + /* Second CONTINUATION, rest of the bytes */ + nghttp2_frame_hd_init(&cont_hd, nghttp2_buf_len(buf), NGHTTP2_CONTINUATION, + NGHTTP2_FLAG_END_HEADERS, 1); + + nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd); + datalen += NGHTTP2_FRAME_HDLEN; + + memcpy(data + datalen, buf->pos, cont_hd.length); + datalen += cont_hd.length; + buf->pos += cont_hd.length; + + CU_ASSERT(0 == nghttp2_buf_len(buf)); + + ud.header_cb_called = 0; + ud.begin_frame_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, data, datalen); + CU_ASSERT((ssize_t)datalen == rv); + CU_ASSERT(4 == ud.header_cb_called); + CU_ASSERT(3 == ud.begin_frame_cb_called); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* HEADERS with padding followed by CONTINUATION */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* make sure that all data is in the first buf */ + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + /* HEADERS payload is 3 byte (1 for padding field, 1 for padding) */ + memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN); + nghttp2_put_uint32be(data, (uint32_t)((3 << 8) + data[3])); + data[4] |= NGHTTP2_FLAG_PADDED; + /* padding field */ + data[NGHTTP2_FRAME_HDLEN] = 1; + data[NGHTTP2_FRAME_HDLEN + 1] = buf->pos[NGHTTP2_FRAME_HDLEN]; + /* padding */ + data[NGHTTP2_FRAME_HDLEN + 2] = 0; + datalen = NGHTTP2_FRAME_HDLEN + 3; + buf->pos += NGHTTP2_FRAME_HDLEN + 1; + + /* CONTINUATION, rest of the bytes */ + nghttp2_frame_hd_init(&cont_hd, nghttp2_buf_len(buf), NGHTTP2_CONTINUATION, + NGHTTP2_FLAG_END_HEADERS, 1); + nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd); + datalen += NGHTTP2_FRAME_HDLEN; + + memcpy(data + datalen, buf->pos, cont_hd.length); + datalen += cont_hd.length; + buf->pos += cont_hd.length; + + CU_ASSERT(0 == nghttp2_buf_len(buf)); + + ud.header_cb_called = 0; + ud.begin_frame_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, data, datalen); + + CU_ASSERT((ssize_t)datalen == rv); + CU_ASSERT(4 == ud.header_cb_called); + CU_ASSERT(2 == ud.begin_frame_cb_called); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Expecting CONTINUATION, but get the other frame */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* HEADERS without END_HEADERS flag */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* make sure that all data is in the first buf */ + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + memcpy(data, buf->pos, nghttp2_buf_len(buf)); + datalen = nghttp2_buf_len(buf); + + /* Followed by PRIORITY */ + nghttp2_priority_spec_default_init(&pri_spec); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + nghttp2_bufs_reset(&bufs); + + nghttp2_frame_pack_priority(&bufs, &frame.priority); + + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + memcpy(data + datalen, buf->pos, nghttp2_buf_len(buf)); + datalen += nghttp2_buf_len(buf); + + ud.begin_headers_cb_called = 0; + rv = nghttp2_session_mem_recv(session, data, datalen); + CU_ASSERT((ssize_t)datalen == rv); + + CU_ASSERT(1 == ud.begin_headers_cb_called); + CU_ASSERT(NGHTTP2_GOAWAY == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_headers_with_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + nghttp2_outbound_item *item; + nghttp2_priority_spec pri_spec; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + open_recv_stream(session, 1); + + /* With NGHTTP2_FLAG_PRIORITY without exclusive flag set */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_priority_spec_init(&pri_spec, 1, 99, 0); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 3, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 3); + + CU_ASSERT(99 == stream->weight); + CU_ASSERT(1 == stream->dep_prev->stream_id); + + nghttp2_bufs_reset(&bufs); + + /* With NGHTTP2_FLAG_PRIORITY, but cut last 1 byte to make it + invalid. */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_priority_spec_init(&pri_spec, 0, 99, 0); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 5, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > NGHTTP2_FRAME_HDLEN + 5); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + /* Make payload shorter than required length to store priority + group */ + nghttp2_put_uint32be(buf->pos, (uint32_t)((4 << 8) + buf->pos[3])); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 5); + + CU_ASSERT(NULL == stream); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == item->frame.goaway.error_code); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Check dep_stream_id == stream_id */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_priority_spec_init(&pri_spec, 1, 0, 0); + + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL == stream); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_headers_with_padding(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + my_user_data ud; + ssize_t rv; + + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.send_callback = null_send_callback; + + /* HEADERS: Wrong padding length */ + nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_session_send(session); + + nghttp2_frame_hd_init(&hd, 10, NGHTTP2_HEADERS, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY | + NGHTTP2_FLAG_PADDED, + 1); + buf = &bufs.head->buf; + nghttp2_frame_pack_frame_hd(buf->last, &hd); + buf->last += NGHTTP2_FRAME_HDLEN; + /* padding is 6 bytes */ + *buf->last++ = 5; + /* priority field */ + nghttp2_put_uint32be(buf->last, 3); + buf->last += sizeof(uint32_t); + *buf->last++ = 1; + /* rest is garbage */ + memset(buf->last, 0, 4); + buf->last += 4; + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_bufs_reset(&bufs); + nghttp2_session_del(session); + + /* PUSH_PROMISE: Wrong padding length */ + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_session_send(session); + + open_sent_stream(session, 1); + + nghttp2_frame_hd_init(&hd, 9, NGHTTP2_PUSH_PROMISE, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED, 1); + buf = &bufs.head->buf; + nghttp2_frame_pack_frame_hd(buf->last, &hd); + buf->last += NGHTTP2_FRAME_HDLEN; + /* padding is 6 bytes */ + *buf->last++ = 5; + /* promised stream ID field */ + nghttp2_put_uint32be(buf->last, 2); + buf->last += sizeof(uint32_t); + /* rest is garbage */ + memset(buf->last, 0, 4); + buf->last += 4; + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + +static int response_on_begin_frame_callback(nghttp2_session *session, + const nghttp2_frame_hd *hd, + void *user_data) { + int rv; + (void)user_data; + + if (hd->type != NGHTTP2_HEADERS) { + return 0; + } + + rv = nghttp2_submit_response(session, hd->stream_id, resnv, ARRLEN(resnv), + NULL); + + CU_ASSERT(0 == rv); + + return 0; +} + +void test_nghttp2_session_recv_headers_early_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + ssize_t rv; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_begin_frame_callback = response_on_begin_frame_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + 1, NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + + /* Only receive 9 bytes headers, and invoke + on_begin_frame_callback */ + rv = nghttp2_session_mem_recv(session, buf->pos, 9); + + CU_ASSERT(9 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + rv = + nghttp2_session_mem_recv(session, buf->pos + 9, nghttp2_buf_len(buf) - 9); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) - 9 == rv); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_CLOSED); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_recv_headers_for_closed_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + nghttp2_stream *stream; + nghttp2_mem *mem; + const uint8_t *data; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_header_callback = on_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* Make sure that on_header callback never be invoked for closed + stream */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.header_cb_called = 0; + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(0 == ud.header_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL != stream); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(rv > 0); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL == stream); + + ud.header_cb_called = 0; + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos + NGHTTP2_FRAME_HDLEN, + nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(0 == ud.header_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_headers_with_extpri(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + nghttp2_hd_deflater deflater; + nghttp2_stream *stream; + nghttp2_mem *mem; + const nghttp2_nv extpri_reqnv[] = { + MAKE_NV(":method", "GET"), MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), + MAKE_NV("priority", "i,u=2"), + }; + nghttp2_settings_entry iv; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(extpri_reqnv); + nghttp2_nv_array_copy(&nva, extpri_reqnv, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* Client should ignore priority header field included in + PUSH_PROMISE. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + open_sent_stream(session, 1); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(extpri_reqnv); + nghttp2_nv_array_copy(&nva, extpri_reqnv, nvlen, mem); + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, nva, nvlen); + + rv = nghttp2_frame_pack_push_promise(&bufs, &frame.push_promise, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + stream = nghttp2_session_get_stream(session, 2); + + CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == + nghttp2_extpri_uint8_urgency(stream->http_extpri)); + CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == + nghttp2_extpri_uint8_urgency(stream->extpri)); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_server_recv_push_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_mem *mem; + nghttp2_frame frame; + nghttp2_hd_deflater deflater; + nghttp2_nv *nva; + size_t nvlen; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + nvlen = ARRLEN(resnv); + nghttp2_nv_array_copy(&nva, resnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_HEADERS, &pri_spec_default, nva, + nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + + ud.invalid_frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.invalid_frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_premature_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_hd_deflater deflater; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + uint32_t payloadlen; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + /* Intentionally feed payload cutting last 1 byte off */ + payloadlen = nghttp2_get_uint32(buf->pos) >> 8; + nghttp2_put_uint32be(buf->pos, ((payloadlen - 1) << 8) + buf->pos[3]); + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf) - 1); + + CU_ASSERT((ssize_t)(nghttp2_buf_len(buf) - 1) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_COMPRESSION_ERROR == item->frame.rst_stream.error_code); + CU_ASSERT(1 == item->frame.hd.stream_id); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Test for PUSH_PROMISE */ + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_hd_deflate_init(&deflater, mem); + + open_sent_stream3(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + + CU_ASSERT(0 == rv); + + buf = &bufs.head->buf; + payloadlen = nghttp2_get_uint32(buf->pos) >> 8; + /* Intentionally feed payload cutting last 1 byte off */ + nghttp2_put_uint32be(buf->pos, ((payloadlen - 1) << 8) + buf->pos[3]); + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf) - 1); + + CU_ASSERT((ssize_t)(nghttp2_buf_len(buf) - 1) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_COMPRESSION_ERROR == item->frame.rst_stream.error_code); + CU_ASSERT(2 == item->frame.hd.stream_id); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_recv_unknown_frame(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + uint8_t data[16384]; + size_t datalen; + nghttp2_frame_hd hd; + ssize_t rv; + + nghttp2_frame_hd_init(&hd, 16000, 99, NGHTTP2_FLAG_NONE, 0); + + nghttp2_frame_pack_frame_hd(data, &hd); + datalen = NGHTTP2_FRAME_HDLEN + hd.length; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + ud.frame_recv_cb_called = 0; + + /* Unknown frame must be ignored */ + rv = nghttp2_session_mem_recv(session, data, datalen); + + CU_ASSERT(rv == (ssize_t)datalen); + CU_ASSERT(0 == ud.frame_recv_cb_called); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_unexpected_continuation(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + uint8_t data[16384]; + size_t datalen; + nghttp2_frame_hd hd; + ssize_t rv; + nghttp2_outbound_item *item; + + nghttp2_frame_hd_init(&hd, 16000, NGHTTP2_CONTINUATION, + NGHTTP2_FLAG_END_HEADERS, 1); + + nghttp2_frame_pack_frame_hd(data, &hd); + datalen = NGHTTP2_FRAME_HDLEN + hd.length; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + ud.frame_recv_cb_called = 0; + + /* unexpected CONTINUATION must be treated as connection error */ + rv = nghttp2_session_mem_recv(session, data, datalen); + + CU_ASSERT(rv == (ssize_t)datalen); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_settings_header_table_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_settings_entry iv[3]; + nghttp2_nv nv = MAKE_NV(":authority", "example.org"); + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 3000; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16384; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2), + 2); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(3000 == session->remote_settings.header_table_size); + CU_ASSERT(16384 == session->remote_settings.initial_window_size); + + nghttp2_bufs_reset(&bufs); + + /* 2 SETTINGS_HEADER_TABLE_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 3001; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16383; + + iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[2].value = 3001; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3), + 3); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)(nghttp2_buf_len(buf)) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(3001 == session->remote_settings.header_table_size); + CU_ASSERT(16383 == session->remote_settings.initial_window_size); + + nghttp2_bufs_reset(&bufs); + + /* 2 SETTINGS_HEADER_TABLE_SIZE; first entry clears dynamic header + table. */ + + nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL); + nghttp2_session_send(session); + + CU_ASSERT(0 < session->hd_deflater.ctx.hd_table.len); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 0; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16382; + + iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[2].value = 4096; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3), + 3); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(4096 == session->remote_settings.header_table_size); + CU_ASSERT(16382 == session->remote_settings.initial_window_size); + CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len); + + nghttp2_bufs_reset(&bufs); + + /* 2 SETTINGS_HEADER_TABLE_SIZE; second entry clears dynamic header + table. */ + + nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL); + nghttp2_session_send(session); + + CU_ASSERT(0 < session->hd_deflater.ctx.hd_table.len); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 3000; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16381; + + iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[2].value = 0; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3), + 3); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + CU_ASSERT(0 == session->remote_settings.header_table_size); + CU_ASSERT(16381 == session->remote_settings.initial_window_size); + CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len); + + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_too_large_frame_length(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t buf[NGHTTP2_FRAME_HDLEN]; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd; + + /* Initial max frame size is NGHTTP2_MAX_FRAME_SIZE_MIN */ + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN + 1, NGHTTP2_HEADERS, + NGHTTP2_FLAG_NONE, 1); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_frame_pack_frame_hd(buf, &hd); + + CU_ASSERT(sizeof(buf) == nghttp2_session_mem_recv(session, buf, sizeof(buf))); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_recv_extension(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_buf buf; + nghttp2_frame_hd hd; + nghttp2_mem *mem; + const char data[] = "Hello World!"; + ssize_t rv; + nghttp2_option *option; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; + callbacks.unpack_extension_callback = unpack_extension_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_user_recv_extension_type(option, 111); + + nghttp2_buf_init2(&ud.scratchbuf, 4096, mem); + nghttp2_buf_init2(&buf, 4096, mem); + + nghttp2_frame_hd_init(&hd, sizeof(data), 111, 0xab, 1000000007); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + buf.last = nghttp2_cpymem(buf.last, data, sizeof(data)); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&ud.recv_frame_hd, 0, 0, 0, 0); + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(111 == ud.recv_frame_hd.type); + CU_ASSERT(0xab == ud.recv_frame_hd.flags); + CU_ASSERT(1000000007 == ud.recv_frame_hd.stream_id); + CU_ASSERT(0 == memcmp(data, ud.scratchbuf.pos, sizeof(data))); + + nghttp2_session_del(session); + + /* cancel in on_extension_chunk_recv_callback */ + nghttp2_buf_reset(&ud.scratchbuf); + + callbacks.on_extension_chunk_recv_callback = + cancel_on_extension_chunk_recv_callback; + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* cancel in unpack_extension_callback */ + nghttp2_buf_reset(&ud.scratchbuf); + + callbacks.on_extension_chunk_recv_callback = on_extension_chunk_recv_callback; + callbacks.unpack_extension_callback = cancel_unpack_extension_callback; + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + hd.length == (size_t)rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_buf_free(&buf, mem); + nghttp2_buf_free(&ud.scratchbuf, mem); + + nghttp2_option_del(option); +} + +void test_nghttp2_session_recv_altsvc(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_buf buf; + nghttp2_frame_hd hd; + nghttp2_mem *mem; + ssize_t rv; + nghttp2_option *option; + static const uint8_t origin[] = "nghttp2.org"; + static const uint8_t field_value[] = "h2=\":443\""; + + mem = nghttp2_mem_default(); + + nghttp2_buf_init2(&buf, NGHTTP2_FRAME_HDLEN + NGHTTP2_MAX_FRAME_SIZE_MIN, + mem); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1, + NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + nghttp2_session_del(session); + + /* size of origin is larger than frame length */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 - 1, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1 - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* zero-length value */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + + ud.invalid_frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.invalid_frame_recv_cb_called); + + nghttp2_session_del(session); + + /* non-empty origin to a stream other than 0 */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + open_sent_stream(session, 1); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1, + NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 1); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + ud.invalid_frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.invalid_frame_recv_cb_called); + + nghttp2_session_del(session); + + /* empty origin to stream 0 */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(field_value) - 1, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, 0); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + ud.invalid_frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.invalid_frame_recv_cb_called); + + nghttp2_session_del(session); + + /* send large frame (16KiB) */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + memset(buf.last, 0, nghttp2_buf_avail(&buf)); + buf.last += nghttp2_buf_avail(&buf); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ALTSVC == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_MAX_FRAME_SIZE_MIN == ud.recv_frame_hd.length); + + nghttp2_session_del(session); + + /* send too large frame */ + nghttp2_buf_reset(&buf); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + session->local_settings.max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN - 1; + + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN + 1, NGHTTP2_ALTSVC, + NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + memset(buf.last, 0, nghttp2_buf_avail(&buf)); + buf.last += nghttp2_buf_avail(&buf); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* received by server */ + nghttp2_buf_reset(&buf); + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_hd_init(&hd, 2 + sizeof(origin) - 1 + sizeof(field_value) - 1, + NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, 0); + nghttp2_frame_pack_frame_hd(buf.last, &hd); + buf.last += NGHTTP2_FRAME_HDLEN; + nghttp2_put_uint16be(buf.last, sizeof(origin) - 1); + buf.last += 2; + buf.last = nghttp2_cpymem(buf.last, origin, sizeof(origin) - 1); + buf.last = nghttp2_cpymem(buf.last, field_value, sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf.pos, nghttp2_buf_len(&buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_buf_free(&buf, mem); + nghttp2_option_del(option); +} + +void test_nghttp2_session_recv_origin(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_option *option; + nghttp2_extension frame; + nghttp2_ext_origin origin; + nghttp2_origin_entry ov; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + + frame_pack_bufs_init(&bufs); + + frame.payload = &origin; + + ov.origin = (uint8_t *)nghttp2; + ov.origin_len = sizeof(nghttp2) - 1; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ORIGIN); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* The length of origin is larger than payload length. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + nghttp2_put_uint16be(bufs.head->buf.pos + NGHTTP2_FRAME_HDLEN, + (uint16_t)sizeof(nghttp2)); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if it is sent to a stream other than + stream 0. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + frame.hd.stream_id = 1; + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if the reserved flag is set */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + frame.hd.flags = 0xf0; + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* A frame should be ignored if it is received by a server. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, &ov, 1); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* Receiving empty ORIGIN frame */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + nghttp2_frame_origin_init(&frame, NULL, 0); + rv = nghttp2_frame_pack_origin(&bufs, &frame); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_ORIGIN == ud.recv_frame_hd.type); + + nghttp2_session_del(session); + + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_recv_priority_update(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_option *option; + nghttp2_extension frame; + nghttp2_ext_priority_update priority_update; + nghttp2_stream *stream; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + uint8_t large_field_value[sizeof(session->iframe.raw_sbuf) + 1]; + nghttp2_outbound_item *item; + size_t i; + int32_t stream_id; + static const uint8_t field_value[] = "u=2,i"; + + mem = nghttp2_mem_default(); + + memset(large_field_value, ' ', sizeof(large_field_value)); + memcpy(large_field_value, field_value, sizeof(field_value) - 1); + + frame_pack_bufs_init(&bufs); + + frame.payload = &priority_update; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, + NGHTTP2_PRIORITY_UPDATE); + + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + open_recv_stream(session, 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* Check that priority which is received in idle state is + retained. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == ud.recv_frame_hd.flags); + CU_ASSERT(0 == ud.recv_frame_hd.stream_id); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_bufs_reset(&bufs); + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + + CU_ASSERT(0 == rv); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.recv_frame_hd.type); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(2 == nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* PRIORITY_UPDATE with too large field_value is discarded */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + + nghttp2_frame_priority_update_init(&frame, 1, large_field_value, + sizeof(large_field_value)); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + open_recv_stream(session, 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == stream->extpri); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* Connection error if client receives PRIORITY_UPDATE. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + open_sent_stream(session, 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_session_del(session); + nghttp2_bufs_reset(&bufs); + + /* The number of idle streams exceeds the maximum. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + session->pending_no_rfc7540_priorities = 1; + session->local_settings.max_concurrent_streams = 100; + + for (i = 0; i < 101; ++i) { + stream_id = (int32_t)(i * 2 + 1); + nghttp2_frame_priority_update_init( + &frame, stream_id, (uint8_t *)field_value, sizeof(field_value) - 1); + + nghttp2_frame_pack_priority_update(&bufs, &frame); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + if (i < 100) { + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == ud.recv_frame_hd.type); + } else { + CU_ASSERT(0 == ud.frame_recv_cb_called); + } + + nghttp2_bufs_reset(&bufs); + } + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_session_del(session); + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_continue(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + const nghttp2_nv nv1[] = {MAKE_NV(":method", "GET"), MAKE_NV(":path", "/")}; + const nghttp2_nv nv2[] = {MAKE_NV("user-agent", "nghttp2/1.0.0"), + MAKE_NV("alpha", "bravo")}; + nghttp2_bufs bufs; + nghttp2_buf *buf; + size_t framelen1, framelen2; + ssize_t rv; + uint8_t buffer[4096]; + nghttp2_buf databuf; + nghttp2_frame frame; + nghttp2_nv *nva; + size_t nvlen; + const nghttp2_frame *recv_frame; + nghttp2_frame_hd data_hd; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nghttp2_buf_wrap_init(&databuf, buffer, sizeof(buffer)); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_data_chunk_recv_callback = pause_on_data_chunk_recv_callback; + callbacks.on_header_callback = pause_on_header_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + /* disable strict HTTP layering checks */ + session->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING; + + nghttp2_hd_deflate_init(&deflater, mem); + + /* Make 2 HEADERS frames */ + nvlen = ARRLEN(nv1); + nghttp2_nv_array_copy(&nva, nv1, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + framelen1 = nghttp2_buf_len(buf); + databuf.last = nghttp2_cpymem(databuf.last, buf->pos, nghttp2_buf_len(buf)); + + nvlen = ARRLEN(nv2); + nghttp2_nv_array_copy(&nva, nv2, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_headers_free(&frame.headers, mem); + + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + framelen2 = nghttp2_buf_len(buf); + databuf.last = nghttp2_cpymem(databuf.last, buf->pos, nghttp2_buf_len(buf)); + + /* Receive 1st HEADERS and pause */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + recv_frame = user_data.frame; + CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type); + CU_ASSERT(framelen1 - NGHTTP2_FRAME_HDLEN == recv_frame->hd.length); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.header_cb_called); + + CU_ASSERT(nghttp2_nv_equal(&nv1[0], &user_data.nv)); + + /* get 2nd header field */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.header_cb_called); + + CU_ASSERT(nghttp2_nv_equal(&nv1[1], &user_data.nv)); + + /* will call end_headers_callback and receive 2nd HEADERS and pause */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + recv_frame = user_data.frame; + CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type); + CU_ASSERT(framelen2 - NGHTTP2_FRAME_HDLEN == recv_frame->hd.length); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.header_cb_called); + + CU_ASSERT(nghttp2_nv_equal(&nv2[0], &user_data.nv)); + + /* get 2nd header field */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.header_cb_called); + + CU_ASSERT(nghttp2_nv_equal(&nv2[1], &user_data.nv)); + + /* No input data, frame_recv_callback is called */ + user_data.begin_headers_cb_called = 0; + user_data.header_cb_called = 0; + user_data.frame_recv_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(rv >= 0); + databuf.pos += rv; + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == user_data.header_cb_called); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + /* Receive DATA */ + nghttp2_frame_hd_init(&data_hd, 16, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1); + + nghttp2_buf_reset(&databuf); + nghttp2_frame_pack_frame_hd(databuf.pos, &data_hd); + + /* Intentionally specify larger buffer size to see pause is kicked + in. */ + databuf.last = databuf.end; + + user_data.frame_recv_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + + CU_ASSERT(16 + NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(0 == user_data.frame_recv_cb_called); + + /* Next nghttp2_session_mem_recv invokes on_frame_recv_callback and + pause again in on_data_chunk_recv_callback since we pass same + DATA frame. */ + user_data.frame_recv_cb_called = 0; + rv = + nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf)); + CU_ASSERT(16 + NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + /* And finally call on_frame_recv_callback with 0 size input */ + user_data.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, NULL, 0); + CU_ASSERT(0 == rv); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); +} + +void test_nghttp2_session_add_frame(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + my_user_data user_data; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + + acc.length = 0; + user_data.acc = &acc; + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &user_data)); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + + nghttp2_frame_headers_init( + &frame->headers, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + (int32_t)session->next_stream_id, NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + + session->next_stream_id += 2; + + CU_ASSERT(0 == nghttp2_session_add_item(session, item)); + CU_ASSERT(NULL != nghttp2_outbound_queue_top(&session->ob_syn)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_HEADERS == acc.buf[3]); + CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY) == acc.buf[4]); + /* check stream id */ + CU_ASSERT(1 == nghttp2_get_uint32(&acc.buf[5])); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_request_headers_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + int32_t stream_id = 1; + nghttp2_nv malformed_nva[] = {MAKE_NV(":path", "\x01")}; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_priority_spec pri_spec; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + + nghttp2_priority_spec_init(&pri_spec, 0, 255, 0); + + nghttp2_frame_headers_init( + &frame.headers, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + stream_id, NGHTTP2_HCAT_REQUEST, &pri_spec, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + stream = nghttp2_session_get_stream(session, stream_id); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(255 == stream->weight); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* More than un-ACKed max concurrent streams leads REFUSED_STREAM */ + session->pending_local_max_concurrent_stream = 1; + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 3, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + nghttp2_frame_headers_free(&frame.headers, mem); + session->local_settings.max_concurrent_streams = + NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS; + + /* Stream ID less than or equal to the previously received request + HEADERS is just ignored due to race condition */ + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 3, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* Stream ID is our side and it is idle stream ID, then treat it as + connection error */ + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 2, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + /* Check malformed headers. The library accept it. */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + + nvlen = ARRLEN(malformed_nva); + nghttp2_nv_array_copy(&nva, malformed_nva, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 1, NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen); + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + /* Check client side */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + + /* Receiving peer's idle stream ID is subject to connection error */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + /* Receiving our's idle stream ID is subject to connection error */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + session->next_stream_id = 5; + session->last_sent_stream_id = 3; + + /* Stream ID which is not idle and not in stream map is just + ignored */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, &user_data); + + /* Stream ID which is equal to local_last_stream_id is ok. */ + session->local_last_stream_id = 3; + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + /* If GOAWAY has been sent, new stream is ignored */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 5, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + session->goaway_flags |= NGHTTP2_GOAWAY_SENT; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, &user_data); + + /* HEADERS to closed stream */ + stream = open_recv_stream(session, 1); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_response_headers_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_response_headers_received(session, &frame, + stream)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_headers_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENED); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + /* stream closed */ + frame.hd.flags |= NGHTTP2_FLAG_END_STREAM; + + CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream)); + CU_ASSERT(2 == user_data.begin_headers_cb_called); + + /* Check to see when NGHTTP2_STREAM_CLOSING, incoming HEADERS is + discarded. */ + stream = open_sent_stream2(session, 3, NGHTTP2_STREAM_CLOSING); + frame.hd.stream_id = 3; + frame.hd.flags = NGHTTP2_FLAG_END_HEADERS; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_headers_received(session, &frame, stream)); + /* See no counters are updated */ + CU_ASSERT(2 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + + /* Server initiated stream */ + stream = open_recv_stream(session, 2); + + frame.hd.flags = NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM; + frame.hd.stream_id = 2; + + CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream)); + CU_ASSERT(3 == user_data.begin_headers_cb_called); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + + /* Further reception of HEADERS is subject to stream error */ + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_headers_received(session, &frame, stream)); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_push_response_headers_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + /* nghttp2_session_on_push_response_headers_received assumes + stream's state is NGHTTP2_STREAM_RESERVED and session->server is + 0. */ + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(0 == nghttp2_session_on_push_response_headers_received( + session, &frame, stream)); + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == session->num_incoming_reserved_streams); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_PUSH)); + + /* If un-ACKed max concurrent streams limit is exceeded, + RST_STREAMed */ + session->pending_local_max_concurrent_stream = 1; + stream = open_recv_stream2(session, 4, NGHTTP2_STREAM_RESERVED); + frame.hd.stream_id = 4; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_response_headers_received(session, &frame, + stream)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == session->num_incoming_streams); + + /* If ACKed max concurrent streams limit is exceeded, GOAWAY is + issued */ + session->local_settings.max_concurrent_streams = 1; + + stream = open_recv_stream2(session, 6, NGHTTP2_STREAM_RESERVED); + frame.hd.stream_id = 6; + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_response_headers_received(session, &frame, + stream)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_priority_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream, *dep_stream; + nghttp2_priority_spec pri_spec; + nghttp2_outbound_item *item; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + stream = open_recv_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 0, 2, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + /* depend on stream 0 */ + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + CU_ASSERT(2 == stream->weight); + + stream = open_sent_stream(session, 2); + dep_stream = open_recv_stream(session, 3); + + frame.hd.stream_id = 2; + + /* using dependency stream */ + nghttp2_priority_spec_init(&frame.priority.pri_spec, 3, 1, 0); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + CU_ASSERT(dep_stream == stream->dep_prev); + + /* PRIORITY against idle stream */ + + frame.hd.stream_id = 100; + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + stream = nghttp2_session_get_stream_raw(session, frame.hd.stream_id); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(dep_stream == stream->dep_prev); + + nghttp2_frame_priority_free(&frame.priority); + nghttp2_session_del(session); + + /* Check dep_stream_id == stream_id case */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + open_recv_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 1, 0, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_priority_free(&frame.priority); + nghttp2_session_del(session); + + /* Check again dep_stream_id == stream_id, and stream_id is idle */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + + nghttp2_priority_spec_init(&pri_spec, 1, 16, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_priority_free(&frame.priority); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_rst_stream_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, &user_data); + open_recv_stream(session, 1); + + nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_PROTOCOL_ERROR); + + CU_ASSERT(0 == nghttp2_session_on_rst_stream_received(session, &frame)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1)); + + nghttp2_frame_rst_stream_free(&frame.rst_stream); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_settings_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_stream *stream1, *stream2; + nghttp2_frame frame; + const size_t niv = 5; + nghttp2_settings_entry iv[255]; + nghttp2_outbound_item *item; + nghttp2_nv nv = MAKE_NV(":authority", "example.org"); + nghttp2_mem *mem; + nghttp2_option *option; + uint8_t data[2048]; + nghttp2_frame_hd hd; + int rv; + ssize_t nread; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 50; + + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[1].value = 1000000009; + + iv[2].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[2].value = 64 * 1024; + + iv[3].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[3].value = 1024; + + iv[4].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[4].value = 0; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + session->remote_settings.initial_window_size = 16 * 1024; + + stream1 = open_sent_stream(session, 1); + stream2 = open_recv_stream(session, 2); + + /* Set window size for each streams and will see how settings + updates these values */ + stream1->remote_window_size = 16 * 1024; + stream2->remote_window_size = -48 * 1024; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(iv, niv), niv); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + CU_ASSERT(1000000009 == session->remote_settings.max_concurrent_streams); + CU_ASSERT(64 * 1024 == session->remote_settings.initial_window_size); + CU_ASSERT(1024 == session->remote_settings.header_table_size); + CU_ASSERT(0 == session->remote_settings.enable_push); + + CU_ASSERT(64 * 1024 == stream1->remote_window_size); + CU_ASSERT(0 == stream2->remote_window_size); + + frame.settings.iv[2].value = 16 * 1024; + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + CU_ASSERT(16 * 1024 == stream1->remote_window_size); + CU_ASSERT(-48 * 1024 == stream2->remote_window_size); + + CU_ASSERT(16 * 1024 == nghttp2_session_get_stream_remote_window_size( + session, stream1->stream_id)); + CU_ASSERT(0 == nghttp2_session_get_stream_remote_window_size( + session, stream2->stream_id)); + + nghttp2_frame_settings_free(&frame.settings, mem); + + nghttp2_session_del(session); + + /* Check ACK with niv > 0 */ + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, dup_iv(iv, 1), + 1); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + + /* Check ACK against no inflight SETTINGS */ + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + + /* Check that 2 SETTINGS_HEADER_TABLE_SIZE 0 and 4096 are included + and header table size is once cleared to 0. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL); + + nghttp2_session_send(session); + + CU_ASSERT(session->hd_deflater.ctx.hd_table.len > 0); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 0; + + iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[1].value = 2048; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2), + 2); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len); + CU_ASSERT(2048 == session->hd_deflater.ctx.hd_table_bufsize_max); + CU_ASSERT(2048 == session->remote_settings.header_table_size); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + + /* Check that remote SETTINGS_MAX_CONCURRENT_STREAMS is set to a value set by + nghttp2_option_set_peer_max_concurrent_streams() and reset to the default + value (unlimited) after receiving initial SETTINGS frame from the peer. */ + nghttp2_option_new(&option); + nghttp2_option_set_peer_max_concurrent_streams(option, 1000); + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + CU_ASSERT(1000 == session->remote_settings.max_concurrent_streams); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS == + session->remote_settings.max_concurrent_streams); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Check too large SETTINGS_MAX_FRAME_SIZE */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1), + 1); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_frame_settings_free(&frame.settings, mem); + nghttp2_session_del(session); + + /* Check the case where stream window size overflows */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream1 = open_recv_stream(session, 1); + + /* This will increment window size by 1 */ + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, + 1); + + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + + nghttp2_frame_window_update_free(&frame.window_update); + + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = NGHTTP2_MAX_WINDOW_SIZE; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1), + 1); + + /* Now window size gets NGHTTP2_MAX_WINDOW_SIZE + 1, which is + unacceptable situation in protocol spec. */ + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + nghttp2_frame_settings_free(&frame.settings, mem); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream1->state); + + nghttp2_session_del(session); + + /* It is invalid that peer disables ENABLE_CONNECT_PROTOCOL once it + has been enabled. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + session->remote_settings.enable_connect_protocol = 1; + + iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL; + iv[0].value = 0; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1), + 1); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + nghttp2_frame_settings_free(&frame.settings, mem); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); + + /* Should send WINDOW_UPDATE with no_auto_window_update option on if + the initial window size is decreased and becomes smaller than or + equal to the amount of data that has already received. */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = 1024; + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = open_recv_stream(session, 1); + + memset(data, 0, sizeof(data)); + hd.length = 1024; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_NONE; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + + nread = + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length); + + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) == nread); + + rv = nghttp2_session_consume(session, 1, hd.length); + + CU_ASSERT(0 == rv); + CU_ASSERT((int32_t)hd.length == stream->recv_window_size); + CU_ASSERT((int32_t)hd.length == stream->consumed_size); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + rv = nghttp2_session_on_settings_received(session, &frame, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(1024 == stream->local_window_size); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == stream->consumed_size); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT((int32_t)hd.length == + item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* It is invalid to change SETTINGS_NO_RFC7540_PRIORITIES in the + following SETTINGS. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + session->remote_settings.no_rfc7540_priorities = 1; + + iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[0].value = 0; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1), + 1); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + nghttp2_frame_settings_free(&frame.settings, mem); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_push_promise_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream, *promised_stream; + nghttp2_outbound_item *item; + nghttp2_nv malformed_nva[] = {MAKE_NV(":path", "\x01")}; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_mem *mem; + nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_begin_headers_callback = on_begin_headers_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + stream = open_sent_stream(session, 1); + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + promised_stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NGHTTP2_STREAM_RESERVED == promised_stream->state); + CU_ASSERT(2 == session->last_recv_stream_id); + + /* Attempt to PUSH_PROMISE against half close (remote) */ + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + frame.push_promise.promised_stream_id = 4; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_STREAM_CLOSED == item->frame.goaway.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(4 == session->last_recv_stream_id); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + stream = open_sent_stream(session, 1); + + /* Attempt to PUSH_PROMISE against stream in closing state */ + stream->state = NGHTTP2_STREAM_CLOSING; + frame.push_promise.promised_stream_id = 6; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(6 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Attempt to PUSH_PROMISE against idle stream */ + frame.hd.stream_id = 3; + frame.push_promise.promised_stream_id = 8; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(0 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + stream = open_sent_stream(session, 1); + + /* Same ID twice */ + frame.hd.stream_id = 1; + frame.push_promise.promised_stream_id = 2; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 2)); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* After GOAWAY, PUSH_PROMISE will be discarded */ + frame.push_promise.promised_stream_id = 10; + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 10)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + /* Attempt to PUSH_PROMISE against reserved (remote) stream */ + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 2, 4, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + /* Disable PUSH */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + + open_sent_stream(session, 1); + + session->local_settings.enable_push = 0; + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, NULL, 0); + + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == user_data.begin_headers_cb_called); + CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + CU_ASSERT(0 == session->num_incoming_reserved_streams); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + /* Check malformed headers. We accept malformed headers */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + + open_sent_stream(session, 1); + + nvlen = ARRLEN(malformed_nva); + nghttp2_nv_array_copy(&nva, malformed_nva, nvlen, mem); + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, nva, nvlen); + user_data.begin_headers_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(1 == user_data.begin_headers_cb_called); + CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + /* If local_settings.enable_push = 0 is pending, but not acked from + peer, incoming PUSH_PROMISE is rejected */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + + open_sent_stream(session, 1); + + /* Submit settings with ENABLE_PUSH = 0 (thus disabling push) */ + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 2, NULL, 0); + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(0 == session->num_incoming_reserved_streams); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); + + /* Check max_incoming_reserved_streams */ + nghttp2_session_client_new(&session, &callbacks, &user_data); + session->max_incoming_reserved_streams = 1; + + open_sent_stream(session, 1); + open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS, + 1, 4, NULL, 0); + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_push_promise_received(session, &frame)); + + CU_ASSERT(1 == session->num_incoming_reserved_streams); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_ping_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_outbound_item *top; + const uint8_t opaque_data[] = "01234567"; + nghttp2_option *option; + + user_data.frame_recv_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_ACK, opaque_data); + + CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + /* Since this ping frame has ACK flag set, no further action is + performed. */ + CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_urgent)); + + /* Clear the flag, and receive it again */ + frame.hd.flags = NGHTTP2_FLAG_NONE; + + CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame)); + CU_ASSERT(2 == user_data.frame_recv_cb_called); + top = nghttp2_outbound_queue_top(&session->ob_urgent); + CU_ASSERT(NGHTTP2_PING == top->frame.hd.type); + CU_ASSERT(NGHTTP2_FLAG_ACK == top->frame.hd.flags); + CU_ASSERT(memcmp(opaque_data, top->frame.ping.opaque_data, 8) == 0); + + nghttp2_frame_ping_free(&frame.ping); + nghttp2_session_del(session); + + /* Use nghttp2_option_set_no_auto_ping_ack() */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_ping_ack(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, &user_data, option); + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + + user_data.frame_recv_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_urgent)); + + nghttp2_frame_ping_free(&frame.ping); + nghttp2_session_del(session); + nghttp2_option_del(option); +} + +void test_nghttp2_session_on_goaway_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + int i; + nghttp2_mem *mem; + const uint8_t *data; + ssize_t datalen; + + mem = nghttp2_mem_default(); + user_data.frame_recv_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + callbacks.on_stream_close_callback = on_stream_close_callback; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + for (i = 1; i <= 7; ++i) { + if (nghttp2_session_is_my_stream_id(session, i)) { + open_sent_stream(session, i); + } else { + open_recv_stream(session, i); + } + } + + nghttp2_frame_goaway_init(&frame.goaway, 3, NGHTTP2_PROTOCOL_ERROR, NULL, 0); + + user_data.stream_close_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_on_goaway_received(session, &frame)); + + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(3 == session->remote_last_stream_id); + /* on_stream_close should be callsed for 2 times (stream 5 and 7) */ + CU_ASSERT(2 == user_data.stream_close_cb_called); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 1)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 2)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 3)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 4)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 5)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 6)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 7)); + + nghttp2_frame_goaway_free(&frame.goaway, mem); + nghttp2_session_del(session); + + /* Make sure that no memory leak when stream_close callback fails + with a fatal error */ + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_stream_close_callback = fatal_error_on_stream_close_callback; + + memset(&user_data, 0, sizeof(user_data)); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + nghttp2_frame_goaway_init(&frame.goaway, 0, NGHTTP2_NO_ERROR, NULL, 0); + + CU_ASSERT(0 == nghttp2_session_on_goaway_received(session, &frame)); + + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + datalen = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == datalen); + CU_ASSERT(1 == user_data.stream_close_cb_called); + + nghttp2_frame_goaway_free(&frame.goaway, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_window_update_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + nghttp2_stream *stream; + nghttp2_outbound_item *data_item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + user_data.frame_recv_cb_called = 0; + user_data.invalid_frame_recv_cb_called = 0; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + stream = open_sent_stream(session, 1); + + data_item = create_data_ob_item(mem); + + CU_ASSERT(0 == nghttp2_stream_attach_item(stream, data_item)); + + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, + 16 * 1024); + + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + CU_ASSERT(1 == user_data.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 16 * 1024 == + stream->remote_window_size); + + nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL); + + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + CU_ASSERT(2 == user_data.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 16 * 1024 * 2 == + stream->remote_window_size); + CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL)); + + nghttp2_frame_window_update_free(&frame.window_update); + + /* Receiving WINDOW_UPDATE on reserved (remote) stream is a + connection error */ + open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 2, + 4096); + + CU_ASSERT(!(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND); + + nghttp2_frame_window_update_free(&frame.window_update); + + nghttp2_session_del(session); + + /* Receiving WINDOW_UPDATE on reserved (local) stream is allowed */ + nghttp2_session_server_new(&session, &callbacks, &user_data); + + stream = open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 2, + 4096); + + CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame)); + CU_ASSERT(!(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND)); + + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 4096 == stream->remote_window_size); + + nghttp2_frame_window_update_free(&frame.window_update); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_data_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_outbound_item *top; + nghttp2_stream *stream; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = open_recv_stream(session, 2); + + nghttp2_frame_hd_init(&frame.hd, 4096, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 2); + + CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame)); + CU_ASSERT(0 == stream->shut_flags); + + frame.hd.flags = NGHTTP2_FLAG_END_STREAM; + + CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame)); + CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags); + + /* If NGHTTP2_STREAM_CLOSING state, DATA frame is discarded. */ + open_sent_stream2(session, 1, NGHTTP2_STREAM_CLOSING); + + frame.hd.flags = NGHTTP2_FLAG_NONE; + frame.hd.stream_id = 1; + + CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame)); + CU_ASSERT(NULL == nghttp2_outbound_queue_top(&session->ob_reg)); + + /* Check INVALID_STREAM case: DATA frame with stream ID which does + not exist. */ + + frame.hd.stream_id = 3; + + CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame)); + top = nghttp2_outbound_queue_top(&session->ob_reg); + /* DATA against nonexistent stream is just ignored for now. */ + CU_ASSERT(top == NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_data_received_fail_fast(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t buf[9]; + nghttp2_stream *stream; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1); + nghttp2_frame_pack_frame_hd(buf, &hd); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* DATA to closed (remote) */ + stream = open_recv_stream(session, 1); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + + CU_ASSERT((ssize_t)sizeof(buf) == + nghttp2_session_mem_recv(session, buf, sizeof(buf))); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* DATA to closed stream with explicit closed (remote) */ + stream = open_recv_stream(session, 1); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + + CU_ASSERT((ssize_t)sizeof(buf) == + nghttp2_session_mem_recv(session, buf, sizeof(buf))); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_altsvc_received(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_frame frame; + nghttp2_option *option; + uint8_t origin[] = "nghttp2.org"; + uint8_t field_value[] = "h2=\":443\""; + int rv; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + /* We just pass the strings without making a copy. This is OK, + since we never call nghttp2_frame_altsvc_free(). */ + nghttp2_frame_altsvc_init(&frame.ext, 0, origin, sizeof(origin) - 1, + field_value, sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Receiving empty origin with stream ID == 0 */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + nghttp2_frame_altsvc_init(&frame.ext, 0, origin, 0, field_value, + sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Receiving non-empty origin with stream ID != 0 */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + open_sent_stream(session, 1); + + nghttp2_frame_altsvc_init(&frame.ext, 1, origin, sizeof(origin) - 1, + field_value, sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Receiving empty origin with stream ID != 0; this is OK */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + open_sent_stream(session, 1); + + nghttp2_frame_altsvc_init(&frame.ext, 1, origin, 0, field_value, + sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + /* Stream does not exist; ALTSVC will be ignored. */ + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + frame.ext.payload = &session->iframe.ext_frame_payload; + + nghttp2_frame_altsvc_init(&frame.ext, 1, origin, 0, field_value, + sizeof(field_value) - 1); + + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_on_altsvc_received(session, &frame); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + nghttp2_session_del(session); + + nghttp2_option_del(option); +} + +void test_nghttp2_session_send_headers_start_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, + (int32_t)session->next_stream_id, + NGHTTP2_HCAT_REQUEST, NULL, NULL, 0); + session->next_stream_id += 2; + + nghttp2_session_add_item(session, item); + CU_ASSERT(0 == nghttp2_session_send(session)); + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_headers_reply(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL)); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 1, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + nghttp2_session_add_item(session, item); + CU_ASSERT(0 == nghttp2_session_send(session)); + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_headers_frame_size_error(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_nv *nva; + size_t nvlen; + size_t vallen = NGHTTP2_HD_MAX_NV; + nghttp2_nv nv[28]; + size_t nnv = ARRLEN(nv); + size_t i; + my_user_data ud; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + for (i = 0; i < nnv; ++i) { + nv[i].name = (uint8_t *)"header"; + nv[i].namelen = strlen((const char *)nv[i].name); + nv[i].value = mem->malloc(vallen + 1, NULL); + memset(nv[i].value, '0' + (int)i, vallen); + nv[i].value[vallen] = '\0'; + nv[i].valuelen = vallen; + nv[i].flags = NGHTTP2_NV_FLAG_NONE; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + nvlen = nnv; + nghttp2_nv_array_copy(&nva, nv, nvlen, mem); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, + (int32_t)session->next_stream_id, + NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen); + + session->next_stream_id += 2; + + nghttp2_session_add_item(session, item); + + ud.frame_not_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == ud.not_sent_error); + + for (i = 0; i < nnv; ++i) { + mem->free(nv[i].value, NULL); + } + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_headers_push_reply(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL)); + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + nghttp2_session_add_item(session, item); + CU_ASSERT(0 == session->num_outgoing_streams); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == session->num_outgoing_streams); + stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_PUSH)); + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_rst_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + nghttp2_session_client_new(&session, &callbacks, &user_data); + open_sent_stream(session, 1); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_rst_stream_init(&frame->rst_stream, 1, NGHTTP2_PROTOCOL_ERROR); + nghttp2_session_add_item(session, item); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_push_promise(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_stream *stream; + nghttp2_settings_entry iv; + my_user_data ud; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + open_recv_stream(session, 1); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_push_promise_init(&frame->push_promise, + NGHTTP2_FLAG_END_HEADERS, 1, + (int32_t)session->next_stream_id, NULL, 0); + + session->next_stream_id += 2; + + nghttp2_session_add_item(session, item); + + CU_ASSERT(0 == nghttp2_session_send(session)); + stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); + + /* Received ENABLE_PUSH = 0 */ + iv.settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv.value = 0; + frame = mem->malloc(sizeof(nghttp2_frame), NULL); + nghttp2_frame_settings_init(&frame->settings, NGHTTP2_FLAG_NONE, + dup_iv(&iv, 1), 1); + nghttp2_session_on_settings_received(session, frame, 1); + nghttp2_frame_settings_free(&frame->settings, mem); + mem->free(frame, NULL); + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_push_promise_init(&frame->push_promise, + NGHTTP2_FLAG_END_HEADERS, 1, -1, NULL, 0); + nghttp2_session_add_item(session, item); + + ud.frame_not_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_PUSH_DISABLED == ud.not_sent_error); + + nghttp2_session_del(session); + + /* PUSH_PROMISE from client is error */ + nghttp2_session_client_new(&session, &callbacks, &ud); + open_sent_stream(session, 1); + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + + nghttp2_outbound_item_init(item); + + frame = &item->frame; + + nghttp2_frame_push_promise_init(&frame->push_promise, + NGHTTP2_FLAG_END_HEADERS, 1, -1, NULL, 0); + nghttp2_session_add_item(session, item); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 3)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_is_my_stream_id(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, NULL); + + CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 0)); + CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 1)); + CU_ASSERT(1 == nghttp2_session_is_my_stream_id(session, 2)); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, NULL); + + CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 0)); + CU_ASSERT(1 == nghttp2_session_is_my_stream_id(session, 1)); + CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 2)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_upgrade2(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t settings_payload[128]; + size_t settings_payloadlen; + nghttp2_settings_entry iv[16]; + nghttp2_stream *stream; + nghttp2_outbound_item *item; + ssize_t rv; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 1; + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 4095; + settings_payloadlen = (size_t)nghttp2_pack_settings_payload( + settings_payload, sizeof(settings_payload), iv, 2); + + /* Check client side */ + nghttp2_session_client_new(&session, &callbacks, NULL); + CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + CU_ASSERT(1 == session->last_sent_stream_id); + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(stream != NULL); + CU_ASSERT(&callbacks == stream->stream_user_data); + CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type); + CU_ASSERT(2 == item->frame.settings.niv); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + item->frame.settings.iv[0].settings_id); + CU_ASSERT(1 == item->frame.settings.iv[0].value); + CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == + item->frame.settings.iv[1].settings_id); + CU_ASSERT(4095 == item->frame.settings.iv[1].value); + + /* Call nghttp2_session_upgrade2() again is error */ + CU_ASSERT(NGHTTP2_ERR_PROTO == + nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + nghttp2_session_del(session); + + /* Make sure that response from server can be received */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + + nghttp2_hd_deflate_init(&deflater, mem); + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + + CU_ASSERT(0 == rv); + + buf = &bufs.head->buf; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT(rv == (ssize_t)nghttp2_buf_len(buf)); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* Check server side */ + nghttp2_session_server_new(&session, &callbacks, NULL); + CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + CU_ASSERT(1 == session->last_recv_stream_id); + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(stream != NULL); + CU_ASSERT(NULL == stream->stream_user_data); + CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(1 == session->remote_settings.max_concurrent_streams); + CU_ASSERT(4095 == session->remote_settings.initial_window_size); + /* Call nghttp2_session_upgrade2() again is error */ + CU_ASSERT(NGHTTP2_ERR_PROTO == + nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, &callbacks)); + nghttp2_session_del(session); + + /* Empty SETTINGS is OK */ + settings_payloadlen = (size_t)nghttp2_pack_settings_payload( + settings_payload, sizeof(settings_payload), NULL, 0); + + nghttp2_session_client_new(&session, &callbacks, NULL); + CU_ASSERT(0 == nghttp2_session_upgrade2(session, settings_payload, + settings_payloadlen, 0, NULL)); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_reprioritize_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_stream *dep_stream; + nghttp2_priority_spec pri_spec; + int rv; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream = open_recv_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 0, 10, 0); + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(10 == stream->weight); + CU_ASSERT(&session->root == stream->dep_prev); + + /* If dependency to idle stream which is not in dependency tree yet */ + + nghttp2_priority_spec_init(&pri_spec, 3, 99, 0); + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(99 == stream->weight); + CU_ASSERT(3 == stream->dep_prev->stream_id); + + dep_stream = nghttp2_session_get_stream_raw(session, 3); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == dep_stream->weight); + + dep_stream = open_recv_stream(session, 3); + + /* Change weight */ + pri_spec.weight = 128; + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(128 == stream->weight); + CU_ASSERT(dep_stream == stream->dep_prev); + + /* Change weight again to test short-path case */ + pri_spec.weight = 100; + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(100 == stream->weight); + CU_ASSERT(dep_stream == stream->dep_prev); + CU_ASSERT(100 == dep_stream->sum_dep_weight); + + /* Test circular dependency; stream 1 is first removed and becomes + root. Then stream 3 depends on it. */ + nghttp2_priority_spec_init(&pri_spec, 1, 1, 0); + + rv = nghttp2_session_reprioritize_stream(session, dep_stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == dep_stream->weight); + CU_ASSERT(stream == dep_stream->dep_prev); + + /* Making priority to closed stream will result in default + priority */ + session->last_recv_stream_id = 9; + + nghttp2_priority_spec_init(&pri_spec, 5, 5, 0); + + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* circular dependency; in case of stream which is not a direct + descendant of root. Use exclusive dependency. */ + stream = open_recv_stream(session, 1); + stream = open_recv_stream_with_dep(session, 3, stream); + stream = open_recv_stream_with_dep(session, 5, stream); + stream = open_recv_stream_with_dep(session, 7, stream); + open_recv_stream_with_dep(session, 9, stream); + + nghttp2_priority_spec_init(&pri_spec, 7, 1, 1); + + stream = nghttp2_session_get_stream(session, 3); + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(7 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 7); + + CU_ASSERT(1 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 9); + + CU_ASSERT(3 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 5); + + CU_ASSERT(3 == stream->dep_prev->stream_id); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* circular dependency; in case of stream which is not a direct + descendant of root. Without exclusive dependency. */ + stream = open_recv_stream(session, 1); + stream = open_recv_stream_with_dep(session, 3, stream); + stream = open_recv_stream_with_dep(session, 5, stream); + stream = open_recv_stream_with_dep(session, 7, stream); + open_recv_stream_with_dep(session, 9, stream); + + nghttp2_priority_spec_init(&pri_spec, 7, 1, 0); + + stream = nghttp2_session_get_stream(session, 3); + rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + CU_ASSERT(0 == rv); + CU_ASSERT(7 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 7); + + CU_ASSERT(1 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 9); + + CU_ASSERT(7 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream(session, 5); + + CU_ASSERT(3 == stream->dep_prev->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream = open_recv_stream(session, 1); + + session->pending_local_max_concurrent_stream = 1; + + nghttp2_priority_spec_init(&pri_spec, 101, 10, 0); + + nghttp2_session_reprioritize_stream(session, stream, &pri_spec); + + /* idle stream is not counteed to max concurrent streams */ + + CU_ASSERT(10 == stream->weight); + CU_ASSERT(101 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream_raw(session, 101); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_frame *frame; + nghttp2_frame_hd hd; + nghttp2_active_outbound_item *aob; + nghttp2_bufs *framebufs; + nghttp2_buf *buf; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + aob = &session->aob; + framebufs = &aob->framebufs; + + open_sent_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.block_count = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + frame = &aob->item->frame; + + buf = &framebufs->head->buf; + nghttp2_frame_unpack_frame_hd(&hd, buf->pos); + + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags); + /* aux_data.data.flags has these flags */ + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_data_read_length_too_large(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_frame *frame; + nghttp2_frame_hd hd; + nghttp2_active_outbound_item *aob; + nghttp2_bufs *framebufs; + nghttp2_buf *buf; + size_t payloadlen; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.read_length_callback = too_large_data_source_length_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + aob = &session->aob; + framebufs = &aob->framebufs; + + open_sent_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.block_count = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + frame = &aob->item->frame; + + buf = &framebufs->head->buf; + nghttp2_frame_unpack_frame_hd(&hd, buf->pos); + + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags); + CU_ASSERT(16384 == hd.length) + /* aux_data.data.flags has these flags */ + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags); + + nghttp2_session_del(session); + + /* Check that buffers are expanded */ + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + + ud.data_source_length = NGHTTP2_MAX_FRAME_SIZE_MAX; + + session->remote_settings.max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MAX; + + open_sent_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.block_count = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + + aob = &session->aob; + + frame = &aob->item->frame; + + framebufs = &aob->framebufs; + + buf = &framebufs->head->buf; + nghttp2_frame_unpack_frame_hd(&hd, buf->pos); + + payloadlen = nghttp2_min(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE, + NGHTTP2_INITIAL_WINDOW_SIZE); + + CU_ASSERT(NGHTTP2_FRAME_HDLEN + 1 + payloadlen == + (size_t)nghttp2_buf_cap(buf)); + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags); + CU_ASSERT(payloadlen == hd.length); + /* aux_data.data.flags has these flags */ + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_data_read_length_smallest(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_frame *frame; + nghttp2_frame_hd hd; + nghttp2_active_outbound_item *aob; + nghttp2_bufs *framebufs; + nghttp2_buf *buf; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.read_length_callback = smallest_length_data_source_length_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + aob = &session->aob; + framebufs = &aob->framebufs; + + open_sent_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.block_count = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + frame = &aob->item->frame; + + buf = &framebufs->head->buf; + nghttp2_frame_unpack_frame_hd(&hd, buf->pos); + + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags); + CU_ASSERT(1 == hd.length) + /* aux_data.data.flags has these flags */ + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags); + + nghttp2_session_del(session); +} + +static ssize_t submit_data_twice_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + (void)session; + (void)stream_id; + (void)buf; + (void)source; + (void)user_data; + + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return (ssize_t)nghttp2_min(len, 16); +} + +static int submit_data_twice_on_frame_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) { + static int called = 0; + int rv; + nghttp2_data_provider data_prd; + (void)user_data; + + if (called == 0) { + called = 1; + + data_prd.read_callback = submit_data_twice_data_source_read_callback; + + rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, + frame->hd.stream_id, &data_prd); + CU_ASSERT(0 == rv); + } + + return 0; +} + +void test_nghttp2_submit_data_twice(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + accumulator acc; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + callbacks.on_frame_send_callback = submit_data_twice_on_frame_send_callback; + + data_prd.read_callback = submit_data_twice_data_source_read_callback; + + acc.length = 0; + ud.acc = &acc; + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + + open_sent_stream(session, 1); + + CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 1, &data_prd)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* We should have sent 2 DATA frame with 16 bytes payload each */ + CU_ASSERT(NGHTTP2_FRAME_HDLEN * 2 + 16 * 2 == acc.length); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_request_with_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = 64 * 1024 - 1; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + &data_prd, NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.data_source_length); + + nghttp2_session_del(session); + + /* nghttp2_submit_request() with server session is error */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + CU_ASSERT(NGHTTP2_ERR_PROTO == nghttp2_submit_request(session, NULL, reqnv, + ARRLEN(reqnv), NULL, + NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_request_without_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + nghttp2_data_provider data_prd = {{-1}, NULL}; + nghttp2_outbound_item *item; + my_user_data ud; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + nghttp2_priority_spec pri_spec; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + &data_prd, NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(reqnv) == out.nvlen); + assert_nv_equal(reqnv, out.nva, out.nvlen, mem); + nghttp2_frame_headers_free(&frame.headers, mem); + nva_out_reset(&out, mem); + + nghttp2_bufs_free(&bufs); + nghttp2_hd_inflate_free(&inflater); + + /* Try to depend on itself is error */ + nghttp2_priority_spec_init(&pri_spec, (int32_t)session->next_stream_id, 16, + 0); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_request(session, &pri_spec, reqnv, ARRLEN(reqnv), + NULL, NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_response_with_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = 64 * 1024 - 1; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.data_source_length); + + nghttp2_session_del(session); + + /* Various error cases */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + /* Calling nghttp2_submit_response() with client session is error */ + CU_ASSERT(NGHTTP2_ERR_PROTO == + nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), NULL)); + + /* Stream ID <= 0 is error */ + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_response(session, 0, resnv, ARRLEN(resnv), NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_response_without_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + nghttp2_data_provider data_prd = {{-1}, NULL}; + nghttp2_outbound_item *item; + my_user_data ud; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(resnv) == out.nvlen); + assert_nv_equal(resnv, out.nva, out.nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_session_del(session); +} + +void test_nghttp2_submit_response_push_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + session->goaway_flags |= NGHTTP2_GOAWAY_RECV; + + CU_ASSERT(0 == + nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL)); + + ud.frame_not_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_not_send_cb_called); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_trailer(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + accumulator acc; + nghttp2_data_provider data_prd; + nghttp2_outbound_item *item; + my_user_data ud; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + data_prd.read_callback = no_end_stream_data_source_read_callback; + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(0 == + nghttp2_submit_trailer(session, 1, trailernv, ARRLEN(trailernv))); + + session->callbacks.send_callback = accumulator_send_callback; + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(NGHTTP2_HCAT_HEADERS == item->frame.headers.cat); + CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(trailernv) == out.nvlen); + assert_nv_equal(trailernv, out.nva, out.nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_hd_inflate_free(&inflater); + nghttp2_session_del(session); + + /* Specifying stream ID <= 0 is error */ + nghttp2_session_server_new(&session, &callbacks, NULL); + open_recv_stream(session, 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_trailer(session, 0, trailernv, ARRLEN(trailernv))); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_trailer(session, -1, trailernv, ARRLEN(trailernv))); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_start_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, reqnv, ARRLEN(reqnv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM) == + item->frame.hd.flags); + CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_reply(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, resnv, ARRLEN(resnv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + /* The transimission will be canceled because the stream 1 is not + open. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + + stream = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, resnv, ARRLEN(resnv), NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_push_reply(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_stream *stream; + int foo; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + stream = open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, + resnv, ARRLEN(resnv), &foo)); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + CU_ASSERT(&foo == stream->stream_user_data); + + nghttp2_session_del(session); + + /* Sending HEADERS from client against stream in reserved state is + error */ + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, + reqnv, ARRLEN(reqnv), NULL)); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + accumulator acc; + nghttp2_frame frame; + nghttp2_hd_inflater inflater; + nva_out out; + nghttp2_bufs bufs; + nghttp2_mem *mem; + nghttp2_priority_spec pri_spec; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + nva_out_init(&out); + acc.length = 0; + ud.acc = &acc; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + + nghttp2_hd_inflate_init(&inflater, mem); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, reqnv, ARRLEN(reqnv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(reqnv) == item->frame.headers.nvlen); + assert_nv_equal(reqnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + /* The transimission will be canceled because the stream 1 is not + open. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + + stream = open_sent_stream(session, 1); + + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, reqnv, ARRLEN(reqnv), NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR); + + CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length)); + + nghttp2_bufs_add(&bufs, acc.buf, acc.length); + inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN, mem); + + CU_ASSERT(ARRLEN(reqnv) == out.nvlen); + assert_nv_equal(reqnv, out.nva, out.nvlen, mem); + + nva_out_reset(&out, mem); + nghttp2_bufs_free(&bufs); + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_hd_inflate_free(&inflater); + + /* Try to depend on itself */ + nghttp2_priority_spec_init(&pri_spec, 3, 16, 0); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, &pri_spec, + reqnv, ARRLEN(reqnv), NULL)); + + session->next_stream_id = 5; + nghttp2_priority_spec_init(&pri_spec, 5, 16, 0); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, &pri_spec, + reqnv, ARRLEN(reqnv), NULL)); + + nghttp2_session_del(session); + + /* Error cases with invalid stream ID */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* Sending nghttp2_submit_headers() with stream_id == 1 and server + session is error */ + CU_ASSERT(NGHTTP2_ERR_PROTO == + nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, reqnv, + ARRLEN(reqnv), NULL)); + + /* Sending stream ID <= 0 is error */ + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 0, NULL, resnv, + ARRLEN(resnv), NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_continuation(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv nv[] = { + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + MAKE_NV("h1", ""), + }; + nghttp2_outbound_item *item; + uint8_t data[4096]; + size_t i; + my_user_data ud; + + memset(data, '0', sizeof(data)); + for (i = 0; i < ARRLEN(nv); ++i) { + nv[i].valuelen = sizeof(data); + nv[i].value = data; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud)); + CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, nv, ARRLEN(nv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY)); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_headers_continuation_extra_large(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv nv[] = { + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""), + }; + nghttp2_outbound_item *item; + uint8_t data[16384]; + size_t i; + my_user_data ud; + nghttp2_option *opt; + + memset(data, '0', sizeof(data)); + for (i = 0; i < ARRLEN(nv); ++i) { + nv[i].valuelen = sizeof(data); + nv[i].value = data; + } + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + /* The default size of max send header block length is too small to + send these header fields. Expand it. */ + nghttp2_option_new(&opt); + nghttp2_option_set_max_send_header_block_length(opt, 102400); + + CU_ASSERT(0 == nghttp2_session_client_new2(&session, &callbacks, &ud, opt)); + CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, nv, ARRLEN(nv), NULL)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) == + item->frame.hd.flags); + CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY)); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_session_del(session); + nghttp2_option_del(opt); +} + +void test_nghttp2_submit_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + my_user_data ud; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + stream = open_sent_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 0, 3, 0); + + /* depends on stream 0 */ + CU_ASSERT(0 == + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(3 == stream->weight); + + /* submit against idle stream */ + CU_ASSERT(0 == + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 3, &pri_spec)); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_settings(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_frame *frame; + nghttp2_settings_entry iv[7]; + nghttp2_frame ack_frame; + const int32_t UNKNOWN_ID = 1000000007; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 5; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16 * 1024; + + iv[2].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[2].value = 50; + + iv[3].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[3].value = 111; + + iv[4].settings_id = UNKNOWN_ID; + iv[4].value = 999; + + iv[5].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[5].value = 1023; + + iv[6].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[6].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + nghttp2_session_server_new(&session, &callbacks, &ud); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 7)); + + /* Make sure that local settings are not changed */ + CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS == + session->local_settings.max_concurrent_streams); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + session->local_settings.initial_window_size); + + /* Now sends without 6th one */ + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 6)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type); + + frame = &item->frame; + CU_ASSERT(6 == frame->settings.niv); + CU_ASSERT(5 == frame->settings.iv[0].value); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + frame->settings.iv[0].settings_id); + + CU_ASSERT(16 * 1024 == frame->settings.iv[1].value); + CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == + frame->settings.iv[1].settings_id); + + CU_ASSERT(UNKNOWN_ID == frame->settings.iv[4].settings_id); + CU_ASSERT(999 == frame->settings.iv[4].value); + + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + + CU_ASSERT(50 == session->pending_local_max_concurrent_stream); + + /* before receiving SETTINGS ACK, local settings have still default + values */ + CU_ASSERT(NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS == + nghttp2_session_get_local_settings( + session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + nghttp2_session_get_local_settings( + session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); + + nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + nghttp2_frame_settings_free(&ack_frame.settings, mem); + + CU_ASSERT(16 * 1024 == session->local_settings.initial_window_size); + CU_ASSERT(111 == session->hd_inflater.ctx.hd_table_bufsize_max); + CU_ASSERT(111 == session->hd_inflater.min_hd_table_bufsize_max); + CU_ASSERT(50 == session->local_settings.max_concurrent_streams); + + CU_ASSERT(50 == nghttp2_session_get_local_settings( + session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); + CU_ASSERT(16 * 1024 == nghttp2_session_get_local_settings( + session, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); + + /* We just keep the last seen value */ + CU_ASSERT(50 == session->pending_local_max_concurrent_stream); + + nghttp2_session_del(session); + + /* Bail out if there are contradicting + SETTINGS_NO_RFC7540_PRIORITIES in one SETTINGS. */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[0].value = 1; + iv[1].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[1].value = 0; + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2)); + + nghttp2_session_del(session); + + /* Attempt to change SETTINGS_NO_RFC7540_PRIORITIES in the 2nd + SETTINGS. */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[0].value = 1; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + + iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv[0].value = 0; + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_settings_update_local_window_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_settings_entry iv[4]; + nghttp2_stream *stream; + nghttp2_frame ack_frame; + nghttp2_mem *mem; + nghttp2_option *option; + + mem = nghttp2_mem_default(); + nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = 16 * 1024; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream = open_recv_stream(session, 1); + stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100; + stream->recv_window_size = 32768; + + open_recv_stream(session, 3); + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(16 * 1024 + 100 == stream->local_window_size); + + stream = nghttp2_session_get_stream(session, 3); + CU_ASSERT(16 * 1024 == stream->local_window_size); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(32768 == item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); + + /* Without auto-window update */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + nghttp2_option_del(option); + + stream = open_recv_stream(session, 1); + stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100; + stream->recv_window_size = 32768; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(32768 == stream->recv_window_size); + CU_ASSERT(16 * 1024 + 100 == stream->local_window_size); + /* Check that we can handle the case where local_window_size < + recv_window_size */ + CU_ASSERT(0 == nghttp2_session_get_stream_local_window_size(session, 1)); + + nghttp2_session_del(session); + + /* Check overflow case */ + iv[0].value = 128 * 1024; + nghttp2_session_server_new(&session, &callbacks, NULL); + stream = open_recv_stream(session, 1); + stream->local_window_size = NGHTTP2_MAX_WINDOW_SIZE; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0)); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_FLOW_CONTROL_ERROR == item->frame.rst_stream.error_code); + + nghttp2_session_del(session); + nghttp2_frame_settings_free(&ack_frame.settings, mem); +} + +void test_nghttp2_submit_settings_multiple_times(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_settings_entry iv[4]; + nghttp2_frame frame; + nghttp2_inflight_settings *inflight_settings; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + /* first SETTINGS */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 100; + + iv[1].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[1].value = 0; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2)); + + inflight_settings = session->inflight_settings_head; + + CU_ASSERT(NULL != inflight_settings); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + inflight_settings->iv[0].settings_id); + CU_ASSERT(100 == inflight_settings->iv[0].value); + CU_ASSERT(2 == inflight_settings->niv); + CU_ASSERT(NULL == inflight_settings->next); + + CU_ASSERT(100 == session->pending_local_max_concurrent_stream); + CU_ASSERT(0 == session->pending_enable_push); + + /* second SETTINGS */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 99; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1)); + + inflight_settings = session->inflight_settings_head->next; + + CU_ASSERT(NULL != inflight_settings); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + inflight_settings->iv[0].settings_id); + CU_ASSERT(99 == inflight_settings->iv[0].value); + CU_ASSERT(1 == inflight_settings->niv); + CU_ASSERT(NULL == inflight_settings->next); + + CU_ASSERT(99 == session->pending_local_max_concurrent_stream); + CU_ASSERT(0 == session->pending_enable_push); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0); + + /* receive SETTINGS ACK */ + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + inflight_settings = session->inflight_settings_head; + + /* first inflight SETTINGS was removed */ + CU_ASSERT(NULL != inflight_settings); + CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS == + inflight_settings->iv[0].settings_id); + CU_ASSERT(99 == inflight_settings->iv[0].value); + CU_ASSERT(1 == inflight_settings->niv); + CU_ASSERT(NULL == inflight_settings->next); + + CU_ASSERT(100 == session->local_settings.max_concurrent_streams); + + /* receive SETTINGS ACK again */ + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + CU_ASSERT(NULL == session->inflight_settings_head); + CU_ASSERT(99 == session->local_settings.max_concurrent_streams); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_push_promise(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + open_recv_stream(session, 1); + CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, + reqnv, ARRLEN(reqnv), &ud)); + + stream = nghttp2_session_get_stream(session, 2); + + CU_ASSERT(NULL != stream); + CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); + CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2)); + + ud.frame_send_cb_called = 0; + ud.sent_frame_type = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type); + + stream = nghttp2_session_get_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state); + CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2)); + + /* submit PUSH_PROMISE while associated stream is not opened */ + CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == + nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, reqnv, + ARRLEN(reqnv), NULL)); + + /* Stream ID <= 0 is error */ + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 0, reqnv, + ARRLEN(reqnv), NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_window_update(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + stream = open_recv_stream(session, 2); + stream->recv_window_size = 4096; + + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 1024)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1024 == item->frame.window_update.window_size_increment); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(3072 == stream->recv_window_size); + + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 4096)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(4096 == item->frame.window_update.window_size_increment); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == stream->recv_window_size); + + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 4096)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(4096 == item->frame.window_update.window_size_increment); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == stream->recv_window_size); + + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 0)); + /* It is ok if stream is closed or does not exist at the call + time */ + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 4, 4096)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_window_update_local_window_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = open_recv_stream(session, 2); + stream->recv_window_size = 4096; + + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + stream->recv_window_size + 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1 == stream->local_window_size); + CU_ASSERT(0 == stream->recv_window_size); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(4097 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Let's decrement local window size */ + stream->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + -stream->local_window_size / 2)); + CU_ASSERT(32768 == stream->local_window_size); + CU_ASSERT(-28672 == stream->recv_window_size); + CU_ASSERT(32768 == stream->recv_reduction); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 16384)); + CU_ASSERT(49152 == stream->local_window_size); + CU_ASSERT(-12288 == stream->recv_window_size); + CU_ASSERT(16384 == stream->recv_reduction); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_MAX_WINDOW_SIZE)); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Check connection-level flow control */ + session->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + session->recv_window_size + 1)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + session->local_window_size); + CU_ASSERT(0 == session->recv_window_size); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(4097 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + session->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + -session->local_window_size / 2)); + CU_ASSERT(32768 == session->local_window_size); + CU_ASSERT(-28672 == session->recv_window_size); + CU_ASSERT(32768 == session->recv_reduction); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 16384)); + CU_ASSERT(49152 == session->local_window_size); + CU_ASSERT(-12288 == session->recv_window_size); + CU_ASSERT(16384 == session->recv_reduction); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL == + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, + NGHTTP2_MAX_WINDOW_SIZE)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_shutdown_notice(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session)); + + ud.frame_send_cb_called = 0; + + nghttp2_session_send(session); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_GOAWAY == ud.sent_frame_type); + CU_ASSERT((1u << 31) - 1 == session->local_last_stream_id); + + /* After another GOAWAY, nghttp2_submit_shutdown_notice() is + noop. */ + CU_ASSERT(0 == nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR)); + + ud.frame_send_cb_called = 0; + + nghttp2_session_send(session); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_GOAWAY == ud.sent_frame_type); + CU_ASSERT(0 == session->local_last_stream_id); + + CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session)); + + ud.frame_send_cb_called = 0; + ud.frame_not_send_cb_called = 0; + + nghttp2_session_send(session); + + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(0 == ud.frame_not_send_cb_called); + + nghttp2_session_del(session); + + /* Using nghttp2_submit_shutdown_notice() with client side session + is error */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == + nghttp2_submit_shutdown_notice(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_invalid_nv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_nv empty_name_nv[] = {MAKE_NV("Version", "HTTP/1.1"), + MAKE_NV("", "empty name")}; + + /* Now invalid header name/value pair in HTTP/1.1 is accepted in + nghttp2 */ + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL)); + + /* nghttp2_submit_response */ + CU_ASSERT(0 == nghttp2_submit_response(session, 2, empty_name_nv, + ARRLEN(empty_name_nv), NULL)); + + /* nghttp2_submit_push_promise */ + open_recv_stream(session, 1); + + CU_ASSERT(0 < nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, + empty_name_nv, + ARRLEN(empty_name_nv), NULL)); + + nghttp2_session_del(session); + + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + + /* nghttp2_submit_request */ + CU_ASSERT(0 < nghttp2_submit_request(session, NULL, empty_name_nv, + ARRLEN(empty_name_nv), NULL, NULL)); + + /* nghttp2_submit_headers */ + CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, + empty_name_nv, ARRLEN(empty_name_nv), + NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_extension(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + accumulator acc; + nghttp2_mem *mem; + const char data[] = "Hello World!"; + size_t len; + int32_t stream_id; + int rv; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + callbacks.pack_extension_callback = pack_extension_callback; + callbacks.send_callback = accumulator_send_callback; + + nghttp2_buf_init2(&ud.scratchbuf, 4096, mem); + + nghttp2_session_client_new(&session, &callbacks, &ud); + + ud.scratchbuf.last = nghttp2_cpymem(ud.scratchbuf.last, data, sizeof(data)); + ud.acc = &acc; + + rv = nghttp2_submit_extension(session, 211, 0x01, 3, &ud.scratchbuf); + + CU_ASSERT(0 == rv); + + acc.length = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_FRAME_HDLEN + sizeof(data) == acc.length); + + len = nghttp2_get_uint32(acc.buf) >> 8; + + CU_ASSERT(sizeof(data) == len); + CU_ASSERT(211 == acc.buf[3]); + CU_ASSERT(0x01 == acc.buf[4]); + + stream_id = (int32_t)nghttp2_get_uint32(acc.buf + 5); + + CU_ASSERT(3 == stream_id); + CU_ASSERT(0 == memcmp(data, &acc.buf[NGHTTP2_FRAME_HDLEN], sizeof(data))); + + nghttp2_session_del(session); + + /* submitting standard HTTP/2 frame is error */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_extension(session, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, + NULL); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_session_del(session); + nghttp2_buf_free(&ud.scratchbuf, mem); +} + +void test_nghttp2_submit_altsvc(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + int rv; + ssize_t len; + const uint8_t *data; + nghttp2_frame_hd hd; + size_t origin_len; + const uint8_t origin[] = "nghttp2.org"; + const uint8_t field_value[] = "h2=\":443\""; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin, + sizeof(origin) - 1, field_value, + sizeof(field_value) - 1); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len == NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1 + + sizeof(field_value) - 1); + + nghttp2_frame_unpack_frame_hd(&hd, data); + + CU_ASSERT(2 + sizeof(origin) - 1 + sizeof(field_value) - 1 == hd.length); + CU_ASSERT(NGHTTP2_ALTSVC == hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + + origin_len = nghttp2_get_uint16(data + NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(sizeof(origin) - 1 == origin_len); + CU_ASSERT(0 == + memcmp(origin, data + NGHTTP2_FRAME_HDLEN + 2, sizeof(origin) - 1)); + CU_ASSERT(0 == memcmp(field_value, + data + NGHTTP2_FRAME_HDLEN + 2 + sizeof(origin) - 1, + hd.length - (sizeof(origin) - 1) - 2)); + + /* submitting empty origin with stream_id == 0 is error */ + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, NULL, 0, + field_value, sizeof(field_value) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* submitting non-empty origin with stream_id != 0 is error */ + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 1, origin, + sizeof(origin) - 1, field_value, + sizeof(field_value) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_session_del(session); + + /* submitting from client side session is error */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_altsvc(session, NGHTTP2_FLAG_NONE, 0, origin, + sizeof(origin) - 1, field_value, + sizeof(field_value) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_origin(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + int rv; + ssize_t len; + const uint8_t *data; + static const uint8_t nghttp2[] = "https://nghttp2.org"; + static const uint8_t examples[] = "https://examples.com"; + static const nghttp2_origin_entry ov[] = { + { + (uint8_t *)nghttp2, + sizeof(nghttp2) - 1, + }, + { + (uint8_t *)examples, + sizeof(examples) - 1, + }, + }; + nghttp2_frame frame; + nghttp2_ext_origin origin; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + + frame.ext.payload = &origin; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, ov, 2); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + rv = nghttp2_frame_unpack_origin_payload( + &frame.ext, data + NGHTTP2_FRAME_HDLEN, (size_t)len - NGHTTP2_FRAME_HDLEN, + mem); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == frame.hd.stream_id); + CU_ASSERT(NGHTTP2_ORIGIN == frame.hd.type); + CU_ASSERT(2 == origin.nov); + CU_ASSERT(0 == memcmp(nghttp2, origin.ov[0].origin, sizeof(nghttp2) - 1)); + CU_ASSERT(sizeof(nghttp2) - 1 == origin.ov[0].origin_len); + CU_ASSERT(0 == memcmp(examples, origin.ov[1].origin, sizeof(examples) - 1)); + CU_ASSERT(sizeof(examples) - 1 == origin.ov[1].origin_len); + + nghttp2_frame_origin_free(&frame.ext, mem); + + nghttp2_session_del(session); + + /* Submitting ORIGIN frame from client session is error */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, ov, 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv); + + nghttp2_session_del(session); + + /* Submitting empty ORIGIN frame */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_submit_origin(session, NGHTTP2_FLAG_NONE, NULL, 0); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len == NGHTTP2_FRAME_HDLEN); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + + CU_ASSERT(NGHTTP2_ORIGIN == frame.hd.type); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_priority_update(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const uint8_t field_value[] = "i"; + my_user_data ud; + const uint8_t *data; + int rv; + nghttp2_frame frame; + nghttp2_ext_priority_update priority_update; + ssize_t len; + int32_t stream_id; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + session->pending_no_rfc7540_priorities = 1; + + stream_id = + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(1 == stream_id); + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + + rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, stream_id, + field_value, sizeof(field_value) - 1); + + CU_ASSERT(0 == rv); + + frame.ext.payload = &priority_update; + + ud.frame_send_cb_called = 0; + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + CU_ASSERT(1 == ud.frame_send_cb_called); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + nghttp2_frame_unpack_priority_update_payload( + &frame.ext, (uint8_t *)(data + NGHTTP2_FRAME_HDLEN), + (size_t)len - NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(0 == frame.hd.stream_id); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == frame.hd.type); + CU_ASSERT(stream_id == priority_update.stream_id); + CU_ASSERT(sizeof(field_value) - 1 == priority_update.field_value_len); + CU_ASSERT(0 == memcmp(field_value, priority_update.field_value, + sizeof(field_value) - 1)); + + nghttp2_session_del(session); + + /* Submitting PRIORITY_UPDATE frame from server session is error */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, 1, + field_value, sizeof(field_value) - 1); + + CU_ASSERT(NGHTTP2_ERR_INVALID_STATE == rv); + + nghttp2_session_del(session); + + /* Submitting PRIORITY_UPDATE with empty field_value */ + nghttp2_session_client_new(&session, &callbacks, &ud); + + stream_id = + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(1 == stream_id); + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + + rv = nghttp2_submit_priority_update(session, NGHTTP2_FLAG_NONE, stream_id, + NULL, 0); + + CU_ASSERT(0 == rv); + + frame.ext.payload = &priority_update; + + len = nghttp2_session_mem_send(session, &data); + + CU_ASSERT(len > 0); + + nghttp2_frame_unpack_frame_hd(&frame.hd, data); + nghttp2_frame_unpack_priority_update_payload( + &frame.ext, (uint8_t *)(data + NGHTTP2_FRAME_HDLEN), + (size_t)len - NGHTTP2_FRAME_HDLEN); + + CU_ASSERT(0 == frame.hd.stream_id); + CU_ASSERT(NGHTTP2_PRIORITY_UPDATE == frame.hd.type); + CU_ASSERT(stream_id == priority_update.stream_id); + CU_ASSERT(0 == priority_update.field_value_len); + CU_ASSERT(NULL == priority_update.field_value); + + nghttp2_session_del(session); +} + +void test_nghttp2_submit_rst_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + int rv; + int32_t stream_id; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + /* Sending RST_STREAM to idle stream (local) is ignored */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL == item); + + nghttp2_session_del(session); + + /* Sending RST_STREAM to idle stream (remote) is ignored */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL == item); + + nghttp2_session_del(session); + + /* Sending RST_STREAM to non-idle stream (local) */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + open_sent_stream(session, 1); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + + nghttp2_session_del(session); + + /* Sending RST_STREAM to non-idle stream (remote) */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + open_recv_stream(session, 2); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(2 == item->frame.hd.stream_id); + + nghttp2_session_del(session); + + /* Sending RST_STREAM to pending stream */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream_id = + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(stream_id > 0); + + item = nghttp2_outbound_queue_top(&session->ob_syn); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(0 == item->aux_data.headers.canceled); + + rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, + NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == rv); + + item = nghttp2_outbound_queue_top(&session->ob_syn); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(1 == item->aux_data.headers.canceled); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_open_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_priority_spec_init(&pri_spec, 0, 245, 0); + + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(0 == session->num_outgoing_streams); + CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state); + CU_ASSERT(245 == stream->weight); + CU_ASSERT(&session->root == stream->dep_prev); + CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags); + + stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(1 == session->num_outgoing_streams); + CU_ASSERT(&session->root == stream->dep_prev); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags); + + stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_RESERVED, NULL); + CU_ASSERT(1 == session->num_incoming_streams); + CU_ASSERT(1 == session->num_outgoing_streams); + CU_ASSERT(&session->root == stream->dep_prev); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags); + + nghttp2_priority_spec_init(&pri_spec, 1, 17, 1); + + stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + CU_ASSERT(17 == stream->weight); + CU_ASSERT(1 == stream->dep_prev->stream_id); + + /* Dependency to idle stream */ + nghttp2_priority_spec_init(&pri_spec, 1000000007, 240, 1); + + stream = nghttp2_session_open_stream(session, 5, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + CU_ASSERT(240 == stream->weight); + CU_ASSERT(1000000007 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream_raw(session, 1000000007); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(&session->root == stream->dep_prev); + + /* Dependency to closed stream which is not in dependency tree */ + session->last_recv_stream_id = 7; + + nghttp2_priority_spec_init(&pri_spec, 7, 10, 0); + + stream = nghttp2_session_open_stream(session, 9, NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_OPENED, NULL); + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(&session->root == stream->dep_prev); + + nghttp2_session_del(session); + + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_RESERVED, NULL); + CU_ASSERT(0 == session->num_incoming_streams); + CU_ASSERT(0 == session->num_outgoing_streams); + CU_ASSERT(&session->root == stream->dep_prev); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_open_stream_with_idle_stream_dep(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* Dependency to idle stream */ + nghttp2_priority_spec_init(&pri_spec, 101, 245, 0); + + stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + + CU_ASSERT(245 == stream->weight); + CU_ASSERT(101 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream_raw(session, 101); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + + nghttp2_priority_spec_init(&pri_spec, 211, 1, 0); + + /* stream 101 was already created as idle. */ + stream = nghttp2_session_open_stream(session, 101, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec, NGHTTP2_STREAM_OPENED, NULL); + + CU_ASSERT(1 == stream->weight); + CU_ASSERT(211 == stream->dep_prev->stream_id); + + stream = nghttp2_session_get_stream_raw(session, 211); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_get_next_ob_item(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_priority_spec pri_spec; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + session->remote_settings.max_concurrent_streams = 2; + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + CU_ASSERT(NGHTTP2_PING == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, NULL, 0, NULL, NULL)); + CU_ASSERT(NGHTTP2_PING == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Incoming stream does not affect the number of outgoing max + concurrent streams. */ + open_recv_stream(session, 2); + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_MAX_WEIGHT, 0); + + CU_ASSERT(3 == + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL)); + CU_ASSERT(NGHTTP2_HEADERS == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(5 == + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + session->remote_settings.max_concurrent_streams = 3; + + CU_ASSERT(NGHTTP2_HEADERS == + nghttp2_session_get_next_ob_item(session)->frame.hd.type); + + nghttp2_session_del(session); + + /* Check that push reply HEADERS are queued into ob_ss_pq */ + nghttp2_session_server_new(&session, &callbacks, NULL); + session->remote_settings.max_concurrent_streams = 0; + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 2, + NULL, NULL, 0, NULL)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_syn)); + nghttp2_session_del(session); +} + +void test_nghttp2_session_pop_next_ob_item(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_priority_spec pri_spec; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + session->remote_settings.max_concurrent_streams = 1; + + CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + + nghttp2_priority_spec_init(&pri_spec, 0, 254, 0); + + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL); + + item = nghttp2_session_pop_next_ob_item(session); + CU_ASSERT(NGHTTP2_PING == item->frame.hd.type); + nghttp2_outbound_item_free(item, mem); + mem->free(item, NULL); + + item = nghttp2_session_pop_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + nghttp2_outbound_item_free(item, mem); + mem->free(item, NULL); + + CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + + /* Incoming stream does not affect the number of outgoing max + concurrent streams. */ + open_recv_stream(session, 4); + /* In-flight outgoing stream */ + open_sent_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_MAX_WEIGHT, 0); + + nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL); + + CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + + session->remote_settings.max_concurrent_streams = 2; + + item = nghttp2_session_pop_next_ob_item(session); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + nghttp2_outbound_item_free(item, mem); + mem->free(item, NULL); + + nghttp2_session_del(session); + + /* Check that push reply HEADERS are queued into ob_ss_pq */ + nghttp2_session_server_new(&session, &callbacks, NULL); + session->remote_settings.max_concurrent_streams = 0; + open_sent_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 2, + NULL, NULL, 0, NULL)); + CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session)); + CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_syn)); + nghttp2_session_del(session); +} + +void test_nghttp2_session_reply_fail(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = fail_send_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + ud.data_source_length = 4 * 1024; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, NULL, 0, &data_prd)); + CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == nghttp2_session_send(session)); + nghttp2_session_del(session); +} + +void test_nghttp2_session_max_concurrent_streams(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + open_recv_stream(session, 1); + + /* Check un-ACKed SETTINGS_MAX_CONCURRENT_STREAMS */ + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3, + NGHTTP2_HCAT_HEADERS, NULL, NULL, 0); + session->pending_local_max_concurrent_stream = 1; + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Check ACKed SETTINGS_MAX_CONCURRENT_STREAMS */ + session->local_settings.max_concurrent_streams = 1; + frame.hd.stream_id = 5; + + CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK == + nghttp2_session_on_request_headers_received(session, &frame)); + + item = nghttp2_outbound_queue_top(&session->ob_reg); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code); + + nghttp2_frame_headers_free(&frame.headers, mem); + nghttp2_session_del(session); +} + +void test_nghttp2_session_stop_data_with_rst_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.send_callback = block_count_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.frame_send_cb_called = 0; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4; + + nghttp2_session_server_new(&session, &callbacks, &ud); + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + nghttp2_submit_response(session, 1, NULL, 0, &data_prd); + + ud.block_count = 2; + /* Sends response HEADERS + DATA[0] */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type); + /* data for DATA[1] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_CANCEL); + CU_ASSERT(0 == nghttp2_session_on_rst_stream_received(session, &frame)); + nghttp2_frame_rst_stream_free(&frame.rst_stream); + + /* Big enough number to send all DATA frames potentially. */ + ud.block_count = 100; + /* Nothing will be sent in the following call. */ + CU_ASSERT(0 == nghttp2_session_send(session)); + /* With RST_STREAM, stream is canceled and further DATA on that + stream are not sent. */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_defer_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.send_callback = block_count_send_callback; + data_prd.read_callback = defer_data_source_read_callback; + + ud.frame_send_cb_called = 0; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4; + + nghttp2_session_server_new(&session, &callbacks, &ud); + stream = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + session->remote_window_size = 1 << 20; + stream->remote_window_size = 1 << 20; + + nghttp2_submit_response(session, 1, NULL, 0, &data_prd); + + ud.block_count = 1; + /* Sends HEADERS reply */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + /* No data is read */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 4); + + ud.block_count = 1; + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + /* Sends PING */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_PING == ud.sent_frame_type); + + /* Resume deferred DATA */ + CU_ASSERT(0 == nghttp2_session_resume_data(session, 1)); + item = stream->item; + item->aux_data.data.data_prd.read_callback = + fixed_length_data_source_read_callback; + ud.block_count = 1; + /* Reads 2 DATA chunks */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + /* Deferred again */ + item->aux_data.data.data_prd.read_callback = defer_data_source_read_callback; + /* This is needed since 16KiB block is already read and waiting to be + sent. No read_callback invocation. */ + ud.block_count = 1; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + /* Resume deferred DATA */ + CU_ASSERT(0 == nghttp2_session_resume_data(session, 1)); + item->aux_data.data.data_prd.read_callback = + fixed_length_data_source_read_callback; + ud.block_count = 1; + /* Reads 2 16KiB blocks */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(ud.data_source_length == 0); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_flow_control(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + nghttp2_stream *stream; + int32_t new_initial_window_size; + nghttp2_settings_entry iv[1]; + nghttp2_frame settings_frame; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = fixed_bytes_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.frame_send_cb_called = 0; + ud.data_source_length = 128 * 1024; + /* Use smaller emission count so that we can check outbound flow + control window calculation is correct. */ + ud.fixed_sendlen = 2 * 1024; + + /* Initial window size to 64KiB - 1*/ + nghttp2_session_client_new(&session, &callbacks, &ud); + /* Change it to 64KiB for easy calculation */ + session->remote_window_size = 64 * 1024; + session->remote_settings.initial_window_size = 64 * 1024; + + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + + /* Sends 64KiB - 1 data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(64 * 1024 == ud.data_source_length); + + /* Back 32KiB in stream window */ + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, + 32 * 1024); + nghttp2_session_on_window_update_received(session, &frame); + + /* Send nothing because of connection-level window */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(64 * 1024 == ud.data_source_length); + + /* Back 32KiB in connection-level window */ + frame.hd.stream_id = 0; + nghttp2_session_on_window_update_received(session, &frame); + + /* Sends another 32KiB data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(32 * 1024 == ud.data_source_length); + + stream = nghttp2_session_get_stream(session, 1); + /* Change initial window size to 16KiB. The window_size becomes + negative. */ + new_initial_window_size = 16 * 1024; + stream->remote_window_size = + new_initial_window_size - + ((int32_t)session->remote_settings.initial_window_size - + stream->remote_window_size); + session->remote_settings.initial_window_size = + (uint32_t)new_initial_window_size; + CU_ASSERT(-48 * 1024 == stream->remote_window_size); + + /* Back 48KiB to stream window */ + frame.hd.stream_id = 1; + frame.window_update.window_size_increment = 48 * 1024; + nghttp2_session_on_window_update_received(session, &frame); + + /* Nothing is sent because window_size is 0 */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(32 * 1024 == ud.data_source_length); + + /* Back 16KiB in stream window */ + frame.hd.stream_id = 1; + frame.window_update.window_size_increment = 16 * 1024; + nghttp2_session_on_window_update_received(session, &frame); + + /* Back 24KiB in connection-level window */ + frame.hd.stream_id = 0; + frame.window_update.window_size_increment = 24 * 1024; + nghttp2_session_on_window_update_received(session, &frame); + + /* Sends another 16KiB data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(16 * 1024 == ud.data_source_length); + + /* Increase initial window size to 32KiB */ + iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[0].value = 32 * 1024; + + nghttp2_frame_settings_init(&settings_frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(iv, 1), 1); + nghttp2_session_on_settings_received(session, &settings_frame, 1); + nghttp2_frame_settings_free(&settings_frame.settings, mem); + + /* Sends another 8KiB data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(8 * 1024 == ud.data_source_length); + + /* Back 8KiB in connection-level window */ + frame.hd.stream_id = 0; + frame.window_update.window_size_increment = 8 * 1024; + nghttp2_session_on_window_update_received(session, &frame); + + /* Sends last 8KiB data */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.data_source_length); + CU_ASSERT(nghttp2_session_get_stream(session, 1)->shut_flags & + NGHTTP2_SHUT_WR); + + nghttp2_frame_window_update_free(&frame.window_update); + nghttp2_session_del(session); +} + +void test_nghttp2_session_flow_control_data_recv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t data[64 * 1024 + 16]; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* Initial window size to 64KiB - 1*/ + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream = open_sent_stream(session, 1); + + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR); + + session->local_window_size = NGHTTP2_MAX_PAYLOADLEN; + stream->local_window_size = NGHTTP2_MAX_PAYLOADLEN; + + /* Create DATA frame */ + memset(data, 0, sizeof(data)); + nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_PAYLOADLEN, NGHTTP2_DATA, + NGHTTP2_FLAG_END_STREAM, 1); + + nghttp2_frame_pack_frame_hd(data, &hd); + CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN == + nghttp2_session_mem_recv( + session, data, NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN)); + + item = nghttp2_session_get_next_ob_item(session); + /* Since this is the last frame, stream-level WINDOW_UPDATE is not + issued, but connection-level is. */ + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN == + item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Receive DATA for closed stream. They are still subject to under + connection-level flow control, since this situation arises when + RST_STREAM is issued by the remote, but the local side keeps + sending DATA frames. Without calculating connection-level window, + the subsequent flow control gets confused. */ + CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN == + nghttp2_session_mem_recv( + session, data, NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN)); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN == + item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_flow_control_data_with_padding_recv(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t data[1024]; + nghttp2_frame_hd hd; + nghttp2_stream *stream; + nghttp2_option *option; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_option_new(&option); + /* Disable auto window update so that we can check padding is + consumed automatically */ + nghttp2_option_set_no_auto_window_update(option, 1); + + /* Initial window size to 64KiB - 1*/ + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + nghttp2_option_del(option); + + stream = open_sent_stream(session, 1); + + /* Create DATA frame */ + memset(data, 0, sizeof(data)); + nghttp2_frame_hd_init(&hd, 357, NGHTTP2_DATA, NGHTTP2_FLAG_PADDED, 1); + + nghttp2_frame_pack_frame_hd(data, &hd); + /* Set Pad Length field, which itself is padding */ + data[NGHTTP2_FRAME_HDLEN] = 255; + + CU_ASSERT( + (ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) == + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length)); + + CU_ASSERT((int32_t)hd.length == session->recv_window_size); + CU_ASSERT((int32_t)hd.length == stream->recv_window_size); + CU_ASSERT(256 == session->consumed_size); + CU_ASSERT(256 == stream->consumed_size); + CU_ASSERT(357 == session->recv_window_size); + CU_ASSERT(357 == stream->recv_window_size); + + /* Receive the same DATA frame, but in 2 parts: first 9 + 1 + 102 + bytes which includes 1st padding byte, and remainder */ + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 103) == + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 103)); + CU_ASSERT(258 == session->consumed_size); + CU_ASSERT(258 == stream->consumed_size); + CU_ASSERT(460 == session->recv_window_size); + CU_ASSERT(460 == stream->recv_window_size); + + /* 357 - 103 = 254 bytes left */ + CU_ASSERT(254 == nghttp2_session_mem_recv(session, data, 254)); + CU_ASSERT(512 == session->consumed_size); + CU_ASSERT(512 == stream->consumed_size); + CU_ASSERT(714 == session->recv_window_size); + CU_ASSERT(714 == stream->recv_window_size); + + /* Receive the same DATA frame, but in 2 parts: first 9 = 1 + 101 + bytes which only includes data without padding, 2nd part is + padding only */ + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 102) == + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 102)); + CU_ASSERT(513 == session->consumed_size); + CU_ASSERT(513 == stream->consumed_size); + CU_ASSERT(816 == session->recv_window_size); + CU_ASSERT(816 == stream->recv_window_size); + + /* 357 - 102 = 255 bytes left */ + CU_ASSERT(255 == nghttp2_session_mem_recv(session, data, 255)); + CU_ASSERT(768 == session->consumed_size); + CU_ASSERT(768 == stream->consumed_size); + CU_ASSERT(1071 == session->recv_window_size); + CU_ASSERT(1071 == stream->recv_window_size); + + /* Receive the same DATA frame, but in 2 parts: first 9 = 1 + 50 + bytes which includes byte up to middle of data, 2nd part is the + remainder */ + CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 51) == + nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 51)); + CU_ASSERT(769 == session->consumed_size); + CU_ASSERT(769 == stream->consumed_size); + CU_ASSERT(1122 == session->recv_window_size); + CU_ASSERT(1122 == stream->recv_window_size); + + /* 357 - 51 = 306 bytes left */ + CU_ASSERT(306 == nghttp2_session_mem_recv(session, data, 306)); + CU_ASSERT(1024 == session->consumed_size); + CU_ASSERT(1024 == stream->consumed_size); + CU_ASSERT(1428 == session->recv_window_size); + CU_ASSERT(1428 == stream->recv_window_size); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_data_read_temporal_failure(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + nghttp2_stream *stream; + size_t data_size = 128 * 1024; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.data_source_length = data_size; + + /* Initial window size is 64KiB - 1 */ + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + + /* Sends NGHTTP2_INITIAL_WINDOW_SIZE data, assuming, it is equal to + or smaller than NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length); + + stream = nghttp2_session_get_stream(session, 1); + CU_ASSERT(NGHTTP2_DATA == stream->item->frame.hd.type); + + stream->item->aux_data.data.data_prd.read_callback = + temporal_failure_data_source_read_callback; + + /* Back NGHTTP2_INITIAL_WINDOW_SIZE to both connection-level and + stream-wise window */ + nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_INITIAL_WINDOW_SIZE); + nghttp2_session_on_window_update_received(session, &frame); + frame.hd.stream_id = 0; + nghttp2_session_on_window_update_received(session, &frame); + nghttp2_frame_window_update_free(&frame.window_update); + + /* Sending data will fail (soft fail) and treated as stream error */ + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_RST_STREAM == ud.sent_frame_type); + + data_prd.read_callback = fail_data_source_read_callback; + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + /* Sending data will fail (hard fail) and session tear down */ + CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == nghttp2_session_send(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_stream_close(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_stream_close_callback = on_stream_close_callback; + user_data.stream_close_cb_called = 0; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + stream = + open_sent_stream3(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, + NGHTTP2_STREAM_OPENED, &user_data); + CU_ASSERT(stream != NULL); + CU_ASSERT(nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR) == 0); + CU_ASSERT(user_data.stream_close_cb_called == 1); + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_ctrl_not_send(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + callbacks.send_callback = null_send_callback; + user_data.frame_not_send_cb_called = 0; + user_data.not_sent_frame_type = 0; + user_data.not_sent_error = 0; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + stream = + open_recv_stream3(session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default, + NGHTTP2_STREAM_OPENING, &user_data); + + /* Check response HEADERS */ + /* Send bogus stream ID */ + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 3, + NULL, NULL, 0, NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == user_data.not_sent_error); + + user_data.frame_not_send_cb_called = 0; + /* Shutdown transmission */ + stream->shut_flags |= NGHTTP2_SHUT_WR; + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, NULL, 0, NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_STREAM_SHUT_WR == user_data.not_sent_error); + + stream->shut_flags = NGHTTP2_SHUT_NONE; + user_data.frame_not_send_cb_called = 0; + /* Queue RST_STREAM */ + CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1, + NULL, NULL, 0, NULL)); + CU_ASSERT(0 == nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_INTERNAL_ERROR)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSING == user_data.not_sent_error); + + nghttp2_session_del(session); + + /* Check request HEADERS */ + user_data.frame_not_send_cb_called = 0; + CU_ASSERT(nghttp2_session_client_new(&session, &callbacks, &user_data) == 0); + /* Maximum Stream ID is reached */ + session->next_stream_id = (1u << 31) + 1; + CU_ASSERT(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE == + nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, NULL, + NULL, 0, NULL)); + + user_data.frame_not_send_cb_called = 0; + /* GOAWAY received */ + session->goaway_flags |= NGHTTP2_GOAWAY_RECV; + session->next_stream_id = 9; + + CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, + NULL, NULL, 0, NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_START_STREAM_NOT_ALLOWED == user_data.not_sent_error); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_get_outbound_queue_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + CU_ASSERT(0 == nghttp2_session_get_outbound_queue_size(session)); + + CU_ASSERT(0 == nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL)); + CU_ASSERT(1 == nghttp2_session_get_outbound_queue_size(session)); + + CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 2, + NGHTTP2_NO_ERROR, NULL, 0)); + CU_ASSERT(2 == nghttp2_session_get_outbound_queue_size(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_get_effective_local_window_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + + stream = open_sent_stream(session, 1); + + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE == + nghttp2_session_get_effective_local_window_size(session)); + CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session)); + + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + nghttp2_session_get_stream_effective_local_window_size(session, 1)); + CU_ASSERT(0 == + nghttp2_session_get_stream_effective_recv_data_length(session, 1)); + + /* Check connection flow control */ + session->recv_window_size = 100; + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 1100); + + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 == + nghttp2_session_get_effective_local_window_size(session)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 == + nghttp2_session_get_local_window_size(session)); + CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session)); + + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, -50); + /* Now session->recv_window_size = -50 */ + CU_ASSERT(-50 == session->recv_window_size); + CU_ASSERT(50 == session->recv_reduction); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 950 == + nghttp2_session_get_effective_local_window_size(session)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 == + nghttp2_session_get_local_window_size(session)); + CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session)); + + session->recv_window_size += 50; + + /* Now session->recv_window_size = 0 */ + + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 950 == + nghttp2_session_get_local_window_size(session)); + + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 100); + CU_ASSERT(50 == session->recv_window_size); + CU_ASSERT(0 == session->recv_reduction); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1050 == + nghttp2_session_get_effective_local_window_size(session)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 == + nghttp2_session_get_local_window_size(session)); + CU_ASSERT(50 == nghttp2_session_get_effective_recv_data_length(session)); + + /* Check stream flow control */ + stream->recv_window_size = 100; + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, 1100); + + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 == + nghttp2_session_get_stream_effective_local_window_size(session, 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 == + nghttp2_session_get_stream_local_window_size(session, 1)); + CU_ASSERT(0 == + nghttp2_session_get_stream_effective_recv_data_length(session, 1)); + + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, -50); + /* Now stream->recv_window_size = -50 */ + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 950 == + nghttp2_session_get_stream_effective_local_window_size(session, 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 == + nghttp2_session_get_stream_local_window_size(session, 1)); + CU_ASSERT(0 == + nghttp2_session_get_stream_effective_recv_data_length(session, 1)); + + stream->recv_window_size += 50; + /* Now stream->recv_window_size = 0 */ + nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, 100); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1050 == + nghttp2_session_get_stream_effective_local_window_size(session, 1)); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 == + nghttp2_session_get_stream_local_window_size(session, 1)); + CU_ASSERT(50 == + nghttp2_session_get_stream_effective_recv_data_length(session, 1)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_set_option(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_option *option; + nghttp2_hd_deflater *deflater; + int rv; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* Test for nghttp2_option_set_no_auto_window_update */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_window_update(option, 1); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE); + + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Test for nghttp2_option_set_peer_max_concurrent_streams */ + nghttp2_option_new(&option); + nghttp2_option_set_peer_max_concurrent_streams(option, 100); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + CU_ASSERT(100 == session->remote_settings.max_concurrent_streams); + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Test for nghttp2_option_set_max_reserved_remote_streams */ + nghttp2_option_new(&option); + nghttp2_option_set_max_reserved_remote_streams(option, 99); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + CU_ASSERT(99 == session->max_incoming_reserved_streams); + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Test for nghttp2_option_set_no_auto_ping_ack */ + nghttp2_option_new(&option); + nghttp2_option_set_no_auto_ping_ack(option, 1); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK); + + nghttp2_session_del(session); + nghttp2_option_del(option); + + /* Test for nghttp2_option_set_max_deflate_dynamic_table_size */ + nghttp2_option_new(&option); + nghttp2_option_set_max_deflate_dynamic_table_size(option, 0); + + nghttp2_session_client_new2(&session, &callbacks, NULL, option); + + deflater = &session->hd_deflater; + + rv = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(1 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == deflater->deflate_hd_table_bufsize_max); + CU_ASSERT(0 == deflater->ctx.hd_table_bufsize); + + nghttp2_session_del(session); + nghttp2_option_del(option); +} + +void test_nghttp2_session_data_backoff_by_high_pri_frame(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_data_provider data_prd; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.frame_send_cb_called = 0; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4; + + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + + session->remote_window_size = 1 << 20; + + ud.block_count = 2; + /* Sends request HEADERS + DATA[0] */ + CU_ASSERT(0 == nghttp2_session_send(session)); + + stream = nghttp2_session_get_stream(session, 1); + stream->remote_window_size = 1 << 20; + + CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type); + /* data for DATA[1] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2); + + nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL); + ud.block_count = 2; + /* Sends DATA[1] + PING, PING is interleaved in DATA sequence */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_PING == ud.sent_frame_type); + /* data for DATA[2] is read from data_prd but it is not sent */ + CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN); + + ud.block_count = 2; + /* Sends DATA[2..3] */ + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR); + + nghttp2_session_del(session); +} + +static void check_session_recv_data_with_padding(nghttp2_bufs *bufs, + size_t datalen, + nghttp2_mem *mem) { + nghttp2_session *session; + my_user_data ud; + nghttp2_session_callbacks callbacks; + uint8_t *in; + size_t inlen; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + inlen = (size_t)nghttp2_bufs_remove(bufs, &in); + + ud.frame_recv_cb_called = 0; + ud.data_chunk_len = 0; + + CU_ASSERT((ssize_t)inlen == nghttp2_session_mem_recv(session, in, inlen)); + + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(datalen == ud.data_chunk_len); + + mem->free(in, NULL); + nghttp2_session_del(session); +} + +void test_nghttp2_session_pack_data_with_padding(void) { + nghttp2_session *session; + my_user_data ud; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + nghttp2_frame *frame; + size_t datalen = 55; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = block_count_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.select_padding_callback = select_padding_callback; + + data_prd.read_callback = fixed_length_data_source_read_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + ud.padlen = 63; + + nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL); + ud.block_count = 1; + ud.data_source_length = datalen; + /* Sends HEADERS */ + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type); + + frame = &session->aob.item->frame; + + CU_ASSERT(ud.padlen == frame->data.padlen); + CU_ASSERT(frame->hd.flags & NGHTTP2_FLAG_PADDED); + + /* Check reception of this DATA frame */ + check_session_recv_data_with_padding(&session->aob.framebufs, datalen, mem); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_pack_headers_with_padding(void) { + nghttp2_session *session, *sv_session; + accumulator acc; + my_user_data ud; + nghttp2_session_callbacks callbacks; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = accumulator_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.select_padding_callback = select_padding_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + acc.length = 0; + ud.acc = &acc; + + nghttp2_session_client_new(&session, &callbacks, &ud); + nghttp2_session_server_new(&sv_session, &callbacks, &ud); + + ud.padlen = 163; + + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + NULL, NULL)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(acc.length < NGHTTP2_MAX_PAYLOADLEN); + ud.frame_recv_cb_called = 0; + CU_ASSERT((ssize_t)acc.length == + nghttp2_session_mem_recv(sv_session, acc.buf, acc.length)); + CU_ASSERT(1 == ud.frame_recv_cb_called); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(sv_session)); + + nghttp2_session_del(sv_session); + nghttp2_session_del(session); +} + +void test_nghttp2_pack_settings_payload(void) { + nghttp2_settings_entry iv[2]; + uint8_t buf[64]; + ssize_t len; + nghttp2_settings_entry *resiv; + size_t resniv; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 1023; + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 4095; + + len = nghttp2_pack_settings_payload(buf, sizeof(buf), iv, 2); + CU_ASSERT(2 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH == len); + CU_ASSERT(0 == nghttp2_frame_unpack_settings_payload2(&resiv, &resniv, buf, + (size_t)len, mem)); + CU_ASSERT(2 == resniv); + CU_ASSERT(NGHTTP2_SETTINGS_HEADER_TABLE_SIZE == resiv[0].settings_id); + CU_ASSERT(1023 == resiv[0].value); + CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == resiv[1].settings_id); + CU_ASSERT(4095 == resiv[1].value); + + mem->free(resiv, NULL); + + len = nghttp2_pack_settings_payload(buf, 9 /* too small */, iv, 2); + CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == len); +} + +#define check_stream_dep_sib(STREAM, DEP_PREV, DEP_NEXT, SIB_PREV, SIB_NEXT) \ + do { \ + CU_ASSERT(DEP_PREV == STREAM->dep_prev); \ + CU_ASSERT(DEP_NEXT == STREAM->dep_next); \ + CU_ASSERT(SIB_PREV == STREAM->sib_prev); \ + CU_ASSERT(SIB_NEXT == STREAM->sib_next); \ + } while (0) + +/* nghttp2_stream_dep_add() and its families functions should be + tested in nghttp2_stream_test.c, but it is easier to use + nghttp2_session_open_stream(). Therefore, we test them here. */ +void test_nghttp2_session_stream_dep_add(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *root; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + + c = open_stream_with_dep(session, 5, a); + b = open_stream_with_dep(session, 3, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, root, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, c); + check_stream_dep_sib(c, a, d, b, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(a == session->root.dep_next); + + e = open_stream_with_dep_excl(session, 9, a); + + /* a + * | + * e + * | + * b--c + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == e->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, root, e, NULL, NULL); + check_stream_dep_sib(e, a, b, NULL, NULL); + check_stream_dep_sib(b, e, NULL, NULL, c); + check_stream_dep_sib(c, e, d, b, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(a == session->root.dep_next); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_remove(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f, *root; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* Remove root */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + nghttp2_stream_dep_remove(a); + + /* becomes: + * c b + * | + * d + */ + + CU_ASSERT(0 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, NULL, NULL, NULL, NULL); + check_stream_dep_sib(b, root, NULL, c, NULL); + check_stream_dep_sib(c, root, d, NULL, b); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(c == session->root.dep_next); + + nghttp2_session_del(session); + + /* Remove right most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + nghttp2_stream_dep_remove(b); + + /* becomes: + * a + * | + * c + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + + check_stream_dep_sib(a, root, c, NULL, NULL); + check_stream_dep_sib(b, NULL, NULL, NULL, NULL); + check_stream_dep_sib(c, a, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + CU_ASSERT(a == session->root.dep_next); + + nghttp2_session_del(session); + + /* Remove left most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + e = open_stream_with_dep(session, 9, c); + + /* a + * | + * c--b + * | + * e--d + */ + + nghttp2_stream_dep_remove(c); + + /* becomes: + * a + * | + * e--d--b + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == c->sum_dep_weight); + CU_ASSERT(0 == e->sum_dep_weight); + + check_stream_dep_sib(a, root, e, NULL, NULL); + check_stream_dep_sib(b, a, NULL, d, NULL); + check_stream_dep_sib(c, NULL, NULL, NULL, NULL); + check_stream_dep_sib(d, a, NULL, e, b); + check_stream_dep_sib(e, a, NULL, NULL, d); + + nghttp2_session_del(session); + + /* Remove middle stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, a); + e = open_stream_with_dep(session, 9, c); + f = open_stream_with_dep(session, 11, c); + + /* a + * | + * d--c--b + * | + * f--e + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == e->sum_dep_weight); + CU_ASSERT(0 == f->sum_dep_weight); + + nghttp2_stream_dep_remove(c); + + /* becomes: + * a + * | + * d--f--e--b + */ + + /* c's weight 16 is distributed evenly to e and f. Each weight of e + and f becomes 8. */ + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 + 8 * 2 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(0 == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == e->sum_dep_weight); + CU_ASSERT(0 == f->sum_dep_weight); + + check_stream_dep_sib(a, root, d, NULL, NULL); + check_stream_dep_sib(b, a, NULL, e, NULL); + check_stream_dep_sib(c, NULL, NULL, NULL, NULL); + check_stream_dep_sib(e, a, NULL, f, b); + check_stream_dep_sib(f, a, NULL, d, e); + check_stream_dep_sib(d, a, NULL, NULL, f); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_add_subtree(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f, *root; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* dep_stream has dep_next */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream(session, 9); + f = open_stream_with_dep(session, 11, e); + + /* a e + * | | + * c--b f + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(e); + nghttp2_stream_dep_add_subtree(a, e); + + /* becomes + * a + * | + * e--c--b + * | | + * f d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == e->sum_dep_weight); + CU_ASSERT(0 == f->sum_dep_weight); + + check_stream_dep_sib(a, root, e, NULL, NULL); + check_stream_dep_sib(b, a, NULL, c, NULL); + check_stream_dep_sib(c, a, d, e, b); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + check_stream_dep_sib(e, a, f, NULL, c); + check_stream_dep_sib(f, e, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* dep_stream has dep_next and now we insert subtree */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream(session, 9); + f = open_stream_with_dep(session, 11, e); + + /* a e + * | | + * c--b f + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(e); + nghttp2_stream_dep_insert_subtree(a, e); + + /* becomes + * a + * | + * e + * | + * f--c--b + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == e->sum_dep_weight); + CU_ASSERT(0 == f->sum_dep_weight); + + check_stream_dep_sib(a, root, e, NULL, NULL); + check_stream_dep_sib(e, a, f, NULL, NULL); + check_stream_dep_sib(f, e, NULL, NULL, c); + check_stream_dep_sib(b, e, NULL, c, NULL); + check_stream_dep_sib(c, e, d, f, b); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_remove_subtree(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *root; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* Remove left most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(c); + + /* becomes + * a c + * | | + * b d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, root, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + check_stream_dep_sib(c, NULL, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* Remove right most stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(b); + + /* becomes + * a b + * | + * c + * | + * d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + + check_stream_dep_sib(a, root, c, NULL, NULL); + check_stream_dep_sib(c, a, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + check_stream_dep_sib(b, NULL, NULL, NULL, NULL); + + nghttp2_session_del(session); + + /* Remove middle stream */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + e = open_stream_with_dep(session, 9, a); + c = open_stream_with_dep(session, 5, a); + b = open_stream_with_dep(session, 3, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * b--c--e + * | + * d + */ + + nghttp2_stream_dep_remove_subtree(c); + + /* becomes + * a c + * | | + * b--e d + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(0 == e->sum_dep_weight); + + check_stream_dep_sib(a, root, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, e); + check_stream_dep_sib(e, a, NULL, b, NULL); + check_stream_dep_sib(c, NULL, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *root; + nghttp2_outbound_item *db, *dc; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + + c = open_stream(session, 5); + + /* a c + * | + * b + */ + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * a + * | + * b + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + + check_stream_dep_sib(c, root, a, NULL, NULL); + check_stream_dep_sib(a, c, b, NULL, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream(session, 3); + c = open_stream(session, 5); + + /* + * a b c + */ + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * b--a + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + CU_ASSERT(0 == a->sum_dep_weight); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + + check_stream_dep_sib(c, root, b, NULL, NULL); + check_stream_dep_sib(b, c, NULL, NULL, a); + check_stream_dep_sib(a, c, NULL, b, NULL); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + + c = open_stream(session, 5); + d = open_stream_with_dep(session, 7, c); + + /* a c + * | | + * b d + */ + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * d--a + * | + * b + */ + + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight); + CU_ASSERT(0 == d->sum_dep_weight); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight); + CU_ASSERT(0 == b->sum_dep_weight); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + check_stream_dep_sib(c, root, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, a); + check_stream_dep_sib(a, c, b, d, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + + c = open_stream(session, 5); + d = open_stream_with_dep(session, 7, c); + + /* a c + * | | + * b d + */ + + db = create_data_ob_item(mem); + + nghttp2_stream_attach_item(b, db); + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * d--a + * | + * b + */ + + CU_ASSERT(c->queued); + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + check_stream_dep_sib(c, root, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, a); + check_stream_dep_sib(a, c, b, d, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + root = &session->root; + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + + c = open_stream(session, 5); + d = open_stream_with_dep(session, 7, c); + + /* a c + * | | + * b d + */ + + db = create_data_ob_item(mem); + dc = create_data_ob_item(mem); + + nghttp2_stream_attach_item(b, db); + nghttp2_stream_attach_item(c, dc); + + nghttp2_stream_dep_remove_subtree(c); + CU_ASSERT(0 == nghttp2_stream_dep_insert_subtree(&session->root, c)); + + /* + * c + * | + * d--a + * | + * b + */ + + CU_ASSERT(c->queued); + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!d->queued); + + check_stream_dep_sib(c, root, d, NULL, NULL); + check_stream_dep_sib(d, c, NULL, NULL, a); + check_stream_dep_sib(a, c, b, d, NULL); + check_stream_dep_sib(b, a, NULL, NULL, NULL); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_attach_item(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e; + nghttp2_outbound_item *da, *db, *dc, *dd; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + db = create_data_ob_item(mem); + + nghttp2_stream_attach_item(b, db); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + + /* Attach item to c */ + dc = create_data_ob_item(mem); + + nghttp2_stream_attach_item(c, dc); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + + /* Attach item to a */ + da = create_data_ob_item(mem); + + nghttp2_stream_attach_item(a, da); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + + /* Detach item from a */ + nghttp2_stream_detach_item(a); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + + /* Attach item to d */ + dd = create_data_ob_item(mem); + + nghttp2_stream_attach_item(d, dd); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + + /* Detach item from c */ + nghttp2_stream_detach_item(c); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + + /* Detach item from b */ + nghttp2_stream_detach_item(b); + + CU_ASSERT(a->queued); + CU_ASSERT(!b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + + /* exercises insertion */ + e = open_stream_with_dep_excl(session, 9, a); + + /* a + * | + * e + * | + * c--b + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + /* exercises deletion */ + nghttp2_stream_dep_remove(e); + + /* a + * | + * c--b + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(!b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + /* e's weight 16 is distributed equally among c and b, both now have + weight 8 each. */ + CU_ASSERT(8 == b->weight); + CU_ASSERT(8 == c->weight); + + /* da, db, dc have been detached */ + nghttp2_outbound_item_free(da, mem); + nghttp2_outbound_item_free(db, mem); + nghttp2_outbound_item_free(dc, mem); + free(da); + free(db); + free(dc); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + /* a + * | + * c--b + * | + * d + */ + + da = create_data_ob_item(mem); + db = create_data_ob_item(mem); + dc = create_data_ob_item(mem); + + nghttp2_stream_attach_item(a, da); + nghttp2_stream_attach_item(b, db); + nghttp2_stream_attach_item(c, dc); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + /* Detach item from a */ + nghttp2_stream_detach_item(a); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(!d->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + + /* da has been detached */ + nghttp2_outbound_item_free(da, mem); + free(da); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_attach_item_subtree(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c, *d, *e, *f; + nghttp2_outbound_item *da, *db, *dd, *de; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep(session, 5, a); + d = open_stream_with_dep(session, 7, c); + + e = open_stream_with_dep_weight(session, 9, 32, &session->root); + f = open_stream_with_dep(session, 11, e); + + /* + * a e + * | | + * c--b f + * | + * d + */ + + de = create_data_ob_item(mem); + + nghttp2_stream_attach_item(e, de); + + db = create_data_ob_item(mem); + + nghttp2_stream_attach_item(b, db); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Insert subtree e under a */ + + nghttp2_stream_dep_remove_subtree(e); + nghttp2_stream_dep_insert_subtree(a, e); + + /* + * a + * | + * e + * | + * f--c--b + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree b */ + + nghttp2_stream_dep_remove_subtree(b); + + CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, b)); + + /* + * a b + * | + * e + * | + * f--c + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree a, and add it to root again */ + + nghttp2_stream_dep_remove_subtree(a); + + CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, a)); + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree c */ + + nghttp2_stream_dep_remove_subtree(c); + + CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, c)); + + /* + * a b c + * | | + * e d + * | + * f + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(!c->queued); + CU_ASSERT(!d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(nghttp2_pq_empty(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + dd = create_data_ob_item(mem); + + nghttp2_stream_attach_item(d, dd); + + /* Add subtree c to a */ + + nghttp2_stream_dep_remove_subtree(c); + nghttp2_stream_dep_add_subtree(a, c); + + /* + * a b + * | + * c--e + * | | + * d f + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(2 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(nghttp2_pq_empty(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Insert b under a */ + + nghttp2_stream_dep_remove_subtree(b); + nghttp2_stream_dep_insert_subtree(a, b); + + /* + * a + * | + * b + * | + * c--e + * | | + * d f + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(1 == nghttp2_pq_size(&a->obq)); + CU_ASSERT(2 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree b */ + + nghttp2_stream_dep_remove_subtree(b); + CU_ASSERT(0 == nghttp2_stream_dep_add_subtree(&session->root, b)); + + /* + * b a + * | + * e--c + * | | + * f d + */ + + CU_ASSERT(!a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(2 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree c, and detach item from b, and then re-add + subtree c under b */ + + nghttp2_stream_dep_remove_subtree(c); + nghttp2_stream_detach_item(b); + nghttp2_stream_dep_add_subtree(b, c); + + /* + * b a + * | + * e--c + * | | + * f d + */ + + CU_ASSERT(!a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(2 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Attach data to a, and add subtree a under b */ + + da = create_data_ob_item(mem); + nghttp2_stream_attach_item(a, da); + nghttp2_stream_dep_remove_subtree(a); + nghttp2_stream_dep_add_subtree(b, a); + + /* + * b + * | + * a--e--c + * | | + * f d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(!f->queued); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(3 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(nghttp2_pq_empty(&e->obq)); + CU_ASSERT(nghttp2_pq_empty(&f->obq)); + + /* Remove subtree c, and add under f */ + nghttp2_stream_dep_remove_subtree(c); + nghttp2_stream_dep_insert_subtree(f, c); + + /* + * b + * | + * a--e + * | + * f + * | + * c + * | + * d + */ + + CU_ASSERT(a->queued); + CU_ASSERT(b->queued); + CU_ASSERT(c->queued); + CU_ASSERT(d->queued); + CU_ASSERT(e->queued); + CU_ASSERT(f->queued); + + CU_ASSERT(nghttp2_pq_empty(&a->obq)); + CU_ASSERT(2 == nghttp2_pq_size(&b->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&c->obq)); + CU_ASSERT(nghttp2_pq_empty(&d->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&e->obq)); + CU_ASSERT(1 == nghttp2_pq_size(&f->obq)); + + /* db has been detached */ + nghttp2_outbound_item_free(db, mem); + free(db); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_stream_get_state(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_mem *mem; + nghttp2_hd_deflater deflater; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_stream *stream; + ssize_t rv; + nghttp2_data_provider data_prd; + nghttp2_frame frame; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + memset(&data_prd, 0, sizeof(data_prd)); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_hd_deflate_init(&deflater, mem); + + CU_ASSERT(NGHTTP2_STREAM_STATE_IDLE == + nghttp2_stream_get_state(nghttp2_session_get_root_stream(session))); + + /* stream 1 HEADERS; without END_STREAM flag set */ + pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 1); + + CU_ASSERT(NULL != stream); + CU_ASSERT(1 == stream->stream_id); + CU_ASSERT(NGHTTP2_STREAM_STATE_OPEN == nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* stream 3 HEADERS; with END_STREAM flag set */ + pack_headers(&bufs, &deflater, 3, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 3); + + CU_ASSERT(NULL != stream); + CU_ASSERT(3 == stream->stream_id); + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* Respond to stream 1 */ + nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Respond to stream 3 */ + nghttp2_submit_response(session, 3, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 3); + + CU_ASSERT(NGHTTP2_STREAM_STATE_CLOSED == nghttp2_stream_get_state(stream)); + + /* stream 5 HEADERS; with END_STREAM flag set */ + pack_headers(&bufs, &deflater, 5, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + nghttp2_bufs_reset(&bufs); + + /* Push stream 2 associated to stream 5 */ + rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv, + ARRLEN(reqnv), NULL); + + CU_ASSERT(2 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Send response to push stream 2 with END_STREAM set */ + nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 2); + + /* At server, pushed stream object is not retained after closed */ + CU_ASSERT(NULL == stream); + + /* Push stream 4 associated to stream 5 */ + rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv, + ARRLEN(reqnv), NULL); + + CU_ASSERT(4 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 4); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_LOCAL == + nghttp2_stream_get_state(stream)); + + /* Send response to push stream 4 without closing */ + data_prd.read_callback = defer_data_source_read_callback; + + nghttp2_submit_response(session, 4, resnv, ARRLEN(resnv), &data_prd); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_find_stream(session, 4); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE == + nghttp2_stream_get_state(stream)); + + /* Create idle stream by PRIORITY frame */ + nghttp2_frame_priority_init(&frame.priority, 7, &pri_spec_default); + + nghttp2_frame_pack_priority(&bufs, &frame.priority); + + nghttp2_frame_priority_free(&frame.priority); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 7); + + CU_ASSERT(NGHTTP2_STREAM_STATE_IDLE == nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Test for client side */ + + nghttp2_session_client_new(&session, &callbacks, NULL); + nghttp2_hd_deflate_init(&deflater, mem); + + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + /* Receive PUSH_PROMISE 2 associated to stream 1 */ + pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, reqnv, + ARRLEN(reqnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_RESERVED_REMOTE == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + /* Receive push response for stream 2 without END_STREAM set */ + pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL == + nghttp2_stream_get_state(stream)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_stream_get_something(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a, *b, *c; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_stream(session, 1); + + CU_ASSERT(nghttp2_session_get_root_stream(session) == + nghttp2_stream_get_parent(a)); + CU_ASSERT(NULL == nghttp2_stream_get_previous_sibling(a)); + CU_ASSERT(NULL == nghttp2_stream_get_next_sibling(a)); + CU_ASSERT(NULL == nghttp2_stream_get_first_child(a)); + + b = open_stream_with_dep(session, 3, a); + c = open_stream_with_dep_weight(session, 5, 11, a); + + CU_ASSERT(a == nghttp2_stream_get_parent(c)); + CU_ASSERT(a == nghttp2_stream_get_parent(b)); + + CU_ASSERT(c == nghttp2_stream_get_first_child(a)); + + CU_ASSERT(b == nghttp2_stream_get_next_sibling(c)); + CU_ASSERT(c == nghttp2_stream_get_previous_sibling(b)); + + CU_ASSERT(27 == nghttp2_stream_get_sum_dependency_weight(a)); + + CU_ASSERT(11 == nghttp2_stream_get_weight(c)); + CU_ASSERT(5 == nghttp2_stream_get_stream_id(c)); + CU_ASSERT(0 == nghttp2_stream_get_stream_id(&session->root)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_find_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + open_recv_stream(session, 1); + + stream = nghttp2_session_find_stream(session, 1); + + CU_ASSERT(NULL != stream); + CU_ASSERT(1 == stream->stream_id); + + stream = nghttp2_session_find_stream(session, 0); + + CU_ASSERT(&session->root == stream); + CU_ASSERT(0 == stream->stream_id); + + stream = nghttp2_session_find_stream(session, 2); + + CU_ASSERT(NULL == stream); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_keep_closed_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const size_t max_concurrent_streams = 5; + nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, + (uint32_t)max_concurrent_streams}; + size_t i; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + for (i = 0; i < max_concurrent_streams; ++i) { + open_recv_stream(session, (int32_t)i * 2 + 1); + } + + CU_ASSERT(0 == session->num_closed_streams); + + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(1 == session->closed_stream_tail->stream_id); + CU_ASSERT(session->closed_stream_tail == session->closed_stream_head); + + nghttp2_session_close_stream(session, 5, NGHTTP2_NO_ERROR); + + CU_ASSERT(2 == session->num_closed_streams); + CU_ASSERT(5 == session->closed_stream_tail->stream_id); + CU_ASSERT(1 == session->closed_stream_head->stream_id); + CU_ASSERT(session->closed_stream_head == + session->closed_stream_tail->closed_prev); + CU_ASSERT(NULL == session->closed_stream_tail->closed_next); + CU_ASSERT(session->closed_stream_tail == + session->closed_stream_head->closed_next); + CU_ASSERT(NULL == session->closed_stream_head->closed_prev); + + open_recv_stream(session, 11); + nghttp2_session_adjust_closed_stream(session); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(5 == session->closed_stream_tail->stream_id); + CU_ASSERT(session->closed_stream_tail == session->closed_stream_head); + CU_ASSERT(NULL == session->closed_stream_head->closed_prev); + CU_ASSERT(NULL == session->closed_stream_head->closed_next); + + open_recv_stream(session, 13); + nghttp2_session_adjust_closed_stream(session); + + CU_ASSERT(0 == session->num_closed_streams); + CU_ASSERT(NULL == session->closed_stream_tail); + CU_ASSERT(NULL == session->closed_stream_head); + + nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(3 == session->closed_stream_head->stream_id); + + /* server initiated stream is not counted to max concurrent limit */ + open_sent_stream(session, 2); + nghttp2_session_adjust_closed_stream(session); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(3 == session->closed_stream_head->stream_id); + + nghttp2_session_close_stream(session, 2, NGHTTP2_NO_ERROR); + + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(3 == session->closed_stream_head->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_keep_idle_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const size_t max_concurrent_streams = 1; + nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, + (uint32_t)max_concurrent_streams}; + int i; + int32_t stream_id; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + /* We at least allow NGHTTP2_MIN_IDLE_STREAM idle streams even if + max concurrent streams is very low. */ + for (i = 0; i < NGHTTP2_MIN_IDLE_STREAMS; ++i) { + open_recv_stream2(session, i * 2 + 1, NGHTTP2_STREAM_IDLE); + nghttp2_session_adjust_idle_stream(session); + } + + CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams); + + stream_id = (NGHTTP2_MIN_IDLE_STREAMS - 1) * 2 + 1; + CU_ASSERT(1 == session->idle_stream_head->stream_id); + CU_ASSERT(stream_id == session->idle_stream_tail->stream_id); + + stream_id += 2; + + open_recv_stream2(session, stream_id, NGHTTP2_STREAM_IDLE); + nghttp2_session_adjust_idle_stream(session); + + CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams); + CU_ASSERT(3 == session->idle_stream_head->stream_id); + CU_ASSERT(stream_id == session->idle_stream_tail->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_detach_idle_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + int i; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + for (i = 1; i <= 3; ++i) { + nghttp2_session_open_stream(session, i, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, NGHTTP2_STREAM_IDLE, NULL); + } + + CU_ASSERT(3 == session->num_idle_streams); + + /* Detach middle stream */ + stream = nghttp2_session_get_stream_raw(session, 2); + + CU_ASSERT(session->idle_stream_head == stream->closed_prev); + CU_ASSERT(session->idle_stream_tail == stream->closed_next); + CU_ASSERT(stream == session->idle_stream_head->closed_next); + CU_ASSERT(stream == session->idle_stream_tail->closed_prev); + + nghttp2_session_detach_idle_stream(session, stream); + + CU_ASSERT(2 == session->num_idle_streams); + + CU_ASSERT(NULL == stream->closed_prev); + CU_ASSERT(NULL == stream->closed_next); + + CU_ASSERT(session->idle_stream_head == + session->idle_stream_tail->closed_prev); + CU_ASSERT(session->idle_stream_tail == + session->idle_stream_head->closed_next); + + /* Detach head stream */ + stream = session->idle_stream_head; + + nghttp2_session_detach_idle_stream(session, stream); + + CU_ASSERT(1 == session->num_idle_streams); + + CU_ASSERT(session->idle_stream_head == session->idle_stream_tail); + CU_ASSERT(NULL == session->idle_stream_head->closed_prev); + CU_ASSERT(NULL == session->idle_stream_head->closed_next); + + /* Detach last stream */ + + stream = session->idle_stream_head; + + nghttp2_session_detach_idle_stream(session, stream); + + CU_ASSERT(0 == session->num_idle_streams); + + CU_ASSERT(NULL == session->idle_stream_head); + CU_ASSERT(NULL == session->idle_stream_tail); + + for (i = 4; i <= 5; ++i) { + nghttp2_session_open_stream(session, i, NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, NGHTTP2_STREAM_IDLE, NULL); + } + + CU_ASSERT(2 == session->num_idle_streams); + + /* Detach tail stream */ + + stream = session->idle_stream_tail; + + nghttp2_session_detach_idle_stream(session, stream); + + CU_ASSERT(1 == session->num_idle_streams); + + CU_ASSERT(session->idle_stream_head == session->idle_stream_tail); + CU_ASSERT(NULL == session->idle_stream_head->closed_prev); + CU_ASSERT(NULL == session->idle_stream_head->closed_next); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_large_dep_tree(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + size_t i; + nghttp2_stream *dep_stream = NULL; + nghttp2_stream *stream; + int32_t stream_id; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream_id = 1; + for (i = 0; i < 250; ++i, stream_id += 2) { + dep_stream = open_stream_with_dep(session, stream_id, dep_stream); + } + + stream_id = 1; + for (i = 0; i < 250; ++i, stream_id += 2) { + stream = nghttp2_session_get_stream(session, stream_id); + CU_ASSERT(nghttp2_stream_dep_find_ancestor(stream, &session->root)); + CU_ASSERT(nghttp2_stream_in_dep_tree(stream)); + } + + nghttp2_session_del(session); +} + +void test_nghttp2_session_graceful_shutdown(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.on_stream_close_callback = on_stream_close_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 301); + open_sent_stream(session, 302); + open_recv_stream(session, 309); + open_recv_stream(session, 311); + open_recv_stream(session, 319); + + CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session)); + + ud.frame_send_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT((1u << 31) - 1 == session->local_last_stream_id); + + CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 311, + NGHTTP2_NO_ERROR, NULL, 0)); + + ud.frame_send_cb_called = 0; + ud.stream_close_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(311 == session->local_last_stream_id); + CU_ASSERT(1 == ud.stream_close_cb_called); + + CU_ASSERT(0 == + nghttp2_session_terminate_session2(session, 301, NGHTTP2_NO_ERROR)); + + ud.frame_send_cb_called = 0; + ud.stream_close_cb_called = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(301 == session->local_last_stream_id); + CU_ASSERT(2 == ud.stream_close_cb_called); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 301)); + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 302)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 309)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 311)); + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 319)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_header_temporal_failure(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_hd_deflater deflater; + nghttp2_nv nv[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")}; + nghttp2_nv *nva; + size_t hdpos; + ssize_t rv; + nghttp2_frame frame; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.on_header_callback = temporal_failure_on_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + frame_pack_bufs_init(&bufs); + + nghttp2_hd_deflate_init(&deflater, mem); + + nghttp2_nv_array_copy(&nva, reqnv, ARRLEN(reqnv), mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1, + NGHTTP2_HCAT_REQUEST, NULL, nva, ARRLEN(reqnv)); + nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + nghttp2_frame_headers_free(&frame.headers, mem); + + /* We are going to create CONTINUATION. First serialize header + block, and then frame header. */ + hdpos = nghttp2_bufs_len(&bufs); + + buf = &bufs.head->buf; + buf->last += NGHTTP2_FRAME_HDLEN; + + nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, &nv[1], 1); + + nghttp2_frame_hd_init(&hd, + nghttp2_bufs_len(&bufs) - hdpos - NGHTTP2_FRAME_HDLEN, + NGHTTP2_CONTINUATION, NGHTTP2_FLAG_END_HEADERS, 1); + + nghttp2_frame_pack_frame_hd(&buf->pos[hdpos], &hd); + + ud.header_cb_called = 0; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.header_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + + /* Make sure no header decompression error occurred */ + CU_ASSERT(NGHTTP2_GOAWAY_NONE == session->goaway_flags); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* Check for PUSH_PROMISE */ + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_session_client_new(&session, &callbacks, &ud); + + open_sent_stream(session, 1); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + ud.header_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(1 == ud.header_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(2 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code); + + nghttp2_session_del(session); + nghttp2_hd_deflate_free(&deflater); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_recv_client_magic(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + ssize_t rv; + nghttp2_frame ping_frame; + uint8_t buf[16]; + + /* enable global nghttp2_enable_strict_preface here */ + nghttp2_enable_strict_preface = 1; + + memset(&callbacks, 0, sizeof(callbacks)); + + /* Check success case */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + rv = nghttp2_session_mem_recv(session, (const uint8_t *)NGHTTP2_CLIENT_MAGIC, + NGHTTP2_CLIENT_MAGIC_LEN); + + CU_ASSERT(rv == NGHTTP2_CLIENT_MAGIC_LEN); + CU_ASSERT(NGHTTP2_IB_READ_FIRST_SETTINGS == session->iframe.state); + + /* Receiving PING is error because we want SETTINGS. */ + nghttp2_frame_ping_init(&ping_frame.ping, NGHTTP2_FLAG_NONE, NULL); + + nghttp2_frame_pack_frame_hd(buf, &ping_frame.ping.hd); + + rv = nghttp2_session_mem_recv(session, buf, NGHTTP2_FRAME_HDLEN); + CU_ASSERT(NGHTTP2_FRAME_HDLEN == rv); + CU_ASSERT(NGHTTP2_IB_IGN_ALL == session->iframe.state); + CU_ASSERT(0 == session->iframe.payloadleft); + + nghttp2_frame_ping_free(&ping_frame.ping); + + nghttp2_session_del(session); + + /* Check bad case */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* Feed magic with one byte less */ + rv = nghttp2_session_mem_recv(session, (const uint8_t *)NGHTTP2_CLIENT_MAGIC, + NGHTTP2_CLIENT_MAGIC_LEN - 1); + + CU_ASSERT(rv == NGHTTP2_CLIENT_MAGIC_LEN - 1); + CU_ASSERT(NGHTTP2_IB_READ_CLIENT_MAGIC == session->iframe.state); + CU_ASSERT(1 == session->iframe.payloadleft); + + rv = nghttp2_session_mem_recv(session, (const uint8_t *)"\0", 1); + + CU_ASSERT(NGHTTP2_ERR_BAD_CLIENT_MAGIC == rv); + + nghttp2_session_del(session); + + /* disable global nghttp2_enable_strict_preface here */ + nghttp2_enable_strict_preface = 0; +} + +void test_nghttp2_session_delete_data_item(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *a; + nghttp2_data_provider prd; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + a = open_recv_stream(session, 1); + open_recv_stream_with_dep(session, 3, a); + + /* We don't care about these members, since we won't send data */ + prd.source.ptr = NULL; + prd.read_callback = fail_data_source_read_callback; + + CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 1, &prd)); + CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 3, &prd)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_open_idle_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_stream *opened_stream; + nghttp2_priority_spec pri_spec; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_priority_spec_init(&pri_spec, 0, 3, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(NULL == stream->closed_prev); + CU_ASSERT(NULL == stream->closed_next); + CU_ASSERT(1 == session->num_idle_streams); + CU_ASSERT(session->idle_stream_head == stream); + CU_ASSERT(session->idle_stream_tail == stream); + + opened_stream = open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + CU_ASSERT(stream == opened_stream); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(0 == session->num_idle_streams); + CU_ASSERT(NULL == session->idle_stream_head); + CU_ASSERT(NULL == session->idle_stream_tail); + + nghttp2_frame_priority_free(&frame.priority); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_cancel_reserved_remote(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_frame frame; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + stream = open_recv_stream2(session, 2, NGHTTP2_STREAM_RESERVED); + + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, NGHTTP2_CANCEL); + + CU_ASSERT(NGHTTP2_STREAM_CLOSING == stream->state); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nvlen = ARRLEN(resnv); + nghttp2_nv_array_copy(&nva, resnv, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2, + NGHTTP2_HCAT_PUSH_RESPONSE, NULL, nva, nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + /* stream is not dangling, so assign NULL */ + stream = NULL; + + /* No RST_STREAM or GOAWAY is generated since stream should be in + NGHTTP2_STREAM_CLOSING and push response should be ignored. */ + CU_ASSERT(0 == nghttp2_outbound_queue_size(&session->ob_reg)); + + /* Check that we can receive push response HEADERS while RST_STREAM + is just queued. */ + open_recv_stream2(session, 4, NGHTTP2_STREAM_RESERVED); + + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 2, NGHTTP2_CANCEL); + + nghttp2_bufs_reset(&bufs); + + frame.hd.stream_id = 4; + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_reg)); + + nghttp2_frame_headers_free(&frame.headers, mem); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_reset_pending_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + int32_t stream_id; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + callbacks.on_stream_close_callback = on_stream_close_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + stream_id = nghttp2_submit_request(session, NULL, NULL, 0, NULL, NULL); + CU_ASSERT(stream_id >= 1); + + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, + NGHTTP2_CANCEL); + + session->remote_settings.max_concurrent_streams = 0; + + /* RST_STREAM cancels pending HEADERS and is not actually sent. */ + ud.frame_send_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(0 == ud.frame_send_cb_called); + + stream = nghttp2_session_get_stream(session, stream_id); + + CU_ASSERT(NULL == stream); + + /* See HEADERS is not sent. on_stream_close is called just like + transmission failure. */ + session->remote_settings.max_concurrent_streams = 1; + + ud.frame_not_send_cb_called = 0; + ud.stream_close_error_code = 0; + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1 == ud.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_HEADERS == ud.not_sent_frame_type); + CU_ASSERT(NGHTTP2_CANCEL == ud.stream_close_error_code); + + stream = nghttp2_session_get_stream(session, stream_id); + + CU_ASSERT(NULL == stream); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_send_data_callback(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + accumulator acc; + nghttp2_frame_hd hd; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = accumulator_send_callback; + callbacks.send_data_callback = send_data_callback; + + data_prd.read_callback = no_copy_data_source_read_callback; + + acc.length = 0; + ud.acc = &acc; + + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + open_sent_stream(session, 1); + + nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT((NGHTTP2_FRAME_HDLEN + NGHTTP2_DATA_PAYLOADLEN) * 2 == acc.length); + + nghttp2_frame_unpack_frame_hd(&hd, acc.buf); + + CU_ASSERT(16384 == hd.length); + CU_ASSERT(NGHTTP2_DATA == hd.type); + CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags); + + nghttp2_frame_unpack_frame_hd(&hd, acc.buf + NGHTTP2_FRAME_HDLEN + hd.length); + + CU_ASSERT(16384 == hd.length); + CU_ASSERT(NGHTTP2_DATA == hd.type); + CU_ASSERT(NGHTTP2_FLAG_END_STREAM == hd.flags); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_on_begin_headers_temporal_failure(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_bufs bufs; + nghttp2_mem *mem; + ssize_t rv; + nghttp2_hd_deflater deflater; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + nghttp2_hd_deflate_init(&deflater, mem); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_begin_headers_callback = + temporal_failure_on_begin_headers_callback; + callbacks.on_header_callback = on_header_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.send_callback = null_send_callback; + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + ud.header_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.header_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code); + + nghttp2_session_del(session); + nghttp2_hd_deflate_free(&deflater); + + nghttp2_bufs_reset(&bufs); + /* check for PUSH_PROMISE */ + nghttp2_hd_deflate_init(&deflater, mem); + nghttp2_session_client_new(&session, &callbacks, &ud); + + open_sent_stream(session, 1); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + ud.header_cb_called = 0; + ud.frame_recv_cb_called = 0; + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == rv); + CU_ASSERT(0 == ud.header_cb_called); + CU_ASSERT(0 == ud.frame_recv_cb_called); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(2 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INTERNAL_ERROR == item->frame.rst_stream.error_code); + + nghttp2_session_del(session); + nghttp2_hd_deflate_free(&deflater); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_defer_then_close(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider prd; + int rv; + const uint8_t *datap; + ssize_t datalen; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + prd.read_callback = defer_data_source_read_callback; + + rv = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), &prd, NULL); + CU_ASSERT(rv > 0); + + /* This sends HEADERS */ + datalen = nghttp2_session_mem_send(session, &datap); + + CU_ASSERT(datalen > 0); + + /* This makes DATA item deferred */ + datalen = nghttp2_session_mem_send(session, &datap); + + CU_ASSERT(datalen == 0); + + nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_CANCEL); + + /* Assertion failure; GH-264 */ + rv = nghttp2_session_on_rst_stream_received(session, &frame); + + CU_ASSERT(rv == 0); + + nghttp2_session_del(session); +} + +static int submit_response_on_stream_close(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data) { + nghttp2_data_provider data_prd; + (void)error_code; + (void)user_data; + + data_prd.read_callback = temporal_failure_data_source_read_callback; + + // Attempt to submit response or data to the stream being closed + switch (stream_id) { + case 1: + CU_ASSERT(0 == nghttp2_submit_response(session, stream_id, resnv, + ARRLEN(resnv), &data_prd)); + break; + case 3: + CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, stream_id, + &data_prd)); + break; + } + + return 0; +} + +void test_nghttp2_session_detach_item_from_closed_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.send_callback = null_send_callback; + callbacks.on_stream_close_callback = submit_response_on_stream_close; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + open_recv_stream(session, 1); + open_recv_stream(session, 3); + + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_flooding(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_frame frame; + nghttp2_mem *mem; + size_t i; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(callbacks)); + + /* PING ACK */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + nghttp2_frame_pack_ping(&bufs, &frame.ping); + nghttp2_frame_ping_free(&frame.ping); + + buf = &bufs.head->buf; + + for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) { + CU_ASSERT( + (ssize_t)nghttp2_buf_len(buf) == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); + } + + CU_ASSERT(NGHTTP2_ERR_FLOODED == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); + + nghttp2_session_del(session); + + /* SETTINGS ACK */ + nghttp2_bufs_reset(&bufs); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0); + nghttp2_frame_pack_settings(&bufs, &frame.settings); + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + + for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) { + CU_ASSERT( + (ssize_t)nghttp2_buf_len(buf) == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); + } + + CU_ASSERT(NGHTTP2_ERR_FLOODED == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); + + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_change_stream_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream1, *stream2, *stream3, *stream5; + nghttp2_priority_spec pri_spec; + int rv; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream1 = open_recv_stream(session, 1); + stream3 = open_recv_stream_with_dep_weight(session, 3, 199, stream1); + stream2 = open_sent_stream_with_dep_weight(session, 2, 101, stream3); + + nghttp2_priority_spec_init(&pri_spec, 1, 256, 0); + + rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec); + + CU_ASSERT(0 == rv); + + CU_ASSERT(stream1 == stream2->dep_prev); + CU_ASSERT(256 == stream2->weight); + + /* Cannot change stream which does not exist */ + rv = nghttp2_session_change_stream_priority(session, 5, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to depend on itself */ + rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to change priority of root stream (0) */ + rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* Depends on the non-existing idle stream. This creates that idle + stream. */ + nghttp2_priority_spec_init(&pri_spec, 5, 9, 1); + + rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec); + + CU_ASSERT(0 == rv); + + stream5 = nghttp2_session_get_stream_raw(session, 5); + + CU_ASSERT(NULL != stream5); + CU_ASSERT(&session->root == stream5->dep_prev); + CU_ASSERT(stream5 == stream2->dep_prev); + CU_ASSERT(9 == stream2->weight); + + nghttp2_session_del(session); + + /* Check that this works in client session too */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream1 = open_sent_stream(session, 1); + + nghttp2_priority_spec_init(&pri_spec, 5, 9, 1); + + rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec); + + CU_ASSERT(0 == rv); + + stream5 = nghttp2_session_get_stream_raw(session, 5); + + CU_ASSERT(NULL != stream5); + CU_ASSERT(&session->root == stream5->dep_prev); + CU_ASSERT(stream5 == stream1->dep_prev); + CU_ASSERT(9 == stream1->weight); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_change_extpri_stream_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + nghttp2_option *option; + nghttp2_extension frame; + nghttp2_ext_priority_update priority_update; + nghttp2_extpri extpri, nextpri; + nghttp2_stream *stream; + static const uint8_t field_value[] = "u=2"; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + frame_pack_bufs_init(&bufs); + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, + NGHTTP2_PRIORITY_UPDATE); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + session->pending_no_rfc7540_priorities = 1; + + open_recv_stream(session, 1); + + extpri.urgency = NGHTTP2_EXTPRI_URGENCY_LOW + 1; + extpri.inc = 1; + + rv = nghttp2_session_change_extpri_stream_priority( + session, 1, &extpri, /* ignore_client_signal = */ 0); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + rv = nghttp2_session_get_extpri_stream_priority(session, &nextpri, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == nextpri.urgency); + CU_ASSERT(1 == nextpri.inc); + + /* Client can still update stream priority. */ + frame.payload = &priority_update; + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + nghttp2_frame_pack_priority_update(&bufs, &frame); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(2 == stream->extpri); + + /* Start to ignore client priority signal for this stream. */ + rv = nghttp2_session_change_extpri_stream_priority( + session, 1, &extpri, /* ignore_client_signal = */ 1); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_session_del(session); + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_create_idle_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream2, *stream4, *stream8, *stream10; + nghttp2_priority_spec pri_spec; + int rv; + int i; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + stream2 = open_sent_stream(session, 2); + + nghttp2_priority_spec_init(&pri_spec, 2, 111, 1); + + rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec); + + CU_ASSERT(0 == rv); + + stream4 = nghttp2_session_get_stream_raw(session, 4); + + CU_ASSERT(4 == stream4->stream_id); + CU_ASSERT(111 == stream4->weight); + CU_ASSERT(stream2 == stream4->dep_prev); + CU_ASSERT(stream4 == stream2->dep_next); + + /* If pri_spec->stream_id does not exist, and it is idle stream, it + is created too */ + nghttp2_priority_spec_init(&pri_spec, 10, 109, 0); + + rv = nghttp2_session_create_idle_stream(session, 8, &pri_spec); + + CU_ASSERT(0 == rv); + + stream8 = nghttp2_session_get_stream_raw(session, 8); + stream10 = nghttp2_session_get_stream_raw(session, 10); + + CU_ASSERT(8 == stream8->stream_id); + CU_ASSERT(109 == stream8->weight); + CU_ASSERT(10 == stream10->stream_id); + CU_ASSERT(16 == stream10->weight); + CU_ASSERT(stream10 == stream8->dep_prev); + CU_ASSERT(&session->root == stream10->dep_prev); + + /* It is an error to attempt to create already existing idle + stream */ + rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to depend on itself */ + pri_spec.stream_id = 6; + + rv = nghttp2_session_create_idle_stream(session, 6, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to create root stream (0) as idle stream */ + rv = nghttp2_session_create_idle_stream(session, 0, &pri_spec); + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + /* It is an error to create non-idle stream */ + session->last_sent_stream_id = 20; + pri_spec.stream_id = 2; + + rv = nghttp2_session_create_idle_stream(session, 18, &pri_spec); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); + + nghttp2_session_del(session); + + /* Check that this works in client session too */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_priority_spec_init(&pri_spec, 4, 99, 1); + + rv = nghttp2_session_create_idle_stream(session, 2, &pri_spec); + + CU_ASSERT(0 == rv); + + stream4 = nghttp2_session_get_stream_raw(session, 4); + stream2 = nghttp2_session_get_stream_raw(session, 2); + + CU_ASSERT(NULL != stream4); + CU_ASSERT(NULL != stream2); + CU_ASSERT(&session->root == stream4->dep_prev); + CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream4->weight); + CU_ASSERT(stream4 == stream2->dep_prev); + CU_ASSERT(99 == stream2->weight); + + nghttp2_session_del(session); + + /* Check that idle stream is reduced when nghttp2_session_send() is + called. */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + session->local_settings.max_concurrent_streams = 30; + + nghttp2_priority_spec_init(&pri_spec, 0, 16, 0); + for (i = 0; i < 100; ++i) { + rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec); + + CU_ASSERT(0 == rv); + + nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0); + } + + CU_ASSERT(100 == session->num_idle_streams); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(30 == session->num_idle_streams); + CU_ASSERT(141 == session->idle_stream_head->stream_id); + + nghttp2_session_del(session); + + /* Check that idle stream is reduced when nghttp2_session_mem_recv() is + called. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + session->local_settings.max_concurrent_streams = 30; + + nghttp2_priority_spec_init(&pri_spec, 0, 16, 0); + for (i = 0; i < 100; ++i) { + rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec); + + CU_ASSERT(0 == rv); + + nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0); + } + + CU_ASSERT(100 == session->num_idle_streams); + CU_ASSERT(0 == nghttp2_session_mem_recv(session, NULL, 0)); + CU_ASSERT(30 == session->num_idle_streams); + CU_ASSERT(141 == session->idle_stream_head->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_repeated_priority_change(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_priority_spec pri_spec; + int32_t stream_id, last_stream_id; + int32_t max_streams = 20; + + memset(&callbacks, 0, sizeof(callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + session->local_settings.max_concurrent_streams = (uint32_t)max_streams; + + /* 1 -> 0 */ + nghttp2_priority_spec_init(&pri_spec, 0, 16, 0); + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + nghttp2_frame_priority_free(&frame.priority); + + last_stream_id = max_streams * 2 + 1; + + for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) { + /* 1 -> stream_id */ + nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0); + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + nghttp2_frame_priority_free(&frame.priority); + } + + CU_ASSERT(20 == session->num_idle_streams); + CU_ASSERT(1 == session->idle_stream_head->stream_id); + + /* 1 -> last_stream_id */ + nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0); + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + nghttp2_frame_priority_free(&frame.priority); + + CU_ASSERT(20 == session->num_idle_streams); + CU_ASSERT(3 == session->idle_stream_head->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_repeated_priority_submission(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_priority_spec pri_spec; + int32_t stream_id, last_stream_id; + uint32_t max_streams = NGHTTP2_MIN_IDLE_STREAMS; + + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + session->local_settings.max_concurrent_streams = max_streams; + + /* 1 -> 0 */ + nghttp2_priority_spec_init(&pri_spec, 0, 16, 0); + + CU_ASSERT(0 == + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + + last_stream_id = (int32_t)(max_streams * 2 + 1); + + for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) { + /* 1 -> stream_id */ + nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0); + + CU_ASSERT( + 0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + } + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(max_streams == session->num_idle_streams); + CU_ASSERT(1 == session->idle_stream_head->stream_id); + + /* 1 -> last_stream_id */ + nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0); + + CU_ASSERT(0 == + nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(max_streams == session->num_idle_streams); + CU_ASSERT(3 == session->idle_stream_head->stream_id); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_set_local_window_size(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_outbound_item *item; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = open_sent_stream(session, 1); + stream->recv_window_size = 4096; + + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 65536)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + stream->local_window_size); + CU_ASSERT(4096 == stream->recv_window_size); + CU_ASSERT(65536 - 4096 == + nghttp2_session_get_stream_local_window_size(session, 1)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1 == item->frame.window_update.hd.stream_id); + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 32768)); + CU_ASSERT(32768 == stream->local_window_size); + CU_ASSERT(-28672 == stream->recv_window_size); + CU_ASSERT(32768 == stream->recv_reduction); + CU_ASSERT(65536 - 4096 == + nghttp2_session_get_stream_local_window_size(session, 1)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 49152)); + CU_ASSERT(49152 == stream->local_window_size); + CU_ASSERT(-12288 == stream->recv_window_size); + CU_ASSERT(16384 == stream->recv_reduction); + CU_ASSERT(65536 - 4096 == + nghttp2_session_get_stream_local_window_size(session, 1)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Increase local window again */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 65537)); + CU_ASSERT(65537 == stream->local_window_size); + CU_ASSERT(4096 == stream->recv_window_size); + CU_ASSERT(0 == stream->recv_reduction); + CU_ASSERT(65537 - 4096 == + nghttp2_session_get_stream_local_window_size(session, 1)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Check connection-level flow control */ + session->recv_window_size = 4096; + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 65536)); + CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 == + session->local_window_size); + CU_ASSERT(4096 == session->recv_window_size); + CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.window_update.hd.stream_id); + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + /* Go decrement part */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 32768)); + CU_ASSERT(32768 == session->local_window_size); + CU_ASSERT(-28672 == session->recv_window_size); + CU_ASSERT(32768 == session->recv_reduction); + CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item == NULL); + + /* Increase local window size */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 49152)); + CU_ASSERT(49152 == session->local_window_size); + CU_ASSERT(-12288 == session->recv_window_size); + CU_ASSERT(16384 == session->recv_reduction); + CU_ASSERT(65536 - 4096 == nghttp2_session_get_local_window_size(session)); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Increase local window again */ + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 65537)); + CU_ASSERT(65537 == session->local_window_size); + CU_ASSERT(4096 == session->recv_window_size); + CU_ASSERT(0 == session->recv_reduction); + CU_ASSERT(65537 - 4096 == nghttp2_session_get_local_window_size(session)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(1 == item->frame.window_update.window_size_increment); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_session_del(session); + + /* Make sure that nghttp2_session_set_local_window_size submits + WINDOW_UPDATE if necessary to increase stream-level window. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + stream = open_sent_stream(session, 1); + stream->recv_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; + + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 1, 0)); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(0 == nghttp2_session_get_stream_local_window_size(session, 1)); + /* This should submit WINDOW_UPDATE frame because stream-level + receiving window is now full. */ + CU_ASSERT(0 == + nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 1, + NGHTTP2_INITIAL_WINDOW_SIZE)); + CU_ASSERT(0 == stream->recv_window_size); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + nghttp2_session_get_stream_local_window_size(session, 1)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); + + /* Make sure that nghttp2_session_set_local_window_size submits + WINDOW_UPDATE if necessary to increase connection-level + window. */ + nghttp2_session_client_new(&session, &callbacks, NULL); + session->recv_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; + + CU_ASSERT(0 == nghttp2_session_set_local_window_size( + session, NGHTTP2_FLAG_NONE, 0, 0)); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(0 == nghttp2_session_get_local_window_size(session)); + /* This should submit WINDOW_UPDATE frame because connection-level + receiving window is now full. */ + CU_ASSERT(0 == + nghttp2_session_set_local_window_size(session, NGHTTP2_FLAG_NONE, 0, + NGHTTP2_INITIAL_WINDOW_SIZE)); + CU_ASSERT(0 == session->recv_window_size); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + nghttp2_session_get_local_window_size(session)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type); + CU_ASSERT(0 == item->frame.hd.stream_id); + CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE == + item->frame.window_update.window_size_increment); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_cancel_from_before_frame_send(void) { + int rv; + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + nghttp2_settings_entry iv; + nghttp2_data_provider data_prd; + int32_t stream_id; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.before_frame_send_callback = cancel_before_frame_send_callback; + callbacks.on_frame_not_send_callback = on_frame_not_send_callback; + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + iv.settings_id = 0; + iv.value = 1000000009; + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + CU_ASSERT(0 == rv); + + ud.frame_send_cb_called = 0; + ud.before_frame_send_cb_called = 0; + ud.frame_not_send_cb_called = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(1 == ud.before_frame_send_cb_called); + CU_ASSERT(1 == ud.frame_not_send_cb_called); + + data_prd.source.ptr = NULL; + data_prd.read_callback = temporal_failure_data_source_read_callback; + + stream_id = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), + &data_prd, NULL); + + CU_ASSERT(stream_id > 0); + + ud.frame_send_cb_called = 0; + ud.before_frame_send_cb_called = 0; + ud.frame_not_send_cb_called = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(1 == ud.before_frame_send_cb_called); + CU_ASSERT(1 == ud.frame_not_send_cb_called); + + stream = nghttp2_session_get_stream_raw(session, stream_id); + + CU_ASSERT(NULL == stream); + + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + stream_id = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, reqnv, + ARRLEN(reqnv), NULL); + + CU_ASSERT(stream_id > 0); + + ud.frame_send_cb_called = 0; + ud.before_frame_send_cb_called = 0; + ud.frame_not_send_cb_called = 0; + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(1 == ud.before_frame_send_cb_called); + CU_ASSERT(1 == ud.frame_not_send_cb_called); + + stream = nghttp2_session_get_stream_raw(session, stream_id); + + CU_ASSERT(NULL == stream); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_too_many_settings(void) { + nghttp2_session *session; + nghttp2_option *option; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + my_user_data ud; + nghttp2_settings_entry iv[3]; + nghttp2_mem *mem; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.send_callback = null_send_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_max_settings(option, 1); + + nghttp2_session_client_new2(&session, &callbacks, &ud, option); + + CU_ASSERT(1 == session->max_settings); + + nghttp2_option_del(option); + + iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; + iv[0].value = 3000; + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16384; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2), + 2); + + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); + + ud.frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + + nghttp2_bufs_reset(&bufs); + nghttp2_bufs_free(&bufs); + nghttp2_session_del(session); +} + +static void +prepare_session_removed_closed_stream(nghttp2_session *session, + nghttp2_hd_deflater *deflater) { + int rv; + nghttp2_settings_entry iv; + nghttp2_bufs bufs; + nghttp2_mem *mem; + ssize_t nread; + int i; + nghttp2_stream *stream; + nghttp2_frame_hd hd; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + iv.settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv.value = 2; + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + for (i = 1; i <= 3; i += 2) { + rv = pack_headers(&bufs, deflater, i, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + CU_ASSERT(0 == rv); + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + nghttp2_bufs_reset(&bufs); + } + + nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR); + + rv = pack_headers(&bufs, deflater, 5, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, reqnv, + ARRLEN(reqnv), mem); + + CU_ASSERT(0 == rv); + + /* Receiving stream 5 will erase stream 3 from closed stream list */ + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + stream = nghttp2_session_get_stream_raw(session, 3); + + CU_ASSERT(NULL == stream); + + /* Since the current max concurrent streams is + NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS, receiving frame on stream + 3 is ignored. */ + nghttp2_bufs_reset(&bufs); + rv = pack_headers(&bufs, deflater, 3, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + trailernv, ARRLEN(trailernv), mem); + + CU_ASSERT(0 == rv); + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 3); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + /* Now server receives SETTINGS ACK */ + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_SETTINGS, NGHTTP2_FLAG_ACK, 0); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_removed_closed_stream(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + int rv; + nghttp2_hd_deflater deflater; + nghttp2_bufs bufs; + nghttp2_mem *mem; + ssize_t nread; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(callbacks)); + + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + /* Now local max concurrent streams is still unlimited, pending max + concurrent streams is now 2. */ + + nghttp2_hd_deflate_init(&deflater, mem); + + prepare_session_removed_closed_stream(session, &deflater); + + /* Now current max concurrent streams is 2. Receiving frame on + stream 3 is ignored because we have no stream object for stream + 3. */ + nghttp2_bufs_reset(&bufs); + rv = pack_headers(&bufs, &deflater, 3, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + trailernv, ARRLEN(trailernv), mem); + + CU_ASSERT(0 == rv); + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL == item); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_session_server_new(&session, &callbacks, NULL); + nghttp2_hd_deflate_init(&deflater, mem); + /* Same setup, and then receive DATA instead of HEADERS */ + + prepare_session_removed_closed_stream(session, &deflater); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 3); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + nread = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_bufs_len(&bufs)); + + CU_ASSERT((ssize_t)nghttp2_bufs_len(&bufs) == nread); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL == item); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +static ssize_t pause_once_data_source_read_callback( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t len, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + my_user_data *ud = user_data; + if (ud->data_source_read_cb_paused == 0) { + ++ud->data_source_read_cb_paused; + return NGHTTP2_ERR_PAUSE; + } + + return fixed_length_data_source_read_callback(session, stream_id, buf, len, + data_flags, source, user_data); +} + +void test_nghttp2_session_pause_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + + data_prd.read_callback = pause_once_data_source_read_callback; + ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + open_recv_stream(session, 1); + + CU_ASSERT( + 0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd)); + + ud.frame_send_cb_called = 0; + ud.data_source_read_cb_paused = 0; + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(0 == ud.frame_send_cb_called); + CU_ASSERT(NULL == session->aob.item); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == ud.frame_send_cb_called); + CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_no_closed_streams(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_option *option; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_option_new(&option); + nghttp2_option_set_no_closed_streams(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + open_recv_stream(session, 1); + + nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR); + + CU_ASSERT(0 == session->num_closed_streams); + + nghttp2_session_del(session); + nghttp2_option_del(option); +} + +void test_nghttp2_session_set_stream_user_data(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + int32_t stream_id; + int user_data1, user_data2; + int rv; + const uint8_t *datap; + ssize_t datalen; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_client_new(&session, &callbacks, NULL); + + stream_id = nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, + &user_data1); + + rv = nghttp2_session_set_stream_user_data(session, stream_id, &user_data2); + + CU_ASSERT(0 == rv); + + datalen = nghttp2_session_mem_send(session, &datap); + + CU_ASSERT(datalen > 0); + + CU_ASSERT(&user_data2 == + nghttp2_session_get_stream_user_data(session, stream_id)); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_session_set_stream_user_data(session, 2, NULL)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_no_rfc7540_priorities(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_data_provider data_prd; + my_user_data ud; + nghttp2_outbound_item *item; + nghttp2_mem *mem; + nghttp2_settings_entry iv; + nghttp2_priority_spec pri_spec; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* Do not use a dependency tree if SETTINGS_NO_RFC7540_PRIORITIES = + 1. */ + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.data_source_length = 128 * 1024; + CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud)); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + + open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING); + CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), + &data_prd)); + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen); + assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen, + mem); + + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == nghttp2_pq_size( + &session->sched[NGHTTP2_EXTPRI_DEFAULT_URGENCY].ob_data)); + CU_ASSERT(nghttp2_pq_empty(&session->root.obq)); + + nghttp2_session_del(session); + + /* Priorities are sent as is before client receives + SETTINGS_NO_RFC7540_PRIORITIES = 1 from server. */ + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1)); + + pri_spec.stream_id = 5; + pri_spec.weight = 111; + pri_spec.exclusive = 1; + + CU_ASSERT(1 == nghttp2_submit_request(session, &pri_spec, reqnv, + ARRLEN(reqnv), NULL, NULL)); + + item = nghttp2_outbound_queue_top(&session->ob_syn); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(pri_spec.stream_id == item->frame.headers.pri_spec.stream_id); + CU_ASSERT(pri_spec.weight == item->frame.headers.pri_spec.weight); + CU_ASSERT(pri_spec.exclusive == item->frame.headers.pri_spec.exclusive); + + nghttp2_session_del(session); + + /* Priorities are defaulted if client received + SETTINGS_NO_RFC7540_PRIORITIES = 1 from server. */ + CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL)); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1)); + + session->remote_settings.no_rfc7540_priorities = 1; + + pri_spec.stream_id = 5; + pri_spec.weight = 111; + pri_spec.exclusive = 1; + + CU_ASSERT(1 == nghttp2_submit_request(session, &pri_spec, reqnv, + ARRLEN(reqnv), NULL, NULL)); + + item = nghttp2_outbound_queue_top(&session->ob_syn); + + CU_ASSERT(NULL != item); + CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type); + CU_ASSERT(nghttp2_priority_spec_check_default(&item->frame.headers.pri_spec)); + + nghttp2_session_del(session); +} + +void test_nghttp2_session_server_fallback_rfc7540_priorities(void) { + nghttp2_session *session; + nghttp2_option *option; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + nghttp2_settings_entry iv; + nghttp2_mem *mem; + nghttp2_hd_deflater deflater; + nghttp2_nv *nva; + size_t nvlen; + nghttp2_priority_spec pri_spec; + nghttp2_stream *anchor_stream, *stream; + my_user_data ud; + nghttp2_ext_priority_update priority_update; + static const uint8_t field_value[] = "u=0"; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_server_fallback_rfc7540_priorities(option, 1); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 1; + + /* Server falls back to RFC 7540 priorities. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0); + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(1 == session->fallback_rfc7540_priorities); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_priority_spec_init(&pri_spec, 3, 111, 1); + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + anchor_stream = nghttp2_session_get_stream_raw(session, 3); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state); + CU_ASSERT( + !(anchor_stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)); + CU_ASSERT(&session->root == anchor_stream->dep_prev); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)); + CU_ASSERT(anchor_stream == stream->dep_prev); + + /* Make sure that PRIORITY frame updates stream priority. */ + nghttp2_priority_spec_init(&pri_spec, 5, 1, 0); + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_priority(&bufs, &frame.priority); + + nghttp2_frame_priority_free(&frame.priority); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + anchor_stream = nghttp2_session_get_stream_raw(session, 5); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state); + CU_ASSERT(&session->root == anchor_stream->dep_prev); + CU_ASSERT(anchor_stream == stream->dep_prev); + + /* Make sure that PRIORITY_UPDATE frame is ignored. */ + frame.ext.payload = &priority_update; + nghttp2_frame_priority_update_init(&frame.ext, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + nghttp2_bufs_reset(&bufs); + nghttp2_frame_pack_priority_update(&bufs, &frame.ext); + + ud.frame_recv_cb_called = 0; + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == ud.frame_recv_cb_called); + CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == stream->extpri); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Server does not fallback to RFC 7540 priorities. */ + nghttp2_session_server_new2(&session, &callbacks, &ud, option); + + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES; + iv.value = 0; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(&iv, 1), 1); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(0 == session->fallback_rfc7540_priorities); + + nghttp2_hd_deflate_init(&deflater, mem); + + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_priority_spec_init(&pri_spec, 3, 111, 1); + nghttp2_frame_headers_init(&frame.headers, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY, + 1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen); + nghttp2_bufs_reset(&bufs); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(NULL == nghttp2_session_get_stream_raw(session, 3)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_session_stream_reset_ratelim(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_frame frame; + ssize_t rv; + nghttp2_bufs bufs; + nghttp2_buf *buf; + nghttp2_mem *mem; + size_t i; + nghttp2_hd_deflater deflater; + size_t nvlen; + nghttp2_nv *nva; + int32_t stream_id; + nghttp2_outbound_item *item; + nghttp2_option *option; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_option_new(&option); + nghttp2_option_set_stream_reset_rate_limit( + option, NGHTTP2_DEFAULT_STREAM_RESET_BURST, 0); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0); + rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); + + CU_ASSERT(0 == rv); + + nghttp2_frame_settings_free(&frame.settings, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + /* Send SETTINGS ACK */ + rv = nghttp2_session_send(session); + + CU_ASSERT(0 == rv); + + nghttp2_hd_deflate_init(&deflater, mem); + + for (i = 0; i < NGHTTP2_DEFAULT_STREAM_RESET_BURST + 2; ++i) { + stream_id = (int32_t)(i * 2 + 1); + + nghttp2_bufs_reset(&bufs); + + /* HEADERS */ + nvlen = ARRLEN(reqnv); + nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, + stream_id, NGHTTP2_HCAT_HEADERS, NULL, nva, + nvlen); + rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater); + + CU_ASSERT(0 == rv); + + nghttp2_frame_headers_free(&frame.headers, mem); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + nghttp2_bufs_reset(&bufs); + + /* RST_STREAM */ + nghttp2_frame_rst_stream_init(&frame.rst_stream, stream_id, + NGHTTP2_NO_ERROR); + nghttp2_frame_pack_rst_stream(&bufs, &frame.rst_stream); + nghttp2_frame_rst_stream_free(&frame.rst_stream); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + + if (i < NGHTTP2_DEFAULT_STREAM_RESET_BURST) { + CU_ASSERT(0 == nghttp2_outbound_queue_size(&session->ob_reg)); + + continue; + } + + CU_ASSERT(1 == nghttp2_outbound_queue_size(&session->ob_reg)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); + CU_ASSERT(NGHTTP2_DEFAULT_STREAM_RESET_BURST * 2 + 1 == + item->frame.goaway.last_stream_id); + } + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); + nghttp2_option_del(option); +} + +static void check_nghttp2_http_recv_headers_fail( + nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id, + int stream_state, const nghttp2_nv *nva, size_t nvlen) { + nghttp2_mem *mem; + ssize_t rv; + nghttp2_outbound_item *item; + nghttp2_bufs bufs; + my_user_data *ud; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + ud = session->user_data; + + if (stream_state != -1) { + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + open_sent_stream2(session, stream_id, (nghttp2_stream_state)stream_state); + } else { + open_recv_stream2(session, stream_id, (nghttp2_stream_state)stream_state); + } + } + + rv = pack_headers(&bufs, deflater, stream_id, NGHTTP2_FLAG_END_HEADERS, nva, + nvlen, mem); + CU_ASSERT(0 == rv); + + ud->invalid_frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == ud->invalid_frame_recv_cb_called); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_free(&bufs); +} + +static void check_nghttp2_http_recv_headers_ok( + nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id, + int stream_state, const nghttp2_nv *nva, size_t nvlen) { + nghttp2_mem *mem; + ssize_t rv; + nghttp2_bufs bufs; + my_user_data *ud; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + ud = session->user_data; + + if (stream_state != -1) { + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + open_sent_stream2(session, stream_id, (nghttp2_stream_state)stream_state); + } else { + open_recv_stream2(session, stream_id, (nghttp2_stream_state)stream_state); + } + } + + rv = pack_headers(&bufs, deflater, stream_id, NGHTTP2_FLAG_END_HEADERS, nva, + nvlen, mem); + CU_ASSERT(0 == rv); + + ud->frame_recv_cb_called = 0; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(1 == ud->frame_recv_cb_called); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_mandatory_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + my_user_data ud; + /* test case for response */ + const nghttp2_nv nostatus_resnv[] = {MAKE_NV("server", "foo")}; + const nghttp2_nv dupstatus_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV(":status", "200")}; + const nghttp2_nv badpseudo_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV(":scheme", "https")}; + const nghttp2_nv latepseudo_resnv[] = {MAKE_NV("server", "foo"), + MAKE_NV(":status", "200")}; + const nghttp2_nv badstatus_resnv[] = {MAKE_NV(":status", "2000")}; + const nghttp2_nv badcl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "-1")}; + const nghttp2_nv dupcl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0"), + MAKE_NV("content-length", "0")}; + const nghttp2_nv badhd_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("connection", "close")}; + const nghttp2_nv cl1xx_resnv[] = {MAKE_NV(":status", "100"), + MAKE_NV("content-length", "0")}; + const nghttp2_nv cl204_resnv[] = {MAKE_NV(":status", "204"), + MAKE_NV("content-length", "0")}; + const nghttp2_nv clnonzero204_resnv[] = {MAKE_NV(":status", "204"), + MAKE_NV("content-length", "100")}; + const nghttp2_nv status101_resnv[] = {MAKE_NV(":status", "101")}; + const nghttp2_nv unexpectedhost_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("host", "/localhost")}; + + /* test case for request */ + const nghttp2_nv nopath_reqnv[] = {MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost")}; + const nghttp2_nv earlyconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), MAKE_NV(":authority", "localhost")}; + const nghttp2_nv lateconnect_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost")}; + const nghttp2_nv duppath_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV(":path", "/")}; + const nghttp2_nv badcl_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("content-length", "-1")}; + const nghttp2_nv dupcl_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("content-length", "0"), MAKE_NV("content-length", "0")}; + const nghttp2_nv badhd_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("connection", "close")}; + const nghttp2_nv badauthority_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "\x0d\x0alocalhost"), MAKE_NV(":path", "/")}; + const nghttp2_nv badhdbtw_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("foo", "\x0d\x0a"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":path", "/")}; + const nghttp2_nv asteriskget1_reqnv[] = { + MAKE_NV(":path", "*"), MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":method", "GET")}; + const nghttp2_nv asteriskget2_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), MAKE_NV(":path", "*")}; + const nghttp2_nv asteriskoptions1_reqnv[] = { + MAKE_NV(":path", "*"), MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":method", "OPTIONS")}; + const nghttp2_nv asteriskoptions2_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "OPTIONS"), MAKE_NV(":path", "*")}; + const nghttp2_nv connectproto_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv connectprotoget_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv connectprotonopath_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv connectprotonoauth_reqnv[] = { + MAKE_NV(":scheme", "http"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV("host", "localhost"), + MAKE_NV(":protocol", "websocket")}; + const nghttp2_nv regularconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost")}; + + mem = nghttp2_mem_default(); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback; + + nghttp2_session_client_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* response header lacks :status */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 1, + NGHTTP2_STREAM_OPENING, nostatus_resnv, + ARRLEN(nostatus_resnv)); + + /* response header has 2 :status */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, + NGHTTP2_STREAM_OPENING, dupstatus_resnv, + ARRLEN(dupstatus_resnv)); + + /* response header has bad pseudo header :scheme */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 5, + NGHTTP2_STREAM_OPENING, badpseudo_resnv, + ARRLEN(badpseudo_resnv)); + + /* response header has :status after regular header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 7, + NGHTTP2_STREAM_OPENING, latepseudo_resnv, + ARRLEN(latepseudo_resnv)); + + /* response header has bad status code */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 9, + NGHTTP2_STREAM_OPENING, badstatus_resnv, + ARRLEN(badstatus_resnv)); + + /* response header has bad content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 11, + NGHTTP2_STREAM_OPENING, badcl_resnv, + ARRLEN(badcl_resnv)); + + /* response header has multiple content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 13, + NGHTTP2_STREAM_OPENING, dupcl_resnv, + ARRLEN(dupcl_resnv)); + + /* response header has disallowed header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 15, + NGHTTP2_STREAM_OPENING, badhd_resnv, + ARRLEN(badhd_resnv)); + + /* response header has content-length with 100 status code */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 17, + NGHTTP2_STREAM_OPENING, cl1xx_resnv, + ARRLEN(cl1xx_resnv)); + + /* response header has 0 content-length with 204 status code */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 19, + NGHTTP2_STREAM_OPENING, cl204_resnv, + ARRLEN(cl204_resnv)); + + /* response header has nonzero content-length with 204 status + code */ + check_nghttp2_http_recv_headers_fail( + session, &deflater, 21, NGHTTP2_STREAM_OPENING, clnonzero204_resnv, + ARRLEN(clnonzero204_resnv)); + + /* status code 101 should not be used in HTTP/2 because it is used + for HTTP Upgrade which HTTP/2 removes. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 23, + NGHTTP2_STREAM_OPENING, status101_resnv, + ARRLEN(status101_resnv)); + + /* Specific characters check for host field in response header + should not be done as its use is undefined. */ + check_nghttp2_http_recv_headers_ok( + session, &deflater, 25, NGHTTP2_STREAM_OPENING, unexpectedhost_resnv, + ARRLEN(unexpectedhost_resnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + /* check server side */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* request header has no :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 1, -1, nopath_reqnv, + ARRLEN(nopath_reqnv)); + + /* request header has CONNECT method, but followed by :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, -1, + earlyconnect_reqnv, + ARRLEN(earlyconnect_reqnv)); + + /* request header has CONNECT method following :path */ + check_nghttp2_http_recv_headers_fail( + session, &deflater, 5, -1, lateconnect_reqnv, ARRLEN(lateconnect_reqnv)); + + /* request header has multiple :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 7, -1, duppath_reqnv, + ARRLEN(duppath_reqnv)); + + /* request header has bad content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 9, -1, badcl_reqnv, + ARRLEN(badcl_reqnv)); + + /* request header has multiple content-length */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 11, -1, dupcl_reqnv, + ARRLEN(dupcl_reqnv)); + + /* request header has disallowed header field */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 13, -1, badhd_reqnv, + ARRLEN(badhd_reqnv)); + + /* request header has :authority header field containing illegal + characters */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 15, -1, + badauthority_reqnv, + ARRLEN(badauthority_reqnv)); + + /* request header has regular header field containing illegal + character before all mandatory header fields are seen. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 17, -1, + badhdbtw_reqnv, ARRLEN(badhdbtw_reqnv)); + + /* request header has "*" in :path header field while method is GET. + :path is received before :method */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 19, -1, + asteriskget1_reqnv, + ARRLEN(asteriskget1_reqnv)); + + /* request header has "*" in :path header field while method is GET. + :method is received before :path */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 21, -1, + asteriskget2_reqnv, + ARRLEN(asteriskget2_reqnv)); + + /* OPTIONS method can include "*" in :path header field. :path is + received before :method. */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 23, -1, + asteriskoptions1_reqnv, + ARRLEN(asteriskoptions1_reqnv)); + + /* OPTIONS method can include "*" in :path header field. :method is + received before :path. */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 25, -1, + asteriskoptions2_reqnv, + ARRLEN(asteriskoptions2_reqnv)); + + /* :protocol is not allowed unless it is enabled by the local + endpoint. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 27, -1, + connectproto_reqnv, + ARRLEN(connectproto_reqnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + /* enable SETTINGS_CONNECT_PROTOCOL */ + nghttp2_session_server_new(&session, &callbacks, &ud); + + session->pending_enable_connect_protocol = 1; + + nghttp2_hd_deflate_init(&deflater, mem); + + /* :protocol is allowed if SETTINGS_CONNECT_PROTOCOL is enabled by + the local endpoint. */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 1, -1, + connectproto_reqnv, + ARRLEN(connectproto_reqnv)); + + /* :protocol is only allowed with CONNECT method. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 3, -1, + connectprotoget_reqnv, + ARRLEN(connectprotoget_reqnv)); + + /* CONNECT method with :protocol requires :path. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 5, -1, + connectprotonopath_reqnv, + ARRLEN(connectprotonopath_reqnv)); + + /* CONNECT method with :protocol requires :authority. */ + check_nghttp2_http_recv_headers_fail(session, &deflater, 7, -1, + connectprotonoauth_reqnv, + ARRLEN(connectprotonoauth_reqnv)); + + /* regular CONNECT method should succeed with + SETTINGS_CONNECT_PROTOCOL */ + check_nghttp2_http_recv_headers_ok(session, &deflater, 9, -1, + regularconnect_reqnv, + ARRLEN(regularconnect_reqnv)); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); +} + +void test_nghttp2_http_content_length(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_stream *stream; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("te", "trailers"), + MAKE_NV("content-length", "9000000000")}; + const nghttp2_nv cl_reqnv[] = { + MAKE_NV(":path", "/"), MAKE_NV(":method", "PUT"), + MAKE_NV(":scheme", "https"), MAKE_NV("te", "trailers"), + MAKE_NV("host", "localhost"), MAKE_NV("content-length", "9000000000")}; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + stream = open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(9000000000LL == stream->content_length); + CU_ASSERT(200 == stream->status_code); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_reset(&bufs); + + /* check server side */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(9000000000LL == stream->content_length); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_content_length_mismatch(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv cl_reqnv[] = { + MAKE_NV(":path", "/"), MAKE_NV(":method", "PUT"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"), + MAKE_NV("content-length", "20")}; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "20")}; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* header says content-length: 20, but HEADERS has END_STREAM flag set */ + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + cl_reqnv, ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 0 byte */ + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 21 bytes */ + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, cl_reqnv, + ARRLEN(cl_reqnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 21, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 5); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 21; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + /* Check for client */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* header says content-length: 20, but HEADERS has END_STREAM flag set */ + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + cl_resnv, ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 1)); + CU_ASSERT(0 == nghttp2_session_send(session)); + /* After sending RST_STREAM, stream must be closed */ + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 0 byte */ + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 3)); + CU_ASSERT(0 == nghttp2_session_send(session)); + /* After sending RST_STREAM, stream must be closed */ + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 3)); + + nghttp2_bufs_reset(&bufs); + + /* header says content-length: 20, but DATA has 21 bytes */ + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 21, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 5); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 21; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(NULL != nghttp2_session_get_stream(session, 5)); + CU_ASSERT(0 == nghttp2_session_send(session)); + /* After sending RST_STREAM, stream must be closed */ + CU_ASSERT(NULL == nghttp2_session_get_stream(session, 5)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_bufs_free(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); +} + +void test_nghttp2_http_non_final_response(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv nonfinal_resnv[] = { + MAKE_NV(":status", "100"), + }; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* non-final HEADERS with END_STREAM is illegal */ + open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by non-empty DATA is illegal */ + open_sent_stream2(session, 3, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 10, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 3); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN + 10; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by empty DATA (without END_STREAM) is + ok */ + open_sent_stream2(session, 5, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 5); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by empty DATA (with END_STREAM) is + illegal */ + open_sent_stream2(session, 7, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 7, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_END_STREAM, 7); + nghttp2_frame_pack_frame_hd(bufs.head->buf.last, &hd); + bufs.head->buf.last += NGHTTP2_FRAME_HDLEN; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* non-final HEADERS followed by final HEADERS is OK */ + open_sent_stream2(session, 9, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 9, NGHTTP2_FLAG_END_HEADERS, + nonfinal_resnv, ARRLEN(nonfinal_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 9, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_trailer_headers(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv trailer_reqnv[] = { + MAKE_NV("foo", "bar"), + }; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* good trailer header */ + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + trailer_reqnv, ARRLEN(trailer_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + /* trailer header without END_STREAM is illegal */ + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, + trailer_reqnv, ARRLEN(trailer_reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + /* trailer header including pseudo header field is illegal */ + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 5, NGHTTP2_FLAG_END_HEADERS, reqnv, + ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + + nghttp2_session_del(session); + + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_ignore_regular_header(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + my_user_data ud; + const nghttp2_nv bad_reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), + MAKE_NV("foo", "\x0zzz"), + MAKE_NV("bar", "buzz"), + }; + const nghttp2_nv bad_ansnv[] = { + MAKE_NV(":authority", "localhost"), MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), MAKE_NV(":method", "GET"), MAKE_NV("bar", "buzz")}; + size_t proclen; + size_t i; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_header_callback = pause_on_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + bad_reqnv, ARRLEN(bad_reqnv), mem); + + CU_ASSERT_FATAL(0 == rv); + + nghttp2_hd_deflate_free(&deflater); + + proclen = 0; + + for (i = 0; i < 4; ++i) { + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + CU_ASSERT_FATAL(rv > 0); + proclen += (size_t)rv; + CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[i], &ud.nv)); + } + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + CU_ASSERT_FATAL(rv > 0); + /* Without on_invalid_frame_recv_callback, bad header causes stream + reset */ + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + + proclen += (size_t)rv; + + CU_ASSERT(nghttp2_buf_len(&bufs.head->buf) == proclen); + + nghttp2_session_del(session); + + /* use on_invalid_header_callback */ + callbacks.on_invalid_header_callback = pause_on_invalid_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + proclen = 0; + + ud.invalid_header_cb_called = 0; + + for (i = 0; i < 4; ++i) { + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + CU_ASSERT_FATAL(rv > 0); + proclen += (size_t)rv; + CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[i], &ud.nv)); + } + + CU_ASSERT(0 == ud.invalid_header_cb_called); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + + CU_ASSERT_FATAL(rv > 0); + CU_ASSERT(1 == ud.invalid_header_cb_called); + CU_ASSERT(nghttp2_nv_equal(&bad_reqnv[4], &ud.nv)); + + proclen += (size_t)rv; + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos + proclen, + nghttp2_buf_len(&bufs.head->buf) - proclen); + + CU_ASSERT(rv > 0); + CU_ASSERT(nghttp2_nv_equal(&bad_ansnv[4], &ud.nv)); + + nghttp2_session_del(session); + + /* make sure that we can reset stream from + on_invalid_header_callback */ + callbacks.on_header_callback = on_header_callback; + callbacks.on_invalid_header_callback = reset_on_invalid_header_callback; + + nghttp2_session_server_new(&session, &callbacks, &ud); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT(rv == (ssize_t)nghttp2_buf_len(&bufs.head->buf)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(1 == item->frame.hd.stream_id); + + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_ignore_content_length(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "304"), + MAKE_NV("content-length", "20")}; + const nghttp2_nv conn_reqnv[] = {MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV("content-length", "999999")}; + const nghttp2_nv conn_cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0")}; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + /* If status 304, content-length must be ignored */ + open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + cl_resnv, ARRLEN(cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + nghttp2_bufs_reset(&bufs); + + /* Content-Length in 200 response to CONNECT is ignored */ + stream = open_sent_stream2(session, 3, NGHTTP2_STREAM_OPENING); + stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT; + + rv = pack_headers(&bufs, &deflater, 3, NGHTTP2_FLAG_END_HEADERS, + conn_cl_resnv, ARRLEN(conn_cl_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + CU_ASSERT(-1 == stream->content_length); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* If request method is CONNECT, content-length must be ignored */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, conn_reqnv, + ARRLEN(conn_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(-1 == stream->content_length); + CU_ASSERT((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) > 0); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_record_request_method(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const nghttp2_nv conn_reqnv[] = {MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost")}; + const nghttp2_nv conn_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "9999")}; + nghttp2_stream *stream; + ssize_t rv; + nghttp2_bufs bufs; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + CU_ASSERT(1 == nghttp2_submit_request(session, NULL, conn_reqnv, + ARRLEN(conn_reqnv), NULL, NULL)); + + CU_ASSERT(0 == nghttp2_session_send(session)); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_HTTP_FLAG_METH_CONNECT == stream->http_flags); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, conn_resnv, + ARRLEN(conn_resnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT((NGHTTP2_HTTP_FLAG_METH_CONNECT & stream->http_flags) > 0); + CU_ASSERT(-1 == stream->content_length); + + /* content-length is ignored in 200 response to a CONNECT request */ + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL == item); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_push_promise(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + nghttp2_stream *stream; + const nghttp2_nv bad_reqnv[] = {MAKE_NV(":method", "GET")}; + nghttp2_outbound_item *item; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* good PUSH_PROMISE case */ + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + open_sent_stream2(session, 1, NGHTTP2_STREAM_OPENING); + + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 2, + reqnv, ARRLEN(reqnv), mem); + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + stream = nghttp2_session_get_stream(session, 2); + CU_ASSERT(NULL != stream); + + nghttp2_bufs_reset(&bufs); + + rv = pack_headers(&bufs, &deflater, 2, NGHTTP2_FLAG_END_HEADERS, resnv, + ARRLEN(resnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session)); + + CU_ASSERT(200 == stream->status_code); + + nghttp2_bufs_reset(&bufs); + + /* PUSH_PROMISE lacks mandatory header */ + rv = pack_push_promise(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, 4, + bad_reqnv, ARRLEN(bad_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(4 == item->frame.hd.stream_id); + + nghttp2_bufs_reset(&bufs); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_head_method_upgrade_workaround(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + const nghttp2_nv cl_resnv[] = {MAKE_NV(":status", "200"), + MAKE_NV("content-length", "1000000007")}; + nghttp2_bufs bufs; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + ssize_t rv; + nghttp2_stream *stream; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_client_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + nghttp2_session_upgrade(session, NULL, 0, NULL); + + rv = pack_headers(&bufs, &deflater, 1, NGHTTP2_FLAG_END_HEADERS, cl_resnv, + ARRLEN(cl_resnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(-1 == stream->content_length); + + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_bufs_free(&bufs); +} + +void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_hd_deflater deflater; + nghttp2_mem *mem; + nghttp2_bufs bufs; + ssize_t rv; + const nghttp2_nv ws_reqnv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + MAKE_NV("foo", "bar "), + }; + nghttp2_outbound_item *item; + nghttp2_option *option; + + mem = nghttp2_mem_default(); + frame_pack_bufs_init(&bufs); + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + + /* By default, the leading and trailing white spaces validation is + enabled as per RFC 9113. */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + ws_reqnv, ARRLEN(ws_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); + CU_ASSERT(0 == nghttp2_session_send(session)); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + + /* Turn off the validation */ + nghttp2_option_new(&option); + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(option, 1); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + nghttp2_hd_deflate_init(&deflater, mem); + + rv = pack_headers(&bufs, &deflater, 1, + NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM, + ws_reqnv, ARRLEN(ws_reqnv), mem); + + CU_ASSERT(0 == rv); + + rv = nghttp2_session_mem_recv(session, bufs.head->buf.pos, + nghttp2_buf_len(&bufs.head->buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(&bufs.head->buf) == rv); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(NULL == item); + + nghttp2_bufs_reset(&bufs); + nghttp2_hd_deflate_free(&deflater); + nghttp2_session_del(session); + nghttp2_option_del(option); + + nghttp2_bufs_free(&bufs); +} diff --git a/lib/nghttp2/tests/nghttp2_session_test.h b/lib/nghttp2/tests/nghttp2_session_test.h new file mode 100644 index 00000000000..be4fdf89762 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_session_test.h @@ -0,0 +1,184 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_SESSION_TEST_H +#define NGHTTP2_SESSION_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +void test_nghttp2_session_recv(void); +void test_nghttp2_session_recv_invalid_stream_id(void); +void test_nghttp2_session_recv_invalid_frame(void); +void test_nghttp2_session_recv_eof(void); +void test_nghttp2_session_recv_data(void); +void test_nghttp2_session_recv_data_no_auto_flow_control(void); +void test_nghttp2_session_recv_continuation(void); +void test_nghttp2_session_recv_headers_with_priority(void); +void test_nghttp2_session_recv_headers_with_padding(void); +void test_nghttp2_session_recv_headers_early_response(void); +void test_nghttp2_session_recv_headers_for_closed_stream(void); +void test_nghttp2_session_recv_headers_with_extpri(void); +void test_nghttp2_session_server_recv_push_response(void); +void test_nghttp2_session_recv_premature_headers(void); +void test_nghttp2_session_recv_unknown_frame(void); +void test_nghttp2_session_recv_unexpected_continuation(void); +void test_nghttp2_session_recv_settings_header_table_size(void); +void test_nghttp2_session_recv_too_large_frame_length(void); +void test_nghttp2_session_recv_extension(void); +void test_nghttp2_session_recv_altsvc(void); +void test_nghttp2_session_recv_origin(void); +void test_nghttp2_session_recv_priority_update(void); +void test_nghttp2_session_continue(void); +void test_nghttp2_session_add_frame(void); +void test_nghttp2_session_on_request_headers_received(void); +void test_nghttp2_session_on_response_headers_received(void); +void test_nghttp2_session_on_headers_received(void); +void test_nghttp2_session_on_push_response_headers_received(void); +void test_nghttp2_session_on_priority_received(void); +void test_nghttp2_session_on_rst_stream_received(void); +void test_nghttp2_session_on_settings_received(void); +void test_nghttp2_session_on_push_promise_received(void); +void test_nghttp2_session_on_ping_received(void); +void test_nghttp2_session_on_goaway_received(void); +void test_nghttp2_session_on_window_update_received(void); +void test_nghttp2_session_on_data_received(void); +void test_nghttp2_session_on_data_received_fail_fast(void); +void test_nghttp2_session_on_altsvc_received(void); +void test_nghttp2_session_send_headers_start_stream(void); +void test_nghttp2_session_send_headers_reply(void); +void test_nghttp2_session_send_headers_frame_size_error(void); +void test_nghttp2_session_send_headers_push_reply(void); +void test_nghttp2_session_send_rst_stream(void); +void test_nghttp2_session_send_push_promise(void); +void test_nghttp2_session_is_my_stream_id(void); +void test_nghttp2_session_upgrade2(void); +void test_nghttp2_session_reprioritize_stream(void); +void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void); +void test_nghttp2_submit_data(void); +void test_nghttp2_submit_data_read_length_too_large(void); +void test_nghttp2_submit_data_read_length_smallest(void); +void test_nghttp2_submit_data_twice(void); +void test_nghttp2_submit_request_with_data(void); +void test_nghttp2_submit_request_without_data(void); +void test_nghttp2_submit_response_with_data(void); +void test_nghttp2_submit_response_without_data(void); +void test_nghttp2_submit_response_push_response(void); +void test_nghttp2_submit_trailer(void); +void test_nghttp2_submit_headers_start_stream(void); +void test_nghttp2_submit_headers_reply(void); +void test_nghttp2_submit_headers_push_reply(void); +void test_nghttp2_submit_headers(void); +void test_nghttp2_submit_headers_continuation(void); +void test_nghttp2_submit_headers_continuation_extra_large(void); +void test_nghttp2_submit_priority(void); +void test_nghttp2_submit_settings(void); +void test_nghttp2_submit_settings_update_local_window_size(void); +void test_nghttp2_submit_settings_multiple_times(void); +void test_nghttp2_submit_push_promise(void); +void test_nghttp2_submit_window_update(void); +void test_nghttp2_submit_window_update_local_window_size(void); +void test_nghttp2_submit_shutdown_notice(void); +void test_nghttp2_submit_invalid_nv(void); +void test_nghttp2_submit_extension(void); +void test_nghttp2_submit_altsvc(void); +void test_nghttp2_submit_origin(void); +void test_nghttp2_submit_priority_update(void); +void test_nghttp2_submit_rst_stream(void); +void test_nghttp2_session_open_stream(void); +void test_nghttp2_session_open_stream_with_idle_stream_dep(void); +void test_nghttp2_session_get_next_ob_item(void); +void test_nghttp2_session_pop_next_ob_item(void); +void test_nghttp2_session_reply_fail(void); +void test_nghttp2_session_max_concurrent_streams(void); +void test_nghttp2_session_stop_data_with_rst_stream(void); +void test_nghttp2_session_defer_data(void); +void test_nghttp2_session_flow_control(void); +void test_nghttp2_session_flow_control_data_recv(void); +void test_nghttp2_session_flow_control_data_with_padding_recv(void); +void test_nghttp2_session_data_read_temporal_failure(void); +void test_nghttp2_session_on_stream_close(void); +void test_nghttp2_session_on_ctrl_not_send(void); +void test_nghttp2_session_get_outbound_queue_size(void); +void test_nghttp2_session_get_effective_local_window_size(void); +void test_nghttp2_session_set_option(void); +void test_nghttp2_session_data_backoff_by_high_pri_frame(void); +void test_nghttp2_session_pack_data_with_padding(void); +void test_nghttp2_session_pack_headers_with_padding(void); +void test_nghttp2_pack_settings_payload(void); +void test_nghttp2_session_stream_dep_add(void); +void test_nghttp2_session_stream_dep_remove(void); +void test_nghttp2_session_stream_dep_add_subtree(void); +void test_nghttp2_session_stream_dep_remove_subtree(void); +void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void); +void test_nghttp2_session_stream_attach_item(void); +void test_nghttp2_session_stream_attach_item_subtree(void); +void test_nghttp2_session_stream_get_state(void); +void test_nghttp2_session_stream_get_something(void); +void test_nghttp2_session_find_stream(void); +void test_nghttp2_session_keep_closed_stream(void); +void test_nghttp2_session_keep_idle_stream(void); +void test_nghttp2_session_detach_idle_stream(void); +void test_nghttp2_session_large_dep_tree(void); +void test_nghttp2_session_graceful_shutdown(void); +void test_nghttp2_session_on_header_temporal_failure(void); +void test_nghttp2_session_recv_client_magic(void); +void test_nghttp2_session_delete_data_item(void); +void test_nghttp2_session_open_idle_stream(void); +void test_nghttp2_session_cancel_reserved_remote(void); +void test_nghttp2_session_reset_pending_headers(void); +void test_nghttp2_session_send_data_callback(void); +void test_nghttp2_session_on_begin_headers_temporal_failure(void); +void test_nghttp2_session_defer_then_close(void); +void test_nghttp2_session_detach_item_from_closed_stream(void); +void test_nghttp2_session_flooding(void); +void test_nghttp2_session_change_stream_priority(void); +void test_nghttp2_session_change_extpri_stream_priority(void); +void test_nghttp2_session_create_idle_stream(void); +void test_nghttp2_session_repeated_priority_change(void); +void test_nghttp2_session_repeated_priority_submission(void); +void test_nghttp2_session_set_local_window_size(void); +void test_nghttp2_session_cancel_from_before_frame_send(void); +void test_nghttp2_session_too_many_settings(void); +void test_nghttp2_session_removed_closed_stream(void); +void test_nghttp2_session_pause_data(void); +void test_nghttp2_session_no_closed_streams(void); +void test_nghttp2_session_set_stream_user_data(void); +void test_nghttp2_session_no_rfc7540_priorities(void); +void test_nghttp2_session_server_fallback_rfc7540_priorities(void); +void test_nghttp2_session_stream_reset_ratelim(void); +void test_nghttp2_http_mandatory_headers(void); +void test_nghttp2_http_content_length(void); +void test_nghttp2_http_content_length_mismatch(void); +void test_nghttp2_http_non_final_response(void); +void test_nghttp2_http_trailer_headers(void); +void test_nghttp2_http_ignore_regular_header(void); +void test_nghttp2_http_ignore_content_length(void); +void test_nghttp2_http_record_request_method(void); +void test_nghttp2_http_push_promise(void); +void test_nghttp2_http_head_method_upgrade_workaround(void); +void test_nghttp2_http_no_rfc9113_leading_and_trailing_ws_validation(void); + +#endif /* NGHTTP2_SESSION_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_stream_test.c b/lib/nghttp2/tests/nghttp2_stream_test.c new file mode 100644 index 00000000000..75b4d68043f --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_stream_test.c @@ -0,0 +1,31 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_stream_test.h" + +#include + +#include + +#include "nghttp2_stream.h" diff --git a/lib/nghttp2/tests/nghttp2_stream_test.h b/lib/nghttp2/tests/nghttp2_stream_test.h new file mode 100644 index 00000000000..ad7be640e63 --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_stream_test.h @@ -0,0 +1,32 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_STREAM_TEST_H +#define NGHTTP2_STREAM_TEST_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#endif /* NGHTTP2_STREAM_TEST_H */ diff --git a/lib/nghttp2/tests/nghttp2_test_helper.c b/lib/nghttp2/tests/nghttp2_test_helper.c new file mode 100644 index 00000000000..1bd4a63fc8c --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_test_helper.c @@ -0,0 +1,435 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 "nghttp2_test_helper.h" + +#include +#include + +#include + +#include "nghttp2_helper.h" +#include "nghttp2_priority_spec.h" + +int unpack_framebuf(nghttp2_frame *frame, nghttp2_bufs *bufs) { + nghttp2_buf *buf; + + /* Assuming we have required data in first buffer. We don't decode + header block so, we don't mind its space */ + buf = &bufs->head->buf; + return unpack_frame(frame, buf->pos, nghttp2_buf_len(buf)); +} + +int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len) { + int rv = 0; + const uint8_t *payload = in + NGHTTP2_FRAME_HDLEN; + size_t payloadlen = len - NGHTTP2_FRAME_HDLEN; + size_t payloadoff; + nghttp2_mem *mem; + + mem = nghttp2_mem_default(); + + nghttp2_frame_unpack_frame_hd(&frame->hd, in); + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + payloadoff = ((frame->hd.flags & NGHTTP2_FLAG_PADDED) > 0); + nghttp2_frame_unpack_headers_payload(&frame->headers, payload + payloadoff); + break; + case NGHTTP2_PRIORITY: + nghttp2_frame_unpack_priority_payload(&frame->priority, payload); + break; + case NGHTTP2_RST_STREAM: + nghttp2_frame_unpack_rst_stream_payload(&frame->rst_stream, payload); + break; + case NGHTTP2_SETTINGS: + rv = nghttp2_frame_unpack_settings_payload2( + &frame->settings.iv, &frame->settings.niv, payload, payloadlen, mem); + break; + case NGHTTP2_PUSH_PROMISE: + nghttp2_frame_unpack_push_promise_payload(&frame->push_promise, payload); + break; + case NGHTTP2_PING: + nghttp2_frame_unpack_ping_payload(&frame->ping, payload); + break; + case NGHTTP2_GOAWAY: + nghttp2_frame_unpack_goaway_payload2(&frame->goaway, payload, payloadlen, + mem); + break; + case NGHTTP2_WINDOW_UPDATE: + nghttp2_frame_unpack_window_update_payload(&frame->window_update, payload); + break; + case NGHTTP2_ALTSVC: + assert(payloadlen > 2); + nghttp2_frame_unpack_altsvc_payload2(&frame->ext, payload, payloadlen, mem); + break; + case NGHTTP2_ORIGIN: + rv = nghttp2_frame_unpack_origin_payload(&frame->ext, payload, payloadlen, + mem); + break; + case NGHTTP2_PRIORITY_UPDATE: + assert(payloadlen >= 4); + nghttp2_frame_unpack_priority_update_payload( + &frame->ext, (uint8_t *)payload, payloadlen); + break; + default: + /* Must not be reachable */ + assert(0); + } + return rv; +} + +int strmemeq(const char *a, const uint8_t *b, size_t bn) { + const uint8_t *c; + if (!a || !b) { + return 0; + } + c = b + bn; + for (; *a && b != c && *a == *b; ++a, ++b) + ; + return !*a && b == c; +} + +int nvnameeq(const char *a, nghttp2_nv *nv) { + return strmemeq(a, nv->name, nv->namelen); +} + +int nvvalueeq(const char *a, nghttp2_nv *nv) { + return strmemeq(a, nv->value, nv->valuelen); +} + +void nva_out_init(nva_out *out) { + memset(out->nva, 0, sizeof(out->nva)); + out->nvlen = 0; +} + +void nva_out_reset(nva_out *out, nghttp2_mem *mem) { + size_t i; + for (i = 0; i < out->nvlen; ++i) { + mem->free(out->nva[i].name, NULL); + mem->free(out->nva[i].value, NULL); + } + memset(out->nva, 0, sizeof(out->nva)); + out->nvlen = 0; +} + +void add_out(nva_out *out, nghttp2_nv *nv, nghttp2_mem *mem) { + nghttp2_nv *onv = &out->nva[out->nvlen]; + if (nv->namelen) { + onv->name = mem->malloc(nv->namelen, NULL); + memcpy(onv->name, nv->name, nv->namelen); + } else { + onv->name = NULL; + } + if (nv->valuelen) { + onv->value = mem->malloc(nv->valuelen, NULL); + memcpy(onv->value, nv->value, nv->valuelen); + } else { + onv->value = NULL; + } + onv->namelen = nv->namelen; + onv->valuelen = nv->valuelen; + + onv->flags = nv->flags; + + ++out->nvlen; +} + +ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out, + nghttp2_bufs *bufs, size_t offset, nghttp2_mem *mem) { + ssize_t rv; + nghttp2_nv nv; + int inflate_flags; + nghttp2_buf_chain *ci; + nghttp2_buf *buf; + nghttp2_buf bp; + int fin; + size_t processed; + + processed = 0; + + for (ci = bufs->head; ci; ci = ci->next) { + buf = &ci->buf; + fin = nghttp2_buf_len(buf) == 0 || ci->next == NULL; + bp = *buf; + + if (offset) { + size_t n; + + n = nghttp2_min(offset, nghttp2_buf_len(&bp)); + bp.pos += n; + offset -= n; + } + + for (;;) { + inflate_flags = 0; + rv = nghttp2_hd_inflate_hd2(inflater, &nv, &inflate_flags, bp.pos, + nghttp2_buf_len(&bp), fin); + + if (rv < 0) { + return rv; + } + + bp.pos += rv; + processed += (size_t)rv; + + if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + if (out) { + add_out(out, &nv, mem); + } + } + if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + break; + } + if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && + nghttp2_buf_len(&bp) == 0) { + break; + } + } + } + + nghttp2_hd_inflate_end_headers(inflater); + + return (ssize_t)processed; +} + +int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem) { + nghttp2_nv *dnva; + nghttp2_frame frame; + int rv; + + nghttp2_nv_array_copy(&dnva, nva, nvlen, mem); + + nghttp2_frame_headers_init(&frame.headers, flags, stream_id, + NGHTTP2_HCAT_HEADERS, NULL, dnva, nvlen); + rv = nghttp2_frame_pack_headers(bufs, &frame.headers, deflater); + + nghttp2_frame_headers_free(&frame.headers, mem); + + return rv; +} + +int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, + int32_t promised_stream_id, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem) { + nghttp2_nv *dnva; + nghttp2_frame frame; + int rv; + + nghttp2_nv_array_copy(&dnva, nva, nvlen, mem); + + nghttp2_frame_push_promise_init(&frame.push_promise, flags, stream_id, + promised_stream_id, dnva, nvlen); + rv = nghttp2_frame_pack_push_promise(bufs, &frame.push_promise, deflater); + + nghttp2_frame_push_promise_free(&frame.push_promise, mem); + + return rv; +} + +int frame_pack_bufs_init(nghttp2_bufs *bufs) { + /* 1 for Pad Length */ + return nghttp2_bufs_init2(bufs, 4096, 16, NGHTTP2_FRAME_HDLEN + 1, + nghttp2_mem_default()); +} + +void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size) { + /* 1 for Pad Length */ + nghttp2_bufs_init2(bufs, chunk_size, 16, NGHTTP2_FRAME_HDLEN + 1, + nghttp2_mem_default()); +} + +static nghttp2_stream *open_stream_with_all(nghttp2_session *session, + int32_t stream_id, int32_t weight, + uint8_t exclusive, + nghttp2_stream *dep_stream) { + nghttp2_priority_spec pri_spec; + int32_t dep_stream_id; + + if (dep_stream) { + dep_stream_id = dep_stream->stream_id; + } else { + dep_stream_id = 0; + } + + nghttp2_priority_spec_init(&pri_spec, dep_stream_id, weight, exclusive); + + return nghttp2_session_open_stream(session, stream_id, + NGHTTP2_STREAM_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_OPENED, NULL); +} + +nghttp2_stream *open_stream(nghttp2_session *session, int32_t stream_id) { + return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 0, + NULL); +} + +nghttp2_stream *open_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) { + return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 0, + dep_stream); +} + +nghttp2_stream *open_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, int32_t weight, + nghttp2_stream *dep_stream) { + return open_stream_with_all(session, stream_id, weight, 0, dep_stream); +} + +nghttp2_stream *open_stream_with_dep_excl(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) { + return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 1, + dep_stream); +} + +nghttp2_outbound_item *create_data_ob_item(nghttp2_mem *mem) { + nghttp2_outbound_item *item; + + item = mem->malloc(sizeof(nghttp2_outbound_item), NULL); + memset(item, 0, sizeof(nghttp2_outbound_item)); + + return item; +} + +nghttp2_stream *open_sent_stream(nghttp2_session *session, int32_t stream_id) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0); + return open_sent_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_OPENED, NULL); +} + +nghttp2_stream *open_sent_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0); + return open_sent_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec, + initial_state, NULL); +} + +nghttp2_stream *open_sent_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data) { + nghttp2_stream *stream; + + assert(nghttp2_session_is_my_stream_id(session, stream_id)); + + stream = nghttp2_session_open_stream(session, stream_id, flags, pri_spec_in, + initial_state, stream_user_data); + session->last_sent_stream_id = + nghttp2_max(session->last_sent_stream_id, stream_id); + session->next_stream_id = + nghttp2_max(session->next_stream_id, (uint32_t)stream_id + 2); + + return stream; +} + +nghttp2_stream *open_sent_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) { + return open_sent_stream_with_dep_weight(session, stream_id, + NGHTTP2_DEFAULT_WEIGHT, dep_stream); +} + +nghttp2_stream *open_sent_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream) { + nghttp2_stream *stream; + + assert(nghttp2_session_is_my_stream_id(session, stream_id)); + + stream = open_stream_with_all(session, stream_id, weight, 0, dep_stream); + + session->last_sent_stream_id = + nghttp2_max(session->last_sent_stream_id, stream_id); + session->next_stream_id = + nghttp2_max(session->next_stream_id, (uint32_t)stream_id + 2); + + return stream; +} + +nghttp2_stream *open_recv_stream(nghttp2_session *session, int32_t stream_id) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0); + return open_recv_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_OPENED, NULL); +} + +nghttp2_stream *open_recv_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state) { + nghttp2_priority_spec pri_spec; + + nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_DEFAULT_WEIGHT, 0); + return open_recv_stream3(session, stream_id, NGHTTP2_FLAG_NONE, &pri_spec, + initial_state, NULL); +} + +nghttp2_stream *open_recv_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data) { + nghttp2_stream *stream; + + assert(!nghttp2_session_is_my_stream_id(session, stream_id)); + + stream = nghttp2_session_open_stream(session, stream_id, flags, pri_spec_in, + initial_state, stream_user_data); + session->last_recv_stream_id = + nghttp2_max(session->last_recv_stream_id, stream_id); + + return stream; +} + +nghttp2_stream *open_recv_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream) { + return open_recv_stream_with_dep_weight(session, stream_id, + NGHTTP2_DEFAULT_WEIGHT, dep_stream); +} + +nghttp2_stream *open_recv_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream) { + nghttp2_stream *stream; + + assert(!nghttp2_session_is_my_stream_id(session, stream_id)); + + stream = open_stream_with_all(session, stream_id, weight, 0, dep_stream); + + session->last_recv_stream_id = + nghttp2_max(session->last_recv_stream_id, stream_id); + + return stream; +} diff --git a/lib/nghttp2/tests/nghttp2_test_helper.h b/lib/nghttp2/tests/nghttp2_test_helper.h new file mode 100644 index 00000000000..c66298a00aa --- /dev/null +++ b/lib/nghttp2/tests/nghttp2_test_helper.h @@ -0,0 +1,158 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 Tatsuhiro Tsujikawa + * + * 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 NGHTTP2_TEST_HELPER_H +#define NGHTTP2_TEST_HELPER_H + +#ifdef HAVE_CONFIG_H +# include +#endif /* HAVE_CONFIG_H */ + +#include "nghttp2_frame.h" +#include "nghttp2_hd.h" +#include "nghttp2_session.h" + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t *)(NAME), (uint8_t *)(VALUE), sizeof((NAME)) - 1, \ + sizeof((VALUE)) - 1, NGHTTP2_NV_FLAG_NONE \ + } +#define ARRLEN(ARR) (sizeof(ARR) / sizeof(ARR[0])) + +#define assert_nv_equal(A, B, len, mem) \ + do { \ + size_t alloclen = sizeof(nghttp2_nv) * len; \ + const nghttp2_nv *sa = A, *sb = B; \ + nghttp2_nv *a = mem->malloc(alloclen, NULL); \ + nghttp2_nv *b = mem->malloc(alloclen, NULL); \ + ssize_t i_; \ + memcpy(a, sa, alloclen); \ + memcpy(b, sb, alloclen); \ + nghttp2_nv_array_sort(a, len); \ + nghttp2_nv_array_sort(b, len); \ + for (i_ = 0; i_ < (ssize_t)len; ++i_) { \ + CU_ASSERT(nghttp2_nv_equal(&a[i_], &b[i_])); \ + } \ + mem->free(b, NULL); \ + mem->free(a, NULL); \ + } while (0); + +int unpack_framebuf(nghttp2_frame *frame, nghttp2_bufs *bufs); + +int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len); + +int strmemeq(const char *a, const uint8_t *b, size_t bn); + +int nvnameeq(const char *a, nghttp2_nv *nv); + +int nvvalueeq(const char *a, nghttp2_nv *nv); + +typedef struct { + nghttp2_nv nva[256]; + size_t nvlen; +} nva_out; + +void nva_out_init(nva_out *out); +void nva_out_reset(nva_out *out, nghttp2_mem *mem); + +void add_out(nva_out *out, nghttp2_nv *nv, nghttp2_mem *mem); + +ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out, + nghttp2_bufs *bufs, size_t offset, nghttp2_mem *mem); + +int pack_headers(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem); + +int pack_push_promise(nghttp2_bufs *bufs, nghttp2_hd_deflater *deflater, + int32_t stream_id, uint8_t flags, + int32_t promised_stream_id, const nghttp2_nv *nva, + size_t nvlen, nghttp2_mem *mem); + +int frame_pack_bufs_init(nghttp2_bufs *bufs); + +void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size); + +nghttp2_stream *open_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, int32_t weight, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_stream_with_dep_excl(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_outbound_item *create_data_ob_item(nghttp2_mem *mem); + +/* Opens stream. This stream is assumed to be sent from |session|, + and session->last_sent_stream_id and session->next_stream_id will + be adjusted accordingly. */ +nghttp2_stream *open_sent_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_sent_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state); + +nghttp2_stream *open_sent_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data); + +nghttp2_stream *open_sent_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_sent_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream); + +/* Opens stream. This stream is assumed to be received by |session|, + and session->last_recv_stream_id will be adjusted accordingly. */ +nghttp2_stream *open_recv_stream(nghttp2_session *session, int32_t stream_id); + +nghttp2_stream *open_recv_stream2(nghttp2_session *session, int32_t stream_id, + nghttp2_stream_state initial_state); + +nghttp2_stream *open_recv_stream3(nghttp2_session *session, int32_t stream_id, + uint8_t flags, + nghttp2_priority_spec *pri_spec_in, + nghttp2_stream_state initial_state, + void *stream_user_data); + +nghttp2_stream *open_recv_stream_with_dep(nghttp2_session *session, + int32_t stream_id, + nghttp2_stream *dep_stream); + +nghttp2_stream *open_recv_stream_with_dep_weight(nghttp2_session *session, + int32_t stream_id, + int32_t weight, + nghttp2_stream *dep_stream); + +#endif /* NGHTTP2_TEST_HELPER_H */ diff --git a/lib/nghttp2/tests/testdata/Makefile.am b/lib/nghttp2/tests/testdata/Makefile.am new file mode 100644 index 00000000000..ee381133649 --- /dev/null +++ b/lib/nghttp2/tests/testdata/Makefile.am @@ -0,0 +1,23 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2012 Tatsuhiro Tsujikawa + +# 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 = cacert.pem index.html privkey.pem diff --git a/lib/nghttp2/tests/testdata/cacert.pem b/lib/nghttp2/tests/testdata/cacert.pem new file mode 100644 index 00000000000..e462790dac9 --- /dev/null +++ b/lib/nghttp2/tests/testdata/cacert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICKTCCAdOgAwIBAgIJAIsolheWrwMZMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTENMAsGA1UEBwwEQ2l0eTESMBAGA1UECgwJU3Bk +eSBUZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHTAbBgkqhkiG9w0BCQEWDnNwZHlA +bG9jYWxob3N0MB4XDTEyMDMwMTE5MTI0NVoXDTIzMDUxOTE5MTI0NVowcDELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQHDARDaXR5MRIwEAYDVQQKDAlT +cGR5IFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYOc3Bk +eUBsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAw/2MgzAdlJDm29qH +ZlAibgs9mH+8keOtsRrb4B1PiCcZoHvN9eCVZ4WnzT+0zhHF+nO3YfwVFVC3w7TF +7fLB3QIDAQABo1AwTjAdBgNVHQ4EFgQUVP2Jw9RX6BB76aV5x2qk5qsrAIQwHwYD +VR0jBBgwFoAUVP2Jw9RX6BB76aV5x2qk5qsrAIQwDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQUFAANBAKd9M5FzQLEZW1KPe9/XNZlgxZ2g3EC5Krxo5I4Ul3MnIYS9 +u4K8t/iprhgOzjFH6+8LVk9v0Za+gU+K43CpUo4= +-----END CERTIFICATE----- diff --git a/lib/nghttp2/tests/testdata/index.html b/lib/nghttp2/tests/testdata/index.html new file mode 100644 index 00000000000..cdd75fc5b9f --- /dev/null +++ b/lib/nghttp2/tests/testdata/index.html @@ -0,0 +1 @@ +small diff --git a/lib/nghttp2/tests/testdata/privkey.pem b/lib/nghttp2/tests/testdata/privkey.pem new file mode 100644 index 00000000000..0ab825b4e1d --- /dev/null +++ b/lib/nghttp2/tests/testdata/privkey.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAMP9jIMwHZSQ5tvah2ZQIm4LPZh/vJHjrbEa2+AdT4gnGaB7zfXg +lWeFp80/tM4Rxfpzt2H8FRVQt8O0xe3ywd0CAwEAAQJBAIQ8PGP/QNYOdlT8OsLj +aneJCgQsm1Rro7ONBbFO1WxslvA6+uJsx4Rs8zLiS8cyqmJ/lmGa7zhwYSOvFQPa +XgECIQDgIcgM/2C67peTm1diKKIoGVVKFCfdRi+Dje6mTl2TQQIhAN/bcFWbG73j +cUVlIsr9Wk1dJzjPPWKeyirF1qd/WbOdAiEApTsCOeLCssxV3jF02B5QfPNAFx6I +zO2C9Z7awque/IECIGCHW3VOoTPMs7dc2Rf3D810cclJdArmtf6juOAZRjDxAiBS +AC+H685IBJ99N5nCbF9NWYIVSkuiKVQ8POYVZX+0Jg== +-----END RSA PRIVATE KEY----- diff --git a/lib/nghttp2/third-party/CMakeLists.txt b/lib/nghttp2/third-party/CMakeLists.txt new file mode 100644 index 00000000000..b0ef0606279 --- /dev/null +++ b/lib/nghttp2/third-party/CMakeLists.txt @@ -0,0 +1,81 @@ +if(ENABLE_THIRD_PARTY) + set(LIBLLHTTP_SOURCES + llhttp/src/api.c + llhttp/src/http.c + llhttp/src/llhttp.c + ) + add_library(llhttp OBJECT ${LIBLLHTTP_SOURCES}) + target_include_directories(llhttp PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/llhttp/include" + ) + set_target_properties(llhttp PROPERTIES + POSITION_INDEPENDENT_CODE ON + ) + + set(LIBURL_PARSER_SOURCES + url-parser/url_parser.c + ) + add_library(url-parser OBJECT ${LIBURL_PARSER_SOURCES}) + set_target_properties(url-parser PROPERTIES + POSITION_INDEPENDENT_CODE ON) + + if(HAVE_NEVERBLEED) + set(NEVERBLEED_SOURCES + neverbleed/neverbleed.c + ) + add_library(neverbleed ${NEVERBLEED_SOURCES}) + target_include_directories(neverbleed PRIVATE ${OPENSSL_INCLUDE_DIRS}) + target_include_directories(neverbleed INTERFACE + "${CMAKE_SOURCE_DIR}/third-party/neverbleed" + ) + target_link_libraries(neverbleed ${OPENSSL_LIBRARIES}) + endif() + + if(HAVE_MRUBY) + # EXTRA_DIST = build_config.rb mruby/* + + set(MRUBY_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/mruby/build") + set(MRUBY_LIBRARY + "${CMAKE_STATIC_LIBRARY_PREFIX}mruby${CMAKE_STATIC_LIBRARY_SUFFIX}" + ) + + # The mruby build needs some env vars. Alternatively, look at cmake -P + if(CMAKE_VERSION VERSION_LESS "3.1") + # XXX works only for Unixes? + set(ENV_COMMAND env) + else() + set(ENV_COMMAND ${CMAKE_COMMAND} -E env) + endif() + # Required for the Ninja generator. For older CMake, you first have to + # invoke 'ninja mruby' before building dependents. + if(CMAKE_VERSION VERSION_LESS "3.2") + set(_byproducts) + else() + set(_byproducts BYPRODUCTS "mruby/build/lib/${MRUBY_LIBRARY}") + endif() + add_custom_target(mruby + COMMAND ${ENV_COMMAND} + "MRUBY_CONFIG=${CMAKE_CURRENT_SOURCE_DIR}/build_config.rb" + "BUILD_DIR=${MRUBY_BUILD_DIR}" + "INSTALL_DIR=${MRUBY_BUILD_DIR}/install/bin" + "MRUBY_CC=${CMAKE_C_COMPILER}" "MRUBY_CXX=${CMAKE_CXX_COMPILER}" + "${CMAKE_CURRENT_SOURCE_DIR}/mruby/minirake" + -f "${CMAKE_CURRENT_SOURCE_DIR}/mruby/Rakefile" + ${_byproducts} + VERBATIM + ) + + # Make the mruby library available to others in this project without them + # having to worry about include dirs and the mruby location. + add_library(mruby-lib STATIC IMPORTED GLOBAL) + set_target_properties(mruby-lib PROPERTIES + IMPORTED_LOCATION "${MRUBY_BUILD_DIR}/lib/${MRUBY_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/mruby/include" + ) + add_dependencies(mruby-lib mruby) + + set_directory_properties(PROPERTIES + ADDITIONAL_MAKE_CLEAN_FILES mruby/build + ) + endif() +endif() diff --git a/lib/nghttp2/third-party/Makefile.am b/lib/nghttp2/third-party/Makefile.am new file mode 100644 index 00000000000..8b47532e2bd --- /dev/null +++ b/lib/nghttp2/third-party/Makefile.am @@ -0,0 +1,593 @@ +# nghttp2 - HTTP/2 C Library + +# Copyright (c) 2014 Tatsuhiro Tsujikawa + +# 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. + +AM_CPPFLAGS = @DEFS@ + +EXTRA_DIST = CMakeLists.txt build_config.rb + +# Enumerate all mruby files with the following command: +# find mruby -type f ! -ipath 'mruby/.*' | awk '{print "\t"$0" \\"}' +EXTRA_DIST += \ + mruby/AUTHORS \ + mruby/codespell.txt \ + mruby/CONTRIBUTING.md \ + mruby/NEWS \ + mruby/CODEOWNERS \ + mruby/appveyor.yml \ + mruby/benchmark/plot.gpl \ + mruby/benchmark/bm_ao_render.rb \ + mruby/benchmark/bm_fib.rb \ + mruby/benchmark/bm_so_lists.rb \ + mruby/benchmark/bm_app_lc_fizzbuzz.rb \ + mruby/Makefile \ + mruby/LEGAL \ + mruby/tasks/libmruby.rake \ + mruby/tasks/core.rake \ + mruby/tasks/toolchains/visualcpp.rake \ + mruby/tasks/toolchains/openwrt.rake \ + mruby/tasks/toolchains/android.rake \ + mruby/tasks/toolchains/gcc.rake \ + mruby/tasks/toolchains/clang.rake \ + mruby/tasks/doc.rake \ + mruby/tasks/benchmark.rake \ + mruby/tasks/mrbgems.rake \ + mruby/tasks/presym.rake \ + mruby/tasks/bin.rake \ + mruby/tasks/test.rake \ + mruby/tasks/mrblib.rake \ + mruby/README.md \ + mruby/oss-fuzz/ruby.proto \ + mruby/oss-fuzz/proto_to_ruby.cpp \ + mruby/oss-fuzz/mruby_proto_fuzzer.cpp \ + mruby/oss-fuzz/config/mruby.dict \ + mruby/oss-fuzz/config/mruby_proto_fuzzer.options \ + mruby/oss-fuzz/config/mruby_fuzzer.options \ + mruby/oss-fuzz/mruby_fuzzer.c \ + mruby/oss-fuzz/proto_to_ruby.h \ + mruby/build_config/cross-mingw-winetest.rb \ + mruby/build_config/host-f32.rb \ + mruby/build_config/chipKITMax32.rb \ + mruby/build_config/gameboyadvance.rb \ + mruby/build_config/minimal.rb \ + mruby/build_config/host-m32.rb \ + mruby/build_config/host-shared.rb \ + mruby/build_config/host-debug.rb \ + mruby/build_config/bench.rb \ + mruby/build_config/host-cxx.rb \ + mruby/build_config/mrbc.rb \ + mruby/build_config/ArduinoDue.rb \ + mruby/build_config/IntelEdison.rb \ + mruby/build_config/boxing.rb \ + mruby/build_config/IntelGalileo.rb \ + mruby/build_config/ci/msvc.rb \ + mruby/build_config/ci/gcc-clang.rb \ + mruby/build_config/nintendo_switch.rb \ + mruby/build_config/serenity.rb \ + mruby/build_config/dreamcast_shelf.rb \ + mruby/build_config/default.rb \ + mruby/build_config/android_armeabi_v7a_neon_hard.rb \ + mruby/build_config/host-gprof.rb \ + mruby/build_config/clang-asan.rb \ + mruby/build_config/RX630.rb \ + mruby/build_config/host-nofloat.rb \ + mruby/build_config/android_armeabi.rb \ + mruby/build_config/android_arm64_v8a.rb \ + mruby/build_config/cross-32bit.rb \ + mruby/build_config/cross-mingw.rb \ + mruby/build_config/helpers/wine_runner.rb \ + mruby/lib/mruby/build/command.rb \ + mruby/lib/mruby/build/load_gems.rb \ + mruby/lib/mruby/lockfile.rb \ + mruby/lib/mruby/build.rb \ + mruby/lib/mruby/core_ext.rb \ + mruby/lib/mruby/doc.rb \ + mruby/lib/mruby/gem.rb \ + mruby/lib/mruby/presym.rb \ + mruby/lib/mruby/source.rb \ + mruby/examples/mrbgems/ruby_extension_example/mrbgem.rake \ + mruby/examples/mrbgems/ruby_extension_example/README.md \ + mruby/examples/mrbgems/ruby_extension_example/test/example.rb \ + mruby/examples/mrbgems/ruby_extension_example/mrblib/example.rb \ + mruby/examples/mrbgems/cdata_extension_example/mrbgem.rake \ + mruby/examples/mrbgems/cdata_extension_example/README.md \ + mruby/examples/mrbgems/cdata_extension_example/test/example.c \ + mruby/examples/mrbgems/cdata_extension_example/src/example.c \ + mruby/examples/mrbgems/c_and_ruby_extension_example/mrbgem.rake \ + mruby/examples/mrbgems/c_and_ruby_extension_example/README.md \ + mruby/examples/mrbgems/c_and_ruby_extension_example/test/example.rb \ + mruby/examples/mrbgems/c_and_ruby_extension_example/mrblib/example.rb \ + mruby/examples/mrbgems/c_and_ruby_extension_example/src/example.c \ + mruby/examples/mrbgems/c_extension_example/mrbgem.rake \ + mruby/examples/mrbgems/c_extension_example/README.md \ + mruby/examples/mrbgems/c_extension_example/test/example.rb \ + mruby/examples/mrbgems/c_extension_example/test/example.c \ + mruby/examples/mrbgems/c_extension_example/src/example.c \ + mruby/examples/mrbgems/mruby-YOUR-bigint/TODO-HINT.md \ + mruby/examples/mrbgems/mruby-YOUR-bigint/core/bigint.c \ + mruby/examples/mrbgems/mruby-YOUR-bigint/mrbgem.rake \ + mruby/test/bintest.rb \ + mruby/test/t/enumerable.rb \ + mruby/test/t/comparable.rb \ + mruby/test/t/false.rb \ + mruby/test/t/exception.rb \ + mruby/test/t/rangeerror.rb \ + mruby/test/t/bs_literal.rb \ + mruby/test/t/regexperror.rb \ + mruby/test/t/class.rb \ + mruby/test/t/iterations.rb \ + mruby/test/t/true.rb \ + mruby/test/t/lang.rb \ + mruby/test/t/range.rb \ + mruby/test/t/kernel.rb \ + mruby/test/t/unicode.rb \ + mruby/test/t/localjumperror.rb \ + mruby/test/t/hash.rb \ + mruby/test/t/syntax.rb \ + mruby/test/t/nameerror.rb \ + mruby/test/t/module.rb \ + mruby/test/t/argumenterror.rb \ + mruby/test/t/methods.rb \ + mruby/test/t/ensure.rb \ + mruby/test/t/indexerror.rb \ + mruby/test/t/runtimeerror.rb \ + mruby/test/t/gc.rb \ + mruby/test/t/array.rb \ + mruby/test/t/string.rb \ + mruby/test/t/proc.rb \ + mruby/test/t/basicobject.rb \ + mruby/test/t/integer.rb \ + mruby/test/t/nomethoderror.rb \ + mruby/test/t/codegen.rb \ + mruby/test/t/literals.rb \ + mruby/test/t/nil.rb \ + mruby/test/t/typeerror.rb \ + mruby/test/t/symbol.rb \ + mruby/test/t/object.rb \ + mruby/test/t/vformat.rb \ + mruby/test/t/numeric.rb \ + mruby/test/t/bs_block.rb \ + mruby/test/t/float.rb \ + mruby/test/t/standarderror.rb \ + mruby/test/t/superclass.rb \ + mruby/test/assert.rb \ + mruby/super-linter.report/.keep \ + mruby/TODO.md \ + mruby/Doxyfile \ + mruby/mrblib/00kernel.rb \ + mruby/mrblib/range.rb \ + mruby/mrblib/kernel.rb \ + mruby/mrblib/hash.rb \ + mruby/mrblib/00class.rb \ + mruby/mrblib/10error.rb \ + mruby/mrblib/array.rb \ + mruby/mrblib/string.rb \ + mruby/mrblib/compar.rb \ + mruby/mrblib/symbol.rb \ + mruby/mrblib/enum.rb \ + mruby/mrblib/numeric.rb \ + mruby/build_config.rb \ + mruby/LICENSE \ + mruby/src/etc.c \ + mruby/src/variable.c \ + mruby/src/cdump.c \ + mruby/src/init.c \ + mruby/src/opcode.h \ + mruby/src/gc.c \ + mruby/src/load.c \ + mruby/src/codedump.c \ + mruby/src/error.c \ + mruby/src/readfloat.c \ + mruby/src/state.c \ + mruby/src/string.c \ + mruby/src/numeric.c \ + mruby/src/readnum.c \ + mruby/src/enum.c \ + mruby/src/print.c \ + mruby/src/readint.c \ + mruby/src/numops.c \ + mruby/src/hash.c \ + mruby/src/version.c \ + mruby/src/backtrace.c \ + mruby/src/fmt_fp.c \ + mruby/src/range.c \ + mruby/src/vm.c \ + mruby/src/debug.c \ + mruby/src/symbol.c \ + mruby/src/error.h \ + mruby/src/kernel.c \ + mruby/src/object.c \ + mruby/src/proc.c \ + mruby/src/array.c \ + mruby/src/dump.c \ + mruby/src/class.c \ + mruby/src/compar.c \ + mruby/src/value_array.h \ + mruby/src/pool.c \ + mruby/mruby-source.gemspec \ + mruby/mrbgems/mruby-eval/mrbgem.rake \ + mruby/mrbgems/mruby-eval/test/eval.rb \ + mruby/mrbgems/mruby-eval/src/eval.c \ + mruby/mrbgems/stdlib.gembox \ + mruby/mrbgems/mruby-string-ext/mrbgem.rake \ + mruby/mrbgems/mruby-string-ext/test/range.rb \ + mruby/mrbgems/mruby-string-ext/test/string.rb \ + mruby/mrbgems/mruby-string-ext/test/numeric.rb \ + mruby/mrbgems/mruby-string-ext/mrblib/string.rb \ + mruby/mrbgems/mruby-string-ext/src/string.c \ + mruby/mrbgems/default-no-stdio.gembox \ + mruby/mrbgems/mruby-set/mrbgem.rake \ + mruby/mrbgems/mruby-set/README.md \ + mruby/mrbgems/mruby-set/test/set.rb \ + mruby/mrbgems/mruby-set/mrblib/set.rb \ + mruby/mrbgems/mruby-set/LICENSE \ + mruby/mrbgems/mruby-set/mruby-set.gem \ + mruby/mrbgems/mruby-method/mrbgem.rake \ + mruby/mrbgems/mruby-method/README.md \ + mruby/mrbgems/mruby-method/test/method.rb \ + mruby/mrbgems/mruby-method/mrblib/kernel.rb \ + mruby/mrbgems/mruby-method/mrblib/method.rb \ + mruby/mrbgems/mruby-method/src/method.c \ + mruby/mrbgems/mruby-random/mrbgem.rake \ + mruby/mrbgems/mruby-random/test/random.rb \ + mruby/mrbgems/mruby-random/src/random.c \ + mruby/mrbgems/mruby-catch/mrbgem.rake \ + mruby/mrbgems/mruby-catch/test/catch.rb \ + mruby/mrbgems/mruby-catch/mrblib/catch.rb \ + mruby/mrbgems/mruby-catch/src/catch.c \ + mruby/mrbgems/mruby-cmath/mrbgem.rake \ + mruby/mrbgems/mruby-cmath/test/cmath.rb \ + mruby/mrbgems/mruby-cmath/src/cmath.c \ + mruby/mrbgems/mruby-compar-ext/mrbgem.rake \ + mruby/mrbgems/mruby-compar-ext/mrblib/compar.rb \ + mruby/mrbgems/mruby-pack/mrbgem.rake \ + mruby/mrbgems/mruby-pack/README.md \ + mruby/mrbgems/mruby-pack/test/pack.rb \ + mruby/mrbgems/mruby-pack/src/pack.c \ + mruby/mrbgems/mruby-struct/mrbgem.rake \ + mruby/mrbgems/mruby-struct/test/struct.rb \ + mruby/mrbgems/mruby-struct/mrblib/struct.rb \ + mruby/mrbgems/mruby-struct/src/struct.c \ + mruby/mrbgems/mruby-bigint/core/bigint.c \ + mruby/mrbgems/mruby-bigint/core/bigint.h \ + mruby/mrbgems/mruby-bigint/mrbgem.rake \ + mruby/mrbgems/mruby-bigint/README.md \ + mruby/mrbgems/mruby-bigint/test/bigint.rb \ + mruby/mrbgems/mruby-bigint/README-fgmp.md \ + mruby/mrbgems/mruby-symbol-ext/mrbgem.rake \ + mruby/mrbgems/mruby-symbol-ext/test/symbol.rb \ + mruby/mrbgems/mruby-symbol-ext/mrblib/symbol.rb \ + mruby/mrbgems/mruby-symbol-ext/src/symbol.c \ + mruby/mrbgems/mruby-io/mrbgem.rake \ + mruby/mrbgems/mruby-io/README.md \ + mruby/mrbgems/mruby-io/test/mruby_io_test.c \ + mruby/mrbgems/mruby-io/test/io.rb \ + mruby/mrbgems/mruby-io/test/file.rb \ + mruby/mrbgems/mruby-io/test/file_test.rb \ + mruby/mrbgems/mruby-io/mrblib/io.rb \ + mruby/mrbgems/mruby-io/mrblib/file.rb \ + mruby/mrbgems/mruby-io/mrblib/file_constants.rb \ + mruby/mrbgems/mruby-io/mrblib/kernel.rb \ + mruby/mrbgems/mruby-io/src/mruby_io_gem.c \ + mruby/mrbgems/mruby-io/src/file_test.c \ + mruby/mrbgems/mruby-io/src/io.c \ + mruby/mrbgems/mruby-io/src/file.c \ + mruby/mrbgems/mruby-io/include/mruby/ext/io.h \ + mruby/mrbgems/mruby-compiler/core/parse.y \ + mruby/mrbgems/mruby-compiler/core/y.tab.c \ + mruby/mrbgems/mruby-compiler/core/node.h \ + mruby/mrbgems/mruby-compiler/core/keywords \ + mruby/mrbgems/mruby-compiler/core/codegen.c \ + mruby/mrbgems/mruby-compiler/core/lex.def \ + mruby/mrbgems/mruby-compiler/mrbgem.rake \ + mruby/mrbgems/mruby-bin-config/mrbgem.rake \ + mruby/mrbgems/mruby-bin-config/mruby-config \ + mruby/mrbgems/mruby-bin-config/mruby-config.bat \ + mruby/mrbgems/mruby-proc-ext/mrbgem.rake \ + mruby/mrbgems/mruby-proc-ext/test/proc.rb \ + mruby/mrbgems/mruby-proc-ext/test/proc.c \ + mruby/mrbgems/mruby-proc-ext/mrblib/proc.rb \ + mruby/mrbgems/mruby-proc-ext/src/proc.c \ + mruby/mrbgems/mruby-data/mrbgem.rake \ + mruby/mrbgems/mruby-data/test/data.rb \ + mruby/mrbgems/mruby-data/src/data.c \ + mruby/mrbgems/mruby-dir/mrbgem.rake \ + mruby/mrbgems/mruby-dir/README.md \ + mruby/mrbgems/mruby-dir/test/dir.rb \ + mruby/mrbgems/mruby-dir/test/dirtest.c \ + mruby/mrbgems/mruby-dir/mrblib/dir.rb \ + mruby/mrbgems/mruby-dir/src/Win/dirent.c \ + mruby/mrbgems/mruby-dir/src/dir.c \ + mruby/mrbgems/mruby-object-ext/mrbgem.rake \ + mruby/mrbgems/mruby-object-ext/test/nil.rb \ + mruby/mrbgems/mruby-object-ext/test/object.rb \ + mruby/mrbgems/mruby-object-ext/mrblib/object.rb \ + mruby/mrbgems/mruby-object-ext/src/object.c \ + mruby/mrbgems/mruby-kernel-ext/mrbgem.rake \ + mruby/mrbgems/mruby-kernel-ext/test/kernel.rb \ + mruby/mrbgems/mruby-kernel-ext/src/kernel.c \ + mruby/mrbgems/mruby-class-ext/mrbgem.rake \ + mruby/mrbgems/mruby-class-ext/test/class.rb \ + mruby/mrbgems/mruby-class-ext/test/module.rb \ + mruby/mrbgems/mruby-class-ext/mrblib/module.rb \ + mruby/mrbgems/mruby-class-ext/src/class.c \ + mruby/mrbgems/mruby-binding/mrbgem.rake \ + mruby/mrbgems/mruby-binding/test/binding.rb \ + mruby/mrbgems/mruby-binding/test/binding.c \ + mruby/mrbgems/mruby-binding/src/binding.c \ + mruby/mrbgems/mruby-print/mrbgem.rake \ + mruby/mrbgems/mruby-print/mrblib/print.rb \ + mruby/mrbgems/mruby-print/src/print.c \ + mruby/mrbgems/mruby-hash-ext/mrbgem.rake \ + mruby/mrbgems/mruby-hash-ext/test/hash.rb \ + mruby/mrbgems/mruby-hash-ext/mrblib/hash.rb \ + mruby/mrbgems/mruby-hash-ext/src/hash-ext.c \ + mruby/mrbgems/mruby-sleep/example/sleep.rb \ + mruby/mrbgems/mruby-sleep/mrbgem.rake \ + mruby/mrbgems/mruby-sleep/README.md \ + mruby/mrbgems/mruby-sleep/test/sleep_test.rb \ + mruby/mrbgems/mruby-sleep/src/sleep.c \ + mruby/mrbgems/mruby-sprintf/mrbgem.rake \ + mruby/mrbgems/mruby-sprintf/test/sprintf.rb \ + mruby/mrbgems/mruby-sprintf/mrblib/string.rb \ + mruby/mrbgems/mruby-sprintf/src/sprintf.c \ + mruby/mrbgems/mruby-enum-chain/mrbgem.rake \ + mruby/mrbgems/mruby-enum-chain/test/enum_chain.rb \ + mruby/mrbgems/mruby-enum-chain/mrblib/chain.rb \ + mruby/mrbgems/mruby-range-ext/mrbgem.rake \ + mruby/mrbgems/mruby-range-ext/test/range.rb \ + mruby/mrbgems/mruby-range-ext/mrblib/range.rb \ + mruby/mrbgems/mruby-range-ext/src/range.c \ + mruby/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c \ + mruby/mrbgems/mruby-bin-mirb/mrbgem.rake \ + mruby/mrbgems/mruby-bin-mirb/bintest/mirb.rb \ + mruby/mrbgems/mruby-metaprog/mrbgem.rake \ + mruby/mrbgems/mruby-metaprog/test/metaprog.rb \ + mruby/mrbgems/mruby-metaprog/src/metaprog.c \ + mruby/mrbgems/mruby-test-inline-struct/mrbgem.rake \ + mruby/mrbgems/mruby-test-inline-struct/test/inline.c \ + mruby/mrbgems/mruby-test-inline-struct/test/inline.rb \ + mruby/mrbgems/mruby-time/mrbgem.rake \ + mruby/mrbgems/mruby-time/test/time.rb \ + mruby/mrbgems/mruby-time/mrblib/time.rb \ + mruby/mrbgems/mruby-time/src/time.c \ + mruby/mrbgems/mruby-time/include/mruby/time.h \ + mruby/mrbgems/mruby-proc-binding/mrbgem.rake \ + mruby/mrbgems/mruby-proc-binding/test/proc-binding.c \ + mruby/mrbgems/mruby-proc-binding/test/proc-binding.rb \ + mruby/mrbgems/mruby-proc-binding/src/proc-binding.c \ + mruby/mrbgems/stdlib-ext.gembox \ + mruby/mrbgems/metaprog.gembox \ + mruby/mrbgems/mruby-enumerator/mrbgem.rake \ + mruby/mrbgems/mruby-enumerator/test/enumerator.rb \ + mruby/mrbgems/mruby-enumerator/mrblib/enumerator.rb \ + mruby/mrbgems/math.gembox \ + mruby/mrbgems/mruby-socket/mrbgem.rake \ + mruby/mrbgems/mruby-socket/README.md \ + mruby/mrbgems/mruby-socket/test/socket.rb \ + mruby/mrbgems/mruby-socket/test/udpsocket.rb \ + mruby/mrbgems/mruby-socket/test/tcpsocket.rb \ + mruby/mrbgems/mruby-socket/test/sockettest.c \ + mruby/mrbgems/mruby-socket/test/basicsocket.rb \ + mruby/mrbgems/mruby-socket/test/addrinfo.rb \ + mruby/mrbgems/mruby-socket/test/unix.rb \ + mruby/mrbgems/mruby-socket/test/ipsocket.rb \ + mruby/mrbgems/mruby-socket/mrblib/socket.rb \ + mruby/mrbgems/mruby-socket/src/socket.c \ + mruby/mrbgems/mruby-socket/src/gen.rb \ + mruby/mrbgems/mruby-socket/src/const.cstub \ + mruby/mrbgems/mruby-socket/src/const.def \ + mruby/mrbgems/mruby-objectspace/mrbgem.rake \ + mruby/mrbgems/mruby-objectspace/test/objectspace.rb \ + mruby/mrbgems/mruby-objectspace/src/mruby_objectspace.c \ + mruby/mrbgems/full-core.gembox \ + mruby/mrbgems/mruby-rational/mrbgem.rake \ + mruby/mrbgems/mruby-rational/test/rational.rb \ + mruby/mrbgems/mruby-rational/mrblib/rational.rb \ + mruby/mrbgems/mruby-rational/src/rational.c \ + mruby/mrbgems/mruby-numeric-ext/mrbgem.rake \ + mruby/mrbgems/mruby-numeric-ext/test/numeric.rb \ + mruby/mrbgems/mruby-numeric-ext/mrblib/numeric_ext.rb \ + mruby/mrbgems/mruby-numeric-ext/src/numeric_ext.c \ + mruby/mrbgems/mruby-binding-core/mrbgem.rake \ + mruby/mrbgems/mruby-binding-core/test/binding-core.rb \ + mruby/mrbgems/mruby-binding-core/src/binding-core.c \ + mruby/mrbgems/mruby-fiber/mrbgem.rake \ + mruby/mrbgems/mruby-fiber/test/fiber.rb \ + mruby/mrbgems/mruby-fiber/src/fiber.c \ + mruby/mrbgems/mruby-complex/mrbgem.rake \ + mruby/mrbgems/mruby-complex/test/complex.rb \ + mruby/mrbgems/mruby-complex/mrblib/complex.rb \ + mruby/mrbgems/mruby-complex/src/complex.c \ + mruby/mrbgems/mruby-exit/mrbgem.rake \ + mruby/mrbgems/mruby-exit/src/mruby-exit.c \ + mruby/mrbgems/mruby-error/mrbgem.rake \ + mruby/mrbgems/mruby-error/test/exception.rb \ + mruby/mrbgems/mruby-error/test/exception.c \ + mruby/mrbgems/mruby-error/src/exception.c \ + mruby/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c \ + mruby/mrbgems/mruby-bin-mruby/mrbgem.rake \ + mruby/mrbgems/mruby-bin-mruby/bintest/mruby.rb \ + mruby/mrbgems/mruby-test/mrbgem.rake \ + mruby/mrbgems/mruby-test/vformat.c \ + mruby/mrbgems/mruby-test/README.md \ + mruby/mrbgems/mruby-test/driver.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdbreak.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdrun.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apistring.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdberror.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apistring.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdmisc.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdbconf.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.h \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c \ + mruby/mrbgems/mruby-bin-debugger/tools/mrdb/apilist.c \ + mruby/mrbgems/mruby-bin-debugger/mrbgem.rake \ + mruby/mrbgems/mruby-bin-debugger/bintest/mrdb.rb \ + mruby/mrbgems/mruby-bin-debugger/bintest/print.rb \ + mruby/mrbgems/mruby-array-ext/mrbgem.rake \ + mruby/mrbgems/mruby-array-ext/test/array.rb \ + mruby/mrbgems/mruby-array-ext/mrblib/array.rb \ + mruby/mrbgems/mruby-array-ext/src/array.c \ + mruby/mrbgems/mruby-enum-lazy/mrbgem.rake \ + mruby/mrbgems/mruby-enum-lazy/test/lazy.rb \ + mruby/mrbgems/mruby-enum-lazy/mrblib/lazy.rb \ + mruby/mrbgems/mruby-bin-strip/tools/mruby-strip/mruby-strip.c \ + mruby/mrbgems/mruby-bin-strip/mrbgem.rake \ + mruby/mrbgems/mruby-bin-strip/bintest/mruby-strip.rb \ + mruby/mrbgems/mruby-errno/mrbgem.rake \ + mruby/mrbgems/mruby-errno/README.md \ + mruby/mrbgems/mruby-errno/test/errno.rb \ + mruby/mrbgems/mruby-errno/mrblib/errno.rb \ + mruby/mrbgems/mruby-errno/src/gen.rb \ + mruby/mrbgems/mruby-errno/src/known_errors_def.cstub \ + mruby/mrbgems/mruby-errno/src/known_errors.def \ + mruby/mrbgems/mruby-errno/src/errno.c \ + mruby/mrbgems/mruby-toplevel-ext/mrbgem.rake \ + mruby/mrbgems/mruby-toplevel-ext/test/toplevel.rb \ + mruby/mrbgems/mruby-toplevel-ext/mrblib/toplevel.rb \ + mruby/mrbgems/mruby-math/mrbgem.rake \ + mruby/mrbgems/mruby-math/test/math.rb \ + mruby/mrbgems/mruby-math/src/math.c \ + mruby/mrbgems/mruby-bin-mrbc/tools/mrbc/stub.c \ + mruby/mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.c \ + mruby/mrbgems/mruby-bin-mrbc/mrbgem.rake \ + mruby/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb \ + mruby/mrbgems/stdlib-io.gembox \ + mruby/mrbgems/default.gembox \ + mruby/mrbgems/mruby-os-memsize/mrbgem.rake \ + mruby/mrbgems/mruby-os-memsize/test/memsize.rb \ + mruby/mrbgems/mruby-os-memsize/src/memsize.c \ + mruby/mrbgems/default-no-fpu.gembox \ + mruby/mrbgems/mruby-enum-ext/mrbgem.rake \ + mruby/mrbgems/mruby-enum-ext/test/enum.rb \ + mruby/mrbgems/mruby-enum-ext/mrblib/enum.rb \ + mruby/Rakefile \ + mruby/SECURITY.md \ + mruby/doc/mruby_logo_red_icon.png \ + mruby/doc/mruby3.0.md \ + mruby/doc/mruby3.1.md \ + mruby/doc/internal/opcode.md \ + mruby/doc/internal/boxing.md \ + mruby/doc/limitations.md \ + mruby/doc/mruby3.2.md \ + mruby/doc/guides/compile.md \ + mruby/doc/guides/mrbgems.md \ + mruby/doc/guides/debugger.md \ + mruby/doc/guides/link.md \ + mruby/doc/guides/symbol.md \ + mruby/doc/guides/gc-arena-howto.md \ + mruby/doc/guides/mrbconf.md \ + mruby/minirake \ + mruby/include/mruby/opcode.h \ + mruby/include/mruby/re.h \ + mruby/include/mruby/hash.h \ + mruby/include/mruby/string.h \ + mruby/include/mruby/presym.h \ + mruby/include/mruby/object.h \ + mruby/include/mruby/class.h \ + mruby/include/mruby/ops.h \ + mruby/include/mruby/irep.h \ + mruby/include/mruby/compile.h \ + mruby/include/mruby/array.h \ + mruby/include/mruby/range.h \ + mruby/include/mruby/throw.h \ + mruby/include/mruby/variable.h \ + mruby/include/mruby/boxing_word.h \ + mruby/include/mruby/istruct.h \ + mruby/include/mruby/data.h \ + mruby/include/mruby/common.h \ + mruby/include/mruby/error.h \ + mruby/include/mruby/debug.h \ + mruby/include/mruby/boxing_no.h \ + mruby/include/mruby/numeric.h \ + mruby/include/mruby/boxing_nan.h \ + mruby/include/mruby/value.h \ + mruby/include/mruby/endian.h \ + mruby/include/mruby/internal.h \ + mruby/include/mruby/dump.h \ + mruby/include/mruby/version.h \ + mruby/include/mruby/khash.h \ + mruby/include/mruby/gc.h \ + mruby/include/mruby/presym/enable.h \ + mruby/include/mruby/presym/disable.h \ + mruby/include/mruby/presym/scanning.h \ + mruby/include/mruby/proc.h \ + mruby/include/mrbconf.h \ + mruby/include/mruby.h + +if ENABLE_THIRD_PARTY + +noinst_LTLIBRARIES = liburl-parser.la +liburl_parser_la_SOURCES = \ + url-parser/url_parser.c \ + url-parser/url_parser.h + +noinst_LTLIBRARIES += libllhttp.la +libllhttp_la_SOURCES = \ + llhttp/src/api.c \ + llhttp/src/http.c \ + llhttp/src/llhttp.c \ + llhttp/include/llhttp.h +libllhttp_la_CPPFLAGS = -I${srcdir}/llhttp/include + +if HAVE_NEVERBLEED +noinst_LTLIBRARIES += libneverbleed.la +libneverbleed_la_CPPFLAGS = @OPENSSL_CFLAGS@ +libneverbleed_la_LIBADD = @OPENSSL_LIBS@ +libneverbleed_la_SOURCES = neverbleed/neverbleed.c neverbleed/neverbleed.h +endif # HAVE_NEVERBLEED + +if HAVE_MRUBY + +.PHONY: all-local clean mruby + +mruby: + mkdir -p "${abs_builddir}/mruby/build" + diff "${srcdir}/build_config.rb" "${abs_builddir}/mruby/build/build_config.rb" >& /dev/null || \ + cp "${srcdir}/build_config.rb" "${abs_builddir}/mruby/build" + MRUBY_CONFIG="${abs_builddir}/mruby/build/build_config.rb" \ + BUILD_DIR="${abs_builddir}/mruby/build" \ + INSTALL_DIR="${abs_builddir}/mruby/build/install/bin" \ + MRUBY_CC="${CC}" MRUBY_CXX="$(firstword $(CXX))" MRUBY_LD="${LD}" \ + MRUBY_AR="${AR}" \ + HOST="${host}" BUILD="${build}" \ + "${srcdir}/mruby/minirake" -f "${srcdir}/mruby/Rakefile" + +all-local: mruby + +clean-local: + [ ! -f "${abs_builddir}/mruby/build/build_config.rb" ] || \ + MRUBY_CONFIG="${abs_builddir}/mruby/build/build_config.rb" \ + BUILD_DIR="${abs_builddir}/mruby/build" \ + MRUBY_CC="${CC}" \ + "${srcdir}/mruby/minirake" -f "${srcdir}/mruby/Rakefile" clean + +endif # HAVE_MRUBY + +endif # ENABLE_THIRD_PARTY diff --git a/lib/nghttp2/third-party/build_config.rb b/lib/nghttp2/third-party/build_config.rb new file mode 100644 index 00000000000..e06a4ccca4e --- /dev/null +++ b/lib/nghttp2/third-party/build_config.rb @@ -0,0 +1,34 @@ +def config(conf) + toolchain :clang if ENV['MRUBY_CC'].include? "clang" + toolchain :gcc if ENV['MRUBY_CC'].include? "gcc" + + conf.cc.command = ENV['MRUBY_CC'] + conf.cxx.command = ENV['MRUBY_CXX'] + + if ENV['MRUBY_LD'] + conf.linker.command = ENV['MRUBY_LD'] + end + if ENV['MRUBY_AR'] + conf.archiver.command = ENV['MRUBY_AR'] + end + + # C++ project needs this. Without this, mruby exception does not + # properly destroy C++ object allocated on stack. + conf.enable_cxx_exception + + conf.build_dir = ENV['BUILD_DIR'] + + # include the default GEMs + conf.gembox 'default' + conf.gem :core => 'mruby-eval' +end + +if ENV['BUILD'] == ENV['HOST'] then + MRuby::Build.new do |conf| + config(conf) + end +else + MRuby::CrossBuild.new(ENV['HOST']) do |conf| + config(conf) + end +end diff --git a/lib/nghttp2/third-party/llhttp/LICENSE-MIT b/lib/nghttp2/third-party/llhttp/LICENSE-MIT new file mode 100644 index 00000000000..6c1512dd6bc --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/LICENSE-MIT @@ -0,0 +1,22 @@ +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +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. diff --git a/lib/nghttp2/third-party/llhttp/README.md b/lib/nghttp2/third-party/llhttp/README.md new file mode 100644 index 00000000000..e982ed0ae71 --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/README.md @@ -0,0 +1,482 @@ +# llhttp +[![CI](https://github.com/nodejs/llhttp/workflows/CI/badge.svg)](https://github.com/nodejs/llhttp/actions?query=workflow%3ACI) + +Port of [http_parser][0] to [llparse][1]. + +## Why? + +Let's face it, [http_parser][0] is practically unmaintainable. Even +introduction of a single new method results in a significant code churn. + +This project aims to: + +* Make it maintainable +* Verifiable +* Improving benchmarks where possible + +More details in [Fedor Indutny's talk at JSConf EU 2019](https://youtu.be/x3k_5Mi66sY) + +## How? + +Over time, different approaches for improving [http_parser][0]'s code base +were tried. However, all of them failed due to resulting significant performance +degradation. + +This project is a port of [http_parser][0] to TypeScript. [llparse][1] is used +to generate the output C source file, which could be compiled and +linked with the embedder's program (like [Node.js][7]). + +## Performance + +So far llhttp outperforms http_parser: + +| | input size | bandwidth | reqs/sec | time | +|:----------------|-----------:|-------------:|-----------:|--------:| +| **llhttp** | 8192.00 mb | 1777.24 mb/s | 3583799.39 req/sec | 4.61 s | +| **http_parser** | 8192.00 mb | 694.66 mb/s | 1406180.33 req/sec | 11.79 s | + +llhttp is faster by approximately **156%**. + +## Maintenance + +llhttp project has about 1400 lines of TypeScript code describing the parser +itself and around 450 lines of C code and headers providing the helper methods. +The whole [http_parser][0] is implemented in approximately 2500 lines of C, and +436 lines of headers. + +All optimizations and multi-character matching in llhttp are generated +automatically, and thus doesn't add any extra maintenance cost. On the contrary, +most of http_parser's code is hand-optimized and unrolled. Instead describing +"how" it should parse the HTTP requests/responses, a maintainer should +implement the new features in [http_parser][0] cautiously, considering +possible performance degradation and manually optimizing the new code. + +## Verification + +The state machine graph is encoded explicitly in llhttp. The [llparse][1] +automatically checks the graph for absence of loops and correct reporting of the +input ranges (spans) like header names and values. In the future, additional +checks could be performed to get even stricter verification of the llhttp. + +## Usage + +```C +#include "stdio.h" +#include "llhttp.h" +#include "string.h" + +int handle_on_message_complete(llhttp_t* parser) { + fprintf(stdout, "Message completed!\n"); + return 0; +} + +int main() { + llhttp_t parser; + llhttp_settings_t settings; + + /*Initialize user callbacks and settings */ + llhttp_settings_init(&settings); + + /*Set user callback */ + settings.on_message_complete = handle_on_message_complete; + + /*Initialize the parser in HTTP_BOTH mode, meaning that it will select between + *HTTP_REQUEST and HTTP_RESPONSE parsing automatically while reading the first + *input. + */ + llhttp_init(&parser, HTTP_BOTH, &settings); + + /*Parse request! */ + const char* request = "GET / HTTP/1.1\r\n\r\n"; + int request_len = strlen(request); + + enum llhttp_errno err = llhttp_execute(&parser, request, request_len); + if (err == HPE_OK) { + fprintf(stdout, "Successfully parsed!\n"); + } else { + fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason); + } +} +``` +For more information on API usage, please refer to [src/native/api.h](https://github.com/nodejs/llhttp/blob/main/src/native/api.h). + +## API + +### llhttp_settings_t + +The settings object contains a list of callbacks that the parser will invoke. + +The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_PAUSED` (pause the parser): + +* `on_message_begin`: Invoked when a new request/response starts. +* `on_message_complete`: Invoked when a request/response has been completedly parsed. +* `on_url_complete`: Invoked after the URL has been parsed. +* `on_method_complete`: Invoked after the HTTP method has been parsed. +* `on_version_complete`: Invoked after the HTTP version has been parsed. +* `on_status_complete`: Invoked after the status code has been parsed. +* `on_header_field_complete`: Invoked after a header name has been parsed. +* `on_header_value_complete`: Invoked after a header value has been parsed. +* `on_chunk_header`: Invoked after a new chunk is started. The current chunk length is stored in `parser->content_length`. +* `on_chunk_extension_name_complete`: Invoked after a chunk extension name is started. +* `on_chunk_extension_value_complete`: Invoked after a chunk extension value is started. +* `on_chunk_complete`: Invoked after a new chunk is received. +* `on_reset`: Invoked after `on_message_complete` and before `on_message_begin` when a new message + is received on the same parser. This is not invoked for the first message of the parser. + +The following callbacks can return `0` (proceed normally), `-1` (error) or `HPE_USER` (error from the callback): + +* `on_url`: Invoked when another character of the URL is received. +* `on_status`: Invoked when another character of the status is received. +* `on_method`: Invoked when another character of the method is received. + When parser is created with `HTTP_BOTH` and the input is a response, this also invoked for the sequence `HTTP/` + of the first message. +* `on_version`: Invoked when another character of the version is received. +* `on_header_field`: Invoked when another character of a header name is received. +* `on_header_value`: Invoked when another character of a header value is received. +* `on_chunk_extension_name`: Invoked when another character of a chunk extension name is received. +* `on_chunk_extension_value`: Invoked when another character of a extension value is received. + +The callback `on_headers_complete`, invoked when headers are completed, can return: + +* `0`: Proceed normally. +* `1`: Assume that request/response has no body, and proceed to parsing the next message. +* `2`: Assume absence of body (as above) and make `llhttp_execute()` return `HPE_PAUSED_UPGRADE`. +* `-1`: Error +* `HPE_PAUSED`: Pause the parser. + +### `void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings)` + +Initialize the parser with specific type and user settings. + +### `uint8_t llhttp_get_type(llhttp_t* parser)` + +Returns the type of the parser. + +### `uint8_t llhttp_get_http_major(llhttp_t* parser)` + +Returns the major version of the HTTP protocol of the current request/response. + +### `uint8_t llhttp_get_http_minor(llhttp_t* parser)` + +Returns the minor version of the HTTP protocol of the current request/response. + +### `uint8_t llhttp_get_method(llhttp_t* parser)` + +Returns the method of the current request. + +### `int llhttp_get_status_code(llhttp_t* parser)` + +Returns the method of the current response. + +### `uint8_t llhttp_get_upgrade(llhttp_t* parser)` + +Returns `1` if request includes the `Connection: upgrade` header. + +### `void llhttp_reset(llhttp_t* parser)` + +Reset an already initialized parser back to the start state, preserving the +existing parser type, callback settings, user data, and lenient flags. + +### `void llhttp_settings_init(llhttp_settings_t* settings)` + +Initialize the settings object. + +### `llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len)` + +Parse full or partial request/response, invoking user callbacks along the way. + +If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing interrupts, +and such errno is returned from `llhttp_execute()`. If `HPE_PAUSED` was used as a errno, +the execution can be resumed with `llhttp_resume()` call. + +In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` is returned +after fully parsing the request/response. If the user wishes to continue parsing, +they need to invoke `llhttp_resume_after_upgrade()`. + +**if this function ever returns a non-pause type error, it will continue to return +the same error upon each successive call up until `llhttp_init()` is called.** + +### `llhttp_errno_t llhttp_finish(llhttp_t* parser)` + +This method should be called when the other side has no further bytes to +send (e.g. shutdown of readable side of the TCP connection.) + +Requests without `Content-Length` and other messages might require treating +all incoming bytes as the part of the body, up to the last byte of the +connection. + +This method will invoke `on_message_complete()` callback if the +request was terminated safely. Otherwise a error code would be returned. + + +### `int llhttp_message_needs_eof(const llhttp_t* parser)` + +Returns `1` if the incoming message is parsed until the last byte, and has to be completed by calling `llhttp_finish()` on EOF. + +### `int llhttp_should_keep_alive(const llhttp_t* parser)` + +Returns `1` if there might be any other messages following the last that was +successfully parsed. + +### `void llhttp_pause(llhttp_t* parser)` + +Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set +appropriate error reason. + +**Do not call this from user callbacks! User callbacks must return +`HPE_PAUSED` if pausing is required.** + +### `void llhttp_resume(llhttp_t* parser)` + +Might be called to resume the execution after the pause in user's callback. + +See `llhttp_execute()` above for details. + +**Call this only if `llhttp_execute()` returns `HPE_PAUSED`.** + +### `void llhttp_resume_after_upgrade(llhttp_t* parser)` + +Might be called to resume the execution after the pause in user's callback. +See `llhttp_execute()` above for details. + +**Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE`** + +### `llhttp_errno_t llhttp_get_errno(const llhttp_t* parser)` + +Returns the latest error. + +### `const char* llhttp_get_error_reason(const llhttp_t* parser)` + +Returns the verbal explanation of the latest returned error. + +**User callback should set error reason when returning the error. See +`llhttp_set_error_reason()` for details.** + +### `void llhttp_set_error_reason(llhttp_t* parser, const char* reason)` + +Assign verbal description to the returned error. Must be called in user +callbacks right before returning the errno. + +**`HPE_USER` error code might be useful in user callbacks.** + +### `const char* llhttp_get_error_pos(const llhttp_t* parser)` + +Returns the pointer to the last parsed byte before the returned error. The +pointer is relative to the `data` argument of `llhttp_execute()`. + +**This method might be useful for counting the number of parsed bytes.** + +### `const char* llhttp_errno_name(llhttp_errno_t err)` + +Returns textual name of error code. + +### `const char* llhttp_method_name(llhttp_method_t method)` + +Returns textual name of HTTP method. + +### `const char* llhttp_status_name(llhttp_status_t status)` + +Returns textual name of HTTP status. + +### `void llhttp_set_lenient_headers(llhttp_t* parser, int enabled)` + +Enables/disables lenient header value parsing (disabled by default). +Lenient parsing disables header value token checks, extending llhttp's +protocol support to highly non-compliant clients/server. + +No `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when +lenient parsing is "on". + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of conflicting `Transfer-Encoding` and +`Content-Length` headers (disabled by default). + +Normally `llhttp` would error when `Transfer-Encoding` is present in +conjunction with `Content-Length`. + +This error is important to prevent HTTP request smuggling, but may be less desirable +for small number of cases involving legacy servers. + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of `Connection: close` and HTTP/1.0 +requests responses. + +Normally `llhttp` would error the HTTP request/response +after the request/response with `Connection: close` and `Content-Length`. + +This is important to prevent cache poisoning attacks, +but might interact badly with outdated and insecure clients. + +With this flag the extra request/response will be parsed normally. + +**Enabling this flag can pose a security issue since you will be exposed to poisoning attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of `Transfer-Encoding` header. + +Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value +and another value after it (either in a single header or in multiple +headers whose value are internally joined using `, `). + +This is mandated by the spec to reliably determine request body size and thus +avoid request smuggling. + +With this flag the extra value will be parsed normally. + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_version(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of HTTP version. + +Normally `llhttp` would error when the HTTP version in the request or status line +is not `0.9`, `1.0`, `1.1` or `2.0`. +With this flag the extra value will be parsed normally. + +**Enabling this flag can pose a security issue since you will allow unsupported HTTP versions. USE WITH CAUTION!** + +### `void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of additional data received after a message ends +and keep-alive is disabled. + +Normally `llhttp` would error when additional unexpected data is received if the message +contains the `Connection` header with `close` value. +With this flag the extra data will discarded without throwing an error. + +**Enabling this flag can pose a security issue since you will be exposed to poisoning attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of incomplete CRLF sequences. + +Normally `llhttp` would error when a CR is not followed by LF when terminating the +request line, the status line, the headers or a chunk header. +With this flag only a CR is required to terminate such sections. + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +### `void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled)` + +Enables/disables lenient handling of chunks not separated via CRLF. + +Normally `llhttp` would error when after a chunk data a CRLF is missing before +starting a new chunk. +With this flag the new chunk can start immediately after the previous one. + +**Enabling this flag can pose a security issue since you will be exposed to request smuggling attacks. USE WITH CAUTION!** + +## Build Instructions + +Make sure you have [Node.js](https://nodejs.org/), npm and npx installed. Then under project directory run: + +```sh +npm install +make +``` + +--- + +### Bindings to other languages + +* Lua: [MunifTanjim/llhttp.lua][11] +* Python: [pallas/pyllhttp][8] +* Ruby: [metabahn/llhttp][9] +* Rust: [JackLiar/rust-llhttp][10] + +### Using with CMake + +If you want to use this library in a CMake project as a shared library, you can use the snippet below. + +``` +FetchContent_Declare(llhttp + URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz") + +FetchContent_MakeAvailable(llhttp) + +# Link with the llhttp_shared target +target_link_libraries(${EXAMPLE_PROJECT_NAME} ${PROJECT_LIBRARIES} llhttp_shared ${PROJECT_NAME}) +``` + +If you want to use this library in a CMake project as a static library, you can set some cache variables first. + +``` +FetchContent_Declare(llhttp + URL "https://github.com/nodejs/llhttp/archive/refs/tags/release/v8.1.0.tar.gz") + +set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "") +set(BUILD_STATIC_LIBS ON CACHE INTERNAL "") +FetchContent_MakeAvailable(llhttp) + +# Link with the llhttp_static target +target_link_libraries(${EXAMPLE_PROJECT_NAME} ${PROJECT_LIBRARIES} llhttp_static ${PROJECT_NAME}) +``` + +_Note that using the git repo directly (e.g., via a git repo url and tag) will not work with FetchContent_Declare because [CMakeLists.txt](./CMakeLists.txt) requires string replacements (e.g., `_RELEASE_`) before it will build._ + +## Building on Windows + +### Installation + +* `choco install git` +* `choco install node` +* `choco install llvm` (or install the `C++ Clang tools for Windows` optional package from the Visual Studio 2019 installer) +* `choco install make` (or if you have MinGW, it comes bundled) + +1. Ensure that `Clang` and `make` are in your system path. +2. Using Git Bash, clone the repo to your preferred location. +3. Cd into the cloned directory and run `npm install` +5. Run `make` +6. Your `repo/build` directory should now have `libllhttp.a` and `libllhttp.so` static and dynamic libraries. +7. When building your executable, you can link to these libraries. Make sure to set the build folder as an include path when building so you can reference the declarations in `repo/build/llhttp.h`. + +### A simple example on linking with the library: + +Assuming you have an executable `main.cpp` in your current working directory, you would run: `clang++ -Os -g3 -Wall -Wextra -Wno-unused-parameter -I/path/to/llhttp/build main.cpp /path/to/llhttp/build/libllhttp.a -o main.exe`. + +If you are getting `unresolved external symbol` linker errors you are likely attempting to build `llhttp.c` without linking it with object files from `api.c` and `http.c`. + +#### LICENSE + +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +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. + +[0]: https://github.com/nodejs/http-parser +[1]: https://github.com/nodejs/llparse +[2]: https://en.wikipedia.org/wiki/Register_allocation#Spilling +[3]: https://en.wikipedia.org/wiki/Tail_call +[4]: https://llvm.org/docs/LangRef.html +[5]: https://llvm.org/docs/LangRef.html#call-instruction +[6]: https://clang.llvm.org/ +[7]: https://github.com/nodejs/node +[8]: https://github.com/pallas/pyllhttp +[9]: https://github.com/metabahn/llhttp +[10]: https://github.com/JackLiar/rust-llhttp +[11]: https://github.com/MunifTanjim/llhttp.lua diff --git a/lib/nghttp2/third-party/llhttp/common.gypi b/lib/nghttp2/third-party/llhttp/common.gypi new file mode 100644 index 00000000000..ef7549f809d --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/common.gypi @@ -0,0 +1,46 @@ +{ + 'target_defaults': { + 'default_configuration': 'Debug', + 'configurations': { + # TODO: hoist these out and put them somewhere common, because + # RuntimeLibrary MUST MATCH across the entire project + 'Debug': { + 'defines': [ 'DEBUG', '_DEBUG' ], + 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 1, # static debug + }, + }, + }, + 'Release': { + 'defines': [ 'NDEBUG' ], + 'cflags': [ '-Wall', '-Wextra', '-O3' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 0, # static release + }, + }, + } + }, + 'msvs_settings': { + 'VCCLCompilerTool': { + # Compile as C++. llhttp.c is actually C99, but C++ is + # close enough in this case. + 'CompileAs': 2, + }, + 'VCLibrarianTool': { + }, + 'VCLinkerTool': { + 'GenerateDebugInformation': 'true', + }, + }, + 'conditions': [ + ['OS == "win"', { + 'defines': [ + 'WIN32' + ], + }] + ], + }, +} diff --git a/lib/nghttp2/third-party/llhttp/include/llhttp.h b/lib/nghttp2/third-party/llhttp/include/llhttp.h new file mode 100644 index 00000000000..6588ae5e368 --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/include/llhttp.h @@ -0,0 +1,871 @@ + +#ifndef INCLUDE_LLHTTP_H_ +#define INCLUDE_LLHTTP_H_ + +#define LLHTTP_VERSION_MAJOR 9 +#define LLHTTP_VERSION_MINOR 0 +#define LLHTTP_VERSION_PATCH 1 + +#ifndef INCLUDE_LLHTTP_ITSELF_H_ +#define INCLUDE_LLHTTP_ITSELF_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct llhttp__internal_s llhttp__internal_t; +struct llhttp__internal_s { + int32_t _index; + void* _span_pos0; + void* _span_cb0; + int32_t error; + const char* reason; + const char* error_pos; + void* data; + void* _current; + uint64_t content_length; + uint8_t type; + uint8_t method; + uint8_t http_major; + uint8_t http_minor; + uint8_t header_state; + uint8_t lenient_flags; + uint8_t upgrade; + uint8_t finish; + uint16_t flags; + uint16_t status_code; + uint8_t initial_message_completed; + void* settings; +}; + +int llhttp__internal_init(llhttp__internal_t* s); +int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* INCLUDE_LLHTTP_ITSELF_H_ */ + + +#ifndef LLLLHTTP_C_HEADERS_ +#define LLLLHTTP_C_HEADERS_ +#ifdef __cplusplus +extern "C" { +#endif + +enum llhttp_errno { + HPE_OK = 0, + HPE_INTERNAL = 1, + HPE_STRICT = 2, + HPE_CR_EXPECTED = 25, + HPE_LF_EXPECTED = 3, + HPE_UNEXPECTED_CONTENT_LENGTH = 4, + HPE_UNEXPECTED_SPACE = 30, + HPE_CLOSED_CONNECTION = 5, + HPE_INVALID_METHOD = 6, + HPE_INVALID_URL = 7, + HPE_INVALID_CONSTANT = 8, + HPE_INVALID_VERSION = 9, + HPE_INVALID_HEADER_TOKEN = 10, + HPE_INVALID_CONTENT_LENGTH = 11, + HPE_INVALID_CHUNK_SIZE = 12, + HPE_INVALID_STATUS = 13, + HPE_INVALID_EOF_STATE = 14, + HPE_INVALID_TRANSFER_ENCODING = 15, + HPE_CB_MESSAGE_BEGIN = 16, + HPE_CB_HEADERS_COMPLETE = 17, + HPE_CB_MESSAGE_COMPLETE = 18, + HPE_CB_CHUNK_HEADER = 19, + HPE_CB_CHUNK_COMPLETE = 20, + HPE_PAUSED = 21, + HPE_PAUSED_UPGRADE = 22, + HPE_PAUSED_H2_UPGRADE = 23, + HPE_USER = 24, + HPE_CB_URL_COMPLETE = 26, + HPE_CB_STATUS_COMPLETE = 27, + HPE_CB_METHOD_COMPLETE = 32, + HPE_CB_VERSION_COMPLETE = 33, + HPE_CB_HEADER_FIELD_COMPLETE = 28, + HPE_CB_HEADER_VALUE_COMPLETE = 29, + HPE_CB_CHUNK_EXTENSION_NAME_COMPLETE = 34, + HPE_CB_CHUNK_EXTENSION_VALUE_COMPLETE = 35, + HPE_CB_RESET = 31 +}; +typedef enum llhttp_errno llhttp_errno_t; + +enum llhttp_flags { + F_CONNECTION_KEEP_ALIVE = 0x1, + F_CONNECTION_CLOSE = 0x2, + F_CONNECTION_UPGRADE = 0x4, + F_CHUNKED = 0x8, + F_UPGRADE = 0x10, + F_CONTENT_LENGTH = 0x20, + F_SKIPBODY = 0x40, + F_TRAILING = 0x80, + F_TRANSFER_ENCODING = 0x200 +}; +typedef enum llhttp_flags llhttp_flags_t; + +enum llhttp_lenient_flags { + LENIENT_HEADERS = 0x1, + LENIENT_CHUNKED_LENGTH = 0x2, + LENIENT_KEEP_ALIVE = 0x4, + LENIENT_TRANSFER_ENCODING = 0x8, + LENIENT_VERSION = 0x10, + LENIENT_DATA_AFTER_CLOSE = 0x20, + LENIENT_OPTIONAL_LF_AFTER_CR = 0x40, + LENIENT_OPTIONAL_CRLF_AFTER_CHUNK = 0x80 +}; +typedef enum llhttp_lenient_flags llhttp_lenient_flags_t; + +enum llhttp_type { + HTTP_BOTH = 0, + HTTP_REQUEST = 1, + HTTP_RESPONSE = 2 +}; +typedef enum llhttp_type llhttp_type_t; + +enum llhttp_finish { + HTTP_FINISH_SAFE = 0, + HTTP_FINISH_SAFE_WITH_CB = 1, + HTTP_FINISH_UNSAFE = 2 +}; +typedef enum llhttp_finish llhttp_finish_t; + +enum llhttp_method { + HTTP_DELETE = 0, + HTTP_GET = 1, + HTTP_HEAD = 2, + HTTP_POST = 3, + HTTP_PUT = 4, + HTTP_CONNECT = 5, + HTTP_OPTIONS = 6, + HTTP_TRACE = 7, + HTTP_COPY = 8, + HTTP_LOCK = 9, + HTTP_MKCOL = 10, + HTTP_MOVE = 11, + HTTP_PROPFIND = 12, + HTTP_PROPPATCH = 13, + HTTP_SEARCH = 14, + HTTP_UNLOCK = 15, + HTTP_BIND = 16, + HTTP_REBIND = 17, + HTTP_UNBIND = 18, + HTTP_ACL = 19, + HTTP_REPORT = 20, + HTTP_MKACTIVITY = 21, + HTTP_CHECKOUT = 22, + HTTP_MERGE = 23, + HTTP_MSEARCH = 24, + HTTP_NOTIFY = 25, + HTTP_SUBSCRIBE = 26, + HTTP_UNSUBSCRIBE = 27, + HTTP_PATCH = 28, + HTTP_PURGE = 29, + HTTP_MKCALENDAR = 30, + HTTP_LINK = 31, + HTTP_UNLINK = 32, + HTTP_SOURCE = 33, + HTTP_PRI = 34, + HTTP_DESCRIBE = 35, + HTTP_ANNOUNCE = 36, + HTTP_SETUP = 37, + HTTP_PLAY = 38, + HTTP_PAUSE = 39, + HTTP_TEARDOWN = 40, + HTTP_GET_PARAMETER = 41, + HTTP_SET_PARAMETER = 42, + HTTP_REDIRECT = 43, + HTTP_RECORD = 44, + HTTP_FLUSH = 45 +}; +typedef enum llhttp_method llhttp_method_t; + +enum llhttp_status { + HTTP_STATUS_CONTINUE = 100, + HTTP_STATUS_SWITCHING_PROTOCOLS = 101, + HTTP_STATUS_PROCESSING = 102, + HTTP_STATUS_EARLY_HINTS = 103, + HTTP_STATUS_RESPONSE_IS_STALE = 110, + HTTP_STATUS_REVALIDATION_FAILED = 111, + HTTP_STATUS_DISCONNECTED_OPERATION = 112, + HTTP_STATUS_HEURISTIC_EXPIRATION = 113, + HTTP_STATUS_MISCELLANEOUS_WARNING = 199, + HTTP_STATUS_OK = 200, + HTTP_STATUS_CREATED = 201, + HTTP_STATUS_ACCEPTED = 202, + HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_STATUS_NO_CONTENT = 204, + HTTP_STATUS_RESET_CONTENT = 205, + HTTP_STATUS_PARTIAL_CONTENT = 206, + HTTP_STATUS_MULTI_STATUS = 207, + HTTP_STATUS_ALREADY_REPORTED = 208, + HTTP_STATUS_TRANSFORMATION_APPLIED = 214, + HTTP_STATUS_IM_USED = 226, + HTTP_STATUS_MISCELLANEOUS_PERSISTENT_WARNING = 299, + HTTP_STATUS_MULTIPLE_CHOICES = 300, + HTTP_STATUS_MOVED_PERMANENTLY = 301, + HTTP_STATUS_FOUND = 302, + HTTP_STATUS_SEE_OTHER = 303, + HTTP_STATUS_NOT_MODIFIED = 304, + HTTP_STATUS_USE_PROXY = 305, + HTTP_STATUS_SWITCH_PROXY = 306, + HTTP_STATUS_TEMPORARY_REDIRECT = 307, + HTTP_STATUS_PERMANENT_REDIRECT = 308, + HTTP_STATUS_BAD_REQUEST = 400, + HTTP_STATUS_UNAUTHORIZED = 401, + HTTP_STATUS_PAYMENT_REQUIRED = 402, + HTTP_STATUS_FORBIDDEN = 403, + HTTP_STATUS_NOT_FOUND = 404, + HTTP_STATUS_METHOD_NOT_ALLOWED = 405, + HTTP_STATUS_NOT_ACCEPTABLE = 406, + HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, + HTTP_STATUS_REQUEST_TIMEOUT = 408, + HTTP_STATUS_CONFLICT = 409, + HTTP_STATUS_GONE = 410, + HTTP_STATUS_LENGTH_REQUIRED = 411, + HTTP_STATUS_PRECONDITION_FAILED = 412, + HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, + HTTP_STATUS_URI_TOO_LONG = 414, + HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, + HTTP_STATUS_EXPECTATION_FAILED = 417, + HTTP_STATUS_IM_A_TEAPOT = 418, + HTTP_STATUS_PAGE_EXPIRED = 419, + HTTP_STATUS_ENHANCE_YOUR_CALM = 420, + HTTP_STATUS_MISDIRECTED_REQUEST = 421, + HTTP_STATUS_UNPROCESSABLE_ENTITY = 422, + HTTP_STATUS_LOCKED = 423, + HTTP_STATUS_FAILED_DEPENDENCY = 424, + HTTP_STATUS_TOO_EARLY = 425, + HTTP_STATUS_UPGRADE_REQUIRED = 426, + HTTP_STATUS_PRECONDITION_REQUIRED = 428, + HTTP_STATUS_TOO_MANY_REQUESTS = 429, + HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430, + HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_STATUS_LOGIN_TIMEOUT = 440, + HTTP_STATUS_NO_RESPONSE = 444, + HTTP_STATUS_RETRY_WITH = 449, + HTTP_STATUS_BLOCKED_BY_PARENTAL_CONTROL = 450, + HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, + HTTP_STATUS_CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460, + HTTP_STATUS_INVALID_X_FORWARDED_FOR = 463, + HTTP_STATUS_REQUEST_HEADER_TOO_LARGE = 494, + HTTP_STATUS_SSL_CERTIFICATE_ERROR = 495, + HTTP_STATUS_SSL_CERTIFICATE_REQUIRED = 496, + HTTP_STATUS_HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497, + HTTP_STATUS_INVALID_TOKEN = 498, + HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499, + HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, + HTTP_STATUS_NOT_IMPLEMENTED = 501, + HTTP_STATUS_BAD_GATEWAY = 502, + HTTP_STATUS_SERVICE_UNAVAILABLE = 503, + HTTP_STATUS_GATEWAY_TIMEOUT = 504, + HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506, + HTTP_STATUS_INSUFFICIENT_STORAGE = 507, + HTTP_STATUS_LOOP_DETECTED = 508, + HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509, + HTTP_STATUS_NOT_EXTENDED = 510, + HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, + HTTP_STATUS_WEB_SERVER_UNKNOWN_ERROR = 520, + HTTP_STATUS_WEB_SERVER_IS_DOWN = 521, + HTTP_STATUS_CONNECTION_TIMEOUT = 522, + HTTP_STATUS_ORIGIN_IS_UNREACHABLE = 523, + HTTP_STATUS_TIMEOUT_OCCURED = 524, + HTTP_STATUS_SSL_HANDSHAKE_FAILED = 525, + HTTP_STATUS_INVALID_SSL_CERTIFICATE = 526, + HTTP_STATUS_RAILGUN_ERROR = 527, + HTTP_STATUS_SITE_IS_OVERLOADED = 529, + HTTP_STATUS_SITE_IS_FROZEN = 530, + HTTP_STATUS_IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561, + HTTP_STATUS_NETWORK_READ_TIMEOUT = 598, + HTTP_STATUS_NETWORK_CONNECT_TIMEOUT = 599 +}; +typedef enum llhttp_status llhttp_status_t; + +#define HTTP_ERRNO_MAP(XX) \ + XX(0, OK, OK) \ + XX(1, INTERNAL, INTERNAL) \ + XX(2, STRICT, STRICT) \ + XX(25, CR_EXPECTED, CR_EXPECTED) \ + XX(3, LF_EXPECTED, LF_EXPECTED) \ + XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \ + XX(30, UNEXPECTED_SPACE, UNEXPECTED_SPACE) \ + XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \ + XX(6, INVALID_METHOD, INVALID_METHOD) \ + XX(7, INVALID_URL, INVALID_URL) \ + XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \ + XX(9, INVALID_VERSION, INVALID_VERSION) \ + XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \ + XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \ + XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \ + XX(13, INVALID_STATUS, INVALID_STATUS) \ + XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \ + XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \ + XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \ + XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \ + XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \ + XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \ + XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \ + XX(21, PAUSED, PAUSED) \ + XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \ + XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \ + XX(24, USER, USER) \ + XX(26, CB_URL_COMPLETE, CB_URL_COMPLETE) \ + XX(27, CB_STATUS_COMPLETE, CB_STATUS_COMPLETE) \ + XX(32, CB_METHOD_COMPLETE, CB_METHOD_COMPLETE) \ + XX(33, CB_VERSION_COMPLETE, CB_VERSION_COMPLETE) \ + XX(28, CB_HEADER_FIELD_COMPLETE, CB_HEADER_FIELD_COMPLETE) \ + XX(29, CB_HEADER_VALUE_COMPLETE, CB_HEADER_VALUE_COMPLETE) \ + XX(34, CB_CHUNK_EXTENSION_NAME_COMPLETE, CB_CHUNK_EXTENSION_NAME_COMPLETE) \ + XX(35, CB_CHUNK_EXTENSION_VALUE_COMPLETE, CB_CHUNK_EXTENSION_VALUE_COMPLETE) \ + XX(31, CB_RESET, CB_RESET) \ + + +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + XX(30, MKCALENDAR, MKCALENDAR) \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + XX(33, SOURCE, SOURCE) \ + + +#define RTSP_METHOD_MAP(XX) \ + XX(1, GET, GET) \ + XX(3, POST, POST) \ + XX(6, OPTIONS, OPTIONS) \ + XX(35, DESCRIBE, DESCRIBE) \ + XX(36, ANNOUNCE, ANNOUNCE) \ + XX(37, SETUP, SETUP) \ + XX(38, PLAY, PLAY) \ + XX(39, PAUSE, PAUSE) \ + XX(40, TEARDOWN, TEARDOWN) \ + XX(41, GET_PARAMETER, GET_PARAMETER) \ + XX(42, SET_PARAMETER, SET_PARAMETER) \ + XX(43, REDIRECT, REDIRECT) \ + XX(44, RECORD, RECORD) \ + XX(45, FLUSH, FLUSH) \ + + +#define HTTP_ALL_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + XX(30, MKCALENDAR, MKCALENDAR) \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + XX(33, SOURCE, SOURCE) \ + XX(34, PRI, PRI) \ + XX(35, DESCRIBE, DESCRIBE) \ + XX(36, ANNOUNCE, ANNOUNCE) \ + XX(37, SETUP, SETUP) \ + XX(38, PLAY, PLAY) \ + XX(39, PAUSE, PAUSE) \ + XX(40, TEARDOWN, TEARDOWN) \ + XX(41, GET_PARAMETER, GET_PARAMETER) \ + XX(42, SET_PARAMETER, SET_PARAMETER) \ + XX(43, REDIRECT, REDIRECT) \ + XX(44, RECORD, RECORD) \ + XX(45, FLUSH, FLUSH) \ + + +#define HTTP_STATUS_MAP(XX) \ + XX(100, CONTINUE, CONTINUE) \ + XX(101, SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS) \ + XX(102, PROCESSING, PROCESSING) \ + XX(103, EARLY_HINTS, EARLY_HINTS) \ + XX(110, RESPONSE_IS_STALE, RESPONSE_IS_STALE) \ + XX(111, REVALIDATION_FAILED, REVALIDATION_FAILED) \ + XX(112, DISCONNECTED_OPERATION, DISCONNECTED_OPERATION) \ + XX(113, HEURISTIC_EXPIRATION, HEURISTIC_EXPIRATION) \ + XX(199, MISCELLANEOUS_WARNING, MISCELLANEOUS_WARNING) \ + XX(200, OK, OK) \ + XX(201, CREATED, CREATED) \ + XX(202, ACCEPTED, ACCEPTED) \ + XX(203, NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION) \ + XX(204, NO_CONTENT, NO_CONTENT) \ + XX(205, RESET_CONTENT, RESET_CONTENT) \ + XX(206, PARTIAL_CONTENT, PARTIAL_CONTENT) \ + XX(207, MULTI_STATUS, MULTI_STATUS) \ + XX(208, ALREADY_REPORTED, ALREADY_REPORTED) \ + XX(214, TRANSFORMATION_APPLIED, TRANSFORMATION_APPLIED) \ + XX(226, IM_USED, IM_USED) \ + XX(299, MISCELLANEOUS_PERSISTENT_WARNING, MISCELLANEOUS_PERSISTENT_WARNING) \ + XX(300, MULTIPLE_CHOICES, MULTIPLE_CHOICES) \ + XX(301, MOVED_PERMANENTLY, MOVED_PERMANENTLY) \ + XX(302, FOUND, FOUND) \ + XX(303, SEE_OTHER, SEE_OTHER) \ + XX(304, NOT_MODIFIED, NOT_MODIFIED) \ + XX(305, USE_PROXY, USE_PROXY) \ + XX(306, SWITCH_PROXY, SWITCH_PROXY) \ + XX(307, TEMPORARY_REDIRECT, TEMPORARY_REDIRECT) \ + XX(308, PERMANENT_REDIRECT, PERMANENT_REDIRECT) \ + XX(400, BAD_REQUEST, BAD_REQUEST) \ + XX(401, UNAUTHORIZED, UNAUTHORIZED) \ + XX(402, PAYMENT_REQUIRED, PAYMENT_REQUIRED) \ + XX(403, FORBIDDEN, FORBIDDEN) \ + XX(404, NOT_FOUND, NOT_FOUND) \ + XX(405, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED) \ + XX(406, NOT_ACCEPTABLE, NOT_ACCEPTABLE) \ + XX(407, PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED) \ + XX(408, REQUEST_TIMEOUT, REQUEST_TIMEOUT) \ + XX(409, CONFLICT, CONFLICT) \ + XX(410, GONE, GONE) \ + XX(411, LENGTH_REQUIRED, LENGTH_REQUIRED) \ + XX(412, PRECONDITION_FAILED, PRECONDITION_FAILED) \ + XX(413, PAYLOAD_TOO_LARGE, PAYLOAD_TOO_LARGE) \ + XX(414, URI_TOO_LONG, URI_TOO_LONG) \ + XX(415, UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE) \ + XX(416, RANGE_NOT_SATISFIABLE, RANGE_NOT_SATISFIABLE) \ + XX(417, EXPECTATION_FAILED, EXPECTATION_FAILED) \ + XX(418, IM_A_TEAPOT, IM_A_TEAPOT) \ + XX(419, PAGE_EXPIRED, PAGE_EXPIRED) \ + XX(420, ENHANCE_YOUR_CALM, ENHANCE_YOUR_CALM) \ + XX(421, MISDIRECTED_REQUEST, MISDIRECTED_REQUEST) \ + XX(422, UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY) \ + XX(423, LOCKED, LOCKED) \ + XX(424, FAILED_DEPENDENCY, FAILED_DEPENDENCY) \ + XX(425, TOO_EARLY, TOO_EARLY) \ + XX(426, UPGRADE_REQUIRED, UPGRADE_REQUIRED) \ + XX(428, PRECONDITION_REQUIRED, PRECONDITION_REQUIRED) \ + XX(429, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS) \ + XX(430, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL) \ + XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE) \ + XX(440, LOGIN_TIMEOUT, LOGIN_TIMEOUT) \ + XX(444, NO_RESPONSE, NO_RESPONSE) \ + XX(449, RETRY_WITH, RETRY_WITH) \ + XX(450, BLOCKED_BY_PARENTAL_CONTROL, BLOCKED_BY_PARENTAL_CONTROL) \ + XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, UNAVAILABLE_FOR_LEGAL_REASONS) \ + XX(460, CLIENT_CLOSED_LOAD_BALANCED_REQUEST, CLIENT_CLOSED_LOAD_BALANCED_REQUEST) \ + XX(463, INVALID_X_FORWARDED_FOR, INVALID_X_FORWARDED_FOR) \ + XX(494, REQUEST_HEADER_TOO_LARGE, REQUEST_HEADER_TOO_LARGE) \ + XX(495, SSL_CERTIFICATE_ERROR, SSL_CERTIFICATE_ERROR) \ + XX(496, SSL_CERTIFICATE_REQUIRED, SSL_CERTIFICATE_REQUIRED) \ + XX(497, HTTP_REQUEST_SENT_TO_HTTPS_PORT, HTTP_REQUEST_SENT_TO_HTTPS_PORT) \ + XX(498, INVALID_TOKEN, INVALID_TOKEN) \ + XX(499, CLIENT_CLOSED_REQUEST, CLIENT_CLOSED_REQUEST) \ + XX(500, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR) \ + XX(501, NOT_IMPLEMENTED, NOT_IMPLEMENTED) \ + XX(502, BAD_GATEWAY, BAD_GATEWAY) \ + XX(503, SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE) \ + XX(504, GATEWAY_TIMEOUT, GATEWAY_TIMEOUT) \ + XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED) \ + XX(506, VARIANT_ALSO_NEGOTIATES, VARIANT_ALSO_NEGOTIATES) \ + XX(507, INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE) \ + XX(508, LOOP_DETECTED, LOOP_DETECTED) \ + XX(509, BANDWIDTH_LIMIT_EXCEEDED, BANDWIDTH_LIMIT_EXCEEDED) \ + XX(510, NOT_EXTENDED, NOT_EXTENDED) \ + XX(511, NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED) \ + XX(520, WEB_SERVER_UNKNOWN_ERROR, WEB_SERVER_UNKNOWN_ERROR) \ + XX(521, WEB_SERVER_IS_DOWN, WEB_SERVER_IS_DOWN) \ + XX(522, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT) \ + XX(523, ORIGIN_IS_UNREACHABLE, ORIGIN_IS_UNREACHABLE) \ + XX(524, TIMEOUT_OCCURED, TIMEOUT_OCCURED) \ + XX(525, SSL_HANDSHAKE_FAILED, SSL_HANDSHAKE_FAILED) \ + XX(526, INVALID_SSL_CERTIFICATE, INVALID_SSL_CERTIFICATE) \ + XX(527, RAILGUN_ERROR, RAILGUN_ERROR) \ + XX(529, SITE_IS_OVERLOADED, SITE_IS_OVERLOADED) \ + XX(530, SITE_IS_FROZEN, SITE_IS_FROZEN) \ + XX(561, IDENTITY_PROVIDER_AUTHENTICATION_ERROR, IDENTITY_PROVIDER_AUTHENTICATION_ERROR) \ + XX(598, NETWORK_READ_TIMEOUT, NETWORK_READ_TIMEOUT) \ + XX(599, NETWORK_CONNECT_TIMEOUT, NETWORK_CONNECT_TIMEOUT) \ + + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* LLLLHTTP_C_HEADERS_ */ + + +#ifndef INCLUDE_LLHTTP_API_H_ +#define INCLUDE_LLHTTP_API_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include + +#if defined(__wasm__) +#define LLHTTP_EXPORT __attribute__((visibility("default"))) +#else +#define LLHTTP_EXPORT +#endif + +typedef llhttp__internal_t llhttp_t; +typedef struct llhttp_settings_s llhttp_settings_t; + +typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); +typedef int (*llhttp_cb)(llhttp_t*); + +struct llhttp_settings_s { + /* Possible return values 0, -1, `HPE_PAUSED` */ + llhttp_cb on_message_begin; + + /* Possible return values 0, -1, HPE_USER */ + llhttp_data_cb on_url; + llhttp_data_cb on_status; + llhttp_data_cb on_method; + llhttp_data_cb on_version; + llhttp_data_cb on_header_field; + llhttp_data_cb on_header_value; + llhttp_data_cb on_chunk_extension_name; + llhttp_data_cb on_chunk_extension_value; + + /* Possible return values: + * 0 - Proceed normally + * 1 - Assume that request/response has no body, and proceed to parsing the + * next message + * 2 - Assume absence of body (as above) and make `llhttp_execute()` return + * `HPE_PAUSED_UPGRADE` + * -1 - Error + * `HPE_PAUSED` + */ + llhttp_cb on_headers_complete; + + /* Possible return values 0, -1, HPE_USER */ + llhttp_data_cb on_body; + + /* Possible return values 0, -1, `HPE_PAUSED` */ + llhttp_cb on_message_complete; + llhttp_cb on_url_complete; + llhttp_cb on_status_complete; + llhttp_cb on_method_complete; + llhttp_cb on_version_complete; + llhttp_cb on_header_field_complete; + llhttp_cb on_header_value_complete; + llhttp_cb on_chunk_extension_name_complete; + llhttp_cb on_chunk_extension_value_complete; + + /* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + * Possible return values 0, -1, `HPE_PAUSED` + */ + llhttp_cb on_chunk_header; + llhttp_cb on_chunk_complete; + llhttp_cb on_reset; +}; + +/* Initialize the parser with specific type and user settings. + * + * NOTE: lifetime of `settings` has to be at least the same as the lifetime of + * the `parser` here. In practice, `settings` has to be either a static + * variable or be allocated with `malloc`, `new`, etc. + */ +LLHTTP_EXPORT +void llhttp_init(llhttp_t* parser, llhttp_type_t type, + const llhttp_settings_t* settings); + +LLHTTP_EXPORT +llhttp_t* llhttp_alloc(llhttp_type_t type); + +LLHTTP_EXPORT +void llhttp_free(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_type(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_http_major(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_http_minor(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_method(llhttp_t* parser); + +LLHTTP_EXPORT +int llhttp_get_status_code(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_upgrade(llhttp_t* parser); + +/* Reset an already initialized parser back to the start state, preserving the + * existing parser type, callback settings, user data, and lenient flags. + */ +LLHTTP_EXPORT +void llhttp_reset(llhttp_t* parser); + +/* Initialize the settings object */ +LLHTTP_EXPORT +void llhttp_settings_init(llhttp_settings_t* settings); + +/* Parse full or partial request/response, invoking user callbacks along the + * way. + * + * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing + * interrupts, and such errno is returned from `llhttp_execute()`. If + * `HPE_PAUSED` was used as a errno, the execution can be resumed with + * `llhttp_resume()` call. + * + * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` + * is returned after fully parsing the request/response. If the user wishes to + * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. + * + * NOTE: if this function ever returns a non-pause type error, it will continue + * to return the same error upon each successive call up until `llhttp_init()` + * is called. + */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); + +/* This method should be called when the other side has no further bytes to + * send (e.g. shutdown of readable side of the TCP connection.) + * + * Requests without `Content-Length` and other messages might require treating + * all incoming bytes as the part of the body, up to the last byte of the + * connection. This method will invoke `on_message_complete()` callback if the + * request was terminated safely. Otherwise a error code would be returned. + */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_finish(llhttp_t* parser); + +/* Returns `1` if the incoming message is parsed until the last byte, and has + * to be completed by calling `llhttp_finish()` on EOF + */ +LLHTTP_EXPORT +int llhttp_message_needs_eof(const llhttp_t* parser); + +/* Returns `1` if there might be any other messages following the last that was + * successfully parsed. + */ +LLHTTP_EXPORT +int llhttp_should_keep_alive(const llhttp_t* parser); + +/* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set + * appropriate error reason. + * + * Important: do not call this from user callbacks! User callbacks must return + * `HPE_PAUSED` if pausing is required. + */ +LLHTTP_EXPORT +void llhttp_pause(llhttp_t* parser); + +/* Might be called to resume the execution after the pause in user's callback. + * See `llhttp_execute()` above for details. + * + * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. + */ +LLHTTP_EXPORT +void llhttp_resume(llhttp_t* parser); + +/* Might be called to resume the execution after the pause in user's callback. + * See `llhttp_execute()` above for details. + * + * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` + */ +LLHTTP_EXPORT +void llhttp_resume_after_upgrade(llhttp_t* parser); + +/* Returns the latest return error */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); + +/* Returns the verbal explanation of the latest returned error. + * + * Note: User callback should set error reason when returning the error. See + * `llhttp_set_error_reason()` for details. + */ +LLHTTP_EXPORT +const char* llhttp_get_error_reason(const llhttp_t* parser); + +/* Assign verbal description to the returned error. Must be called in user + * callbacks right before returning the errno. + * + * Note: `HPE_USER` error code might be useful in user callbacks. + */ +LLHTTP_EXPORT +void llhttp_set_error_reason(llhttp_t* parser, const char* reason); + +/* Returns the pointer to the last parsed byte before the returned error. The + * pointer is relative to the `data` argument of `llhttp_execute()`. + * + * Note: this method might be useful for counting the number of parsed bytes. + */ +LLHTTP_EXPORT +const char* llhttp_get_error_pos(const llhttp_t* parser); + +/* Returns textual name of error code */ +LLHTTP_EXPORT +const char* llhttp_errno_name(llhttp_errno_t err); + +/* Returns textual name of HTTP method */ +LLHTTP_EXPORT +const char* llhttp_method_name(llhttp_method_t method); + +/* Returns textual name of HTTP status */ +LLHTTP_EXPORT +const char* llhttp_status_name(llhttp_status_t status); + +/* Enables/disables lenient header value parsing (disabled by default). + * + * Lenient parsing disables header value token checks, extending llhttp's + * protocol support to highly non-compliant clients/server. No + * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when + * lenient parsing is "on". + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_headers(llhttp_t* parser, int enabled); + + +/* Enables/disables lenient handling of conflicting `Transfer-Encoding` and + * `Content-Length` headers (disabled by default). + * + * Normally `llhttp` would error when `Transfer-Encoding` is present in + * conjunction with `Content-Length`. This error is important to prevent HTTP + * request smuggling, but may be less desirable for small number of cases + * involving legacy servers. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled); + + +/* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 + * requests responses. + * + * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) + * the HTTP request/response after the request/response with `Connection: close` + * and `Content-Length`. This is important to prevent cache poisoning attacks, + * but might interact badly with outdated and insecure clients. With this flag + * the extra request/response will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * poisoning attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of `Transfer-Encoding` header. + * + * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value + * and another value after it (either in a single header or in multiple + * headers whose value are internally joined using `, `). + * This is mandated by the spec to reliably determine request body size and thus + * avoid request smuggling. + * With this flag the extra value will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of HTTP version. + * + * Normally `llhttp` would error when the HTTP version in the request or status line + * is not `0.9`, `1.0`, `1.1` or `2.0`. + * With this flag the invalid value will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will allow unsupported + * HTTP versions. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_version(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of additional data received after a message ends + * and keep-alive is disabled. + * + * Normally `llhttp` would error when additional unexpected data is received if the message + * contains the `Connection` header with `close` value. + * With this flag the extra data will discarded without throwing an error. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * poisoning attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of incomplete CRLF sequences. + * + * Normally `llhttp` would error when a CR is not followed by LF when terminating the + * request line, the status line, the headers or a chunk header. + * With this flag only a CR is required to terminate such sections. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of chunks not separated via CRLF. + * + * Normally `llhttp` would error when after a chunk data a CRLF is missing before + * starting a new chunk. + * With this flag the new chunk can start immediately after the previous one. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* INCLUDE_LLHTTP_API_H_ */ + + +#endif /* INCLUDE_LLHTTP_H_ */ diff --git a/lib/nghttp2/third-party/llhttp/llhttp.gyp b/lib/nghttp2/third-party/llhttp/llhttp.gyp new file mode 100644 index 00000000000..c7b8800a4f1 --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/llhttp.gyp @@ -0,0 +1,22 @@ +{ + 'variables': { + 'llhttp_sources': [ + 'src/llhttp.c', + 'src/api.c', + 'src/http.c', + ] + }, + 'targets': [ + { + 'target_name': 'llhttp', + 'type': 'static_library', + 'include_dirs': [ '.', 'include' ], + 'direct_dependent_settings': { + 'include_dirs': [ 'include' ], + }, + 'sources': [ + '<@(llhttp_sources)', + ], + }, + ] +} diff --git a/lib/nghttp2/third-party/llhttp/src/api.c b/lib/nghttp2/third-party/llhttp/src/api.c new file mode 100644 index 00000000000..e0d03850514 --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/src/api.c @@ -0,0 +1,494 @@ +#include +#include +#include + +#include "llhttp.h" + +#define CALLBACK_MAYBE(PARSER, NAME) \ + do { \ + const llhttp_settings_t* settings; \ + settings = (const llhttp_settings_t*) (PARSER)->settings; \ + if (settings == NULL || settings->NAME == NULL) { \ + err = 0; \ + break; \ + } \ + err = settings->NAME((PARSER)); \ + } while (0) + +#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \ + do { \ + const llhttp_settings_t* settings; \ + settings = (const llhttp_settings_t*) (PARSER)->settings; \ + if (settings == NULL || settings->NAME == NULL) { \ + err = 0; \ + break; \ + } \ + err = settings->NAME((PARSER), (START), (LEN)); \ + if (err == -1) { \ + err = HPE_USER; \ + llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \ + } \ + } while (0) + +void llhttp_init(llhttp_t* parser, llhttp_type_t type, + const llhttp_settings_t* settings) { + llhttp__internal_init(parser); + + parser->type = type; + parser->settings = (void*) settings; +} + + +#if defined(__wasm__) + +extern int wasm_on_message_begin(llhttp_t * p); +extern int wasm_on_url(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_status(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_headers_complete(llhttp_t * p, int status_code, + uint8_t upgrade, int should_keep_alive); +extern int wasm_on_body(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_message_complete(llhttp_t * p); + +static int wasm_on_headers_complete_wrap(llhttp_t* p) { + return wasm_on_headers_complete(p, p->status_code, p->upgrade, + llhttp_should_keep_alive(p)); +} + +const llhttp_settings_t wasm_settings = { + wasm_on_message_begin, + wasm_on_url, + wasm_on_status, + NULL, + NULL, + wasm_on_header_field, + wasm_on_header_value, + NULL, + NULL, + wasm_on_headers_complete_wrap, + wasm_on_body, + wasm_on_message_complete, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + + +llhttp_t* llhttp_alloc(llhttp_type_t type) { + llhttp_t* parser = malloc(sizeof(llhttp_t)); + llhttp_init(parser, type, &wasm_settings); + return parser; +} + +void llhttp_free(llhttp_t* parser) { + free(parser); +} + +#endif // defined(__wasm__) + +/* Some getters required to get stuff from the parser */ + +uint8_t llhttp_get_type(llhttp_t* parser) { + return parser->type; +} + +uint8_t llhttp_get_http_major(llhttp_t* parser) { + return parser->http_major; +} + +uint8_t llhttp_get_http_minor(llhttp_t* parser) { + return parser->http_minor; +} + +uint8_t llhttp_get_method(llhttp_t* parser) { + return parser->method; +} + +int llhttp_get_status_code(llhttp_t* parser) { + return parser->status_code; +} + +uint8_t llhttp_get_upgrade(llhttp_t* parser) { + return parser->upgrade; +} + + +void llhttp_reset(llhttp_t* parser) { + llhttp_type_t type = parser->type; + const llhttp_settings_t* settings = parser->settings; + void* data = parser->data; + uint8_t lenient_flags = parser->lenient_flags; + + llhttp__internal_init(parser); + + parser->type = type; + parser->settings = (void*) settings; + parser->data = data; + parser->lenient_flags = lenient_flags; +} + + +llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { + return llhttp__internal_execute(parser, data, data + len); +} + + +void llhttp_settings_init(llhttp_settings_t* settings) { + memset(settings, 0, sizeof(*settings)); +} + + +llhttp_errno_t llhttp_finish(llhttp_t* parser) { + int err; + + /* We're in an error state. Don't bother doing anything. */ + if (parser->error != 0) { + return 0; + } + + switch (parser->finish) { + case HTTP_FINISH_SAFE_WITH_CB: + CALLBACK_MAYBE(parser, on_message_complete); + if (err != HPE_OK) return err; + + /* FALLTHROUGH */ + case HTTP_FINISH_SAFE: + return HPE_OK; + case HTTP_FINISH_UNSAFE: + parser->reason = "Invalid EOF state"; + return HPE_INVALID_EOF_STATE; + default: + abort(); + } +} + + +void llhttp_pause(llhttp_t* parser) { + if (parser->error != HPE_OK) { + return; + } + + parser->error = HPE_PAUSED; + parser->reason = "Paused"; +} + + +void llhttp_resume(llhttp_t* parser) { + if (parser->error != HPE_PAUSED) { + return; + } + + parser->error = 0; +} + + +void llhttp_resume_after_upgrade(llhttp_t* parser) { + if (parser->error != HPE_PAUSED_UPGRADE) { + return; + } + + parser->error = 0; +} + + +llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { + return parser->error; +} + + +const char* llhttp_get_error_reason(const llhttp_t* parser) { + return parser->reason; +} + + +void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { + parser->reason = reason; +} + + +const char* llhttp_get_error_pos(const llhttp_t* parser) { + return parser->error_pos; +} + + +const char* llhttp_errno_name(llhttp_errno_t err) { +#define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; + switch (err) { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) + default: abort(); + } +#undef HTTP_ERRNO_GEN +} + + +const char* llhttp_method_name(llhttp_method_t method) { +#define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; + switch (method) { + HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) + default: abort(); + } +#undef HTTP_METHOD_GEN +} + +const char* llhttp_status_name(llhttp_status_t status) { +#define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING; + switch (status) { + HTTP_STATUS_MAP(HTTP_STATUS_GEN) + default: abort(); + } +#undef HTTP_STATUS_GEN +} + + +void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_HEADERS; + } else { + parser->lenient_flags &= ~LENIENT_HEADERS; + } +} + + +void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; + } else { + parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; + } +} + + +void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_KEEP_ALIVE; + } else { + parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; + } +} + +void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_TRANSFER_ENCODING; + } else { + parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING; + } +} + +void llhttp_set_lenient_version(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_VERSION; + } else { + parser->lenient_flags &= ~LENIENT_VERSION; + } +} + +void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_DATA_AFTER_CLOSE; + } else { + parser->lenient_flags &= ~LENIENT_DATA_AFTER_CLOSE; + } +} + +void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR; + } else { + parser->lenient_flags &= ~LENIENT_OPTIONAL_LF_AFTER_CR; + } +} + +void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; + } else { + parser->lenient_flags &= ~LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; + } +} + +/* Callbacks */ + + +int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_message_begin); + return err; +} + + +int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); + return err; +} + + +int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_url_complete); + return err; +} + + +int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); + return err; +} + + +int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_status_complete); + return err; +} + + +int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p); + return err; +} + + +int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_method_complete); + return err; +} + + +int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p); + return err; +} + + +int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_version_complete); + return err; +} + + +int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); + return err; +} + + +int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_header_field_complete); + return err; +} + + +int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); + return err; +} + + +int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_header_value_complete); + return err; +} + + +int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_headers_complete); + return err; +} + + +int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_message_complete); + return err; +} + + +int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); + return err; +} + + +int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_header); + return err; +} + + +int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p); + return err; +} + + +int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_extension_name_complete); + return err; +} + + +int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p); + return err; +} + + +int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_extension_value_complete); + return err; +} + + +int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_complete); + return err; +} + + +int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_reset); + return err; +} + + +/* Private */ + + +void llhttp__debug(llhttp_t* s, const char* p, const char* endp, + const char* msg) { + if (p == endp) { + fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, + s->flags, msg); + } else { + fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, + s->type, s->flags, *p, msg); + } +} diff --git a/lib/nghttp2/third-party/llhttp/src/http.c b/lib/nghttp2/third-party/llhttp/src/http.c new file mode 100644 index 00000000000..3a66044f5fb --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/src/http.c @@ -0,0 +1,150 @@ +#include +#ifndef LLHTTP__TEST +# include "llhttp.h" +#else +# define llhttp_t llparse_t +#endif /* */ + +int llhttp_message_needs_eof(const llhttp_t* parser); +int llhttp_should_keep_alive(const llhttp_t* parser); + +int llhttp__before_headers_complete(llhttp_t* parser, const char* p, + const char* endp) { + /* Set this here so that on_headers_complete() callbacks can see it */ + if ((parser->flags & F_UPGRADE) && + (parser->flags & F_CONNECTION_UPGRADE)) { + /* For responses, "Upgrade: foo" and "Connection: upgrade" are + * mandatory only when it is a 101 Switching Protocols response, + * otherwise it is purely informational, to announce support. + */ + parser->upgrade = + (parser->type == HTTP_REQUEST || parser->status_code == 101); + } else { + parser->upgrade = (parser->method == HTTP_CONNECT); + } + return 0; +} + + +/* Return values: + * 0 - No body, `restart`, message_complete + * 1 - CONNECT request, `restart`, message_complete, and pause + * 2 - chunk_size_start + * 3 - body_identity + * 4 - body_identity_eof + * 5 - invalid transfer-encoding for request + */ +int llhttp__after_headers_complete(llhttp_t* parser, const char* p, + const char* endp) { + int hasBody; + + hasBody = parser->flags & F_CHUNKED || parser->content_length > 0; + if (parser->upgrade && (parser->method == HTTP_CONNECT || + (parser->flags & F_SKIPBODY) || !hasBody)) { + /* Exit, the rest of the message is in a different protocol. */ + return 1; + } + + if (parser->flags & F_SKIPBODY) { + return 0; + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header, prepare for a chunk */ + return 2; + } else if (parser->flags & F_TRANSFER_ENCODING) { + if (parser->type == HTTP_REQUEST && + (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && + (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field + * is present in a request and the chunked transfer coding is not + * the final encoding, the message body length cannot be determined + * reliably; the server MUST respond with the 400 (Bad Request) + * status code and then close the connection. + */ + return 5; + } else { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field is present in a response and + * the chunked transfer coding is not the final encoding, the + * message body length is determined by reading the connection until + * it is closed by the server. + */ + return 4; + } + } else { + if (!(parser->flags & F_CONTENT_LENGTH)) { + if (!llhttp_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + return 0; + } else { + /* Read body until EOF */ + return 4; + } + } else if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + return 0; + } else { + /* Content-Length header given and non-zero */ + return 3; + } + } +} + + +int llhttp__after_message_complete(llhttp_t* parser, const char* p, + const char* endp) { + int should_keep_alive; + + should_keep_alive = llhttp_should_keep_alive(parser); + parser->finish = HTTP_FINISH_SAFE; + parser->flags = 0; + + /* NOTE: this is ignored in loose parsing mode */ + return should_keep_alive; +} + + +int llhttp_message_needs_eof(const llhttp_t* parser) { + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + (parser->flags & F_SKIPBODY)) { /* response to a HEAD request */ + return 0; + } + + /* RFC 7230 3.3.3, see `llhttp__after_headers_complete` */ + if ((parser->flags & F_TRANSFER_ENCODING) && + (parser->flags & F_CHUNKED) == 0) { + return 1; + } + + if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) { + return 0; + } + + return 1; +} + + +int llhttp_should_keep_alive(const llhttp_t* parser) { + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !llhttp_message_needs_eof(parser); +} diff --git a/lib/nghttp2/third-party/llhttp/src/llhttp.c b/lib/nghttp2/third-party/llhttp/src/llhttp.c new file mode 100644 index 00000000000..03f6d57c8cc --- /dev/null +++ b/lib/nghttp2/third-party/llhttp/src/llhttp.c @@ -0,0 +1,9537 @@ +#include +#include +#include + +#ifdef __SSE4_2__ + #ifdef _MSC_VER + #include + #else /* !_MSC_VER */ + #include + #endif /* _MSC_VER */ +#endif /* __SSE4_2__ */ + +#ifdef _MSC_VER + #define ALIGN(n) _declspec(align(n)) +#else /* !_MSC_VER */ + #define ALIGN(n) __attribute__((aligned(n))) +#endif /* _MSC_VER */ + +#include "llhttp.h" + +typedef int (*llhttp__internal__span_cb)( + llhttp__internal_t*, const char*, const char*); + +static const unsigned char llparse_blob0[] = { + 0xd, 0xa +}; +static const unsigned char llparse_blob1[] = { + 'o', 'n' +}; +static const unsigned char llparse_blob2[] = { + 'e', 'c', 't', 'i', 'o', 'n' +}; +static const unsigned char llparse_blob3[] = { + 'l', 'o', 's', 'e' +}; +static const unsigned char llparse_blob4[] = { + 'e', 'e', 'p', '-', 'a', 'l', 'i', 'v', 'e' +}; +static const unsigned char llparse_blob5[] = { + 'p', 'g', 'r', 'a', 'd', 'e' +}; +static const unsigned char llparse_blob6[] = { + 'c', 'h', 'u', 'n', 'k', 'e', 'd' +}; +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob7[] = { + 0x9, 0x9, ' ', '~', 0x80, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0 +}; +#endif /* __SSE4_2__ */ +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob8[] = { + '!', '!', '#', '\'', '*', '+', '-', '.', '0', '9', 'A', + 'Z', '^', 'z', '|', '|' +}; +#endif /* __SSE4_2__ */ +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob9[] = { + '~', '~', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0 +}; +#endif /* __SSE4_2__ */ +static const unsigned char llparse_blob10[] = { + 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h' +}; +static const unsigned char llparse_blob11[] = { + 'r', 'o', 'x', 'y', '-', 'c', 'o', 'n', 'n', 'e', 'c', + 't', 'i', 'o', 'n' +}; +static const unsigned char llparse_blob12[] = { + 'r', 'a', 'n', 's', 'f', 'e', 'r', '-', 'e', 'n', 'c', + 'o', 'd', 'i', 'n', 'g' +}; +static const unsigned char llparse_blob13[] = { + 'p', 'g', 'r', 'a', 'd', 'e' +}; +static const unsigned char llparse_blob14[] = { + 'T', 'T', 'P', '/' +}; +static const unsigned char llparse_blob15[] = { + 0xd, 0xa, 0xd, 0xa, 'S', 'M', 0xd, 0xa, 0xd, 0xa +}; +static const unsigned char llparse_blob16[] = { + 'C', 'E', '/' +}; +static const unsigned char llparse_blob17[] = { + 'T', 'S', 'P', '/' +}; +static const unsigned char llparse_blob18[] = { + 'N', 'O', 'U', 'N', 'C', 'E' +}; +static const unsigned char llparse_blob19[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob20[] = { + 'E', 'C', 'K', 'O', 'U', 'T' +}; +static const unsigned char llparse_blob21[] = { + 'N', 'E', 'C', 'T' +}; +static const unsigned char llparse_blob22[] = { + 'E', 'T', 'E' +}; +static const unsigned char llparse_blob23[] = { + 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob24[] = { + 'L', 'U', 'S', 'H' +}; +static const unsigned char llparse_blob25[] = { + 'E', 'T' +}; +static const unsigned char llparse_blob26[] = { + 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R' +}; +static const unsigned char llparse_blob27[] = { + 'E', 'A', 'D' +}; +static const unsigned char llparse_blob28[] = { + 'N', 'K' +}; +static const unsigned char llparse_blob29[] = { + 'C', 'K' +}; +static const unsigned char llparse_blob30[] = { + 'S', 'E', 'A', 'R', 'C', 'H' +}; +static const unsigned char llparse_blob31[] = { + 'R', 'G', 'E' +}; +static const unsigned char llparse_blob32[] = { + 'C', 'T', 'I', 'V', 'I', 'T', 'Y' +}; +static const unsigned char llparse_blob33[] = { + 'L', 'E', 'N', 'D', 'A', 'R' +}; +static const unsigned char llparse_blob34[] = { + 'V', 'E' +}; +static const unsigned char llparse_blob35[] = { + 'O', 'T', 'I', 'F', 'Y' +}; +static const unsigned char llparse_blob36[] = { + 'P', 'T', 'I', 'O', 'N', 'S' +}; +static const unsigned char llparse_blob37[] = { + 'C', 'H' +}; +static const unsigned char llparse_blob38[] = { + 'S', 'E' +}; +static const unsigned char llparse_blob39[] = { + 'A', 'Y' +}; +static const unsigned char llparse_blob40[] = { + 'S', 'T' +}; +static const unsigned char llparse_blob41[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob42[] = { + 'A', 'T', 'C', 'H' +}; +static const unsigned char llparse_blob43[] = { + 'G', 'E' +}; +static const unsigned char llparse_blob44[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob45[] = { + 'O', 'R', 'D' +}; +static const unsigned char llparse_blob46[] = { + 'I', 'R', 'E', 'C', 'T' +}; +static const unsigned char llparse_blob47[] = { + 'O', 'R', 'T' +}; +static const unsigned char llparse_blob48[] = { + 'R', 'C', 'H' +}; +static const unsigned char llparse_blob49[] = { + 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R' +}; +static const unsigned char llparse_blob50[] = { + 'U', 'R', 'C', 'E' +}; +static const unsigned char llparse_blob51[] = { + 'B', 'S', 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob52[] = { + 'A', 'R', 'D', 'O', 'W', 'N' +}; +static const unsigned char llparse_blob53[] = { + 'A', 'C', 'E' +}; +static const unsigned char llparse_blob54[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob55[] = { + 'N', 'K' +}; +static const unsigned char llparse_blob56[] = { + 'C', 'K' +}; +static const unsigned char llparse_blob57[] = { + 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob58[] = { + 'H', 'T', 'T', 'P', '/' +}; +static const unsigned char llparse_blob59[] = { + 'A', 'D' +}; +static const unsigned char llparse_blob60[] = { + 'T', 'P', '/' +}; + +enum llparse_match_status_e { + kMatchComplete, + kMatchPause, + kMatchMismatch +}; +typedef enum llparse_match_status_e llparse_match_status_t; + +struct llparse_match_s { + llparse_match_status_t status; + const unsigned char* current; +}; +typedef struct llparse_match_s llparse_match_t; + +static llparse_match_t llparse__match_sequence_id( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = *p; + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +static llparse_match_t llparse__match_sequence_to_lower( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = ((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p)); + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +static llparse_match_t llparse__match_sequence_to_lower_unsafe( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = ((*p) | 0x20); + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +enum llparse_state_e { + s_error, + s_n_llhttp__internal__n_closed, + s_n_llhttp__internal__n_invoke_llhttp__after_message_complete, + s_n_llhttp__internal__n_pause_1, + s_n_llhttp__internal__n_chunk_data_almost_done, + s_n_llhttp__internal__n_consume_content_length, + s_n_llhttp__internal__n_span_start_llhttp__on_body, + s_n_llhttp__internal__n_invoke_is_equal_content_length, + s_n_llhttp__internal__n_chunk_size_almost_done, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete, + s_n_llhttp__internal__n_chunk_extension_quoted_value_done, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1, + s_n_llhttp__internal__n_error_21, + s_n_llhttp__internal__n_chunk_extension_quoted_value, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2, + s_n_llhttp__internal__n_error_23, + s_n_llhttp__internal__n_chunk_extension_value, + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value, + s_n_llhttp__internal__n_error_24, + s_n_llhttp__internal__n_chunk_extension_name, + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name, + s_n_llhttp__internal__n_chunk_extensions, + s_n_llhttp__internal__n_chunk_size_otherwise, + s_n_llhttp__internal__n_chunk_size, + s_n_llhttp__internal__n_chunk_size_digit, + s_n_llhttp__internal__n_invoke_update_content_length_1, + s_n_llhttp__internal__n_invoke_is_equal_upgrade, + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2, + s_n_llhttp__internal__n_consume_content_length_1, + s_n_llhttp__internal__n_span_start_llhttp__on_body_1, + s_n_llhttp__internal__n_eof, + s_n_llhttp__internal__n_span_start_llhttp__on_body_2, + s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete, + s_n_llhttp__internal__n_error_5, + s_n_llhttp__internal__n_headers_almost_done, + s_n_llhttp__internal__n_header_field_colon_discard_ws, + s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete, + s_n_llhttp__internal__n_span_start_llhttp__on_header_value, + s_n_llhttp__internal__n_header_value_discard_lws, + s_n_llhttp__internal__n_header_value_discard_ws_almost_done, + s_n_llhttp__internal__n_header_value_lws, + s_n_llhttp__internal__n_header_value_almost_done, + s_n_llhttp__internal__n_header_value_lenient, + s_n_llhttp__internal__n_error_41, + s_n_llhttp__internal__n_header_value_otherwise, + s_n_llhttp__internal__n_header_value_connection_token, + s_n_llhttp__internal__n_header_value_connection_ws, + s_n_llhttp__internal__n_header_value_connection_1, + s_n_llhttp__internal__n_header_value_connection_2, + s_n_llhttp__internal__n_header_value_connection_3, + s_n_llhttp__internal__n_header_value_connection, + s_n_llhttp__internal__n_error_43, + s_n_llhttp__internal__n_error_44, + s_n_llhttp__internal__n_header_value_content_length_ws, + s_n_llhttp__internal__n_header_value_content_length, + s_n_llhttp__internal__n_error_46, + s_n_llhttp__internal__n_error_45, + s_n_llhttp__internal__n_header_value_te_token_ows, + s_n_llhttp__internal__n_header_value, + s_n_llhttp__internal__n_header_value_te_token, + s_n_llhttp__internal__n_header_value_te_chunked_last, + s_n_llhttp__internal__n_header_value_te_chunked, + s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1, + s_n_llhttp__internal__n_header_value_discard_ws, + s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete, + s_n_llhttp__internal__n_header_field_general_otherwise, + s_n_llhttp__internal__n_header_field_general, + s_n_llhttp__internal__n_header_field_colon, + s_n_llhttp__internal__n_header_field_3, + s_n_llhttp__internal__n_header_field_4, + s_n_llhttp__internal__n_header_field_2, + s_n_llhttp__internal__n_header_field_1, + s_n_llhttp__internal__n_header_field_5, + s_n_llhttp__internal__n_header_field_6, + s_n_llhttp__internal__n_header_field_7, + s_n_llhttp__internal__n_header_field, + s_n_llhttp__internal__n_span_start_llhttp__on_header_field, + s_n_llhttp__internal__n_header_field_start, + s_n_llhttp__internal__n_headers_start, + s_n_llhttp__internal__n_url_to_http_09, + s_n_llhttp__internal__n_url_skip_to_http09, + s_n_llhttp__internal__n_url_skip_lf_to_http09_1, + s_n_llhttp__internal__n_url_skip_lf_to_http09, + s_n_llhttp__internal__n_req_pri_upgrade, + s_n_llhttp__internal__n_req_http_complete_crlf, + s_n_llhttp__internal__n_req_http_complete, + s_n_llhttp__internal__n_invoke_load_method_1, + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete, + s_n_llhttp__internal__n_error_51, + s_n_llhttp__internal__n_error_57, + s_n_llhttp__internal__n_req_http_minor, + s_n_llhttp__internal__n_error_58, + s_n_llhttp__internal__n_req_http_dot, + s_n_llhttp__internal__n_error_59, + s_n_llhttp__internal__n_req_http_major, + s_n_llhttp__internal__n_span_start_llhttp__on_version, + s_n_llhttp__internal__n_req_http_start_1, + s_n_llhttp__internal__n_req_http_start_2, + s_n_llhttp__internal__n_req_http_start_3, + s_n_llhttp__internal__n_req_http_start, + s_n_llhttp__internal__n_url_to_http, + s_n_llhttp__internal__n_url_skip_to_http, + s_n_llhttp__internal__n_url_fragment, + s_n_llhttp__internal__n_span_end_stub_query_3, + s_n_llhttp__internal__n_url_query, + s_n_llhttp__internal__n_url_query_or_fragment, + s_n_llhttp__internal__n_url_path, + s_n_llhttp__internal__n_span_start_stub_path_2, + s_n_llhttp__internal__n_span_start_stub_path, + s_n_llhttp__internal__n_span_start_stub_path_1, + s_n_llhttp__internal__n_url_server_with_at, + s_n_llhttp__internal__n_url_server, + s_n_llhttp__internal__n_url_schema_delim_1, + s_n_llhttp__internal__n_url_schema_delim, + s_n_llhttp__internal__n_span_end_stub_schema, + s_n_llhttp__internal__n_url_schema, + s_n_llhttp__internal__n_url_start, + s_n_llhttp__internal__n_span_start_llhttp__on_url_1, + s_n_llhttp__internal__n_url_entry_normal, + s_n_llhttp__internal__n_span_start_llhttp__on_url, + s_n_llhttp__internal__n_url_entry_connect, + s_n_llhttp__internal__n_req_spaces_before_url, + s_n_llhttp__internal__n_req_first_space_before_url, + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1, + s_n_llhttp__internal__n_after_start_req_2, + s_n_llhttp__internal__n_after_start_req_3, + s_n_llhttp__internal__n_after_start_req_1, + s_n_llhttp__internal__n_after_start_req_4, + s_n_llhttp__internal__n_after_start_req_6, + s_n_llhttp__internal__n_after_start_req_8, + s_n_llhttp__internal__n_after_start_req_9, + s_n_llhttp__internal__n_after_start_req_7, + s_n_llhttp__internal__n_after_start_req_5, + s_n_llhttp__internal__n_after_start_req_12, + s_n_llhttp__internal__n_after_start_req_13, + s_n_llhttp__internal__n_after_start_req_11, + s_n_llhttp__internal__n_after_start_req_10, + s_n_llhttp__internal__n_after_start_req_14, + s_n_llhttp__internal__n_after_start_req_17, + s_n_llhttp__internal__n_after_start_req_16, + s_n_llhttp__internal__n_after_start_req_15, + s_n_llhttp__internal__n_after_start_req_18, + s_n_llhttp__internal__n_after_start_req_20, + s_n_llhttp__internal__n_after_start_req_21, + s_n_llhttp__internal__n_after_start_req_19, + s_n_llhttp__internal__n_after_start_req_23, + s_n_llhttp__internal__n_after_start_req_24, + s_n_llhttp__internal__n_after_start_req_26, + s_n_llhttp__internal__n_after_start_req_28, + s_n_llhttp__internal__n_after_start_req_29, + s_n_llhttp__internal__n_after_start_req_27, + s_n_llhttp__internal__n_after_start_req_25, + s_n_llhttp__internal__n_after_start_req_30, + s_n_llhttp__internal__n_after_start_req_22, + s_n_llhttp__internal__n_after_start_req_31, + s_n_llhttp__internal__n_after_start_req_32, + s_n_llhttp__internal__n_after_start_req_35, + s_n_llhttp__internal__n_after_start_req_36, + s_n_llhttp__internal__n_after_start_req_34, + s_n_llhttp__internal__n_after_start_req_37, + s_n_llhttp__internal__n_after_start_req_38, + s_n_llhttp__internal__n_after_start_req_42, + s_n_llhttp__internal__n_after_start_req_43, + s_n_llhttp__internal__n_after_start_req_41, + s_n_llhttp__internal__n_after_start_req_40, + s_n_llhttp__internal__n_after_start_req_39, + s_n_llhttp__internal__n_after_start_req_45, + s_n_llhttp__internal__n_after_start_req_44, + s_n_llhttp__internal__n_after_start_req_33, + s_n_llhttp__internal__n_after_start_req_48, + s_n_llhttp__internal__n_after_start_req_49, + s_n_llhttp__internal__n_after_start_req_50, + s_n_llhttp__internal__n_after_start_req_51, + s_n_llhttp__internal__n_after_start_req_47, + s_n_llhttp__internal__n_after_start_req_46, + s_n_llhttp__internal__n_after_start_req_54, + s_n_llhttp__internal__n_after_start_req_56, + s_n_llhttp__internal__n_after_start_req_57, + s_n_llhttp__internal__n_after_start_req_55, + s_n_llhttp__internal__n_after_start_req_53, + s_n_llhttp__internal__n_after_start_req_58, + s_n_llhttp__internal__n_after_start_req_59, + s_n_llhttp__internal__n_after_start_req_52, + s_n_llhttp__internal__n_after_start_req_61, + s_n_llhttp__internal__n_after_start_req_62, + s_n_llhttp__internal__n_after_start_req_60, + s_n_llhttp__internal__n_after_start_req_65, + s_n_llhttp__internal__n_after_start_req_67, + s_n_llhttp__internal__n_after_start_req_68, + s_n_llhttp__internal__n_after_start_req_66, + s_n_llhttp__internal__n_after_start_req_69, + s_n_llhttp__internal__n_after_start_req_64, + s_n_llhttp__internal__n_after_start_req_63, + s_n_llhttp__internal__n_after_start_req, + s_n_llhttp__internal__n_span_start_llhttp__on_method_1, + s_n_llhttp__internal__n_res_line_almost_done, + s_n_llhttp__internal__n_res_status, + s_n_llhttp__internal__n_span_start_llhttp__on_status, + s_n_llhttp__internal__n_res_status_start, + s_n_llhttp__internal__n_res_status_code_otherwise, + s_n_llhttp__internal__n_res_status_code_digit_3, + s_n_llhttp__internal__n_res_status_code_digit_2, + s_n_llhttp__internal__n_res_status_code_digit_1, + s_n_llhttp__internal__n_res_after_version, + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1, + s_n_llhttp__internal__n_error_73, + s_n_llhttp__internal__n_error_85, + s_n_llhttp__internal__n_res_http_minor, + s_n_llhttp__internal__n_error_86, + s_n_llhttp__internal__n_res_http_dot, + s_n_llhttp__internal__n_error_87, + s_n_llhttp__internal__n_res_http_major, + s_n_llhttp__internal__n_span_start_llhttp__on_version_1, + s_n_llhttp__internal__n_start_res, + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete, + s_n_llhttp__internal__n_req_or_res_method_2, + s_n_llhttp__internal__n_invoke_update_type_1, + s_n_llhttp__internal__n_req_or_res_method_3, + s_n_llhttp__internal__n_req_or_res_method_1, + s_n_llhttp__internal__n_req_or_res_method, + s_n_llhttp__internal__n_span_start_llhttp__on_method, + s_n_llhttp__internal__n_start_req_or_res, + s_n_llhttp__internal__n_invoke_load_type, + s_n_llhttp__internal__n_invoke_update_finish, + s_n_llhttp__internal__n_start, +}; +typedef enum llparse_state_e llparse_state_t; + +int llhttp__on_method( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_url( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_version( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_header_field( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_header_value( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_body( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_name( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_value( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_status( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_initial_message_completed( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->initial_message_completed; +} + +int llhttp__on_reset( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_finish( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 2; + return 0; +} + +int llhttp__on_message_begin( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_type( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->type; +} + +int llhttp__internal__c_store_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->method = match; + return 0; +} + +int llhttp__on_method_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->method == 5; +} + +int llhttp__internal__c_update_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->http_major = 0; + return 0; +} + +int llhttp__internal__c_update_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->http_minor = 9; + return 0; +} + +int llhttp__on_url_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_test_lenient_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 1) == 1; +} + +int llhttp__after_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_message_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__after_message_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->content_length = 0; + return 0; +} + +int llhttp__internal__c_update_initial_message_completed( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->initial_message_completed = 1; + return 0; +} + +int llhttp__internal__c_update_finish_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 0; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_2( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 4) == 4; +} + +int llhttp__internal__c_test_lenient_flags_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 32) == 32; +} + +int llhttp__internal__c_mul_add_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->content_length > 0xffffffffffffffffULL / 16) { + return 1; + } + + state->content_length *= 16; + + /* Addition overflow */ + if (match >= 0) { + if (state->content_length > 0xffffffffffffffffULL - match) { + return 1; + } + } else { + if (state->content_length < 0ULL - match) { + return 1; + } + } + state->content_length += match; + return 0; +} + +int llhttp__on_chunk_header( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->content_length == 0; +} + +int llhttp__on_chunk_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_test_lenient_flags_4( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 128) == 128; +} + +int llhttp__internal__c_or_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 128; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_5( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 64) == 64; +} + +int llhttp__on_chunk_extension_name_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_value_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_upgrade( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->upgrade == 1; +} + +int llhttp__internal__c_update_finish_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 1; + return 0; +} + +int llhttp__internal__c_test_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 128) == 128; +} + +int llhttp__internal__c_test_flags_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 544) == 544; +} + +int llhttp__internal__c_test_lenient_flags_6( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 2) == 2; +} + +int llhttp__before_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_or_flags_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 64; + return 0; +} + +int llhttp__internal__c_update_upgrade( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->upgrade = 1; + return 0; +} + +int llhttp__internal__c_store_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->header_state = match; + return 0; +} + +int llhttp__on_header_field_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->header_state; +} + +int llhttp__internal__c_or_flags_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 1; + return 0; +} + +int llhttp__internal__c_update_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 1; + return 0; +} + +int llhttp__on_header_value_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_or_flags_4( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 2; + return 0; +} + +int llhttp__internal__c_or_flags_5( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 4; + return 0; +} + +int llhttp__internal__c_or_flags_6( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 8; + return 0; +} + +int llhttp__internal__c_update_header_state_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 6; + return 0; +} + +int llhttp__internal__c_update_header_state_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 0; + return 0; +} + +int llhttp__internal__c_update_header_state_6( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 5; + return 0; +} + +int llhttp__internal__c_update_header_state_7( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 7; + return 0; +} + +int llhttp__internal__c_test_flags_2( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 32) == 32; +} + +int llhttp__internal__c_mul_add_content_length_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->content_length > 0xffffffffffffffffULL / 10) { + return 1; + } + + state->content_length *= 10; + + /* Addition overflow */ + if (match >= 0) { + if (state->content_length > 0xffffffffffffffffULL - match) { + return 1; + } + } else { + if (state->content_length < 0ULL - match) { + return 1; + } + } + state->content_length += match; + return 0; +} + +int llhttp__internal__c_or_flags_15( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 32; + return 0; +} + +int llhttp__internal__c_test_flags_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 8) == 8; +} + +int llhttp__internal__c_test_lenient_flags_13( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 8) == 8; +} + +int llhttp__internal__c_or_flags_16( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 512; + return 0; +} + +int llhttp__internal__c_and_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags &= -9; + return 0; +} + +int llhttp__internal__c_update_header_state_8( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 8; + return 0; +} + +int llhttp__internal__c_or_flags_18( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 16; + return 0; +} + +int llhttp__internal__c_load_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->method; +} + +int llhttp__internal__c_store_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->http_major = match; + return 0; +} + +int llhttp__internal__c_store_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->http_minor = match; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_15( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 16) == 16; +} + +int llhttp__on_version_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->http_major; +} + +int llhttp__internal__c_load_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->http_minor; +} + +int llhttp__internal__c_update_status_code( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->status_code = 0; + return 0; +} + +int llhttp__internal__c_mul_add_status_code( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->status_code > 0xffff / 10) { + return 1; + } + + state->status_code *= 10; + + /* Addition overflow */ + if (match >= 0) { + if (state->status_code > 0xffff - match) { + return 1; + } + } else { + if (state->status_code < 0 - match) { + return 1; + } + } + state->status_code += match; + return 0; +} + +int llhttp__on_status_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_type( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->type = 1; + return 0; +} + +int llhttp__internal__c_update_type_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->type = 2; + return 0; +} + +int llhttp__internal_init(llhttp__internal_t* state) { + memset(state, 0, sizeof(*state)); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_start; + return 0; +} + +static llparse_state_t llhttp__internal__run( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + int match; + switch ((llparse_state_t) (intptr_t) state->_current) { + case s_n_llhttp__internal__n_closed: + s_n_llhttp__internal__n_closed: { + if (p == endp) { + return s_n_llhttp__internal__n_closed; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_closed; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_closed; + } + default: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_3; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: + s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: { + switch (llhttp__after_message_complete(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_update_content_length; + default: + goto s_n_llhttp__internal__n_invoke_update_finish_1; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_pause_1: + s_n_llhttp__internal__n_pause_1: { + state->error = 0x16; + state->reason = "Pause on CONNECT/Upgrade"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_data_almost_done: + s_n_llhttp__internal__n_chunk_data_almost_done: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_chunk_data_almost_done; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob0, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; + } + case kMatchPause: { + return s_n_llhttp__internal__n_chunk_data_almost_done; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_4; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_consume_content_length: + s_n_llhttp__internal__n_consume_content_length: { + size_t avail; + uint64_t need; + + avail = endp - p; + need = state->content_length; + if (avail >= need) { + p += need; + state->content_length = 0; + goto s_n_llhttp__internal__n_span_end_llhttp__on_body; + } + + state->content_length -= avail; + return s_n_llhttp__internal__n_consume_content_length; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body: + s_n_llhttp__internal__n_span_start_llhttp__on_body: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_consume_content_length; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_is_equal_content_length: + s_n_llhttp__internal__n_invoke_is_equal_content_length: { + switch (llhttp__internal__c_is_equal_content_length(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body; + default: + goto s_n_llhttp__internal__n_invoke_or_flags; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_size_almost_done: + s_n_llhttp__internal__n_chunk_size_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_5; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + case 21: + goto s_n_llhttp__internal__n_pause_5; + default: + goto s_n_llhttp__internal__n_error_15; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extensions; + case 21: + goto s_n_llhttp__internal__n_pause_6; + default: + goto s_n_llhttp__internal__n_error_16; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + case 21: + goto s_n_llhttp__internal__n_pause_7; + default: + goto s_n_llhttp__internal__n_error_18; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_quoted_value_done: + s_n_llhttp__internal__n_chunk_extension_quoted_value_done: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + } + switch (*p) { + case 13: { + p++; + goto s_n_llhttp__internal__n_chunk_size_almost_done; + } + case ';': { + p++; + goto s_n_llhttp__internal__n_chunk_extensions; + } + default: { + goto s_n_llhttp__internal__n_error_20; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + case 21: + goto s_n_llhttp__internal__n_pause_8; + default: + goto s_n_llhttp__internal__n_error_19; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_21: + s_n_llhttp__internal__n_error_21: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions quoted value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_quoted_value: + s_n_llhttp__internal__n_chunk_extension_quoted_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_size_otherwise; + case 21: + goto s_n_llhttp__internal__n_pause_9; + default: + goto s_n_llhttp__internal__n_error_22; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_23: + s_n_llhttp__internal__n_error_23: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_value: + s_n_llhttp__internal__n_chunk_extension_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 3, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 4, 0, 0, 0, 0, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_value; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_value; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_chunk_extension_value; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_24: + s_n_llhttp__internal__n_error_24: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions name"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extension_name: + s_n_llhttp__internal__n_chunk_extension_name: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 2, 2, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 3, 0, 4, 0, 0, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_name; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_name; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_chunk_extension_name; + goto s_n_llhttp__internal__n_chunk_extension_name; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_extensions: + s_n_llhttp__internal__n_chunk_extensions: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extensions; + } + switch (*p) { + case 13: { + p++; + goto s_n_llhttp__internal__n_error_13; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_error_14; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_size_otherwise: + s_n_llhttp__internal__n_chunk_size_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_otherwise; + } + switch (*p) { + case 13: { + p++; + goto s_n_llhttp__internal__n_chunk_size_almost_done; + } + case ';': { + p++; + goto s_n_llhttp__internal__n_chunk_extensions; + } + default: { + goto s_n_llhttp__internal__n_error_25; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_size: + s_n_llhttp__internal__n_chunk_size: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'A': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'B': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'C': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'D': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'E': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'F': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'a': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'b': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'c': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'd': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'e': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'f': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + default: { + goto s_n_llhttp__internal__n_chunk_size_otherwise; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_chunk_size_digit: + s_n_llhttp__internal__n_chunk_size_digit: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_digit; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'A': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'B': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'C': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'D': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'E': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'F': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'a': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'b': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'c': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'd': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'e': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'f': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + default: { + goto s_n_llhttp__internal__n_error_27; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_update_content_length_1: + s_n_llhttp__internal__n_invoke_update_content_length_1: { + switch (llhttp__internal__c_update_content_length(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_chunk_size_digit; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_is_equal_upgrade: + s_n_llhttp__internal__n_invoke_is_equal_upgrade: { + switch (llhttp__internal__c_is_equal_upgrade(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + default: + goto s_n_llhttp__internal__n_pause_1; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_is_equal_upgrade; + case 21: + goto s_n_llhttp__internal__n_pause_11; + default: + goto s_n_llhttp__internal__n_error_28; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_consume_content_length_1: + s_n_llhttp__internal__n_consume_content_length_1: { + size_t avail; + uint64_t need; + + avail = endp - p; + need = state->content_length; + if (avail >= need) { + p += need; + state->content_length = 0; + goto s_n_llhttp__internal__n_span_end_llhttp__on_body_1; + } + + state->content_length -= avail; + return s_n_llhttp__internal__n_consume_content_length_1; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body_1: + s_n_llhttp__internal__n_span_start_llhttp__on_body_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_consume_content_length_1; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_eof: + s_n_llhttp__internal__n_eof: { + if (p == endp) { + return s_n_llhttp__internal__n_eof; + } + p++; + goto s_n_llhttp__internal__n_eof; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body_2: + s_n_llhttp__internal__n_span_start_llhttp__on_body_2: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body_2; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_eof; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: + s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: { + switch (llhttp__after_headers_complete(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1; + case 2: + goto s_n_llhttp__internal__n_invoke_update_content_length_1; + case 3: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body_1; + case 4: + goto s_n_llhttp__internal__n_invoke_update_finish_3; + case 5: + goto s_n_llhttp__internal__n_error_29; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_5: + s_n_llhttp__internal__n_error_5: { + state->error = 0xa; + state->reason = "Invalid header field char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_headers_almost_done: + s_n_llhttp__internal__n_headers_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_headers_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_flags; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_7; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_colon_discard_ws: + s_n_llhttp__internal__n_header_field_colon_discard_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_colon_discard_ws; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_field_colon_discard_ws; + } + default: { + goto s_n_llhttp__internal__n_header_field_colon; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: { + switch (llhttp__on_header_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_header_field_start; + case 21: + goto s_n_llhttp__internal__n_pause_14; + default: + goto s_n_llhttp__internal__n_error_37; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_value: + s_n_llhttp__internal__n_span_start_llhttp__on_header_value: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_value; + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_discard_lws: + s_n_llhttp__internal__n_header_value_discard_lws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_lws; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_10; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_10; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_header_state; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_discard_ws_almost_done: + s_n_llhttp__internal__n_header_value_discard_ws_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_ws_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_lws; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_lws: + s_n_llhttp__internal__n_header_value_lws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_lws; + } + switch (*p) { + case 9: { + goto s_n_llhttp__internal__n_invoke_load_header_state_3; + } + case ' ': { + goto s_n_llhttp__internal__n_invoke_load_header_state_3; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_header_state_4; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_almost_done: + s_n_llhttp__internal__n_header_value_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_header_value_lws; + } + default: { + goto s_n_llhttp__internal__n_error_40; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_lenient: + s_n_llhttp__internal__n_header_value_lenient: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_lenient; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4; + } + default: { + p++; + goto s_n_llhttp__internal__n_header_value_lenient; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_41: + s_n_llhttp__internal__n_error_41: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_otherwise: + s_n_llhttp__internal__n_header_value_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_otherwise; + } + switch (*p) { + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_12; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_token: + s_n_llhttp__internal__n_header_value_connection_token: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_token; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value_connection_token; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + default: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_ws: + s_n_llhttp__internal__n_header_value_connection_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_ws; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + case 13: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + case ',': { + p++; + goto s_n_llhttp__internal__n_invoke_load_header_state_5; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_5; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_1: + s_n_llhttp__internal__n_header_value_connection_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_1; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob3, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_3; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_2: + s_n_llhttp__internal__n_header_value_connection_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_2; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob4, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_6; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection_3: + s_n_llhttp__internal__n_header_value_connection_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_3; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob5, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_7; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_connection: + s_n_llhttp__internal__n_header_value_connection: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + case 'c': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_1; + } + case 'k': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_2; + } + case 'u': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_3; + } + default: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_43: + s_n_llhttp__internal__n_error_43: { + state->error = 0xb; + state->reason = "Content-Length overflow"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_44: + s_n_llhttp__internal__n_error_44: { + state->error = 0xb; + state->reason = "Invalid character in Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_content_length_ws: + s_n_llhttp__internal__n_header_value_content_length_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_content_length_ws; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_invoke_or_flags_15; + } + case 13: { + goto s_n_llhttp__internal__n_invoke_or_flags_15; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_content_length_ws; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_content_length: + s_n_llhttp__internal__n_header_value_content_length: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_content_length; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + default: { + goto s_n_llhttp__internal__n_header_value_content_length_ws; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_46: + s_n_llhttp__internal__n_error_46: { + state->error = 0xf; + state->reason = "Invalid `Transfer-Encoding` header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_45: + s_n_llhttp__internal__n_error_45: { + state->error = 0xf; + state->reason = "Invalid `Transfer-Encoding` header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_te_token_ows: + s_n_llhttp__internal__n_header_value_te_token_ows: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_token_ows; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + default: { + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value: + s_n_llhttp__internal__n_header_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value; + } + #ifdef __SSE4_2__ + if (endp - p >= 16) { + __m128i ranges; + __m128i input; + int avail; + int match_len; + + /* Load input */ + input = _mm_loadu_si128((__m128i const*) p); + ranges = _mm_loadu_si128((__m128i const*) llparse_blob7); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 6, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_value; + } + goto s_n_llhttp__internal__n_header_value_otherwise; + } + #endif /* __SSE4_2__ */ + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value; + } + default: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_te_token: + s_n_llhttp__internal__n_header_value_te_token: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_token; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_9; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_te_chunked_last: + s_n_llhttp__internal__n_header_value_te_chunked_last: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_chunked_last; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_invoke_update_header_state_8; + } + case 13: { + goto s_n_llhttp__internal__n_invoke_update_header_state_8; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_te_chunked_last; + } + case ',': { + goto s_n_llhttp__internal__n_invoke_load_type_1; + } + default: { + goto s_n_llhttp__internal__n_header_value_te_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_te_chunked: + s_n_llhttp__internal__n_header_value_te_chunked: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_chunked; + } + match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob6, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_header_value_te_chunked_last; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_te_chunked; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_te_token; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: + s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_value; + goto s_n_llhttp__internal__n_invoke_load_header_state_2; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_value_discard_ws: + s_n_llhttp__internal__n_header_value_discard_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_ws; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_9; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws_almost_done; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: { + switch (llhttp__on_header_field_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_header_value_discard_ws; + case 21: + goto s_n_llhttp__internal__n_pause_15; + default: + goto s_n_llhttp__internal__n_error_34; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_general_otherwise: + s_n_llhttp__internal__n_header_field_general_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_general_otherwise; + } + switch (*p) { + case ':': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2; + } + default: { + goto s_n_llhttp__internal__n_error_47; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_general: + s_n_llhttp__internal__n_header_field_general: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_field_general; + } + #ifdef __SSE4_2__ + if (endp - p >= 16) { + __m128i ranges; + __m128i input; + int avail; + int match_len; + + /* Load input */ + input = _mm_loadu_si128((__m128i const*) p); + ranges = _mm_loadu_si128((__m128i const*) llparse_blob8); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 16, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_field_general; + } + ranges = _mm_loadu_si128((__m128i const*) llparse_blob9); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 2, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_field_general; + } + goto s_n_llhttp__internal__n_header_field_general_otherwise; + } + #endif /* __SSE4_2__ */ + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_field_general; + } + default: { + goto s_n_llhttp__internal__n_header_field_general_otherwise; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_colon: + s_n_llhttp__internal__n_header_field_colon: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_colon; + } + switch (*p) { + case ' ': { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_8; + } + case ':': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_10; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_3: + s_n_llhttp__internal__n_header_field_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_3; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob2, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_4: + s_n_llhttp__internal__n_header_field_4: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_4; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob10, 10); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_4; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_2: + s_n_llhttp__internal__n_header_field_2: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_2; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 'n': { + p++; + goto s_n_llhttp__internal__n_header_field_3; + } + case 't': { + p++; + goto s_n_llhttp__internal__n_header_field_4; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_1: + s_n_llhttp__internal__n_header_field_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_1; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob1, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_header_field_2; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_5: + s_n_llhttp__internal__n_header_field_5: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_5; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob11, 15); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_5; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_6: + s_n_llhttp__internal__n_header_field_6: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_6; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob12, 16); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_6; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_7: + s_n_llhttp__internal__n_header_field_7: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_7; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob13, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_7; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field: + s_n_llhttp__internal__n_header_field: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 'c': { + p++; + goto s_n_llhttp__internal__n_header_field_1; + } + case 'p': { + p++; + goto s_n_llhttp__internal__n_header_field_5; + } + case 't': { + p++; + goto s_n_llhttp__internal__n_header_field_6; + } + case 'u': { + p++; + goto s_n_llhttp__internal__n_header_field_7; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_field: + s_n_llhttp__internal__n_span_start_llhttp__on_header_field: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_field; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_field; + goto s_n_llhttp__internal__n_header_field; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_header_field_start: + s_n_llhttp__internal__n_header_field_start: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_start; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_1; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_headers_almost_done; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_field; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_headers_start: + s_n_llhttp__internal__n_headers_start: { + if (p == endp) { + return s_n_llhttp__internal__n_headers_start; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags; + } + default: { + goto s_n_llhttp__internal__n_header_field_start; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_to_http_09: + s_n_llhttp__internal__n_url_to_http_09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_to_http_09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_http_major; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_skip_to_http09: + s_n_llhttp__internal__n_url_skip_to_http09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_to_http09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + p++; + goto s_n_llhttp__internal__n_url_to_http_09; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_skip_lf_to_http09_1: + s_n_llhttp__internal__n_url_skip_lf_to_http09_1: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_lf_to_http09_1; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_url_to_http_09; + } + default: { + goto s_n_llhttp__internal__n_error_48; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_skip_lf_to_http09: + s_n_llhttp__internal__n_url_skip_lf_to_http09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_lf_to_http09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_url_skip_lf_to_http09_1; + } + default: { + goto s_n_llhttp__internal__n_error_48; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_pri_upgrade: + s_n_llhttp__internal__n_req_pri_upgrade: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_pri_upgrade; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob15, 10); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_error_55; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_pri_upgrade; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_56; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_complete_crlf: + s_n_llhttp__internal__n_req_http_complete_crlf: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_complete_crlf; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_headers_start; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_16; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_complete: + s_n_llhttp__internal__n_req_http_complete: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_complete; + } + switch (*p) { + case 13: { + p++; + goto s_n_llhttp__internal__n_req_http_complete_crlf; + } + default: { + goto s_n_llhttp__internal__n_error_54; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_load_method_1: + s_n_llhttp__internal__n_invoke_load_method_1: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 34: + goto s_n_llhttp__internal__n_req_pri_upgrade; + default: + goto s_n_llhttp__internal__n_req_http_complete; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: { + switch (llhttp__on_version_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_method_1; + case 21: + goto s_n_llhttp__internal__n_pause_17; + default: + goto s_n_llhttp__internal__n_error_52; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_51: + s_n_llhttp__internal__n_error_51: { + state->error = 0x9; + state->reason = "Invalid HTTP version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_57: + s_n_llhttp__internal__n_error_57: { + state->error = 0x9; + state->reason = "Invalid minor version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_minor: + s_n_llhttp__internal__n_req_http_minor: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_minor; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_2; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_58: + s_n_llhttp__internal__n_error_58: { + state->error = 0x9; + state->reason = "Expected dot"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_dot: + s_n_llhttp__internal__n_req_http_dot: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_dot; + } + switch (*p) { + case '.': { + p++; + goto s_n_llhttp__internal__n_req_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_3; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_59: + s_n_llhttp__internal__n_error_59: { + state->error = 0x9; + state->reason = "Invalid major version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_major: + s_n_llhttp__internal__n_req_http_major: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_major; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_4; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_version: + s_n_llhttp__internal__n_span_start_llhttp__on_version: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_version; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_version; + goto s_n_llhttp__internal__n_req_http_major; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_start_1: + s_n_llhttp__internal__n_req_http_start_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start_1; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob14, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_load_method; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_http_start_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_62; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_start_2: + s_n_llhttp__internal__n_req_http_start_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start_2; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob16, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_load_method_2; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_http_start_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_62; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_start_3: + s_n_llhttp__internal__n_req_http_start_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob17, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_load_method_3; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_http_start_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_62; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_http_start: + s_n_llhttp__internal__n_req_http_start: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_http_start; + } + case 'H': { + p++; + goto s_n_llhttp__internal__n_req_http_start_1; + } + case 'I': { + p++; + goto s_n_llhttp__internal__n_req_http_start_2; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_req_http_start_3; + } + default: { + goto s_n_llhttp__internal__n_error_62; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_to_http: + s_n_llhttp__internal__n_url_to_http: { + if (p == endp) { + return s_n_llhttp__internal__n_url_to_http; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_skip_to_http: + s_n_llhttp__internal__n_url_skip_to_http: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_to_http; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + p++; + goto s_n_llhttp__internal__n_url_to_http; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_fragment: + s_n_llhttp__internal__n_url_fragment: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_fragment; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_6; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_7; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_8; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_fragment; + } + default: { + goto s_n_llhttp__internal__n_error_63; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_end_stub_query_3: + s_n_llhttp__internal__n_span_end_stub_query_3: { + if (p == endp) { + return s_n_llhttp__internal__n_span_end_stub_query_3; + } + p++; + goto s_n_llhttp__internal__n_url_fragment; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_query: + s_n_llhttp__internal__n_url_query: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_query; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_9; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_10; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_11; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 6: { + goto s_n_llhttp__internal__n_span_end_stub_query_3; + } + default: { + goto s_n_llhttp__internal__n_error_64; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_query_or_fragment: + s_n_llhttp__internal__n_url_query_or_fragment: { + if (p == endp) { + return s_n_llhttp__internal__n_url_query_or_fragment; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_3; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_4; + } + case ' ': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_5; + } + case '#': { + p++; + goto s_n_llhttp__internal__n_url_fragment; + } + case '?': { + p++; + goto s_n_llhttp__internal__n_url_query; + } + default: { + goto s_n_llhttp__internal__n_error_65; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_path: + s_n_llhttp__internal__n_url_path: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_path; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_url_path; + } + default: { + goto s_n_llhttp__internal__n_url_query_or_fragment; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_stub_path_2: + s_n_llhttp__internal__n_span_start_stub_path_2: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path_2; + } + p++; + goto s_n_llhttp__internal__n_url_path; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_stub_path: + s_n_llhttp__internal__n_span_start_stub_path: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path; + } + p++; + goto s_n_llhttp__internal__n_url_path; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_stub_path_1: + s_n_llhttp__internal__n_span_start_stub_path_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path_1; + } + p++; + goto s_n_llhttp__internal__n_url_path; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_server_with_at: + s_n_llhttp__internal__n_url_server_with_at: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7, + 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5, + 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_server_with_at; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_12; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_13; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_14; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_server; + } + case 6: { + goto s_n_llhttp__internal__n_span_start_stub_path_1; + } + case 7: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 8: { + p++; + goto s_n_llhttp__internal__n_error_66; + } + default: { + goto s_n_llhttp__internal__n_error_67; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_server: + s_n_llhttp__internal__n_url_server: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7, + 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5, + 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_server; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_1; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_2; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_server; + } + case 6: { + goto s_n_llhttp__internal__n_span_start_stub_path; + } + case 7: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 8: { + p++; + goto s_n_llhttp__internal__n_url_server_with_at; + } + default: { + goto s_n_llhttp__internal__n_error_68; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_schema_delim_1: + s_n_llhttp__internal__n_url_schema_delim_1: { + if (p == endp) { + return s_n_llhttp__internal__n_url_schema_delim_1; + } + switch (*p) { + case '/': { + p++; + goto s_n_llhttp__internal__n_url_server; + } + default: { + goto s_n_llhttp__internal__n_error_69; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_schema_delim: + s_n_llhttp__internal__n_url_schema_delim: { + if (p == endp) { + return s_n_llhttp__internal__n_url_schema_delim; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 10: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case '/': { + p++; + goto s_n_llhttp__internal__n_url_schema_delim_1; + } + default: { + goto s_n_llhttp__internal__n_error_69; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_end_stub_schema: + s_n_llhttp__internal__n_span_end_stub_schema: { + if (p == endp) { + return s_n_llhttp__internal__n_span_end_stub_schema; + } + p++; + goto s_n_llhttp__internal__n_url_schema_delim; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_schema: + s_n_llhttp__internal__n_url_schema: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_schema; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_stub_schema; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_url_schema; + } + default: { + goto s_n_llhttp__internal__n_error_70; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_start: + s_n_llhttp__internal__n_url_start: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_start; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_start_stub_path_2; + } + case 3: { + goto s_n_llhttp__internal__n_url_schema; + } + default: { + goto s_n_llhttp__internal__n_error_71; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_url_1: + s_n_llhttp__internal__n_span_start_llhttp__on_url_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_url_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_url; + goto s_n_llhttp__internal__n_url_start; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_entry_normal: + s_n_llhttp__internal__n_url_entry_normal: { + if (p == endp) { + return s_n_llhttp__internal__n_url_entry_normal; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_url_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_url: + s_n_llhttp__internal__n_span_start_llhttp__on_url: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_url; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_url; + goto s_n_llhttp__internal__n_url_server; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_url_entry_connect: + s_n_llhttp__internal__n_url_entry_connect: { + if (p == endp) { + return s_n_llhttp__internal__n_url_entry_connect; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_url; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_spaces_before_url: + s_n_llhttp__internal__n_req_spaces_before_url: { + if (p == endp) { + return s_n_llhttp__internal__n_req_spaces_before_url; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_spaces_before_url; + } + default: { + goto s_n_llhttp__internal__n_invoke_is_equal_method; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_first_space_before_url: + s_n_llhttp__internal__n_req_first_space_before_url: { + if (p == endp) { + return s_n_llhttp__internal__n_req_first_space_before_url; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_spaces_before_url; + } + default: { + goto s_n_llhttp__internal__n_error_72; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: { + switch (llhttp__on_method_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_first_space_before_url; + case 21: + goto s_n_llhttp__internal__n_pause_22; + default: + goto s_n_llhttp__internal__n_error_89; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_2: + s_n_llhttp__internal__n_after_start_req_2: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_2; + } + switch (*p) { + case 'L': { + p++; + match = 19; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_3: + s_n_llhttp__internal__n_after_start_req_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob18, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 36; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_1: + s_n_llhttp__internal__n_after_start_req_1: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_1; + } + switch (*p) { + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_2; + } + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_3; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_4: + s_n_llhttp__internal__n_after_start_req_4: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_4; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob19, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 16; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_4; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_6: + s_n_llhttp__internal__n_after_start_req_6: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_6; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob20, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 22; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_6; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_8: + s_n_llhttp__internal__n_after_start_req_8: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_8; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob21, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_8; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_9: + s_n_llhttp__internal__n_after_start_req_9: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_9; + } + switch (*p) { + case 'Y': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_7: + s_n_llhttp__internal__n_after_start_req_7: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_7; + } + switch (*p) { + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_8; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_9; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_5: + s_n_llhttp__internal__n_after_start_req_5: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_5; + } + switch (*p) { + case 'H': { + p++; + goto s_n_llhttp__internal__n_after_start_req_6; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_7; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_12: + s_n_llhttp__internal__n_after_start_req_12: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_12; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob22, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_12; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_13: + s_n_llhttp__internal__n_after_start_req_13: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_13; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob23, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 35; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_13; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_11: + s_n_llhttp__internal__n_after_start_req_11: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_11; + } + switch (*p) { + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_12; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_13; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_10: + s_n_llhttp__internal__n_after_start_req_10: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_10; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_11; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_14: + s_n_llhttp__internal__n_after_start_req_14: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_14; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob24, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 45; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_14; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_17: + s_n_llhttp__internal__n_after_start_req_17: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_17; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob26, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 41; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_17; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_16: + s_n_llhttp__internal__n_after_start_req_16: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_16; + } + switch (*p) { + case '_': { + p++; + goto s_n_llhttp__internal__n_after_start_req_17; + } + default: { + match = 1; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_15: + s_n_llhttp__internal__n_after_start_req_15: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_15; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob25, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_after_start_req_16; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_15; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_18: + s_n_llhttp__internal__n_after_start_req_18: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_18; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob27, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_18; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_20: + s_n_llhttp__internal__n_after_start_req_20: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_20; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob28, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 31; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_20; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_21: + s_n_llhttp__internal__n_after_start_req_21: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_21; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob29, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_21; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_19: + s_n_llhttp__internal__n_after_start_req_19: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_19; + } + switch (*p) { + case 'I': { + p++; + goto s_n_llhttp__internal__n_after_start_req_20; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_21; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_23: + s_n_llhttp__internal__n_after_start_req_23: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_23; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob30, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 24; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_23; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_24: + s_n_llhttp__internal__n_after_start_req_24: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_24; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob31, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 23; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_24; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_26: + s_n_llhttp__internal__n_after_start_req_26: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_26; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob32, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 21; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_26; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_28: + s_n_llhttp__internal__n_after_start_req_28: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_28; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob33, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 30; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_28; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_29: + s_n_llhttp__internal__n_after_start_req_29: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_29; + } + switch (*p) { + case 'L': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_27: + s_n_llhttp__internal__n_after_start_req_27: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_27; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_28; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_29; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_25: + s_n_llhttp__internal__n_after_start_req_25: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_25; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_26; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_27; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_30: + s_n_llhttp__internal__n_after_start_req_30: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_30; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob34, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_30; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_22: + s_n_llhttp__internal__n_after_start_req_22: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_22; + } + switch (*p) { + case '-': { + p++; + goto s_n_llhttp__internal__n_after_start_req_23; + } + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_24; + } + case 'K': { + p++; + goto s_n_llhttp__internal__n_after_start_req_25; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_30; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_31: + s_n_llhttp__internal__n_after_start_req_31: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_31; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob35, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 25; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_31; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_32: + s_n_llhttp__internal__n_after_start_req_32: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_32; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob36, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_32; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_35: + s_n_llhttp__internal__n_after_start_req_35: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_35; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob37, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 28; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_35; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_36: + s_n_llhttp__internal__n_after_start_req_36: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_36; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob38, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 39; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_36; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_34: + s_n_llhttp__internal__n_after_start_req_34: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_34; + } + switch (*p) { + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_35; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_36; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_37: + s_n_llhttp__internal__n_after_start_req_37: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_37; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob39, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 38; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_37; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_38: + s_n_llhttp__internal__n_after_start_req_38: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_38; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob40, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_38; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_42: + s_n_llhttp__internal__n_after_start_req_42: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_42; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob41, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_42; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_43: + s_n_llhttp__internal__n_after_start_req_43: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_43; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob42, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_43; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_41: + s_n_llhttp__internal__n_after_start_req_41: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_41; + } + switch (*p) { + case 'F': { + p++; + goto s_n_llhttp__internal__n_after_start_req_42; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_43; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_40: + s_n_llhttp__internal__n_after_start_req_40: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_40; + } + switch (*p) { + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_41; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_39: + s_n_llhttp__internal__n_after_start_req_39: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_39; + } + switch (*p) { + case 'I': { + p++; + match = 34; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_40; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_45: + s_n_llhttp__internal__n_after_start_req_45: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_45; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob43, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 29; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_45; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_44: + s_n_llhttp__internal__n_after_start_req_44: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_44; + } + switch (*p) { + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_45; + } + case 'T': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_33: + s_n_llhttp__internal__n_after_start_req_33: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_33; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_34; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_37; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_38; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_39; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_44; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_48: + s_n_llhttp__internal__n_after_start_req_48: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_48; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob44, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 17; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_48; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_49: + s_n_llhttp__internal__n_after_start_req_49: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_49; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob45, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 44; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_49; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_50: + s_n_llhttp__internal__n_after_start_req_50: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_50; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob46, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 43; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_50; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_51: + s_n_llhttp__internal__n_after_start_req_51: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_51; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob47, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 20; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_51; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_47: + s_n_llhttp__internal__n_after_start_req_47: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_47; + } + switch (*p) { + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_48; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_49; + } + case 'D': { + p++; + goto s_n_llhttp__internal__n_after_start_req_50; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_51; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_46: + s_n_llhttp__internal__n_after_start_req_46: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_46; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_47; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_54: + s_n_llhttp__internal__n_after_start_req_54: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_54; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob48, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_54; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_56: + s_n_llhttp__internal__n_after_start_req_56: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_56; + } + switch (*p) { + case 'P': { + p++; + match = 37; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_57: + s_n_llhttp__internal__n_after_start_req_57: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_57; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob49, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 42; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_57; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_55: + s_n_llhttp__internal__n_after_start_req_55: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_55; + } + switch (*p) { + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_56; + } + case '_': { + p++; + goto s_n_llhttp__internal__n_after_start_req_57; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_53: + s_n_llhttp__internal__n_after_start_req_53: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_53; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_54; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_55; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_58: + s_n_llhttp__internal__n_after_start_req_58: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_58; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob50, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 33; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_58; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_59: + s_n_llhttp__internal__n_after_start_req_59: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_59; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob51, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 26; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_59; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_52: + s_n_llhttp__internal__n_after_start_req_52: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_52; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_53; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_58; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_59; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_61: + s_n_llhttp__internal__n_after_start_req_61: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_61; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob52, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 40; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_61; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_62: + s_n_llhttp__internal__n_after_start_req_62: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_62; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob53, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_62; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_60: + s_n_llhttp__internal__n_after_start_req_60: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_60; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_61; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_62; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_65: + s_n_llhttp__internal__n_after_start_req_65: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_65; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob54, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 18; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_65; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_67: + s_n_llhttp__internal__n_after_start_req_67: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_67; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob55, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 32; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_67; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_68: + s_n_llhttp__internal__n_after_start_req_68: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_68; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob56, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_68; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_66: + s_n_llhttp__internal__n_after_start_req_66: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_66; + } + switch (*p) { + case 'I': { + p++; + goto s_n_llhttp__internal__n_after_start_req_67; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_68; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_69: + s_n_llhttp__internal__n_after_start_req_69: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_69; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob57, 8); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 27; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_69; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_64: + s_n_llhttp__internal__n_after_start_req_64: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_64; + } + switch (*p) { + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_65; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_66; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_69; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req_63: + s_n_llhttp__internal__n_after_start_req_63: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_63; + } + switch (*p) { + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_64; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_after_start_req: + s_n_llhttp__internal__n_after_start_req: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_1; + } + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_4; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_5; + } + case 'D': { + p++; + goto s_n_llhttp__internal__n_after_start_req_10; + } + case 'F': { + p++; + goto s_n_llhttp__internal__n_after_start_req_14; + } + case 'G': { + p++; + goto s_n_llhttp__internal__n_after_start_req_15; + } + case 'H': { + p++; + goto s_n_llhttp__internal__n_after_start_req_18; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_19; + } + case 'M': { + p++; + goto s_n_llhttp__internal__n_after_start_req_22; + } + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_31; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_32; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_33; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_46; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_52; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_60; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_63; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_method_1: + s_n_llhttp__internal__n_span_start_llhttp__on_method_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_method; + goto s_n_llhttp__internal__n_after_start_req; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_line_almost_done: + s_n_llhttp__internal__n_res_line_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_res_line_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_18; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status: + s_n_llhttp__internal__n_res_status: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_status; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_status_1; + } + default: { + p++; + goto s_n_llhttp__internal__n_res_status; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_status: + s_n_llhttp__internal__n_span_start_llhttp__on_status: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_status; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_status; + goto s_n_llhttp__internal__n_res_status; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_start: + s_n_llhttp__internal__n_res_status_start: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_start; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_res_line_almost_done; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_status; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_code_otherwise: + s_n_llhttp__internal__n_res_status_code_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_otherwise; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_res_status_start; + } + case 13: { + goto s_n_llhttp__internal__n_res_status_start; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_res_status_start; + } + default: { + goto s_n_llhttp__internal__n_error_77; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_code_digit_3: + s_n_llhttp__internal__n_res_status_code_digit_3: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_3; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + default: { + goto s_n_llhttp__internal__n_error_79; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_code_digit_2: + s_n_llhttp__internal__n_res_status_code_digit_2: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_2; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + default: { + goto s_n_llhttp__internal__n_error_81; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_status_code_digit_1: + s_n_llhttp__internal__n_res_status_code_digit_1: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_1; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + default: { + goto s_n_llhttp__internal__n_error_83; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_after_version: + s_n_llhttp__internal__n_res_after_version: { + if (p == endp) { + return s_n_llhttp__internal__n_res_after_version; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_update_status_code; + } + default: { + goto s_n_llhttp__internal__n_error_84; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: { + switch (llhttp__on_version_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_res_after_version; + case 21: + goto s_n_llhttp__internal__n_pause_21; + default: + goto s_n_llhttp__internal__n_error_74; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_73: + s_n_llhttp__internal__n_error_73: { + state->error = 0x9; + state->reason = "Invalid HTTP version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_85: + s_n_llhttp__internal__n_error_85: { + state->error = 0x9; + state->reason = "Invalid minor version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_http_minor: + s_n_llhttp__internal__n_res_http_minor: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_minor; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_7; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_86: + s_n_llhttp__internal__n_error_86: { + state->error = 0x9; + state->reason = "Expected dot"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_http_dot: + s_n_llhttp__internal__n_res_http_dot: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_dot; + } + switch (*p) { + case '.': { + p++; + goto s_n_llhttp__internal__n_res_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_8; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_error_87: + s_n_llhttp__internal__n_error_87: { + state->error = 0x9; + state->reason = "Invalid major version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_res_http_major: + s_n_llhttp__internal__n_res_http_major: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_major; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_9; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_version_1: + s_n_llhttp__internal__n_span_start_llhttp__on_version_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_version; + goto s_n_llhttp__internal__n_res_http_major; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_start_res: + s_n_llhttp__internal__n_start_res: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_start_res; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob58, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_start_res; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_91; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: { + switch (llhttp__on_method_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_first_space_before_url; + case 21: + goto s_n_llhttp__internal__n_pause_19; + default: + goto s_n_llhttp__internal__n_error_1; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_or_res_method_2: + s_n_llhttp__internal__n_req_or_res_method_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_2; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob59, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_method; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_or_res_method_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_88; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_update_type_1: + s_n_llhttp__internal__n_invoke_update_type_1: { + switch (llhttp__internal__c_update_type_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_or_res_method_3: + s_n_llhttp__internal__n_req_or_res_method_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob60, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_or_res_method_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_88; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_or_res_method_1: + s_n_llhttp__internal__n_req_or_res_method_1: { + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_1; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_2; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_3; + } + default: { + goto s_n_llhttp__internal__n_error_88; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_req_or_res_method: + s_n_llhttp__internal__n_req_or_res_method: { + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method; + } + switch (*p) { + case 'H': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_88; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_span_start_llhttp__on_method: + s_n_llhttp__internal__n_span_start_llhttp__on_method: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_method; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_method; + goto s_n_llhttp__internal__n_req_or_res_method; + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_start_req_or_res: + s_n_llhttp__internal__n_start_req_or_res: { + if (p == endp) { + return s_n_llhttp__internal__n_start_req_or_res; + } + switch (*p) { + case 'H': { + goto s_n_llhttp__internal__n_span_start_llhttp__on_method; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_type_2; + } + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_load_type: + s_n_llhttp__internal__n_invoke_load_type: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + case 2: + goto s_n_llhttp__internal__n_start_res; + default: + goto s_n_llhttp__internal__n_start_req_or_res; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_invoke_update_finish: + s_n_llhttp__internal__n_invoke_update_finish: { + switch (llhttp__internal__c_update_finish(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_begin; + } + /* UNREACHABLE */; + abort(); + } + case s_n_llhttp__internal__n_start: + s_n_llhttp__internal__n_start: { + if (p == endp) { + return s_n_llhttp__internal__n_start; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_start; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_start; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_initial_message_completed; + } + } + /* UNREACHABLE */; + abort(); + } + default: + /* UNREACHABLE */ + abort(); + } + s_n_llhttp__internal__n_error_2: { + state->error = 0x7; + state->reason = "Invalid characters in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_finish_2: { + switch (llhttp__internal__c_update_finish_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_start; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_initial_message_completed: { + switch (llhttp__internal__c_update_initial_message_completed(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_finish_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_content_length: { + switch (llhttp__internal__c_update_content_length(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_initial_message_completed; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_7: { + state->error = 0x5; + state->reason = "Data after `Connection: close`"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_3: { + switch (llhttp__internal__c_test_lenient_flags_3(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_closed; + default: + goto s_n_llhttp__internal__n_error_7; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_2: { + switch (llhttp__internal__c_test_lenient_flags_2(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_update_initial_message_completed; + default: + goto s_n_llhttp__internal__n_closed; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_finish_1: { + switch (llhttp__internal__c_update_finish_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_2: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_pause_1; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_8: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_pause_1; + case 21: + goto s_n_llhttp__internal__n_pause_2; + default: + goto s_n_llhttp__internal__n_error_8; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_26: { + state->error = 0xc; + state->reason = "Chunk size overflow"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_3: { + state->error = 0x15; + state->reason = "on_chunk_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_content_length_1; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_10: { + state->error = 0x14; + state->reason = "`on_chunk_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete: { + switch (llhttp__on_chunk_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_update_content_length_1; + case 21: + goto s_n_llhttp__internal__n_pause_3; + default: + goto s_n_llhttp__internal__n_error_10; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_11: { + state->error = 0x2; + state->reason = "Expected LF after chunk data"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_4: { + switch (llhttp__internal__c_test_lenient_flags_4(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; + default: + goto s_n_llhttp__internal__n_error_11; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_body: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_body(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_data_almost_done; + return s_error; + } + goto s_n_llhttp__internal__n_chunk_data_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags: { + switch (llhttp__internal__c_or_flags(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_start; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_4: { + state->error = 0x15; + state->reason = "on_chunk_header pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_content_length; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_9: { + state->error = 0x13; + state->reason = "`on_chunk_header` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header: { + switch (llhttp__on_chunk_header(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_is_equal_content_length; + case 21: + goto s_n_llhttp__internal__n_pause_4; + default: + goto s_n_llhttp__internal__n_error_9; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_12: { + state->error = 0x2; + state->reason = "Expected LF after chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_5: { + switch (llhttp__internal__c_test_lenient_flags_5(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; + default: + goto s_n_llhttp__internal__n_error_12; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_13: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_14: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_5: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_15: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_6: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extensions; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_16: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_7: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_18: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_20: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions quote value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_8: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_19: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_21; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_21; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_9: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_otherwise; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_22: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_23; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_23; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_10: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_value; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_17: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extension_value; + case 21: + goto s_n_llhttp__internal__n_pause_10; + default: + goto s_n_llhttp__internal__n_error_17; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_24; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_24; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_25: { + state->error = 0xc; + state->reason = "Invalid character in chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_content_length: { + switch (llhttp__internal__c_mul_add_content_length(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_26; + default: + goto s_n_llhttp__internal__n_chunk_size; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_27: { + state->error = 0xc; + state->reason = "Invalid character in chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_11: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_upgrade; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_28: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_body_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_body(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_finish_3: { + switch (llhttp__internal__c_update_finish_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_29: { + state->error = 0xf; + state->reason = "Request has invalid `Transfer-Encoding`"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_6: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + case 21: + goto s_n_llhttp__internal__n_pause; + default: + goto s_n_llhttp__internal__n_error_6; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_1: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + default: + goto s_n_llhttp__internal__n_error_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_13: { + state->error = 0x15; + state->reason = "on_chunk_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_32: { + state->error = 0x14; + state->reason = "`on_chunk_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1: { + switch (llhttp__on_chunk_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + case 21: + goto s_n_llhttp__internal__n_pause_13; + default: + goto s_n_llhttp__internal__n_error_32; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_31: { + state->error = 0x4; + state->reason = "Content-Length can't be present with Transfer-Encoding"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_1: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_2: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_upgrade: { + switch (llhttp__internal__c_update_upgrade(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_or_flags_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_12: { + state->error = 0x15; + state->reason = "Paused by on_headers_complete"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_30: { + state->error = 0x11; + state->reason = "User callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete: { + switch (llhttp__on_headers_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + case 1: + goto s_n_llhttp__internal__n_invoke_or_flags_1; + case 2: + goto s_n_llhttp__internal__n_invoke_update_upgrade; + case 21: + goto s_n_llhttp__internal__n_pause_12; + default: + goto s_n_llhttp__internal__n_error_30; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete: { + switch (llhttp__before_headers_complete(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_6: { + switch (llhttp__internal__c_test_lenient_flags_6(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_error_31; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags_1: { + switch (llhttp__internal__c_test_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_6; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags: { + switch (llhttp__internal__c_test_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1; + default: + goto s_n_llhttp__internal__n_invoke_test_flags_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_33: { + state->error = 0x2; + state->reason = "Expected LF after headers"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_7: { + switch (llhttp__internal__c_test_lenient_flags_5(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_flags; + default: + goto s_n_llhttp__internal__n_error_33; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_5; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_5; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_8: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_field_colon_discard_ws; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_36: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_10: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_ws; + default: + goto s_n_llhttp__internal__n_error_36; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_38: { + state->error = 0xb; + state->reason = "Empty Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_14: { + state->error = 0x15; + state->reason = "on_header_value_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_field_start; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_37: { + state->error = 0x1d; + state->reason = "`on_header_value_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_3: { + switch (llhttp__internal__c_or_flags_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_4: { + switch (llhttp__internal__c_or_flags_4(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_5: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_6: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_1: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_3; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_4; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_5; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_6; + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 2: + goto s_n_llhttp__internal__n_error_38; + default: + goto s_n_llhttp__internal__n_invoke_load_header_state_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_35: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_9: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_lws; + default: + goto s_n_llhttp__internal__n_error_35; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_39: { + state->error = 0x2; + state->reason = "Expected LF after CR"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_11: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_lws; + default: + goto s_n_llhttp__internal__n_error_39; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_1: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_3: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 8: + goto s_n_llhttp__internal__n_invoke_update_header_state_1; + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_2: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_7: { + switch (llhttp__internal__c_or_flags_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_8: { + switch (llhttp__internal__c_or_flags_4(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_9: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_10: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_4: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_7; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_8; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_9; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_10; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_40: { + state->error = 0x3; + state->reason = "Missing expected LF after header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_header_value_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + goto s_n_llhttp__internal__n_header_value_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_header_value_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_41; + return s_error; + } + goto s_n_llhttp__internal__n_error_41; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_12: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_lenient; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_4: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_11: { + switch (llhttp__internal__c_or_flags_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_12: { + switch (llhttp__internal__c_or_flags_4(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_13: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_14: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_5: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_11; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_12; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_13; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_14; + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_5: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_token; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_3: { + switch (llhttp__internal__c_update_header_state_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_6: { + switch (llhttp__internal__c_update_header_state_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_7: { + switch (llhttp__internal__c_update_header_state_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_43; + return s_error; + } + goto s_n_llhttp__internal__n_error_43; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_content_length_1: { + switch (llhttp__internal__c_mul_add_content_length_1(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5; + default: + goto s_n_llhttp__internal__n_header_value_content_length; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_15: { + switch (llhttp__internal__c_or_flags_15(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_otherwise; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_44; + return s_error; + } + goto s_n_llhttp__internal__n_error_44; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_42: { + state->error = 0x4; + state->reason = "Duplicate Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags_2: { + switch (llhttp__internal__c_test_flags_2(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_header_value_content_length; + default: + goto s_n_llhttp__internal__n_error_42; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_46; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_46; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_8: { + switch (llhttp__internal__c_update_header_state_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_otherwise; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_45; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_45; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_13: { + switch (llhttp__internal__c_test_lenient_flags_13(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7; + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_type_1: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_13; + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_9: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_and_flags: { + switch (llhttp__internal__c_and_flags(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_17: { + switch (llhttp__internal__c_or_flags_16(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_and_flags; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_14: { + switch (llhttp__internal__c_test_lenient_flags_13(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_17; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_type_2: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_14; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_17; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_16: { + switch (llhttp__internal__c_or_flags_16(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_and_flags; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_flags_3: { + switch (llhttp__internal__c_test_flags_3(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_load_type_2; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_16; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_or_flags_18: { + switch (llhttp__internal__c_or_flags_18(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_9; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_header_state_2: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_connection; + case 2: + goto s_n_llhttp__internal__n_invoke_test_flags_2; + case 3: + goto s_n_llhttp__internal__n_invoke_test_flags_3; + case 4: + goto s_n_llhttp__internal__n_invoke_or_flags_18; + default: + goto s_n_llhttp__internal__n_header_value; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_15: { + state->error = 0x15; + state->reason = "on_header_field_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_discard_ws; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_34: { + state->error = 0x1c; + state->reason = "`on_header_field_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_47: { + state->error = 0xa; + state->reason = "Invalid header token"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_10: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_general; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_header_state: { + switch (llhttp__internal__c_store_header_state(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_header_field_colon; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_header_state_11: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_general; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_4: { + state->error = 0x1e; + state->reason = "Unexpected space after start line"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_field_start; + default: + goto s_n_llhttp__internal__n_error_4; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_16: { + state->error = 0x15; + state->reason = "on_url_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_3: { + state->error = 0x1a; + state->reason = "`on_url_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_url_complete: { + switch (llhttp__on_url_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_headers_start; + case 21: + goto s_n_llhttp__internal__n_pause_16; + default: + goto s_n_llhttp__internal__n_error_3; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_http_minor: { + switch (llhttp__internal__c_update_http_minor(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_http_major: { + switch (llhttp__internal__c_update_http_major(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_http_minor; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_48: { + state->error = 0x7; + state->reason = "Expected CRLF"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_55: { + state->error = 0x17; + state->reason = "Pause on PRI/Upgrade"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_56: { + state->error = 0x9; + state->reason = "Expected HTTP/2 Connection Preface"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_53: { + state->error = 0x2; + state->reason = "Expected CRLF after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_16: { + switch (llhttp__internal__c_test_lenient_flags_5(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_headers_start; + default: + goto s_n_llhttp__internal__n_error_53; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_54: { + state->error = 0x9; + state->reason = "Expected CRLF after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_17: { + state->error = 0x15; + state->reason = "on_version_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_1; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_52: { + state->error = 0x21; + state->reason = "`on_version_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_51; + return s_error; + } + goto s_n_llhttp__internal__n_error_51; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 9: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_1: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_2: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_major: { + switch (llhttp__internal__c_load_http_major(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_http_minor; + case 1: + goto s_n_llhttp__internal__n_invoke_load_http_minor_1; + case 2: + goto s_n_llhttp__internal__n_invoke_load_http_minor_2; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_15: { + switch (llhttp__internal__c_test_lenient_flags_15(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_invoke_load_http_major; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_http_minor: { + switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_15; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_57; + return s_error; + } + goto s_n_llhttp__internal__n_error_57; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_58; + return s_error; + } + goto s_n_llhttp__internal__n_error_58; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_http_major: { + switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_req_http_dot; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_59; + return s_error; + } + goto s_n_llhttp__internal__n_error_59; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_50: { + state->error = 0x8; + state->reason = "Invalid method for HTTP/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_method: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 1: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 2: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 3: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 4: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 5: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 6: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 7: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 8: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 9: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 10: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 11: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 12: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 13: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 14: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 15: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 16: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 17: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 18: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 19: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 20: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 21: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 22: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 23: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 24: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 25: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 26: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 27: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 28: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 29: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 30: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 31: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 32: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 33: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 34: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + default: + goto s_n_llhttp__internal__n_error_50; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_62: { + state->error = 0x8; + state->reason = "Expected HTTP/"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_60: { + state->error = 0x8; + state->reason = "Expected SOURCE method for ICE/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_method_2: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 33: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + default: + goto s_n_llhttp__internal__n_error_60; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_61: { + state->error = 0x8; + state->reason = "Invalid method for RTSP/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_method_3: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 3: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 6: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 35: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 36: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 37: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 38: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 39: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 40: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 41: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 42: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 43: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 44: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + case 45: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + default: + goto s_n_llhttp__internal__n_error_61; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_18: { + state->error = 0x15; + state->reason = "on_url_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_http_start; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_49: { + state->error = 0x1a; + state->reason = "`on_url_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1: { + switch (llhttp__on_url_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_http_start; + case 21: + goto s_n_llhttp__internal__n_pause_18; + default: + goto s_n_llhttp__internal__n_error_49; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_63: { + state->error = 0x7; + state->reason = "Invalid char in url fragment start"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_9: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_10: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_11: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_64: { + state->error = 0x7; + state->reason = "Invalid char in url query"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_65: { + state->error = 0x7; + state->reason = "Invalid char in url path"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_12: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_13: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_14: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_66: { + state->error = 0x7; + state->reason = "Double @ in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_67: { + state->error = 0x7; + state->reason = "Unexpected char in url server"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_68: { + state->error = 0x7; + state->reason = "Unexpected char in url server"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_69: { + state->error = 0x7; + state->reason = "Unexpected char in url schema"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_70: { + state->error = 0x7; + state->reason = "Unexpected char in url schema"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_71: { + state->error = 0x7; + state->reason = "Unexpected start char in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_is_equal_method: { + switch (llhttp__internal__c_is_equal_method(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_url_entry_normal; + default: + goto s_n_llhttp__internal__n_url_entry_connect; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_72: { + state->error = 0x6; + state->reason = "Expected space after method"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_22: { + state->error = 0x15; + state->reason = "on_method_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_89: { + state->error = 0x20; + state->reason = "`on_method_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_method_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_method_1: { + switch (llhttp__internal__c_store_method(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_method_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_90: { + state->error = 0x6; + state->reason = "Invalid method encountered"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_82: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_80: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_78: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_20: { + state->error = 0x15; + state->reason = "on_status_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_75: { + state->error = 0x1b; + state->reason = "`on_status_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_status_complete: { + switch (llhttp__on_status_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_headers_start; + case 21: + goto s_n_llhttp__internal__n_pause_20; + default: + goto s_n_llhttp__internal__n_error_75; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_76: { + state->error = 0x2; + state->reason = "Expected LF after CR"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_18: { + switch (llhttp__internal__c_test_lenient_flags_5(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + default: + goto s_n_llhttp__internal__n_error_76; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_status: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_status(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_line_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_res_line_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_status_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_status(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_line_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_res_line_almost_done; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_77: { + state->error = 0xd; + state->reason = "Invalid response status"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_status_code_2: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_78; + default: + goto s_n_llhttp__internal__n_res_status_code_otherwise; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_79: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_status_code_1: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_80; + default: + goto s_n_llhttp__internal__n_res_status_code_digit_3; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_81: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_mul_add_status_code: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_82; + default: + goto s_n_llhttp__internal__n_res_status_code_digit_2; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_83: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_status_code: { + switch (llhttp__internal__c_update_status_code(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_res_status_code_digit_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_84: { + state->error = 0x9; + state->reason = "Expected space after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_21: { + state->error = 0x15; + state->reason = "on_version_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_after_version; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_74: { + state->error = 0x21; + state->reason = "`on_version_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_73; + return s_error; + } + goto s_n_llhttp__internal__n_error_73; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_3: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 9: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_4: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_minor_5: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_http_major_1: { + switch (llhttp__internal__c_load_http_major(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_http_minor_3; + case 1: + goto s_n_llhttp__internal__n_invoke_load_http_minor_4; + case 2: + goto s_n_llhttp__internal__n_invoke_load_http_minor_5; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_17: { + switch (llhttp__internal__c_test_lenient_flags_15(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_invoke_load_http_major_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_http_minor_1: { + switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_17; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_85; + return s_error; + } + goto s_n_llhttp__internal__n_error_85; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_86; + return s_error; + } + goto s_n_llhttp__internal__n_error_86; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_http_major_1: { + switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_res_http_dot; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_9: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_87; + return s_error; + } + goto s_n_llhttp__internal__n_error_87; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_91: { + state->error = 0x8; + state->reason = "Expected HTTP/"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_19: { + state->error = 0x15; + state->reason = "on_method_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_1: { + state->error = 0x20; + state->reason = "`on_method_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_method: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_type: { + switch (llhttp__internal__c_update_type(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_method; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_store_method: { + switch (llhttp__internal__c_store_method(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_update_type; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_88: { + state->error = 0x8; + state->reason = "Invalid word encountered"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_span_end_llhttp__on_method_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_type_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_update_type_1; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_update_type_2: { + switch (llhttp__internal__c_update_type(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_23: { + state->error = 0x15; + state->reason = "on_message_begin pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_type; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error: { + state->error = 0x10; + state->reason = "`on_message_begin` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_begin: { + switch (llhttp__on_message_begin(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_type; + case 21: + goto s_n_llhttp__internal__n_pause_23; + default: + goto s_n_llhttp__internal__n_error; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_pause_24: { + state->error = 0x15; + state->reason = "on_reset pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_finish; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_error_92: { + state->error = 0x1f; + state->reason = "`on_reset` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_llhttp__on_reset: { + switch (llhttp__on_reset(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_update_finish; + case 21: + goto s_n_llhttp__internal__n_pause_24; + default: + goto s_n_llhttp__internal__n_error_92; + } + /* UNREACHABLE */; + abort(); + } + s_n_llhttp__internal__n_invoke_load_initial_message_completed: { + switch (llhttp__internal__c_load_initial_message_completed(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_reset; + default: + goto s_n_llhttp__internal__n_invoke_update_finish; + } + /* UNREACHABLE */; + abort(); + } +} + +int llhttp__internal_execute(llhttp__internal_t* state, const char* p, const char* endp) { + llparse_state_t next; + + /* check lingering errors */ + if (state->error != 0) { + return state->error; + } + + /* restart spans */ + if (state->_span_pos0 != NULL) { + state->_span_pos0 = (void*) p; + } + + next = llhttp__internal__run(state, (const unsigned char*) p, (const unsigned char*) endp); + if (next == s_error) { + return state->error; + } + state->_current = (void*) (intptr_t) next; + + /* execute spans */ + if (state->_span_pos0 != NULL) { + int error; + + error = ((llhttp__internal__span_cb) state->_span_cb0)(state, state->_span_pos0, (const char*) endp); + if (error != 0) { + state->error = error; + state->error_pos = endp; + return error; + } + } + + return 0; +} \ No newline at end of file diff --git a/lib/nghttp2/third-party/url-parser/.gitignore b/lib/nghttp2/third-party/url-parser/.gitignore new file mode 100644 index 00000000000..c83c0130e87 --- /dev/null +++ b/lib/nghttp2/third-party/url-parser/.gitignore @@ -0,0 +1 @@ +/.dirstamp diff --git a/lib/nghttp2/third-party/url-parser/AUTHORS b/lib/nghttp2/third-party/url-parser/AUTHORS new file mode 100644 index 00000000000..5323b685cae --- /dev/null +++ b/lib/nghttp2/third-party/url-parser/AUTHORS @@ -0,0 +1,68 @@ +# Authors ordered by first contribution. +Ryan Dahl +Jeremy Hinegardner +Sergey Shepelev +Joe Damato +tomika +Phoenix Sol +Cliff Frey +Ewen Cheslack-Postava +Santiago Gala +Tim Becker +Jeff Terrace +Ben Noordhuis +Nathan Rajlich +Mark Nottingham +Aman Gupta +Tim Becker +Sean Cunningham +Peter Griess +Salman Haq +Cliff Frey +Jon Kolb +Fouad Mardini +Paul Querna +Felix Geisendörfer +koichik +Andre Caron +Ivo Raisr +James McLaughlin +David Gwynne +Thomas LE ROUX +Randy Rizun +Andre Louis Caron +Simon Zimmermann +Erik Dubbelboer +Martell Malone +Bertrand Paquet +BogDan Vatra +Peter Faiman +Corey Richardson +Tóth Tamás +Cam Swords +Chris Dickinson +Uli Köhler +Charlie Somerville +Patrik Stutz +Fedor Indutny +runner +Alexis Campailla +David Wragg +Vinnie Falco +Alex Butum +Rex Feng +Alex Kocharin +Mark Koopman +Helge Heß +Alexis La Goutte +George Miroshnykov +Maciej Małecki +Marc O'Morain +Jeff Pinner +Timothy J Fontaine +Akagi201 +Romain Giraud +Jay Satiro +Arne Steen +Kjell Schubert +Olivier Mengué diff --git a/lib/nghttp2/third-party/url-parser/LICENSE-MIT b/lib/nghttp2/third-party/url-parser/LICENSE-MIT new file mode 100644 index 00000000000..1ec0ab4e174 --- /dev/null +++ b/lib/nghttp2/third-party/url-parser/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright Joyent, Inc. and other Node 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. diff --git a/lib/nghttp2/third-party/url-parser/url_parser.c b/lib/nghttp2/third-party/url-parser/url_parser.c new file mode 100644 index 00000000000..4912ee206a0 --- /dev/null +++ b/lib/nghttp2/third-party/url-parser/url_parser.c @@ -0,0 +1,652 @@ +/* Copyright Joyent, Inc. and other Node 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 "url_parser.h" +#include +#include +#include +#include +#include + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_http_major + , s_res_http_dot + , s_res_http_minor + , s_res_http_end + , s_res_first_status_code + , s_res_status_code + , s_res_status_start + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_http_I + , s_req_http_IC + , s_req_http_major + , s_req_http_dot + , s_req_http_minor + , s_req_http_end + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#define STRICT_TOKEN(c) ((c == ' ') ? 0 : tokens[(unsigned char)c]) + +#if HTTP_PARSER_STRICT +#define TOKEN(c) STRICT_TOKEN(c) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) tokens[(unsigned char)c] +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* fall through */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* fall through */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* fall through */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* fall through */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + assert(u->field_set & (1 << UF_HOST)); + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = (uint16_t)(p - buf); + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = (uint16_t)(p - buf); + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = (uint16_t)(p - buf); + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = (uint16_t)(p - buf); + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +void +http_parser_url_init(struct http_parser_url *u) { + memset(u, 0, sizeof(*u)); +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + if (buflen == 0) { + return 1; + } + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* fall through */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = (uint16_t)(p - buf); + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + uint16_t off; + uint16_t len; + const char* p; + const char* end; + unsigned long v; + + off = u->field_data[UF_PORT].off; + len = u->field_data[UF_PORT].len; + end = buf + off + len; + + /* NOTE: The characters are already validated and are in the [0-9] range */ + assert(off + len <= buflen && "Port number overflow"); + v = 0; + for (p = buf + off; p < end; p++) { + v *= 10; + v += *p - '0'; + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + } + + u->port = (uint16_t) v; + } + + return 0; +} diff --git a/lib/nghttp2/third-party/url-parser/url_parser.h b/lib/nghttp2/third-party/url-parser/url_parser.h new file mode 100644 index 00000000000..78b3096c531 --- /dev/null +++ b/lib/nghttp2/third-party/url-parser/url_parser.h @@ -0,0 +1,94 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * 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 url_parser_h +#define url_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +/* Also update SONAME in the Makefile whenever you change these. */ +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 9 +#define HTTP_PARSER_VERSION_PATCH 1 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) +#include +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + +/* Initialize all http_parser_url members to 0 */ +void http_parser_url_init(struct http_parser_url *u); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); +#ifdef __cplusplus +} +#endif +#endif diff --git a/plugins/in_elasticsearch/in_elasticsearch.c b/plugins/in_elasticsearch/in_elasticsearch.c index 24e997511d6..d3963d7ff34 100644 --- a/plugins/in_elasticsearch/in_elasticsearch.c +++ b/plugins/in_elasticsearch/in_elasticsearch.c @@ -25,6 +25,7 @@ #include "in_elasticsearch.h" #include "in_elasticsearch_config.h" +#include "in_elasticsearch_bulk_prot.h" #include "in_elasticsearch_bulk_conn.h" /* @@ -135,39 +136,81 @@ static int in_elasticsearch_bulk_init(struct flb_input_instance *ins, bytes_to_nodename(rand, ctx->node_name, 12); - ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, - ins->flags, - ctx->listen, - port, - ins->tls, - config, - &ins->net_setup); + if (ctx->enable_http2) { + ret = flb_http_server_init(&ctx->http_server, + HTTP_PROTOCOL_AUTODETECT, + FLB_HTTP_SERVER_FLAG_AUTO_INFLATE, + NULL, + ins->host.listen, + ins->host.port, + ins->tls, + ins->flags, + &ins->net_setup, + flb_input_event_loop_get(ins), + ins->config, + (void *) ctx); - if (ctx->downstream == NULL) { - flb_plg_error(ctx->ins, - "could not initialize downstream on %s:%s. Aborting", - ctx->listen, ctx->tcp_port); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not initialize http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - in_elasticsearch_config_destroy(ctx); + in_elasticsearch_config_destroy(ctx); - return -1; - } + return -1; + } - flb_input_downstream_set(ctx->downstream, ctx->ins); + ret = flb_http_server_start(&ctx->http_server); - /* Collect upon data available on the standard input */ - ret = flb_input_set_collector_socket(ins, - in_elasticsearch_bulk_collect, - ctx->downstream->server_fd, - config); - if (ret == -1) { - flb_plg_error(ctx->ins, "Could not set collector for IN_ELASTICSEARCH input plugin"); - in_elasticsearch_config_destroy(ctx); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not start http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - return -1; - } + in_elasticsearch_config_destroy(ctx); - ctx->collector_id = ret; + return -1; + } + + ctx->http_server.request_callback = in_elasticsearch_bulk_prot_handle_ng; + + flb_input_downstream_set(ctx->http_server.downstream, ctx->ins); + } + else { + ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + ins->flags, + ctx->listen, + port, + ins->tls, + config, + &ins->net_setup); + + if (ctx->downstream == NULL) { + flb_plg_error(ctx->ins, + "could not initialize downstream on %s:%s. Aborting", + ctx->listen, ctx->tcp_port); + + in_elasticsearch_config_destroy(ctx); + + return -1; + } + + flb_input_downstream_set(ctx->downstream, ctx->ins); + + /* Collect upon data available on the standard input */ + ret = flb_input_set_collector_socket(ins, + in_elasticsearch_bulk_collect, + ctx->downstream->server_fd, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "Could not set collector for IN_ELASTICSEARCH input plugin"); + in_elasticsearch_config_destroy(ctx); + + return -1; + } + + ctx->collector_id = ret; + } return 0; } @@ -189,6 +232,12 @@ static int in_elasticsearch_bulk_exit(void *data, struct flb_config *config) /* Configuration properties map */ static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_BOOL, "http2", "true", + 0, FLB_TRUE, offsetof(struct flb_in_elasticsearch, enable_http2), + NULL + }, + { FLB_CONFIG_MAP_SIZE, "buffer_max_size", HTTP_BUFFER_MAX_SIZE, 0, FLB_TRUE, offsetof(struct flb_in_elasticsearch, buffer_max_size), diff --git a/plugins/in_elasticsearch/in_elasticsearch.h b/plugins/in_elasticsearch/in_elasticsearch.h index 665995c824c..6af189559a5 100644 --- a/plugins/in_elasticsearch/in_elasticsearch.h +++ b/plugins/in_elasticsearch/in_elasticsearch.h @@ -27,6 +27,7 @@ #include #include +#include #define HTTP_BUFFER_MAX_SIZE "4M" #define HTTP_BUFFER_CHUNK_SIZE "512K" @@ -41,6 +42,15 @@ struct flb_in_elasticsearch { char cluster_name[16]; char node_name[12]; + struct flb_log_event_encoder log_encoder; + + struct flb_input_instance *ins; + + /* New gen HTTP server */ + int enable_http2; + struct flb_http_server http_server; + + /* Legacy HTTP server */ int collector_id; size_t buffer_max_size; /* Maximum buffer size */ @@ -49,10 +59,7 @@ struct flb_in_elasticsearch { struct flb_downstream *downstream; /* Client manager */ struct mk_list connections; /* linked list of connections */ - struct flb_log_event_encoder log_encoder; - struct mk_server *server; - struct flb_input_instance *ins; }; diff --git a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c index e304947df7a..ca9f7acfefc 100644 --- a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c +++ b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.c @@ -920,3 +920,257 @@ int in_elasticsearch_bulk_prot_handle_error(struct flb_in_elasticsearch *ctx, send_response(conn, 400, "error: invalid request\n"); return -1; } + + + + +/* New gen HTTP server */ +static int send_response_ng(struct flb_http_response *response, + int http_status, + char *content_type, + char *message) +{ + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + if (content_type != NULL) { + flb_http_response_set_header(response, + "content-type", 0, + content_type, 0); + } + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int send_json_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + return send_response_ng(response, http_status, "application/json", message); +} + +static int send_version_message_response_ng(struct flb_http_response *response, + struct flb_in_elasticsearch *ctx, + int http_status) +{ + flb_sds_t message; + + if (http_status != 200) { + return 0; + } + + message = flb_sds_create_size(384); + + if (message == NULL) { + return -1; + } + + flb_sds_printf(&message, + ES_VERSION_RESPONSE_TEMPLATE, + ctx->es_version); + + send_json_response_ng(response, http_status, message); + + cfl_sds_destroy(message); + + return 0; +} + +static int send_dummy_sniffer_response_ng(struct flb_http_response *response, + struct flb_in_elasticsearch *ctx, + int http_status) +{ + flb_sds_t hostname; + flb_sds_t resp; + + if (http_status != 200) { + return 0; + } + + if (ctx->hostname != NULL) { + hostname = ctx->hostname; + } + else { + hostname = "localhost"; + } + + resp = flb_sds_create_size(384); + if (!resp) { + return -1; + } + + flb_sds_printf(&resp, + ES_NODES_TEMPLATE, + ctx->cluster_name, ctx->node_name, + hostname, ctx->tcp_port, ctx->buffer_max_size); + + send_json_response_ng(response, http_status, resp); + + flb_sds_destroy(resp); + + return 0; +} + +static int process_payload_ng(struct flb_http_request *request, + struct flb_http_response *response, + struct flb_in_elasticsearch *context, + flb_sds_t tag, + flb_sds_t bulk_statuses) +{ + if (request->content_type == NULL) { + send_response_ng(response, 400, NULL, "error: header 'Content-Type' is not set\n"); + + return -1; + } + + if (strncasecmp(request->content_type, "application/x-ndjson", 20) != 0 && + strncasecmp(request->content_type, "application/json", 16) != 0) { + send_response_ng(response, 400, NULL, "error: invalid 'Content-Type'\n"); + + return -1; + } + + if (request->body == NULL || cfl_sds_len(request->body) == 0) { + send_response_ng(response, 400, NULL, "error: no payload found\n"); + return -1; + } + + parse_payload_ndjson(context, tag, request->body, cfl_sds_len(request->body), bulk_statuses); + + return 0; +} + +int in_elasticsearch_bulk_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response) +{ + flb_sds_t bulk_statuses; + flb_sds_t bulk_response; + const char *error_str; + struct flb_in_elasticsearch *context; + flb_sds_t tag; + size_t len; + + bulk_statuses = NULL; + bulk_response = NULL; + + context = (struct flb_in_elasticsearch *) response->stream->user_data; + + if (request->path[0] != '/') { + send_response_ng(response, 400, NULL, "error: invalid request\n"); + return -1; + } + + /* HTTP/1.1 needs Host header */ + if (request->protocol_version == HTTP_PROTOCOL_HTTP1 && + request->host == NULL) { + + return -1; + } + + if (request->method == HTTP_METHOD_HEAD) { + send_response_ng(response, 200, NULL, NULL); + + return -1; + } + else if (request->method == HTTP_METHOD_PUT) { + send_json_response_ng(response, 200, "{}"); + + return -1; + } + else if (request->method == HTTP_METHOD_GET) { + if (strncmp(request->path, "/_nodes/http", 12) == 0) { + send_dummy_sniffer_response_ng(response, context, 200); + } + else if (strcmp(request->path, "/") == 0) { + send_version_message_response_ng(response, context, 200); + } + else { + send_json_response_ng(response, 200, "{}"); + } + + return 0; + } + else if (request->method == HTTP_METHOD_POST) { + if (strcmp(request->path, "/_bulk") == 0) { + bulk_statuses = flb_sds_create_size(context->buffer_max_size); + + if (bulk_statuses == NULL) { + return -1; + } + + bulk_response = flb_sds_create_size(context->buffer_max_size); + + if (bulk_response == NULL) { + flb_sds_destroy(bulk_statuses); + return -1; + } + + tag = flb_sds_create(context->ins->tag); + + if (tag == NULL) { + flb_sds_destroy(bulk_statuses); + flb_sds_destroy(bulk_response); + return -1; + } + + process_payload_ng(request, response, context, tag, bulk_statuses); + + flb_sds_destroy(tag); + + len = flb_sds_len(bulk_statuses); + + if (flb_sds_alloc(bulk_response) < len + 27) { + bulk_response = flb_sds_increase(bulk_response, len + 27 - flb_sds_alloc(bulk_response)); + } + + error_str = strstr(bulk_statuses, "\"status\":40"); + + if (error_str){ + flb_sds_cat(bulk_response, "{\"errors\":true,\"items\":[", 24); + } + else { + flb_sds_cat(bulk_response, "{\"errors\":false,\"items\":[", 25); + } + + flb_sds_cat(bulk_response, bulk_statuses, flb_sds_len(bulk_statuses)); + flb_sds_cat(bulk_response, "]}", 2); + + send_json_response_ng(response, 200, bulk_response); + + flb_sds_destroy(bulk_statuses); + flb_sds_destroy(bulk_response); + + } else { + send_response_ng(response, 400, NULL, "error: invalid HTTP endpoint\n"); + + return -1; + } + } + else { + send_response_ng(response, 400, NULL, "error: invalid HTTP method\n"); + + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.h b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.h index 677e2e289ef..28acce9d5fd 100644 --- a/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.h +++ b/plugins/in_elasticsearch/in_elasticsearch_bulk_prot.h @@ -37,4 +37,8 @@ int in_elasticsearch_bulk_prot_handle_error(struct flb_in_elasticsearch *ctx, struct mk_http_session *session, struct mk_http_request *request); + +int in_elasticsearch_bulk_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response); + #endif diff --git a/plugins/in_elasticsearch/in_elasticsearch_config.c b/plugins/in_elasticsearch/in_elasticsearch_config.c index 687c7695ad4..e8c53f12bc7 100644 --- a/plugins/in_elasticsearch/in_elasticsearch_config.c +++ b/plugins/in_elasticsearch/in_elasticsearch_config.c @@ -92,6 +92,10 @@ int in_elasticsearch_config_destroy(struct flb_in_elasticsearch *ctx) flb_downstream_destroy(ctx->downstream); } + if (ctx->enable_http2) { + flb_http_server_destroy(&ctx->http_server); + } + if (ctx->server) { flb_free(ctx->server); } diff --git a/plugins/in_http/http.c b/plugins/in_http/http.c index 9d9ac2ebaa5..62f355ba293 100644 --- a/plugins/in_http/http.c +++ b/plugins/in_http/http.c @@ -24,6 +24,7 @@ #include "http.h" #include "http_conn.h" +#include "http_prot.h" #include "http_config.h" /* @@ -92,25 +93,67 @@ static int in_http_init(struct flb_input_instance *ins, port = (unsigned short int) strtoul(ctx->tcp_port, NULL, 10); - ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, - ins->flags, - ctx->listen, - port, - ins->tls, - config, - &ins->net_setup); + if (ctx->enable_http2) { + ret = flb_http_server_init(&ctx->http_server, + HTTP_PROTOCOL_AUTODETECT, + FLB_HTTP_SERVER_FLAG_AUTO_INFLATE, + NULL, + ins->host.listen, + ins->host.port, + ins->tls, + ins->flags, + &ins->net_setup, + flb_input_event_loop_get(ins), + ins->config, + (void *) ctx); - if (ctx->downstream == NULL) { - flb_plg_error(ctx->ins, - "could not initialize downstream on %s:%s. Aborting", - ctx->listen, ctx->tcp_port); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not initialize http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - http_config_destroy(ctx); + http_config_destroy(ctx); - return -1; - } + return -1; + } + + ret = flb_http_server_start(&ctx->http_server); + + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not start http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - flb_input_downstream_set(ctx->downstream, ctx->ins); + http_config_destroy(ctx); + + return -1; + } + + ctx->http_server.request_callback = http_prot_handle_ng; + + flb_input_downstream_set(ctx->http_server.downstream, ctx->ins); + } + else { + ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + ins->flags, + ctx->listen, + port, + ins->tls, + config, + &ins->net_setup); + + if (ctx->downstream == NULL) { + flb_plg_error(ctx->ins, + "could not initialize downstream on %s:%s. Aborting", + ctx->listen, ctx->tcp_port); + + http_config_destroy(ctx); + + return -1; + } + + flb_input_downstream_set(ctx->downstream, ctx->ins); + } if (ctx->successful_response_code != 200 && ctx->successful_response_code != 201 && @@ -120,19 +163,21 @@ static int in_http_init(struct flb_input_instance *ins, ctx->successful_response_code = 201; } - /* Collect upon data available on the standard input */ - ret = flb_input_set_collector_socket(ins, - in_http_collect, - ctx->downstream->server_fd, - config); - if (ret == -1) { - flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); - http_config_destroy(ctx); + if (!ctx->enable_http2) { + /* Collect upon data available on the standard input */ + ret = flb_input_set_collector_socket(ins, + in_http_collect, + ctx->downstream->server_fd, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); + http_config_destroy(ctx); - return -1; - } + return -1; + } - ctx->collector_id = ret; + ctx->collector_id = ret; + } return 0; } @@ -154,6 +199,12 @@ static int in_http_exit(void *data, struct flb_config *config) /* Configuration properties map */ static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_BOOL, "http2", "true", + 0, FLB_TRUE, offsetof(struct flb_http, enable_http2), + NULL + }, + { FLB_CONFIG_MAP_SIZE, "buffer_max_size", HTTP_BUFFER_MAX_SIZE, 0, FLB_TRUE, offsetof(struct flb_http, buffer_max_size), diff --git a/plugins/in_http/http.h b/plugins/in_http/http.h index fd2d83ff7d5..4298a370c9c 100644 --- a/plugins/in_http/http.h +++ b/plugins/in_http/http.h @@ -27,6 +27,7 @@ #include #include +#include #define HTTP_BUFFER_MAX_SIZE "4M" #define HTTP_BUFFER_CHUNK_SIZE "512K" @@ -37,21 +38,29 @@ struct flb_http { flb_sds_t tcp_port; const char *tag_key; - int collector_id; - /* Success HTTP headers */ struct mk_list *success_headers; - flb_sds_t success_headers_str; - - size_t buffer_max_size; /* Maximum buffer size */ - size_t buffer_chunk_size; /* Chunk allocation size */ struct flb_log_event_encoder log_encoder; + + struct flb_input_instance *ins; + + /* New gen HTTP server */ + int enable_http2; + struct flb_http_server http_server; + + /* Legacy HTTP server */ struct flb_downstream *downstream; /* Client manager */ struct mk_list connections; /* linked list of connections */ + flb_sds_t success_headers_str; + + size_t buffer_max_size; /* Maximum buffer size */ + size_t buffer_chunk_size; /* Chunk allocation size */ + struct mk_server *server; - struct flb_input_instance *ins; + + int collector_id; }; diff --git a/plugins/in_http/http_config.c b/plugins/in_http/http_config.c index 7db71d9bd6c..343e69925c4 100644 --- a/plugins/in_http/http_config.c +++ b/plugins/in_http/http_config.c @@ -145,6 +145,10 @@ int http_config_destroy(struct flb_http *ctx) flb_free(ctx->server); } + if (ctx->enable_http2) { + flb_http_server_destroy(&ctx->http_server); + } + if (ctx->success_headers_str != NULL) { flb_sds_destroy(ctx->success_headers_str); } diff --git a/plugins/in_http/http_prot.c b/plugins/in_http/http_prot.c index f2c465f41a5..3b7507a0415 100644 --- a/plugins/in_http/http_prot.c +++ b/plugins/in_http/http_prot.c @@ -21,13 +21,10 @@ #include #include #include -#include #include #include -#include - #include "http.h" #include "http_conn.h" @@ -523,12 +520,6 @@ static int process_payload(struct flb_http *ctx, struct http_conn *conn, int ret; int type = -1; struct mk_http_header *header; - char *uncompressed_data; - size_t uncompressed_size = 0; - - /* used when checking content-encoding */ - int gzip_compressed = FLB_FALSE; - int gzip_ret; header = &session->parser.headers[MK_HEADER_CONTENT_TYPE]; if (header->key.data == NULL) { @@ -546,31 +537,18 @@ static int process_payload(struct flb_http *ctx, struct http_conn *conn, type = HTTP_CONTENT_URLENCODED; } - gzip_compressed = flb_is_http_session_gzip_compressed(session); - - if (gzip_compressed == FLB_TRUE) { - gzip_ret = flb_gzip_uncompress(request->data.data, request->data.len, (void **)&uncompressed_data, &uncompressed_size); - if (gzip_ret == -1) { - flb_error("[http] gzip decompression failed"); - return -1; - } - } else { - uncompressed_data = request->data.data; - uncompressed_size = request->data.len; - } - if (type == -1) { send_response(conn, 400, "error: invalid 'Content-Type'\n"); return -1; } - if (uncompressed_size <= 0) { + if (request->data.len <= 0) { send_response(conn, 400, "error: no payload found\n"); return -1; } if (type == HTTP_CONTENT_JSON) { - parse_payload_json(ctx, tag, uncompressed_data, uncompressed_size); + parse_payload_json(ctx, tag, request->data.data, request->data.len); } else if (type == HTTP_CONTENT_URLENCODED) { ret = parse_payload_urlencoded(ctx, tag, request->data.data, request->data.len); @@ -579,10 +557,6 @@ static int process_payload(struct flb_http *ctx, struct http_conn *conn, return -1; } } - - if (gzip_compressed) { - flb_free(uncompressed_data); - } return 0; } @@ -672,18 +646,23 @@ int http_prot_handle(struct flb_http *ctx, struct http_conn *conn, } mk_mem_free(uri); + /* Check if we have a Host header: Hostname ; port */ mk_http_point_header(&request->host, &session->parser, MK_HEADER_HOST); + /* Header: Connection */ mk_http_point_header(&request->connection, &session->parser, MK_HEADER_CONNECTION); + /* HTTP/1.1 needs Host header */ if (!request->host.data && request->protocol == MK_HTTP_PROTOCOL_11) { flb_sds_destroy(tag); return -1; } + /* Should we close the session after this request ? */ mk_http_keepalive_check(session, request, ctx->server); + /* Content Length */ header = &session->parser.headers[MK_HEADER_CONTENT_LENGTH]; if (header->type == MK_HEADER_CONTENT_LENGTH) { @@ -693,11 +672,13 @@ int http_prot_handle(struct flb_http *ctx, struct http_conn *conn, else { request->_content_length.data = NULL; } + if (request->method != MK_METHOD_POST) { flb_sds_destroy(tag); send_response(conn, 400, "error: invalid HTTP method\n"); return -1; } + ret = process_payload(ctx, conn, tag, session, request); flb_sds_destroy(tag); @@ -708,6 +689,7 @@ int http_prot_handle(struct flb_http *ctx, struct http_conn *conn, return ret; } + /* * Handle an incoming request which has resulted in an http parser error. */ @@ -718,3 +700,455 @@ int http_prot_handle_error(struct flb_http *ctx, struct http_conn *conn, send_response(conn, 400, "error: invalid request\n"); return -1; } + +/* New gen HTTP server */ + +static int send_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + struct mk_list *header_iterator; + struct flb_slist_entry *header_value; + struct flb_slist_entry *header_name; + struct flb_config_map_val *header_pair; + struct flb_http *context; + + context = (struct flb_http *) response->stream->user_data; + + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + if (http_status == 200 || + http_status == 201 || + http_status == 204) { + + flb_config_map_foreach(header_iterator, + header_pair, + context->success_headers) { + header_name = mk_list_entry_first(header_pair->val.list, + struct flb_slist_entry, + _head); + + header_value = mk_list_entry_last(header_pair->val.list, + struct flb_slist_entry, + _head); + + flb_http_response_set_header(response, + header_name->str, 0, + header_value->str, 0); + } + } + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int process_pack_ng(struct flb_http *ctx, flb_sds_t tag, char *buf, size_t size) +{ + int ret; + size_t off = 0; + msgpack_unpacked result; + struct flb_time tm; + int i = 0; + msgpack_object *obj; + msgpack_object record; + flb_sds_t tag_from_record = NULL; + + flb_time_get(&tm); + + msgpack_unpacked_init(&result); + while (msgpack_unpack_next(&result, buf, size, &off) == MSGPACK_UNPACK_SUCCESS) { + if (result.data.type == MSGPACK_OBJECT_MAP) { + tag_from_record = NULL; + if (ctx->tag_key) { + obj = &result.data; + tag_from_record = tag_key(ctx, obj); + } + + ret = flb_log_event_encoder_begin_record(&ctx->log_encoder); + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_timestamp( + &ctx->log_encoder, + &tm); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_body_from_msgpack_object( + &ctx->log_encoder, + &result.data); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_commit_record(&ctx->log_encoder); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + if (tag_from_record) { + flb_input_log_append(ctx->ins, + tag_from_record, + flb_sds_len(tag_from_record), + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + + flb_sds_destroy(tag_from_record); + } + else if (tag) { + flb_input_log_append(ctx->ins, tag, flb_sds_len(tag), + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + } + else { + /* use default plugin Tag (it internal name, e.g: http.0 */ + flb_input_log_append(ctx->ins, NULL, 0, + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + } + } + else { + flb_plg_error(ctx->ins, "Error encoding record : %d", ret); + } + + flb_log_event_encoder_reset(&ctx->log_encoder); + } + else if (result.data.type == MSGPACK_OBJECT_ARRAY) { + obj = &result.data; + for (i = 0; i < obj->via.array.size; i++) + { + record = obj->via.array.ptr[i]; + + tag_from_record = NULL; + if (ctx->tag_key) { + tag_from_record = tag_key(ctx, &record); + } + + ret = flb_log_event_encoder_begin_record(&ctx->log_encoder); + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_timestamp( + &ctx->log_encoder, + &tm); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_body_from_msgpack_object( + &ctx->log_encoder, + &record); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_commit_record(&ctx->log_encoder); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + if (tag_from_record) { + flb_input_log_append(ctx->ins, + tag_from_record, + flb_sds_len(tag_from_record), + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + + flb_sds_destroy(tag_from_record); + } + else if (tag) { + flb_input_log_append(ctx->ins, tag, flb_sds_len(tag), + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + } + else { + /* use default plugin Tag (it internal name, e.g: http.0 */ + flb_input_log_append(ctx->ins, NULL, 0, + ctx->log_encoder.output_buffer, + ctx->log_encoder.output_length); + } + } + else { + flb_plg_error(ctx->ins, "Error encoding record : %d", ret); + } + + /* TODO : Optimize this + * + * This is wasteful, considering that we are emitting a series + * of records we should start and commit each one and then + * emit them all at once after the loop. + */ + + flb_log_event_encoder_reset(&ctx->log_encoder); + } + + break; + } + else { + flb_plg_error(ctx->ins, "skip record from invalid type: %i", + result.data.type); + + msgpack_unpacked_destroy(&result); + + return -1; + } + } + + msgpack_unpacked_destroy(&result); + + return 0; +} + +static ssize_t parse_payload_json_ng(flb_sds_t tag, + struct flb_http_request *request) +{ + int ret; + int out_size; + char *pack; + struct flb_pack_state pack_state; + struct flb_http *ctx; + char *payload; + size_t size; + + ctx = (struct flb_http *) request->stream->user_data; + payload = (char *) request->body; + size = cfl_sds_len(request->body); + + /* Initialize packer */ + flb_pack_state_init(&pack_state); + + /* Pack JSON as msgpack */ + ret = flb_pack_json_state(payload, size, + &pack, &out_size, &pack_state); + flb_pack_state_reset(&pack_state); + + /* Handle exceptions */ + if (ret == FLB_ERR_JSON_PART) { + flb_plg_warn(ctx->ins, "JSON data is incomplete, skipping"); + return -1; + } + else if (ret == FLB_ERR_JSON_INVAL) { + flb_plg_warn(ctx->ins, "invalid JSON message, skipping"); + return -1; + } + else if (ret == -1) { + return -1; + } + + /* Process the packaged JSON and return the last byte used */ + process_pack_ng(ctx, tag, pack, out_size); + flb_free(pack); + + return 0; +} + +static ssize_t parse_payload_urlencoded_ng(flb_sds_t tag, + struct flb_http_request *request) +{ + struct mk_list *kvs; + struct mk_list *head = NULL; + struct flb_split_entry *cur = NULL; + char **keys = NULL; + char **vals = NULL; + char *sep; + char *start; + int idx = 0; + int ret = -1; + msgpack_packer pck; + msgpack_sbuffer sbuf; + struct flb_http *ctx; + char *payload; + + ctx = (struct flb_http *) request->stream->user_data; + payload = (char *) request->body; + + /* initialize buffers */ + msgpack_sbuffer_init(&sbuf); + msgpack_packer_init(&pck, &sbuf, msgpack_sbuffer_write); + + kvs = flb_utils_split(payload, '&', -1 ); + if (kvs == NULL) { + goto split_error; + } + + keys = flb_calloc(mk_list_size(kvs), sizeof(char *)); + if (keys == NULL) { + goto keys_calloc_error; + } + + vals = flb_calloc(mk_list_size(kvs), sizeof(char *)); + if (vals == NULL) { + goto vals_calloc_error; + } + + mk_list_foreach(head, kvs) { + cur = mk_list_entry(head, struct flb_split_entry, _head); + if (cur->value[0] == '\n') { + start = &cur->value[1]; + } else { + start = cur->value; + } + sep = strchr(start, '='); + if (sep == NULL) { + vals[idx] = NULL; + continue; + } + *sep++ = '\0'; + + keys[idx] = flb_sds_create_len(start, strlen(start)); + vals[idx] = flb_sds_create_len(sep, strlen(sep)); + + flb_sds_trim(keys[idx]); + flb_sds_trim(vals[idx]); + idx++; + } + + msgpack_pack_map(&pck, mk_list_size(kvs)); + for (idx = 0; idx < mk_list_size(kvs); idx++) { + msgpack_pack_str(&pck, flb_sds_len(keys[idx])); + msgpack_pack_str_body(&pck, keys[idx], flb_sds_len(keys[idx])); + + if (sds_uri_decode(vals[idx]) != 0) { + goto decode_error; + } else { + msgpack_pack_str(&pck, flb_sds_len(vals[idx])); + msgpack_pack_str_body(&pck, vals[idx], strlen(vals[idx])); + } + } + + ret = process_pack(ctx, tag, sbuf.data, sbuf.size); + +decode_error: + for (idx = 0; idx < mk_list_size(kvs); idx++) { + if (keys[idx]) { + flb_sds_destroy(keys[idx]); + } + if (vals[idx]) { + flb_sds_destroy(vals[idx]); + } + } + flb_free(vals); +vals_calloc_error: + flb_free(keys); +keys_calloc_error: + flb_utils_split_free(kvs); +split_error: + msgpack_sbuffer_destroy(&sbuf); + return ret; +} + +static int process_payload_ng(flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + int type; + + type = -1; + + if (request->content_type == NULL) { + send_response_ng(response, 400, "error: header 'Content-Type' is not set\n"); + return -1; + } + + if (strcasecmp(request->content_type, "application/json") == 0) { + type = HTTP_CONTENT_JSON; + } + + if (strcasecmp(request->content_type, "application/x-www-form-urlencoded") == 0) { + type = HTTP_CONTENT_URLENCODED; + } + + if (type == -1) { + send_response_ng(response, 400, "error: invalid 'Content-Type'\n"); + return -1; + } + + if (request->body == NULL || + cfl_sds_len(request->body) == 0) { + send_response_ng(response, 400, "error: no payload found\n"); + return -1; + } + + if (type == HTTP_CONTENT_JSON) { + parse_payload_json_ng(tag, request); + } else if (type == HTTP_CONTENT_URLENCODED) { + parse_payload_urlencoded_ng(tag, request); + } + + return 0; +} + +int http_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response) +{ + int i; + int ret; + int len; + flb_sds_t tag; + struct flb_http *context; + + context = (struct flb_http *) response->stream->user_data; + + if (request->path[0] != '/') { + send_response_ng(response, 400, "error: invalid request\n"); + return -1; + } + + /* Compose the query string using the URI */ + len = cfl_sds_len(request->path); + + if (len == 1) { + tag = NULL; /* use default tag */ + } + else { + tag = flb_sds_create(&request->path[1]); + + if (tag == NULL) { + return -1; + } + + /* Sanitize, only allow alphanum chars */ + for (i = 0; i < flb_sds_len(tag); i++) { + if (!isalnum(tag[i]) && tag[i] != '_' && tag[i] != '.') { + tag[i] = '_'; + } + } + } + + /* ToDo: Fix me */ + /* HTTP/1.1 needs Host header */ + if (request->protocol_version == HTTP_PROTOCOL_HTTP1 && + request->host == NULL) { + flb_sds_destroy(tag); + + return -1; + } + + if (request->method != HTTP_METHOD_POST) { + send_response_ng(response, 400, "error: invalid HTTP method\n"); + flb_sds_destroy(tag); + + return -1; + } + + ret = process_payload_ng(tag, request, response); + + flb_sds_destroy(tag); + + send_response_ng(response, context->successful_response_code, NULL); + + return ret; +} diff --git a/plugins/in_http/http_prot.h b/plugins/in_http/http_prot.h index 202ab14d2be..461ba14acbd 100644 --- a/plugins/in_http/http_prot.h +++ b/plugins/in_http/http_prot.h @@ -20,10 +20,15 @@ #ifndef FLB_IN_HTTP_PROT #define FLB_IN_HTTP_PROT +#include + int http_prot_handle(struct flb_http *ctx, struct http_conn *conn, struct mk_http_session *session, struct mk_http_request *request); +int http_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response); + int http_prot_handle_error(struct flb_http *ctx, struct http_conn *conn, struct mk_http_session *session, struct mk_http_request *request); diff --git a/plugins/in_opentelemetry/opentelemetry.c b/plugins/in_opentelemetry/opentelemetry.c index 48056fcae15..e01eeacc936 100644 --- a/plugins/in_opentelemetry/opentelemetry.c +++ b/plugins/in_opentelemetry/opentelemetry.c @@ -25,6 +25,7 @@ #include "http_conn.h" #include "opentelemetry.h" +#include "opentelemetry_prot.h" #include "opentelemetry_config.h" /* @@ -89,25 +90,80 @@ static int in_opentelemetry_init(struct flb_input_instance *ins, port = (unsigned short int) strtoul(ctx->tcp_port, NULL, 10); - ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, - ins->flags, - ctx->listen, - port, - ins->tls, - config, - &ins->net_setup); + if (ctx->enable_http2) { + ret = flb_http_server_init(&ctx->http_server, + HTTP_PROTOCOL_AUTODETECT, + FLB_HTTP_SERVER_FLAG_AUTO_INFLATE, + NULL, + ins->host.listen, + ins->host.port, + ins->tls, + ins->flags, + &ins->net_setup, + flb_input_event_loop_get(ins), + ins->config, + (void *) ctx); - if (ctx->downstream == NULL) { - flb_plg_error(ctx->ins, - "could not initialize downstream on %s:%s. Aborting", - ctx->listen, ctx->tcp_port); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not initialize http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - opentelemetry_config_destroy(ctx); + opentelemetry_config_destroy(ctx); - return -1; - } + return -1; + } + + ret = flb_http_server_start(&ctx->http_server); + + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not start http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - flb_input_downstream_set(ctx->downstream, ctx->ins); + opentelemetry_config_destroy(ctx); + + return -1; + } + + ctx->http_server.request_callback = opentelemetry_prot_handle_ng; + + flb_input_downstream_set(ctx->http_server.downstream, ctx->ins); + } + else { + ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + ins->flags, + ctx->listen, + port, + ins->tls, + config, + &ins->net_setup); + + if (ctx->downstream == NULL) { + flb_plg_error(ctx->ins, + "could not initialize downstream on %s:%s. Aborting", + ctx->listen, ctx->tcp_port); + + opentelemetry_config_destroy(ctx); + + return -1; + } + + flb_input_downstream_set(ctx->downstream, ctx->ins); + + /* Collect upon data available on the standard input */ + ret = flb_input_set_collector_socket(ins, + in_opentelemetry_collect, + ctx->downstream->server_fd, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); + opentelemetry_config_destroy(ctx); + return -1; + } + + ctx->collector_id = ret; + } flb_plg_info(ctx->ins, "listening on %s:%s", ctx->listen, ctx->tcp_port); @@ -119,19 +175,6 @@ static int in_opentelemetry_init(struct flb_input_instance *ins, ctx->successful_response_code = 201; } - /* Collect upon data available on the standard input */ - ret = flb_input_set_collector_socket(ins, - in_opentelemetry_collect, - ctx->downstream->server_fd, - config); - if (ret == -1) { - flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); - opentelemetry_config_destroy(ctx); - return -1; - } - - ctx->collector_id = ret; - return 0; } @@ -152,6 +195,12 @@ static int in_opentelemetry_exit(void *data, struct flb_config *config) /* Configuration properties map */ static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_BOOL, "http2", "true", + 0, FLB_TRUE, offsetof(struct flb_opentelemetry, enable_http2), + NULL + }, + { FLB_CONFIG_MAP_SIZE, "buffer_max_size", HTTP_BUFFER_MAX_SIZE, 0, FLB_TRUE, offsetof(struct flb_opentelemetry, buffer_max_size), diff --git a/plugins/in_opentelemetry/opentelemetry.h b/plugins/in_opentelemetry/opentelemetry.h index de42b48f549..dafc88fa8c4 100644 --- a/plugins/in_opentelemetry/opentelemetry.h +++ b/plugins/in_opentelemetry/opentelemetry.h @@ -25,6 +25,7 @@ #include #include +#include #define HTTP_BUFFER_MAX_SIZE "4M" #define HTTP_BUFFER_CHUNK_SIZE "512K" @@ -37,6 +38,13 @@ struct flb_opentelemetry { bool raw_traces; int tag_from_uri; + struct flb_input_instance *ins; + + /* New gen HTTP server */ + int enable_http2; + struct flb_http_server http_server; + + /* Legacy HTTP server */ size_t buffer_max_size; /* Maximum buffer size */ size_t buffer_chunk_size; /* Chunk allocation size */ @@ -45,7 +53,6 @@ struct flb_opentelemetry { struct mk_list connections; /* linked list of connections */ struct mk_server *server; - struct flb_input_instance *ins; }; diff --git a/plugins/in_opentelemetry/opentelemetry_config.c b/plugins/in_opentelemetry/opentelemetry_config.c index f6896f979d7..3e6b947e6cf 100644 --- a/plugins/in_opentelemetry/opentelemetry_config.c +++ b/plugins/in_opentelemetry/opentelemetry_config.c @@ -80,6 +80,10 @@ int opentelemetry_config_destroy(struct flb_opentelemetry *ctx) flb_downstream_destroy(ctx->downstream); } + if (ctx->enable_http2) { + flb_http_server_destroy(&ctx->http_server); + } + if (ctx->server) { flb_free(ctx->server); } diff --git a/plugins/in_opentelemetry/opentelemetry_prot.c b/plugins/in_opentelemetry/opentelemetry_prot.c index 189f3c262c0..80dcc1108a7 100644 --- a/plugins/in_opentelemetry/opentelemetry_prot.c +++ b/plugins/in_opentelemetry/opentelemetry_prot.c @@ -27,6 +27,8 @@ #include #include +#include + #include #include @@ -1682,3 +1684,519 @@ int opentelemetry_prot_handle_error(struct flb_opentelemetry *ctx, struct http_c send_response(conn, 400, "error: invalid request\n"); return -1; } + + + + + + + + + +/* New gen HTTP server */ +static int send_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int send_grpc_response_ng(struct flb_http_response *response, + uint8_t *message_buffer, + size_t message_length, + int grpc_status, + char *grpc_message) +{ + char grpc_status_as_string[16]; + uint32_t wire_message_length; + size_t body_buffer_size; + cfl_sds_t body_buffer; + + body_buffer_size = 5 + message_length; + + if (body_buffer_size < 65) { + body_buffer_size = 65; + } + + body_buffer = cfl_sds_create_size(body_buffer_size); + + if (body_buffer == NULL) { + return -1; + } + + sprintf(grpc_status_as_string, "%u", grpc_status); + + wire_message_length = (uint32_t) message_length; + + cfl_sds_cat(body_buffer, "\x00----", 5); + + ((uint8_t *) body_buffer)[1] = (wire_message_length & 0xFF000000) >> 24; + ((uint8_t *) body_buffer)[2] = (wire_message_length & 0x00FF0000) >> 16; + ((uint8_t *) body_buffer)[3] = (wire_message_length & 0x0000FF00) >> 8; + ((uint8_t *) body_buffer)[4] = (wire_message_length & 0x000000FF) >> 0; + + if (message_buffer != NULL) { + cfl_sds_cat(body_buffer, (char *) message_buffer, message_length); + } + + flb_http_response_set_status(response, 200); + + flb_http_response_set_body(response, + (unsigned char *) body_buffer, + 5 + message_length); + + flb_http_response_set_header(response, + "content-type", 0, + "application/grpc", 0); + + flb_http_response_set_trailer_header(response, + "grpc-status", 0, + grpc_status_as_string, 0); + + flb_http_response_set_trailer_header(response, + "grpc-message", 0, + grpc_message, 0); + + flb_http_response_commit(response); + + cfl_sds_destroy(body_buffer); + + return 0; +} + +static int send_export_logs_service_response_ng(struct flb_http_response *response, + int status) +{ + uint8_t *message_buffer; + size_t message_length; + const char *grpc_message; + int grpc_status; + Opentelemetry__Proto__Collector__Logs__V1__ExportLogsServiceResponse message; + + if (status == 0) { + opentelemetry__proto__collector__logs__v1__export_logs_service_response__init(&message); + + message_length = opentelemetry__proto__collector__logs__v1__export_logs_service_response__get_packed_size(&message); + + message_buffer = flb_calloc(message_length, sizeof(uint8_t)); + + if (message_buffer == NULL) { + return -1; + } + + opentelemetry__proto__collector__logs__v1__export_logs_service_response__pack(&message, message_buffer); + + grpc_status = 0; + grpc_message = ""; + } + else { + grpc_status = 2; /* gRPC UNKNOWN */ + grpc_message = "Serialization error."; + message_buffer = NULL; + message_length = 0; + } + + send_grpc_response_ng(response, message_buffer, message_length, grpc_status, grpc_message); + + if (message_buffer != NULL) { + flb_free(message_buffer); + } + + return 0; +} + +static int send_export_metrics_service_response_ng(struct flb_http_response *response, + int status) +{ + uint8_t *message_buffer; + size_t message_length; + const char *grpc_message; + int grpc_status; + Opentelemetry__Proto__Collector__Metrics__V1__ExportMetricsServiceResponse message; + + if (status == 0) { + opentelemetry__proto__collector__metrics__v1__export_metrics_service_response__init(&message); + + message_length = opentelemetry__proto__collector__metrics__v1__export_metrics_service_response__get_packed_size(&message); + + message_buffer = flb_calloc(message_length, sizeof(uint8_t)); + + if (message_buffer == NULL) { + return -1; + } + + opentelemetry__proto__collector__metrics__v1__export_metrics_service_response__pack(&message, message_buffer); + + grpc_status = 0; + grpc_message = "-"; + } + else { + grpc_status = 2; /* gRPC UNKNOWN */ + grpc_message = "Serialization error."; + message_buffer = NULL; + message_length = 0; + } + + send_grpc_response_ng(response, message_buffer, message_length, grpc_status, grpc_message); + + if (message_buffer != NULL) { + flb_free(message_buffer); + } + + return 0; +} + +static int send_export_traces_service_response_ng(struct flb_http_response *response, + int status) +{ + uint8_t *message_buffer; + size_t message_length; + const char *grpc_message; + int grpc_status; + Opentelemetry__Proto__Collector__Trace__V1__ExportTraceServiceResponse message; + + if (status == 0) { + opentelemetry__proto__collector__trace__v1__export_trace_service_response__init(&message); + + message_length = opentelemetry__proto__collector__trace__v1__export_trace_service_response__get_packed_size(&message); + + message_buffer = flb_calloc(message_length, sizeof(uint8_t)); + + if (message_buffer == NULL) { + return -1; + } + + opentelemetry__proto__collector__trace__v1__export_trace_service_response__pack(&message, message_buffer); + + grpc_status = 0; + grpc_message = "-"; + } + else { + grpc_status = 2; /* gRPC UNKNOWN */ + grpc_message = "Serialization error."; + message_buffer = NULL; + message_length = 0; + } + + send_grpc_response_ng(response, message_buffer, message_length, grpc_status, grpc_message); + + if (message_buffer != NULL) { + flb_free(message_buffer); + } + + return 0; +} +static int process_payload_metrics_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + struct cfl_list decoded_contexts; + struct cfl_list *iterator; + struct cmt *context; + size_t offset; + int result; + + offset = 0; + + if (strcasecmp(request->content_type, "application/grpc") == 0) { + if (cfl_sds_len(request->body) < 5) { + return -1; + } + + result = cmt_decode_opentelemetry_create(&decoded_contexts, + &request->body[5], + cfl_sds_len(request->body) - 5, + &offset); + } + else { + result = cmt_decode_opentelemetry_create(&decoded_contexts, + request->body, + cfl_sds_len(request->body), + &offset); + } + + if (result == CMT_DECODE_OPENTELEMETRY_SUCCESS) { + cfl_list_foreach(iterator, &decoded_contexts) { + context = cfl_list_entry(iterator, struct cmt, _head); + + result = flb_input_metrics_append(ctx->ins, NULL, 0, context); + + if (result != 0) { + flb_plg_debug(ctx->ins, "could not ingest metrics context : %d", result); + } + } + + cmt_decode_opentelemetry_destroy(&decoded_contexts); + } + + return 0; +} + + +static int process_payload_traces_proto_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + struct ctrace *decoded_context; + size_t offset; + int result; + + offset = 0; + + if (strcasecmp(request->content_type, "application/grpc") == 0) { + if (cfl_sds_len(request->body) < 5) { + return -1; + } + + result = ctr_decode_opentelemetry_create(&decoded_context, + &request->body[5], + cfl_sds_len(request->body) - 5, + &offset); + } + else { + result = ctr_decode_opentelemetry_create(&decoded_context, + request->body, + cfl_sds_len(request->body), + &offset); + } + + if (result == 0) { + result = flb_input_trace_append(ctx->ins, NULL, 0, decoded_context); + ctr_decode_opentelemetry_destroy(decoded_context); + } + + return result; +} + +static int process_payload_raw_traces_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + int ret; + int root_type; + char *out_buf = NULL; + size_t out_size; + + msgpack_packer mp_pck; + msgpack_sbuffer mp_sbuf; + + msgpack_sbuffer_init(&mp_sbuf); + msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write); + + msgpack_pack_array(&mp_pck, 2); + flb_pack_time_now(&mp_pck); + + /* Check if the incoming payload is a valid JSON message and convert it to msgpack */ + ret = flb_pack_json(request->body, cfl_sds_len(request->body), + &out_buf, &out_size, &root_type, NULL); + + if (ret == 0 && root_type == JSMN_OBJECT) { + /* JSON found, pack it msgpack representation */ + msgpack_sbuffer_write(&mp_sbuf, out_buf, out_size); + } + else { + /* the content might be a binary payload or invalid JSON */ + msgpack_pack_map(&mp_pck, 1); + msgpack_pack_str_with_body(&mp_pck, "trace", 5); + msgpack_pack_str_with_body(&mp_pck, request->body, cfl_sds_len(request->body)); + } + + /* release 'out_buf' if it was allocated */ + if (out_buf) { + flb_free(out_buf); + } + + flb_input_log_append(ctx->ins, tag, flb_sds_len(tag), mp_sbuf.data, mp_sbuf.size); + msgpack_sbuffer_destroy(&mp_sbuf); + + return 0; +} + +static int process_payload_traces_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + int result; + + if (ctx->raw_traces) { + result = process_payload_raw_traces_ng(ctx, tag, request, response); + } + else { + result = process_payload_traces_proto_ng(ctx, tag, request, response); + } + + return result; +} + +static int process_payload_logs_ng(struct flb_opentelemetry *ctx, + flb_sds_t tag, + struct flb_http_request *request, + struct flb_http_response *response) +{ + struct flb_log_event_encoder *encoder; + int ret; + + encoder = flb_log_event_encoder_create(FLB_LOG_EVENT_FORMAT_FLUENT_BIT_V2); + + if (encoder == NULL) { + return -1; + } + + if (request->content_type == NULL) { + flb_error("[otel] content type missing"); + + ret = -1; + } + else if (strcasecmp(request->content_type, "application/json") == 0) { + ret = json_payload_to_msgpack(ctx, + encoder, + request->body, + cfl_sds_len(request->body)); + } + else if (strcasecmp(request->content_type, "application/x-protobuf") == 0) { + ret = binary_payload_to_msgpack(encoder, + (uint8_t *) request->body, + cfl_sds_len(request->body)); + } + else if (strcasecmp(request->content_type, "application/grpc") == 0) { + if (cfl_sds_len(request->body) < 5) { + return -1; + } + + ret = binary_payload_to_msgpack(encoder, + &((uint8_t *) request->body)[5], + (cfl_sds_len(request->body)) - 5); + } + else { + flb_error("[otel] Unsupported content type %s", request->content_type); + + ret = -1; + } + + if (ret == 0) { + ret = flb_input_log_append(ctx->ins, + tag, + flb_sds_len(tag), + encoder->output_buffer, + encoder->output_length); + } + + flb_log_event_encoder_destroy(encoder); + + return ret; +} + + +static int send_export_service_response_ng(struct flb_http_response *response, + int result, + char payload_type) +{ + switch (payload_type) { + case 'M': + return send_export_metrics_service_response_ng(response, result); + case 'T': + return send_export_traces_service_response_ng(response, result); + case 'L': + return send_export_logs_service_response_ng(response, result); + default: + return -1; + } +} + +int opentelemetry_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response) +{ + char payload_type; + int grpc_request; + struct flb_opentelemetry *context; + int result; + + context = (struct flb_opentelemetry *) response->stream->user_data; + + if (request->path[0] != '/') { + send_response_ng(response, 400, "error: invalid request\n"); + return -1; + } + + if (strcmp(request->path, "/v1/metrics") == 0 || + strcmp(request->path, "/v1/traces") == 0 || + strcmp(request->path, "/v1/logs") == 0) { + grpc_request = FLB_FALSE; + } + else if(strcmp(request->path, "/opentelemetry.proto.collector.metrics.v1.MetricsService/Export") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.traces.v1.TracesService/Export") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.logs.v1.LogsService/Export") == 0) { + grpc_request = FLB_TRUE; + } + else { + send_response_ng(response, 400, "error: invalid endpoint\n"); + + return -1; + } + + /* ToDo: Fix me */ + /* HTTP/1.1 needs Host header */ + if (request->protocol_version == HTTP_PROTOCOL_HTTP1 && + request->host == NULL) { + + return -1; + } + + if (request->method != HTTP_METHOD_POST) { + send_response_ng(response, 400, "error: invalid HTTP method\n"); + + return -1; + } + + if (strcmp(request->path, "/v1/metrics") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.metrics.v1.MetricsService/Export") == 0) { + payload_type = 'M'; + result = process_payload_metrics_ng(context, context->ins->tag, request, response); + } + else if (strcmp(request->path, "/v1/traces") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.traces.v1.TracesService/Export") == 0) { + payload_type = 'T'; + result = process_payload_traces_ng(context, context->ins->tag, request, response); + } + else if (strcmp(request->path, "/v1/logs") == 0 || + strcmp(request->path, "/opentelemetry.proto.collector.logs.v1.LogsService/Export") == 0) { + payload_type = 'L'; + result = process_payload_logs_ng(context, context->ins->tag, request, response); + } + + if (grpc_request) { + send_export_service_response_ng(response, result, payload_type); + } + else { + send_response_ng(response, context->successful_response_code, NULL); + } + + return result; +} diff --git a/plugins/in_opentelemetry/opentelemetry_prot.h b/plugins/in_opentelemetry/opentelemetry_prot.h index 1a9113c8065..39165c8087b 100644 --- a/plugins/in_opentelemetry/opentelemetry_prot.h +++ b/plugins/in_opentelemetry/opentelemetry_prot.h @@ -20,6 +20,8 @@ #ifndef FLB_IN_OPENTELEMETRY_PROT #define FLB_IN_OPENTELEMETRY_PROT +#include + int opentelemetry_prot_handle(struct flb_opentelemetry *ctx, struct http_conn *conn, struct mk_http_session *session, struct mk_http_request *request); @@ -28,4 +30,8 @@ int opentelemetry_prot_handle_error(struct flb_opentelemetry *ctx, struct http_c struct mk_http_session *session, struct mk_http_request *request); + +int opentelemetry_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response); + #endif diff --git a/plugins/in_splunk/splunk.c b/plugins/in_splunk/splunk.c index 7436477c24c..0a1a37bd7cf 100644 --- a/plugins/in_splunk/splunk.c +++ b/plugins/in_splunk/splunk.c @@ -24,6 +24,7 @@ #include "splunk.h" #include "splunk_conn.h" +#include "splunk_prot.h" #include "splunk_config.h" /* @@ -92,39 +93,82 @@ static int in_splunk_init(struct flb_input_instance *ins, port = (unsigned short int) strtoul(ctx->tcp_port, NULL, 10); - ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, - ins->flags, - ctx->listen, - port, - ins->tls, - config, - &ins->net_setup); - if (ctx->downstream == NULL) { - flb_plg_error(ctx->ins, - "could not initialize downstream on %s:%s. Aborting", - ctx->listen, ctx->tcp_port); + if (ctx->enable_http2) { + ret = flb_http_server_init(&ctx->http_server, + HTTP_PROTOCOL_AUTODETECT, + FLB_HTTP_SERVER_FLAG_AUTO_INFLATE, + NULL, + ins->host.listen, + ins->host.port, + ins->tls, + ins->flags, + &ins->net_setup, + flb_input_event_loop_get(ins), + ins->config, + (void *) ctx); - splunk_config_destroy(ctx); + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not initialize http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); - return -1; - } + splunk_config_destroy(ctx); - flb_input_downstream_set(ctx->downstream, ctx->ins); + return -1; + } - /* Collect upon data available on the standard input */ - ret = flb_input_set_collector_socket(ins, - in_splunk_collect, - ctx->downstream->server_fd, - config); - if (ret == -1) { - flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); - splunk_config_destroy(ctx); + ret = flb_http_server_start(&ctx->http_server); - return -1; - } + if (ret != 0) { + flb_plg_error(ctx->ins, + "could not start http server on %s:%u. Aborting", + ins->host.listen, ins->host.port); + + splunk_config_destroy(ctx); - ctx->collector_id = ret; + return -1; + } + + ctx->http_server.request_callback = splunk_prot_handle_ng; + + flb_input_downstream_set(ctx->http_server.downstream, ctx->ins); + } + else { + ctx->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + ins->flags, + ctx->listen, + port, + ins->tls, + config, + &ins->net_setup); + + if (ctx->downstream == NULL) { + flb_plg_error(ctx->ins, + "could not initialize downstream on %s:%s. Aborting", + ctx->listen, ctx->tcp_port); + + splunk_config_destroy(ctx); + + return -1; + } + + flb_input_downstream_set(ctx->downstream, ctx->ins); + + /* Collect upon data available on the standard input */ + ret = flb_input_set_collector_socket(ins, + in_splunk_collect, + ctx->downstream->server_fd, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "Could not set collector for IN_TCP input plugin"); + splunk_config_destroy(ctx); + + return -1; + } + + ctx->collector_id = ret; + } return 0; } @@ -162,6 +206,12 @@ static void in_splunk_resume(void *data, struct flb_config *config) /* Configuration properties map */ static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_BOOL, "http2", "true", + 0, FLB_TRUE, offsetof(struct flb_splunk, enable_http2), + NULL + }, + { FLB_CONFIG_MAP_SIZE, "buffer_max_size", HTTP_BUFFER_MAX_SIZE, 0, FLB_TRUE, offsetof(struct flb_splunk, buffer_max_size), diff --git a/plugins/in_splunk/splunk.h b/plugins/in_splunk/splunk.h index 42f6bdd9184..e55bbc2a897 100644 --- a/plugins/in_splunk/splunk.h +++ b/plugins/in_splunk/splunk.h @@ -27,6 +27,7 @@ #include #include +#include #define HTTP_BUFFER_MAX_SIZE "4M" #define HTTP_BUFFER_CHUNK_SIZE "512K" @@ -36,24 +37,28 @@ struct flb_splunk { flb_sds_t tcp_port; const char *tag_key; - int collector_id; - /* Success HTTP headers */ struct mk_list *success_headers; - flb_sds_t success_headers_str; - - size_t buffer_max_size; /* Maximum buffer size */ - size_t buffer_chunk_size; /* Chunk allocation size */ /* Token Auth */ flb_sds_t auth_header; struct flb_log_event_encoder log_encoder; + + struct flb_input_instance *ins; + + /* New gen HTTP server */ + int enable_http2; + struct flb_http_server http_server; + + /* Legacy HTTP server */ + flb_sds_t success_headers_str; + int collector_id; + size_t buffer_max_size; /* Maximum buffer size */ + size_t buffer_chunk_size; /* Chunk allocation size */ struct flb_downstream *downstream; /* Client manager */ struct mk_list connections; /* linked list of connections */ - struct mk_server *server; - struct flb_input_instance *ins; }; diff --git a/plugins/in_splunk/splunk_config.c b/plugins/in_splunk/splunk_config.c index baaf82fc22c..8a7f3dbc999 100644 --- a/plugins/in_splunk/splunk_config.c +++ b/plugins/in_splunk/splunk_config.c @@ -168,6 +168,10 @@ int splunk_config_destroy(struct flb_splunk *ctx) flb_downstream_destroy(ctx->downstream); } + if (ctx->enable_http2) { + flb_http_server_destroy(&ctx->http_server); + } + if (ctx->server) { flb_free(ctx->server); } diff --git a/plugins/in_splunk/splunk_prot.c b/plugins/in_splunk/splunk_prot.c index 64490c57d5b..0da2bd64b03 100644 --- a/plugins/in_splunk/splunk_prot.c +++ b/plugins/in_splunk/splunk_prot.c @@ -779,3 +779,265 @@ int splunk_prot_handle_error(struct flb_splunk *ctx, struct splunk_conn *conn, send_response(conn, 400, "error: invalid request\n"); return -1; } + + + + + + + +/* New gen HTTP server */ + +static int send_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int send_json_message_response_ng(struct flb_http_response *response, + int http_status, + char *message) +{ + flb_http_response_set_status(response, http_status); + + if (http_status == 201) { + flb_http_response_set_message(response, "Created"); + } + else if (http_status == 200) { + flb_http_response_set_message(response, "OK"); + } + else if (http_status == 204) { + flb_http_response_set_message(response, "No Content"); + } + else if (http_status == 400) { + flb_http_response_set_message(response, "Forbidden"); + } + + flb_http_response_set_header(response, + "content-type", 0, + "application/json", 0); + + if (message != NULL) { + flb_http_response_set_body(response, + (unsigned char *) message, + strlen(message)); + } + + flb_http_response_commit(response); + + return 0; +} + +static int validate_auth_header_ng(struct flb_splunk *ctx, struct flb_http_request *request) +{ + char *auth_header; + + if (ctx->auth_header == NULL) { + return SPLUNK_AUTH_UNAUTH; + } + + auth_header = flb_http_request_get_header(request, "authorization"); + + if (auth_header == NULL) { + return SPLUNK_AUTH_MISSING_CRED; + } + + if (auth_header != NULL && strlen(auth_header) > 0) { + if (strncmp(ctx->auth_header, + auth_header, + strlen(ctx->auth_header)) == 0) { + return SPLUNK_AUTH_SUCCESS; + } + else { + return SPLUNK_AUTH_UNAUTHORIZED; + } + } + else { + return SPLUNK_AUTH_MISSING_CRED; + } + + return SPLUNK_AUTH_SUCCESS; +} + +static int process_hec_payload_ng(struct flb_http_request *request, + struct flb_http_response *response, + flb_sds_t tag, + struct flb_splunk *ctx) +{ + int type = -1; + + type = HTTP_CONTENT_UNKNOWN; + + if (request->content_type != NULL) { + if (strcasecmp(request->content_type, "application/json") == 0) { + type = HTTP_CONTENT_JSON; + } + else if (strcasecmp(request->content_type, "text/plain") == 0) { + type = HTTP_CONTENT_TEXT; + } + else { + /* Not neccesary to specify content-type for Splunk HEC. */ + flb_plg_debug(ctx->ins, "Mark as unknown type for ingested payloads"); + } + } + + if (request->body == NULL || cfl_sds_len(request->body) <= 0) { + send_response_ng(response, 400, "error: no payload found\n"); + + return -1; + } + + return handle_hec_payload(ctx, type, tag, request->body, cfl_sds_len(request->body)); +} + +static int process_hec_raw_payload_ng(struct flb_http_request *request, + struct flb_http_response *response, + flb_sds_t tag, + struct flb_splunk *ctx) +{ + if (request->content_type == NULL) { + send_response_ng(response, 400, "error: header 'Content-Type' is not set\n"); + + return -1; + } + else if (strcasecmp(request->content_type, "text/plain") != 0) { + /* Not neccesary to specify content-type for Splunk HEC. */ + flb_plg_debug(ctx->ins, "Mark as unknown type for ingested payloads"); + } + + if (request->body == NULL || cfl_sds_len(request->body) == 0) { + send_response_ng(response, 400, "error: no payload found\n"); + + return -1; + } + + /* Always handle as raw type of payloads here */ + return process_raw_payload_pack(ctx, tag, request->body, cfl_sds_len(request->body)); +} + +int splunk_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response) +{ + struct flb_splunk *context; + int ret; + flb_sds_t tag; + + context = (struct flb_splunk *) response->stream->user_data; + + if (request->path[0] != '/') { + send_response_ng(response, 400, "error: invalid request\n"); + return -1; + } + + /* HTTP/1.1 needs Host header */ + if (request->protocol_version == HTTP_PROTOCOL_HTTP1 && + request->host == NULL) { + + return -1; + } + + if (request->method == HTTP_METHOD_GET) { + /* Handle health minotoring of splunk hec endpoint for load balancers */ + if (strcasecmp(request->path, "/services/collector/health") == 0) { + send_json_message_response_ng(response, 200, "{\"text\":\"Success\",\"code\":200}"); + } + else { + send_response_ng(response, 400, "error: invalid HTTP endpoint\n"); + } + + return 0; + } + + /* Under services/collector endpoints are required for + * authentication if provided splunk_token */ + ret = validate_auth_header_ng(context, request); + + if (ret < 0) { + send_response_ng(response, 401, "error: unauthorized\n"); + + if (ret == SPLUNK_AUTH_MISSING_CRED) { + flb_plg_warn(context->ins, "missing credentials in request headers"); + } + else if (ret == SPLUNK_AUTH_UNAUTHORIZED) { + flb_plg_warn(context->ins, "wrong credentials in request headers"); + } + + return -1; + } + + /* Handle every ingested payload cleanly */ + flb_log_event_encoder_reset(&context->log_encoder); + + if (request->method != HTTP_METHOD_POST) { + /* HEAD, PUT, PATCH, and DELETE methods are prohibited to use.*/ + send_response_ng(response, 400, "error: invalid HTTP method\n"); + + return -1; + } + + tag = flb_sds_create(context->ins->tag); + + if (tag == NULL) { + return -1; + } + + if (strcasecmp(request->path, "/services/collector/raw") == 0) { + ret = process_hec_raw_payload_ng(request, response, tag, context); + + if (ret != 0) { + send_json_message_response_ng(response, 400, "{\"text\":\"Invalid data format\",\"code\":6}"); + } + else { + send_json_message_response_ng(response, 200, "{\"text\":\"Success\",\"code\":0}"); + } + + ret = 0; + } + else if (strcasecmp(request->path, "/services/collector/event") == 0 || + strcasecmp(request->path, "/services/collector") == 0) { + ret = process_hec_payload_ng(request, response, tag, context); + + if (ret != 0) { + send_json_message_response_ng(response, 400, "{\"text\":\"Invalid data format\",\"code\":6}"); + } + else { + send_json_message_response_ng(response, 200, "{\"text\":\"Success\",\"code\":0}"); + } + + ret = 0; + } + else { + send_response_ng(response, 400, "error: invalid HTTP endpoint\n"); + + ret = -1; + } + + flb_sds_destroy(tag); + + + return ret; +} \ No newline at end of file diff --git a/plugins/in_splunk/splunk_prot.h b/plugins/in_splunk/splunk_prot.h index 472f43a952c..99e704e8f96 100644 --- a/plugins/in_splunk/splunk_prot.h +++ b/plugins/in_splunk/splunk_prot.h @@ -25,6 +25,8 @@ #define SPLUNK_AUTH_MISSING_CRED -1 #define SPLUNK_AUTH_UNAUTHORIZED -2 +#include + int splunk_prot_handle(struct flb_splunk *ctx, struct splunk_conn *conn, struct mk_http_session *session, struct mk_http_request *request); @@ -33,4 +35,7 @@ int splunk_prot_handle_error(struct flb_splunk *ctx, struct splunk_conn *conn, struct mk_http_session *session, struct mk_http_request *request); +int splunk_prot_handle_ng(struct flb_http_request *request, + struct flb_http_response *response); + #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b6233d9f721..78e1876a315 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,6 +62,7 @@ set(src flb_gzip.c flb_snappy.c flb_compression.c + flb_http_common.c flb_http_client.c flb_callback.c flb_strptime.c @@ -210,7 +211,7 @@ endif() if(FLB_REGEX) set(FLB_DEPS - ${FLB_DEPSS} + ${FLB_DEPS} onigmo-static) set(src ${src} @@ -372,6 +373,7 @@ set(FLB_DEPS c-ares snappy-c lwrb + nghttp2_static ) if(OPENSSL_FOUND) diff --git a/src/flb_http_client.c b/src/flb_http_client.c index a3afa5264d1..4917da4fb19 100644 --- a/src/flb_http_client.c +++ b/src/flb_http_client.c @@ -270,7 +270,7 @@ static int process_chunked_data(struct flb_http_client *c) long val; char *p; char tmp[32]; - struct flb_http_response *r = &c->resp; + struct flb_http_client_response *r = &c->resp; chunk_start: p = strstr(r->chunk_processed_end, "\r\n"); diff --git a/src/flb_http_common.c b/src/flb_http_common.c new file mode 100644 index 00000000000..fbd4273356c --- /dev/null +++ b/src/flb_http_common.c @@ -0,0 +1,442 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +/* HTTP REQUEST */ + +int flb_http_request_init(struct flb_http_request *request) +{ + flb_http_request_destroy(request); + + cfl_list_entry_init(&request->_head); + + request->headers = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, 16, -1); + + if (request->headers == NULL) { + return -1; + } + + return 0; +} + +void flb_http_request_destroy(struct flb_http_request *request) +{ + if (request->path != NULL) { + cfl_sds_destroy(request->path); + } + + if (request->host != NULL) { + cfl_sds_destroy(request->host); + } + + if (request->content_type != NULL) { + cfl_sds_destroy(request->content_type); + } + + if (request->query_string != NULL) { + cfl_sds_destroy(request->query_string); + } + + if (request->body != NULL) { + cfl_sds_destroy(request->body); + } + + if (request->headers != NULL) { + flb_hash_table_destroy(request->headers); + } + + if (!cfl_list_entry_is_orphan(&request->_head)) { + cfl_list_del(&request->_head); + } + + memset(request, 0, sizeof(struct flb_http_request)); +} + +char *flb_http_request_get_header(struct flb_http_request *request, + char *name) +{ + char *lowercase_name; + size_t value_length; + int result; + void *value; + + lowercase_name = flb_http_server_convert_string_to_lowercase( + name, strlen(name)); + + if (lowercase_name == NULL) { + return NULL; + } + + result = flb_hash_table_get(request->headers, + lowercase_name, + strlen(lowercase_name), + &value, &value_length); + + flb_free(lowercase_name); + + if (result == -1) { + return NULL; + } + + return (char *) value; +} + +int flb_http_request_set_header(struct flb_http_request *request, + char *name, size_t name_length, + char *value, size_t value_length) +{ + char *lowercase_name; + int result; + + lowercase_name = flb_http_server_convert_string_to_lowercase( + name, name_length); + + if (lowercase_name == NULL) { + return -1; + } + + if (name_length == 0) { + name_length = strlen(name); + } + + if (value_length == 0) { + if (value[0] == '\0') { + value_length = 1; + } + else { + value_length = strlen(value); + } + } + + result = flb_hash_table_add(request->headers, + (const char *) lowercase_name, + name_length, + (void *) value, + value_length); + + flb_free(lowercase_name); + + if (result == -1) { + return -1; + } + + return 0; +} + +int flb_http_request_unset_header(struct flb_http_request *request, + char *name) +{ + char *lowercase_name; + int result; + + lowercase_name = flb_http_server_convert_string_to_lowercase( + name, strlen(name)); + + if (lowercase_name == NULL) { + return -1; + } + + result = flb_hash_table_del(request->headers, + (const char *) lowercase_name); + + flb_free(lowercase_name); + + if (result == -1) { + return -1; + } + + return 0; +} + +/* HTTP RESPONSE */ + +int flb_http_response_init(struct flb_http_response *response) +{ + flb_http_response_destroy(response); + + response->headers = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, 16, -1); + + if (response->headers == NULL) { + return -1; + } + + response->trailer_headers = flb_hash_table_create(FLB_HASH_TABLE_EVICT_NONE, 16, -1); + + if (response->trailer_headers == NULL) { + flb_http_response_destroy(response); + + return -1; + } + + return 0; +} + +void flb_http_response_destroy(struct flb_http_response *response) +{ + if (response->message != NULL) { + cfl_sds_destroy(response->message); + } + + if (response->body != NULL) { + cfl_sds_destroy(response->body); + } + + if (response->headers != NULL) { + flb_hash_table_destroy(response->headers); + } + + if (response->trailer_headers != NULL) { + flb_hash_table_destroy(response->trailer_headers); + } + + memset(response, 0, sizeof(struct flb_http_response)); +} + +struct flb_http_response *flb_http_response_begin( + struct flb_http_server_session *session, + void *stream) +{ + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_begin(&session->http2, stream); + } + else { + return flb_http1_response_begin(&session->http1, stream); + } +} + +int flb_http_response_commit(struct flb_http_response *response) +{ + struct flb_http_server_session *session; + + if (response->body == NULL) { + flb_http_response_set_header(response, + "content-length", + strlen("content-length"), + "0", + 1); + } + + session = (struct flb_http_server_session *) response->stream->parent; + + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_commit(response); + } + + return flb_http1_response_commit(response); +} + +int flb_http_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length) +{ + struct flb_http_server_session *session; + + if (name_length == 0) { + name_length = strlen(name); + } + + if (value_length == 0) { + if (value[0] == '\0') { + value_length = 1; + } + else { + value_length = strlen(value); + } + } + + session = (struct flb_http_server_session *) response->stream->parent; + + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_set_header(response, + name, name_length, + value, value_length); + } + else { + return flb_http1_response_set_header(response, + name, name_length, + value, value_length); + } +} + +int flb_http_response_set_trailer_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length) +{ + char *lowercase_name; + int result; + + if (name_length == 0) { + name_length = strlen(name); + } + + if (value_length == 0) { + if (value[0] == '\0') { + value_length = 1; + } + else { + value_length = strlen(value); + } + } + + lowercase_name = flb_http_server_convert_string_to_lowercase( + name, name_length); + + if (lowercase_name == NULL) { + return -1; + } + + result = flb_hash_table_add(response->trailer_headers, + (const char *) lowercase_name, + name_length, + (void *) value, + value_length); + + flb_free(lowercase_name); + + if (result == -1) { + return -1; + } + + return 0; +} + +int flb_http_response_set_status(struct flb_http_response *response, + int status) +{ + struct flb_http_server_session *session; + + session = (struct flb_http_server_session *) response->stream->parent; + + response->status = status; + + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_set_status(response, status); + } + + return flb_http1_response_set_status(response, status); +} + +int flb_http_response_set_message(struct flb_http_response *response, + char *message) +{ + if (response->message != NULL) { + cfl_sds_destroy(response->message); + + response->message = NULL; + } + + response->message = cfl_sds_create((const char *) message); + + if (response->message == NULL) { + return -1; + } + + return 0; +} + +int flb_http_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length) +{ + struct flb_http_server_session *session; + + session = (struct flb_http_server_session *) response->stream->parent; + + response->body = cfl_sds_create_len((const char *) body, body_length); + + if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_response_set_body(response, body, body_length); + } + + return flb_http1_response_set_body(response, body, body_length); +} + +/* HTTP STREAM */ + +int flb_http_stream_init(struct flb_http_stream *stream, + void *parent, + int32_t stream_id, + int role, + void *user_data) +{ + int result; + + stream->id = stream_id; + stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; + + result = flb_http_request_init(&stream->request); + + if (result != 0) { + return -1; + } + + result = flb_http_response_init(&stream->response); + + if (result != 0) { + return -2; + } + + stream->role = role; + stream->parent = parent; + stream->user_data = user_data; + + stream->request.stream = stream; + stream->response.stream = stream; + + return 0; +} + +struct flb_http_stream *flb_http_stream_create(void *parent, + int32_t stream_id, + int role, + void *user_data) +{ + struct flb_http_stream *stream; + int result; + + stream = flb_calloc(1, sizeof(struct flb_http_stream)); + + if (stream == NULL) { + return NULL; + } + + stream->releasable = FLB_TRUE; + + result = flb_http_stream_init(stream, parent, stream_id, role, user_data); + + if (result != 0) { + flb_http_stream_destroy(stream); + } + + return stream; +} + +void flb_http_stream_destroy(struct flb_http_stream *stream) +{ + if (stream != NULL) { + if (!cfl_list_entry_is_orphan(&stream->_head)) { + cfl_list_del(&stream->_head); + } + + flb_free(stream); + } +} diff --git a/src/http_server/CMakeLists.txt b/src/http_server/CMakeLists.txt index acded936da8..8004475b35f 100644 --- a/src/http_server/CMakeLists.txt +++ b/src/http_server/CMakeLists.txt @@ -7,6 +7,9 @@ set(src flb_hs.c flb_hs_endpoints.c flb_hs_utils.c + flb_http_server.c + flb_http_server_http1.c + flb_http_server_http2.c ) # api/v1 diff --git a/src/http_server/flb_http_server.c b/src/http_server/flb_http_server.c new file mode 100644 index 00000000000..302ded9be6a --- /dev/null +++ b/src/http_server/flb_http_server.c @@ -0,0 +1,811 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include + +static \ +int uncompress_zlib(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +static \ +int uncompress_zstd(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +static \ +int uncompress_deflate(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +static \ +int uncompress_snappy(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +static \ +int uncompress_gzip(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size); + +/* COMMON */ + +char *flb_http_server_convert_string_to_lowercase(char *input_buffer, + size_t length) +{ + char *output_buffer; + size_t index; + + output_buffer = flb_calloc(1, length + 1); + + if (output_buffer != NULL) { + for (index = 0 ; index < length ; index++) { + output_buffer[index] = tolower(input_buffer[index]); + } + + } + + return output_buffer; +} + + +int flb_http_server_strncasecmp(const uint8_t *first_buffer, + size_t first_length, + const char *second_buffer, + size_t second_length) +{ + const char *first_buffer_; + const char *second_buffer_; + + first_buffer_ = (const char *) first_buffer; + second_buffer_ = (const char *) second_buffer; + + if (first_length == 0) { + first_length = strlen(first_buffer_); + } + + if (second_length == 0) { + second_length = strlen(second_buffer_); + } + + if (first_length < second_length) { + return -1; + } + else if (first_length > second_length) { + return 1; + } + + return strncasecmp(first_buffer_, second_buffer_, first_length); +} + + + + + +/* PRIVATE */ + +static int flb_http_server_session_read(struct flb_http_server_session *session) +{ + unsigned char input_buffer[1024]; + ssize_t result; + + result = flb_io_net_read(session->connection, + (void *) &input_buffer, + sizeof(input_buffer)); + + if (result <= 0) { + return -1; + } + + result = (ssize_t) flb_http_server_session_ingest(session, + input_buffer, + result); + + if (result < 0) { + return -1; + } + + return 0; +} + +static int flb_http_server_session_write(struct flb_http_server_session *session) +{ + size_t data_length; + size_t data_sent; + int result; + + if (session == NULL) { + return -1; + } + + if (session->outgoing_data == NULL) { + return 0; + } + + data_length = cfl_sds_len(session->outgoing_data); + + if (data_length > 0) { + result = flb_io_net_write(session->connection, + (void *) session->outgoing_data, + data_length, + &data_sent); + + if (result == -1) { + return -2; + } + + if (data_sent < data_length) { + memmove(session->outgoing_data, + &session->outgoing_data[data_sent], + data_length - data_sent); + + cfl_sds_set_len(session->outgoing_data, + data_length - data_sent); + } + else { + cfl_sds_set_len(session->outgoing_data, 0); + } + } + + return 0; +} + + + +static int flb_http_server_inflate_request_body( + struct flb_http_request *request) +{ + char *content_encoding_header_value; + char new_content_length[21]; + struct flb_http_server_session *parent_session; + cfl_sds_t inflated_body; + char *output_buffer; + size_t output_size; + struct flb_http_server *server; + int result; + + parent_session = (struct flb_http_server_session *) request->stream->parent; + + server = parent_session->parent; + result = 0; + + if (request->body == NULL) { + return 0; + } + + if ((server->flags & FLB_HTTP_SERVER_FLAG_AUTO_INFLATE) == 0) { + return 0; + } + + content_encoding_header_value = flb_http_request_get_header( + request, + "content-encoding"); + + if (content_encoding_header_value == NULL) { + return 0; + } + + if (strncasecmp(content_encoding_header_value, "gzip", 4) == 0) { + result = uncompress_gzip(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + else if (strncasecmp(content_encoding_header_value, "zlib", 4) == 0) { + result = uncompress_zlib(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + else if (strncasecmp(content_encoding_header_value, "zstd", 4) == 0) { + result = uncompress_zstd(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + else if (strncasecmp(content_encoding_header_value, "snappy", 6) == 0) { + result = uncompress_snappy(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + else if (strncasecmp(content_encoding_header_value, "deflate", 4) == 0) { + result = uncompress_deflate(&output_buffer, + &output_size, + request->body, + cfl_sds_len(request->body)); + } + + if (result == 1) { + inflated_body = cfl_sds_create_len(output_buffer, output_size); + + flb_free(output_buffer); + + if (inflated_body == NULL) { + return -1; + } + + cfl_sds_destroy(request->body); + + request->body = inflated_body; + + snprintf(new_content_length, + sizeof(new_content_length), + "%zu", + output_size); + + flb_http_request_unset_header(request, "content-encoding"); + flb_http_request_set_header(request, + "content-length", strlen("content-length"), + new_content_length, strlen(new_content_length)); + + request->content_length = output_size; + } + + return 0; +} + +static int flb_http_server_should_connection_be_closed( + struct flb_http_request *request) +{ + char *connection_header_value; + struct flb_http_server_session *parent_session; + struct flb_http_server *server; + + parent_session = (struct flb_http_server_session *) request->stream->parent; + + server = parent_session->parent; + + /* Version behaviors implemented in the following block : + * HTTP/0.9 keep-alive is opt-in + * HTTP/1.0 keep-alive is opt-in + * HTTP/1.1 keep-alive is opt-out + * HTTP/2 keep-alive is "mandatory" + */ + + if (request->protocol_version < HTTP_PROTOCOL_VERSION_20) { + if ((server->flags & FLB_HTTP_SERVER_FLAG_KEEPALIVE) == 0) { + return FLB_TRUE; + } + else { + connection_header_value = flb_http_request_get_header(request, + "connection"); + + if (connection_header_value == NULL) { + if (request->protocol_version < HTTP_PROTOCOL_VERSION_11) { + return FLB_TRUE; + } + } + else { + if (strcasecmp(connection_header_value, "keep-alive") != 0) { + return FLB_TRUE; + } + } + } + } + + return FLB_FALSE; +} + +static int flb_http_server_client_activity_event_handler(void *data) +{ + int close_connection; + struct cfl_list *backup_iterator; + struct flb_connection *connection; + struct cfl_list *iterator; + struct flb_http_response *response; + struct flb_http_request *request; + struct flb_http_server_session *session; + struct flb_http_server *server; + struct flb_http_stream *stream; + int result; + struct mk_event *event; + + connection = (struct flb_connection *) data; + + session = (struct flb_http_server_session *) connection->user_data; + + server = session->parent; + + event = &connection->event; + + if (event->mask & MK_EVENT_READ) { + result = flb_http_server_session_read(session); + + if (result != 0) { + flb_http_server_session_destroy(session); + + return -1; + } + } + + close_connection = FLB_FALSE; + + cfl_list_foreach_safe(iterator, + backup_iterator, + &session->request_queue) { + request = cfl_list_entry(iterator, struct flb_http_request, _head); + + stream = (struct flb_http_stream *) request->stream; + + response = flb_http_response_begin(session, stream); + + if (request->body != NULL && request->content_length == 0) { + request->content_length = cfl_sds_len(request->body); + } + + result = flb_http_server_inflate_request_body(request); + + if (result != 0) { + flb_http_server_session_destroy(session); + + return -1; + } + + if (server->request_callback != NULL) { + result = server->request_callback(request, response); + } + else { + /* Report */ + } + + close_connection = flb_http_server_should_connection_be_closed(request); + + flb_http_request_destroy(&stream->request); + flb_http_response_destroy(&stream->response); + } + + result = flb_http_server_session_write(session); + + if (result != 0) { + flb_http_server_session_destroy(session); + + return -4; + } + + if (close_connection) { + flb_http_server_session_destroy(session); + } + + return 0; +} + +static int flb_http_server_client_connection_event_handler(void *data) +{ + struct flb_connection *connection; + struct flb_http_server_session *session; + struct flb_http_server *server; + int result; + + server = (struct flb_http_server *) data; + + connection = flb_downstream_conn_get(server->downstream); + + if (connection == NULL) { + return -1; + } + + session = flb_http_server_session_create(server->protocol_version); + + if (session == NULL) { + flb_downstream_conn_release(connection); + + return -2; + } + + session->parent = server; + session->connection = connection; + + MK_EVENT_NEW(&connection->event); + + connection->user_data = (void *) session; + connection->event.type = FLB_ENGINE_EV_CUSTOM; + connection->event.handler = flb_http_server_client_activity_event_handler; + + result = mk_event_add(server->event_loop, + connection->fd, + FLB_ENGINE_EV_CUSTOM, + MK_EVENT_READ, + &connection->event); + + if (result == -1) { + flb_http_server_session_destroy(session); + + return -3; + } + + cfl_list_add(&session->_head, &server->clients); + + result = flb_http_server_session_write(session); + + if (result != 0) { + flb_http_server_session_destroy(session); + + return -4; + } + + return 0; +} + +/* HTTP SERVER */ + +int flb_http_server_init(struct flb_http_server *session, + int protocol_version, + uint64_t flags, + flb_http_server_request_processor_callback + request_callback, + char *address, + unsigned short int port, + struct flb_tls *tls_provider, + int networking_flags, + struct flb_net_setup *networking_setup, + struct mk_event_loop *event_loop, + struct flb_config *system_context, + void *user_data) +{ + session->status = HTTP_SERVER_UNINITIALIZED; + session->protocol_version = protocol_version; + session->flags = flags; + session->request_callback = request_callback; + session->user_data = user_data; + + session->address = address; + session->port = port; + session->tls_provider = tls_provider; + session->networking_flags = networking_flags; + session->networking_setup = networking_setup; + session->event_loop = event_loop; + session->system_context = system_context; + + session->downstream = NULL; + + cfl_list_init(&session->clients); + + MK_EVENT_NEW(&session->listener_event); + + session->status = HTTP_SERVER_INITIALIZED; + + return 0; +} + +int flb_http_server_start(struct flb_http_server *session) +{ + int result; + + if (session->tls_provider != NULL) { + result = flb_tls_set_alpn(session->tls_provider, "h2,http/1.0,http/1.1"); + + if (result != 0) { + return -1; + } + } + + session->downstream = flb_downstream_create(FLB_TRANSPORT_TCP, + session->networking_flags, + session->address, + session->port, + session->tls_provider, + session->system_context, + session->networking_setup); + + if (session->downstream == NULL) { + return -1; + } + + session->listener_event.type = FLB_ENGINE_EV_CUSTOM; + session->listener_event.handler = flb_http_server_client_connection_event_handler; + + /* Register instance into the event loop */ + result = mk_event_add(session->event_loop, + session->downstream->server_fd, + FLB_ENGINE_EV_CUSTOM, + MK_EVENT_READ, + &session->listener_event); + + if (result == -1) { + return -1; + } + + session->status = HTTP_SERVER_RUNNING; + + return 0; +} + +int flb_http_server_stop(struct flb_http_server *server) +{ + struct cfl_list *iterator_backup; + struct cfl_list *iterator; + struct flb_http_server_session *session; + + if (server->status == HTTP_SERVER_RUNNING) { + if (MK_EVENT_IS_REGISTERED((&server->listener_event))) { + mk_event_del(server->event_loop, &server->listener_event); + } + + mk_list_foreach_safe(iterator, iterator_backup, &server->clients) { + session = cfl_list_entry(iterator, + struct flb_http_server_session, + _head); + + flb_http_server_session_destroy(session); + } + + server->status = HTTP_SERVER_STOPPED; + } + + return 0; +} + +int flb_http_server_destroy(struct flb_http_server *server) +{ + flb_http_server_stop(server); + + if (server->downstream != NULL) { + flb_downstream_destroy(server->downstream); + + server->downstream = NULL; + } + + return 0; +} + +/* HTTP SESSION */ + +int flb_http_server_session_init(struct flb_http_server_session *session, int version) +{ + int result; + + memset(session, 0, sizeof(struct flb_http_server_session)); + + cfl_list_init(&session->request_queue); + cfl_list_entry_init(&session->_head); + + session->incoming_data = cfl_sds_create_size(HTTP_SERVER_INITIAL_BUFFER_SIZE); + + if (session->incoming_data == NULL) { + return -1; + } + + session->outgoing_data = cfl_sds_create_size(HTTP_SERVER_INITIAL_BUFFER_SIZE); + + if (session->outgoing_data == NULL) { + return -2; + } + + session->version = version; + + if (session->version == HTTP_PROTOCOL_HTTP2) { + result = flb_http2_server_session_init(&session->http2, session); + + if (result != 0) { + return -3; + } + } + else if (session->version == HTTP_PROTOCOL_HTTP1) { + result = flb_http1_server_session_init(&session->http1, session); + + if (result != 0) { + return -4; + } + } + + return 0; +} + +struct flb_http_server_session *flb_http_server_session_create(int version) +{ + struct flb_http_server_session *session; + int result; + + session = flb_calloc(1, sizeof(struct flb_http_server_session)); + + if (session != NULL) { + result = flb_http_server_session_init(session, version); + + session->releasable = FLB_TRUE; + + if (result != 0) { + flb_http_server_session_destroy(session); + + session = NULL; + } + } + + return session; +} + +void flb_http_server_session_destroy(struct flb_http_server_session *session) +{ + if (session != NULL) { + if (session->connection != NULL) { + flb_downstream_conn_release(session->connection); + } + + if (!cfl_list_entry_is_orphan(&session->_head)) { + cfl_list_del(&session->_head); + } + + if (session->incoming_data != NULL) { + cfl_sds_destroy(session->incoming_data); + } + + if (session->outgoing_data != NULL) { + cfl_sds_destroy(session->outgoing_data); + } + + flb_http1_server_session_destroy(&session->http1); + flb_http2_server_session_destroy(&session->http2); + + if (session->releasable) { + flb_free(session); + } + } +} + +int flb_http_server_session_ingest(struct flb_http_server_session *session, + unsigned char *buffer, + size_t length) +{ + cfl_sds_t resized_buffer; + int result; + + if (session->version == HTTP_PROTOCOL_AUTODETECT || + session->version == HTTP_PROTOCOL_HTTP1) { + resized_buffer = cfl_sds_cat(session->incoming_data, + (const char *) buffer, + length); + + if (resized_buffer == NULL) { + return HTTP_SERVER_ALLOCATION_ERROR; + } + + session->incoming_data = resized_buffer; + } + + if (session->version == HTTP_PROTOCOL_AUTODETECT) { + if (cfl_sds_len(session->incoming_data) >= 24) { + if (strncmp(session->incoming_data, + "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", + 24) == 0) { + session->version = HTTP_PROTOCOL_HTTP2; + } + else { + session->version = HTTP_PROTOCOL_HTTP1; + } + } + else if (cfl_sds_len(session->incoming_data) >= 4) { + if (strncmp(session->incoming_data, "PRI ", 4) != 0) { + session->version = HTTP_PROTOCOL_HTTP1; + } + } + + if (session->version == HTTP_PROTOCOL_HTTP1) { + result = flb_http1_server_session_init(&session->http1, session); + + if (result != 0) { + return -1; + } + } + else if (session->version == HTTP_PROTOCOL_HTTP2) { + result = flb_http2_server_session_init(&session->http2, session); + + if (result != 0) { + return -1; + } + } + } + + if (session->version == HTTP_PROTOCOL_HTTP1) { + return flb_http1_server_session_ingest(&session->http1, + buffer, + length); + } + else if (session->version == HTTP_PROTOCOL_HTTP2) { + return flb_http2_server_session_ingest(&session->http2, + buffer, + length); + } + + return -1; +} + + +/* PRIVATE */ + +static \ +int uncompress_zlib(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + return 0; +} + +static \ +int uncompress_zstd(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + return 0; +} + +static \ +int uncompress_deflate(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + return 0; +} + +static \ +int uncompress_snappy(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + int ret; + + ret = flb_snappy_uncompress_framed_data(input_buffer, + input_size, + output_buffer, + output_size); + + if (ret != 0) { + flb_error("[opentelemetry] snappy decompression failed"); + + return -1; + } + + return 1; +} + +static \ +int uncompress_gzip(char **output_buffer, + size_t *output_size, + char *input_buffer, + size_t input_size) +{ + int ret; + + ret = flb_gzip_uncompress(input_buffer, + input_size, + (void *) output_buffer, + output_size); + + if (ret == -1) { + flb_error("[opentelemetry] gzip decompression failed"); + + return -1; + } + + return 1; +} + diff --git a/src/http_server/flb_http_server_http1.c b/src/http_server/flb_http_server_http1.c new file mode 100644 index 00000000000..334a2274e83 --- /dev/null +++ b/src/http_server/flb_http_server_http1.c @@ -0,0 +1,499 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/* PRIVATE */ + +static void dummy_mk_http_session_init(struct mk_http_session *session, + struct mk_server *server) +{ + session->_sched_init = MK_TRUE; + session->pipelined = MK_FALSE; + session->counter_connections = 0; + session->close_now = MK_FALSE; + session->status = MK_REQUEST_STATUS_INCOMPLETE; + session->server = server; + session->socket = -1; + + /* creation time in unix time */ + session->init_time = time(NULL); + + session->channel = mk_channel_new(MK_CHANNEL_SOCKET, -1); + session->channel->io = session->server->network; + + /* Init session request list */ + mk_list_init(&session->request_list); + + /* Initialize the parser */ + mk_http_parser_init(&session->parser); +} + +static void dummy_mk_http_request_init(struct mk_http_session *session, + struct mk_http_request *request) +{ + memset(request, 0, sizeof(struct mk_http_request)); + + mk_http_request_init(session, request, session->server); + + request->in_headers.type = MK_STREAM_IOV; + request->in_headers.dynamic = MK_FALSE; + request->in_headers.cb_consumed = NULL; + request->in_headers.cb_finished = NULL; + request->in_headers.stream = &request->stream; + + mk_list_add(&request->in_headers._head, &request->stream.inputs); + + request->session = session; +} + +static int http1_evict_request(struct flb_http1_server_session *session) +{ + uintptr_t session_buffer_upper_bound; + uintptr_t session_buffer_lower_bound; + size_t session_buffer_length; + cfl_sds_t session_buffer; + size_t content_length; + size_t request_length; + uintptr_t request_end; + + request_end = 0; + content_length = 0; + session_buffer = session->parent->incoming_data; + + if (session_buffer == NULL) { + return -1; + } + + session_buffer_length = cfl_sds_len(session_buffer); + + if (session->inner_request.data.data != NULL) { + content_length = session->inner_request.data.len; + + request_end = (uintptr_t) session->inner_request.data.data; + request_end += content_length; + } + else { + request_end = (uintptr_t) strstr(session_buffer, + "\r\n\r\n"); + + if(request_end != 0) { + request_end += 4; + } + } + + if (request_end != 0) { + session_buffer_lower_bound = (uintptr_t) session_buffer; + session_buffer_upper_bound = (uintptr_t) &session_buffer[session_buffer_length]; + + if (request_end < session_buffer_lower_bound || + request_end > session_buffer_upper_bound) { + return -1; + } + + request_length = (size_t) (request_end - session_buffer_lower_bound); + + if (request_length == session_buffer_length) { + session_buffer_length = 0; + } + else { + session_buffer_length -= request_length; + + memmove(session_buffer, + &session_buffer[request_length], + session_buffer_length); + + session_buffer[session_buffer_length] = '\0'; + } + + cfl_sds_set_len(session_buffer, session_buffer_length); + } + + return 0; +} + +static int http1_session_process_request(struct flb_http1_server_session *session) +{ + struct mk_list *iterator; + struct mk_http_header *header; + int result; + + if (session->inner_request.uri_processed.data != NULL) { + session->stream.request.path = \ + cfl_sds_create_len(session->inner_request.uri_processed.data, + session->inner_request.uri_processed.len); + } + else { + session->stream.request.path = \ + cfl_sds_create_len(session->inner_request.uri.data, + session->inner_request.uri.len); + } + + if (session->stream.request.path == NULL) { + return -1; + } + + switch (session->inner_request.protocol) { + case MK_HTTP_PROTOCOL_09: + session->stream.request.protocol_version = HTTP_PROTOCOL_VERSION_09; + break; + case MK_HTTP_PROTOCOL_10: + session->stream.request.protocol_version = HTTP_PROTOCOL_VERSION_10; + break; + case MK_HTTP_PROTOCOL_11: + session->stream.request.protocol_version = HTTP_PROTOCOL_VERSION_11; + break; + default: + session->stream.request.protocol_version = HTTP_PROTOCOL_VERSION_10; + } + + switch (session->inner_request.method) { + case MK_METHOD_GET: + session->stream.request.method = HTTP_METHOD_GET; + break; + case MK_METHOD_POST: + session->stream.request.method = HTTP_METHOD_POST; + break; + case MK_METHOD_HEAD: + session->stream.request.method = HTTP_METHOD_HEAD; + break; + case MK_METHOD_PUT: + session->stream.request.method = HTTP_METHOD_PUT; + break; + case MK_METHOD_DELETE: + session->stream.request.method = HTTP_METHOD_DELETE; + break; + case MK_METHOD_OPTIONS: + session->stream.request.method = HTTP_METHOD_OPTIONS; + break; + default: + session->stream.request.method = HTTP_METHOD_UNKNOWN; + break; + } + + session->stream.request.content_length = session->inner_request.content_length; + + mk_list_foreach(iterator, + &session->inner_parser.header_list) { + header = mk_list_entry(iterator, struct mk_http_header, _head); + + if (header->key.data != NULL && header->key.len > 0 && + header->val.data != NULL && header->val.len > 0) { + + if (flb_http_server_strncasecmp( + (const uint8_t *) header->key.data, + header->key.len, + "host", 0) == 0) { + session->stream.request.host = \ + cfl_sds_create_len((const char *) header->val.data, + header->val.len); + + if (session->stream.request.host == NULL) { + return -1; + } + } + else if (flb_http_server_strncasecmp( + (const uint8_t *) header->key.data, + header->key.len, + "content-type", 0) == 0) { + session->stream.request.content_type = \ + cfl_sds_create_len((const char *) header->val.data, + header->val.len); + + if (session->stream.request.content_type == NULL) { + return -1; + } + } + + result = flb_http_request_set_header(&session->stream.request, + header->key.data, + header->key.len, + (void *) header->val.data, + header->val.len); + + if (result != 0) { + return -1; + } + } + } + + if (session->stream.request.host == NULL) { + session->stream.request.host = cfl_sds_create(""); + + if (session->stream.request.host == NULL) { + return -1; + } + } + + if (session->inner_request.data.data != NULL) { + session->stream.request.body = \ + cfl_sds_create_len(session->inner_request.data.data, + session->inner_request.data.len); + + if (session->stream.request.body == NULL) { + session->stream.status = HTTP_STREAM_STATUS_ERROR; + + return -1; + } + } + + session->stream.status = HTTP_STREAM_STATUS_READY; + + if (!cfl_list_entry_is_orphan(&session->stream.request._head)) { + cfl_list_del(&session->stream.request._head); + } + + cfl_list_add(&session->stream.request._head, + &session->parent->request_queue); + + return 0; +} + +/* RESPONSE */ + +struct flb_http_response *flb_http1_response_begin( + struct flb_http1_server_session *session, + struct flb_http_stream *stream) +{ + int result; + + result = flb_http_response_init(&stream->response); + + if (result != 0) { + return NULL; + } + + stream->response.stream = stream; + + return &stream->response; +} + +int flb_http1_response_commit(struct flb_http_response *response) +{ + struct mk_list *header_iterator; + cfl_sds_t response_buffer; + struct flb_http_server_session *parent_session; + struct flb_hash_table_entry *header_entry; + cfl_sds_t sds_result; + struct flb_http1_server_session *session; + struct flb_http_stream *stream; + + parent_session = (struct flb_http_server_session *) response->stream->parent; + + if (parent_session == NULL) { + return -1; + } + + session = &parent_session->http1; + + if (session == NULL) { + return -1; + } + + stream = (struct flb_http_stream *) response->stream; + + if (stream == NULL) { + return -2; + } + + response_buffer = cfl_sds_create_size(128); + + if (response_buffer == NULL) { + return -3; + } + + if (response->message != NULL) { + sds_result = cfl_sds_printf(&response_buffer, "HTTP/1.1 %d %s\r\n", response->status, response->message); + } + else { + sds_result = cfl_sds_printf(&response_buffer, "HTTP/1.1 %d\r\n", response->status); + } + + if (sds_result == NULL) { + cfl_sds_destroy(response_buffer); + + return -4; + } + + mk_list_foreach(header_iterator, &response->headers->entries) { + header_entry = mk_list_entry(header_iterator, + struct flb_hash_table_entry, + _head_parent); + + if (header_entry == NULL) { + cfl_sds_destroy(response_buffer); + + return -5; + } + + sds_result = cfl_sds_printf(&response_buffer, + "%.*s: %.*s\r\n", + (int) header_entry->key_len, + (const char *) header_entry->key, + (int) header_entry->val_size, + (const char *) header_entry->val); + + if (sds_result == NULL) { + cfl_sds_destroy(response_buffer); + + return -6; + } + } + + sds_result = cfl_sds_cat(response_buffer, "\r\n", 2); + + if (sds_result == NULL) { + cfl_sds_destroy(response_buffer); + + return -7; + } + + if (response->body != NULL) { + sds_result = cfl_sds_cat(response_buffer, + response->body, + cfl_sds_len(response->body)); + + if (sds_result == NULL) { + cfl_sds_destroy(response_buffer); + + return -8; + } + + response_buffer = sds_result; + } + + sds_result = cfl_sds_cat(session->parent->outgoing_data, + response_buffer, + cfl_sds_len(response_buffer)); + + cfl_sds_destroy(response_buffer); + + if (sds_result == NULL) { + return -9; + } + + session->parent->outgoing_data = sds_result; + + return 0; +} + + +int flb_http1_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length) +{ + int result; + + result = flb_hash_table_add(response->headers, + (const char *) name, (int) name_length, + (void *) value, (ssize_t) value_length); + + if (result < 0) { + return -1; + } + + return 0; +} + +int flb_http1_response_set_status(struct flb_http_response *response, + int status) +{ + return 0; +} + +int flb_http1_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length) +{ + return 0; +} + +/* SESSION */ + +int flb_http1_server_session_init(struct flb_http1_server_session *session, + struct flb_http_server_session *parent) +{ + void *user_data; + int result; + + if (parent != NULL && parent->parent != NULL) { + user_data = parent->parent->user_data; + } + else { + user_data = NULL; + } + + session->initialized = FLB_TRUE; + + dummy_mk_http_session_init(&session->inner_session, &session->inner_server); + + dummy_mk_http_request_init(&session->inner_session, &session->inner_request); + + mk_http_parser_init(&session->inner_parser); + + result = flb_http_stream_init(&session->stream, parent, 0, HTTP_STREAM_ROLE_SERVER, + user_data); + + if (result != 0) { + return -1; + } + + session->parent = parent; + + return 0; +} + +void flb_http1_server_session_destroy(struct flb_http1_server_session *session) +{ + if (session->initialized) { + if (session->inner_session.channel != NULL) { + mk_channel_release(session->inner_session.channel); + + session->inner_session.channel = NULL; + } + + session->initialized = FLB_FALSE; + } +} + +int flb_http1_server_session_ingest(struct flb_http1_server_session *session, + unsigned char *buffer, + size_t length) +{ + int result; + + result = mk_http_parser(&session->inner_request, + &session->inner_parser, + session->parent->incoming_data, + cfl_sds_len(session->parent->incoming_data), + &session->inner_server); + + if (result == MK_HTTP_PARSER_OK) { + result = http1_session_process_request(session); + + if (result != 0) { + session->stream.status = HTTP_STREAM_STATUS_ERROR; + + return HTTP_SERVER_PROVIDER_ERROR; + } + + http1_evict_request(session); + } + + return HTTP_SERVER_SUCCESS; +} diff --git a/src/http_server/flb_http_server_http2.c b/src/http_server/flb_http_server_http2.c new file mode 100644 index 00000000000..dc9c106664a --- /dev/null +++ b/src/http_server/flb_http_server_http2.c @@ -0,0 +1,862 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +/* PRIVATE */ +static ssize_t http2_send_callback(nghttp2_session *inner_session, + const uint8_t *data, + size_t length, + int flags, + void *user_data); + +static int http2_header_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + const uint8_t *name, + size_t namelen, + const uint8_t *value, + size_t valuelen, + uint8_t flags, + void *user_data); + +static int http2_frame_recv_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + void *user_data); + +static int http2_stream_close_callback(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data); + +static int http2_begin_headers_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + void *user_data); + +static int http2_data_chunk_recv_callback(nghttp2_session *inner_session, + uint8_t flags, + int32_t stream_id, + const uint8_t *data, + size_t len, + void *user_data); + +static ssize_t http2_data_source_read_callback(nghttp2_session *session, + int32_t stream_id, + uint8_t *buf, + size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data); + +static inline size_t http2_lower_value(size_t left_value, size_t right_value) +{ + if (left_value < right_value) { + return left_value; + } + + return right_value; +} + +/* RESPONSE */ + +struct flb_http_response *flb_http2_response_begin( + struct flb_http2_server_session *session, + struct flb_http_stream *stream) +{ + + int result; + + result = flb_http_response_init(&stream->response); + + if (result != 0) { + return NULL; + } + + stream->response.stream = stream; + + return &stream->response; +} + +int flb_http2_response_set_header(struct flb_http_response *response, + char *name, size_t name_length, + char *value, size_t value_length) +{ + int result; + + result = flb_hash_table_add(response->headers, + (const char *) name, (int) name_length, + (void *) value, (ssize_t) value_length); + + if (result < 0) { + return -1; + } + + return 0; +} + +int flb_http2_response_set_status(struct flb_http_response *response, + int status) +{ + return 0; +} + +int flb_http2_response_set_body(struct flb_http_response *response, + unsigned char *body, size_t body_length) +{ + return 0; +} + +/* +int flb_http2_response_commit(struct flb_http_response *response) +{ + char status_as_text[16]; + struct mk_list *header_iterator; + struct flb_http_server_session *parent_session; + nghttp2_data_provider data_provider; + size_t header_count; + size_t header_index; + struct flb_hash_table_entry *header_entry; + nghttp2_nv *headers; + struct flb_http2_server_session *session; + struct flb_http_stream *stream; + int result; + + FLB_HTTP_STREAM_GET_SESSION(response->stream, &parent_session); + + if (parent_session == NULL) { + return -1; + } + + session = &parent_session->http2; + + if (session == NULL) { + return -1; + } + + stream = (struct flb_http_stream *) response->stream; + + if (stream == NULL) { + return -2; + } + + header_count = response->headers->total_count + 1; + + headers = flb_calloc(header_count, sizeof(nghttp2_nv)); + + if (headers == NULL) { + return -3; + } + + snprintf(status_as_text, + sizeof(status_as_text) - 1, + "%d", + response->status); + + headers[0].name = (uint8_t *) ":status"; + headers[0].namelen = strlen(":status"); + headers[0].value = (uint8_t *) status_as_text; + headers[0].valuelen = strlen(status_as_text); + + header_index = 1; + + mk_list_foreach(header_iterator, &response->headers->entries) { + header_entry = mk_list_entry(header_iterator, + struct flb_hash_table_entry, + _head_parent); + + if (header_entry == NULL) { + return -4; + } + + headers[header_index].name = (uint8_t *) header_entry->key; + headers[header_index].namelen = header_entry->key_len; + headers[header_index].value = (uint8_t *) header_entry->val; + headers[header_index].valuelen = header_entry->val_size; + + header_index++; + } + + data_provider.source.fd = 0; + data_provider.read_callback = http2_data_source_read_callback; + + stream->status = HTTP_STREAM_STATUS_PROCESSING; + + result = nghttp2_submit_response(session->inner_session, + stream->id, + headers, + header_count, + &data_provider); + + flb_free(headers); + + if (result != 0) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -5; + } + + result = nghttp2_session_send(session->inner_session); + + if (result != 0) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -6; + } + + stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; + + flb_http_response_destroy(&stream->response); + + return 0; +} +*/ +int flb_http2_response_commit(struct flb_http_response *response) +{ + size_t trailer_header_count; + char status_as_text[16]; + struct mk_list *header_iterator; + nghttp2_nv *trailer_headers; + struct flb_http_server_session *parent_session; + nghttp2_data_provider data_provider; + size_t header_count; + size_t header_index; + struct flb_hash_table_entry *header_entry; + nghttp2_nv *headers; + struct flb_http2_server_session *session; + struct flb_http_stream *stream; + int result; + + parent_session = (struct flb_http_server_session *) response->stream->parent; + + if (parent_session == NULL) { + return -1; + } + + session = &parent_session->http2; + + if (session == NULL) { + return -1; + } + + stream = (struct flb_http_stream *) response->stream; + + if (stream == NULL) { + return -2; + } + + header_count = response->headers->total_count + 1; + + headers = flb_calloc(header_count, sizeof(nghttp2_nv)); + + if (headers == NULL) { + return -3; + } + + snprintf(status_as_text, + sizeof(status_as_text) - 1, + "%d", + response->status); + + headers[0].name = (uint8_t *) ":status"; + headers[0].namelen = strlen(":status"); + headers[0].value = (uint8_t *) status_as_text; + headers[0].valuelen = strlen(status_as_text); + + header_index = 1; + + mk_list_foreach(header_iterator, &response->headers->entries) { + header_entry = mk_list_entry(header_iterator, + struct flb_hash_table_entry, + _head_parent); + + if (header_entry == NULL) { + flb_free(headers); + + return -4; + } + + headers[header_index].name = (uint8_t *) header_entry->key; + headers[header_index].namelen = header_entry->key_len; + headers[header_index].value = (uint8_t *) header_entry->val; + headers[header_index].valuelen = header_entry->val_size; + + if (headers[header_index].value[0] == '\0') { + headers[header_index].valuelen = 0; + } + + header_index++; + } + + data_provider.source.fd = 0; + data_provider.read_callback = http2_data_source_read_callback; + + stream->status = HTTP_STREAM_STATUS_PROCESSING; + + result = nghttp2_submit_response(session->inner_session, + stream->id, + headers, + header_count, + &data_provider); + + if (result != 0) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + flb_free(headers); + + return -5; + } + + result = nghttp2_session_send(session->inner_session); + + if (mk_list_is_empty(&response->trailer_headers->entries) != 0) { + trailer_header_count = response->trailer_headers->total_count; + + trailer_headers = flb_calloc(trailer_header_count, sizeof(nghttp2_nv)); + + if (trailer_headers == NULL) { + flb_free(headers); + + return -6; + } + + header_index = 0; + + mk_list_foreach(header_iterator, &response->trailer_headers->entries) { + header_entry = mk_list_entry(header_iterator, + struct flb_hash_table_entry, + _head_parent); + + if (header_entry == NULL) { + flb_free(trailer_headers); + flb_free(headers); + + return -7; + } + + trailer_headers[header_index].name = (uint8_t *) header_entry->key; + trailer_headers[header_index].namelen = header_entry->key_len; + trailer_headers[header_index].value = (uint8_t *) header_entry->val; + trailer_headers[header_index].valuelen = header_entry->val_size; + + if (trailer_headers[header_index].value[0] == '\0') { + trailer_headers[header_index].valuelen = 0; + } + + header_index++; + } + + result = nghttp2_submit_trailer(session->inner_session, + stream->id, + trailer_headers, + trailer_header_count); + } + else { + trailer_headers = NULL; + } + + result = nghttp2_session_send(session->inner_session); + + if (trailer_headers != NULL) { + flb_free(trailer_headers); + } + + flb_free(headers); + + if (result != 0) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -8; + } + + stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; + + return 0; +} + +/* SESSION */ + +int flb_http2_server_session_init(struct flb_http2_server_session *session, + struct flb_http_server_session *parent) +{ + nghttp2_settings_entry session_settings[1]; + nghttp2_session_callbacks *callbacks; + int result; + + session->parent = parent; + session->initialized = FLB_TRUE; + session->inner_session = NULL; + + cfl_list_init(&session->streams); + + result = nghttp2_session_callbacks_new(&callbacks); + + if (result != 0) { + return -1; + } + + nghttp2_session_callbacks_set_send_callback(callbacks, http2_send_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, http2_frame_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, http2_stream_close_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, http2_begin_headers_callback); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, http2_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_header_callback(callbacks, http2_header_callback); + + result = nghttp2_session_server_new(&session->inner_session, callbacks, session); + + nghttp2_session_callbacks_del(callbacks); + + if (result != 0) { + return -2; + } + + session_settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + session_settings[0].value = 1; + + result = nghttp2_submit_settings(session->inner_session, + NGHTTP2_FLAG_NONE, + session_settings, + 1); + + if (result != 0) { + return -3; + } + + result = nghttp2_session_send(session->inner_session); + + if (result != 0) { + return -4; + } + + return 0; +} + +void flb_http2_server_session_destroy(struct flb_http2_server_session *session) +{ + struct cfl_list *iterator_backup; + struct cfl_list *iterator; + struct flb_http_stream *stream; + + if (session != NULL) { + if (session->initialized) { + cfl_list_foreach_safe(iterator, + iterator_backup, + &session->streams) { + stream = cfl_list_entry(iterator, struct flb_http_stream, _head); + + flb_http_stream_destroy(stream); + } + + nghttp2_session_del(session->inner_session); + + session->initialized = FLB_FALSE; + } + } +} + +int flb_http2_server_session_ingest(struct flb_http2_server_session *session, + unsigned char *buffer, + size_t length) +{ + ssize_t result; + + result = nghttp2_session_mem_recv(session->inner_session, buffer, length); + + if (result < 0) { + return HTTP_SERVER_PROVIDER_ERROR; + } + + return HTTP_SERVER_SUCCESS; +} + + +/* PRIVATE */ + +static ssize_t http2_send_callback(nghttp2_session *inner_session, + const uint8_t *data, + size_t length, + int flags, + void *user_data) +{ + cfl_sds_t resized_buffer; + struct flb_http2_server_session *session; + + session = (struct flb_http2_server_session *) user_data; + + resized_buffer = cfl_sds_cat(session->parent->outgoing_data, + (const char *) data, + length); + + if (resized_buffer == NULL) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + session->parent->outgoing_data = resized_buffer; + + return length; +} + +static int http2_header_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + const uint8_t *name, + size_t name_length, + const uint8_t *value, + size_t value_length, + uint8_t flags, + void *user_data) +{ + char temporary_buffer[16]; + struct flb_http_stream *stream; + int result; + + stream = nghttp2_session_get_stream_user_data(inner_session, + frame->hd.stream_id); + + if (stream == NULL) { + return 0; + } + + if (flb_http_server_strncasecmp(name, name_length, ":method", 0) == 0) { + strncpy(temporary_buffer, + (const char *) value, + http2_lower_value(sizeof(temporary_buffer), value_length + 1)); + + temporary_buffer[sizeof(temporary_buffer) - 1] = '\0'; + + if (strcasecmp(temporary_buffer, "GET") == 0) { + stream->request.method = HTTP_METHOD_GET; + } + else if (strcasecmp(temporary_buffer, "POST") == 0) { + stream->request.method = HTTP_METHOD_POST; + } + else if (strcasecmp(temporary_buffer, "HEAD") == 0) { + stream->request.method = HTTP_METHOD_HEAD; + } + else if (strcasecmp(temporary_buffer, "PUT") == 0) { + stream->request.method = HTTP_METHOD_PUT; + } + else if (strcasecmp(temporary_buffer, "DELETE") == 0) { + stream->request.method = HTTP_METHOD_DELETE; + } + else if (strcasecmp(temporary_buffer, "OPTIONS") == 0) { + stream->request.method = HTTP_METHOD_OPTIONS; + } + else { + stream->request.method = HTTP_METHOD_UNKNOWN; + } + } + else if (flb_http_server_strncasecmp(name, name_length, ":path", 0) == 0) { + stream->request.path = cfl_sds_create_len((const char *) value, value_length); + + if (stream->request.path == NULL) { + return -1; + } + } + else if (flb_http_server_strncasecmp( + name, name_length, ":authority", 0) == 0) { + + stream->request.host = cfl_sds_create_len((const char *) value, value_length); + + if (stream->request.host == NULL) { + return -1; + } + + result = flb_hash_table_add(stream->request.headers, + "host", 4, + (void *) value, value_length); + + if (result < 0) { + return -1; + } + } + else if (flb_http_server_strncasecmp( + name, name_length, "content-type", 0) == 0) { + + stream->request.content_type = cfl_sds_create_len((const char *) value, value_length); + + if (stream->request.content_type == NULL) { + return -1; + } + } + else if (flb_http_server_strncasecmp( + name, name_length, "content-length", 0) == 0) { + strncpy(temporary_buffer, + (const char *) value, + http2_lower_value(sizeof(temporary_buffer), value_length + 1)); + + temporary_buffer[sizeof(temporary_buffer) - 1] = '\0'; + + stream->request.content_length = strtoull(temporary_buffer, NULL, 10); + } + + result = flb_http_request_set_header(&stream->request, + (char *) name, + name_length, + (void *) value, + value_length); + + if (result != 0) { + return -1; + } + + return 0; +} + +static int http2_frame_recv_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + void *user_data) +{ + struct flb_http_server_session *parent_session; + struct flb_http_stream *stream; + + stream = nghttp2_session_get_stream_user_data(inner_session, + frame->hd.stream_id); + + if (stream == NULL) { + return 0; + } + + switch (frame->hd.type) { + case NGHTTP2_CONTINUATION: + case NGHTTP2_HEADERS: + if ((frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) != 0) { + stream->status = HTTP_STREAM_STATUS_RECEIVING_DATA; + } + else { + stream->status = HTTP_STREAM_STATUS_RECEIVING_HEADERS; + } + + break; + default: + break; + } + + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) { + stream->status = HTTP_STREAM_STATUS_READY; + + if (!cfl_list_entry_is_orphan(&stream->request._head)) { + cfl_list_del(&stream->request._head); + } + + parent_session = (struct flb_http_server_session *) stream->parent; + + if (parent_session == NULL) { + return -1; + } + + cfl_list_add(&stream->request._head, + &parent_session->request_queue); + } + + return 0; +} + +static int http2_stream_close_callback(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data) +{ + struct flb_http_stream *stream; + + stream = nghttp2_session_get_stream_user_data(session, stream_id); + + if (stream == NULL) { + return 0; + } + + stream->status = HTTP_STREAM_STATUS_CLOSED; + + return 0; +} + +static int http2_begin_headers_callback(nghttp2_session *inner_session, + const nghttp2_frame *frame, + void *inner_user_data) +{ + void *user_data; + struct flb_http2_server_session *session; + struct flb_http_stream *stream; + + session = (struct flb_http2_server_session *) inner_user_data; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + if (session->parent != NULL && session->parent->parent != NULL) { + user_data = session->parent->parent->user_data; + } + else { + user_data = NULL; + } + + stream = flb_http_stream_create(session->parent, + frame->hd.stream_id, + HTTP_STREAM_ROLE_SERVER, + user_data); + + if (stream == NULL) { + return -1; + } + + stream->request.protocol_version = HTTP_PROTOCOL_VERSION_20; + + cfl_list_add(&stream->_head, &session->streams); + + nghttp2_session_set_stream_user_data(inner_session, + frame->hd.stream_id, + stream); + + return 0; +} + +static int http2_data_chunk_recv_callback(nghttp2_session *inner_session, + uint8_t flags, + int32_t stream_id, + const uint8_t *data, + size_t len, + void *user_data) +{ + struct flb_http_server_session *parent_session; + cfl_sds_t resized_buffer; + struct flb_http_stream *stream; + + stream = nghttp2_session_get_stream_user_data(inner_session, stream_id); + + if (stream == NULL) { + return 0; + } + + if (stream->status != HTTP_STREAM_STATUS_RECEIVING_DATA) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -1; + } + + if (stream->request.body == NULL) { + stream->request.body = cfl_sds_create_size(len); + + if (stream->request.body == NULL) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -1; + } + + memcpy(stream->request.body, data, len); + + cfl_sds_set_len(stream->request.body, len); + } + else { + resized_buffer = cfl_sds_cat(stream->request.body, + (const char *) data, + len); + + if (resized_buffer == NULL) { + stream->status = HTTP_STREAM_STATUS_ERROR; + + return -1; + } + + stream->request.body = resized_buffer; + } + + if (stream->status == HTTP_STREAM_STATUS_RECEIVING_DATA) { + if (stream->request.content_length == cfl_sds_len(stream->request.body)) { + stream->status = HTTP_STREAM_STATUS_READY; + + if (!cfl_list_entry_is_orphan(&stream->request._head)) { + cfl_list_del(&stream->request._head); + } + + parent_session = (struct flb_http_server_session *) stream->parent; + + if (parent_session == NULL) { + return -1; + } + + cfl_list_add(&stream->request._head, + &parent_session->request_queue); + } + } + + return 0; +} + +static ssize_t http2_data_source_read_callback(nghttp2_session *session, + int32_t stream_id, + uint8_t *buf, + size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) +{ + size_t content_length; + size_t body_offset; + struct flb_http_stream *stream; + ssize_t result; + + stream = nghttp2_session_get_stream_user_data(session, + stream_id); + + if (stream == NULL) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if (stream->response.body != NULL) { + body_offset = stream->response.body_read_offset; + content_length = cfl_sds_len(stream->response.body) - body_offset; + } + else { + body_offset = 0; + content_length = 0; + } + + if (content_length > length) { + memcpy(buf, + &stream->response.body[body_offset], length); + + result = length; + + stream->response.body_read_offset += length; + } + else { + if (content_length > 0) { + memcpy(buf, stream->response.body, content_length); + + stream->response.body_read_offset += content_length; + } + + result = content_length; + + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + if (mk_list_is_empty(&stream->response.trailer_headers->entries) != 0) { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + } + + return result; +} + diff --git a/src/tls/flb_tls.c b/src/tls/flb_tls.c index 8fc711ab389..8510381a850 100644 --- a/src/tls/flb_tls.c +++ b/src/tls/flb_tls.c @@ -222,6 +222,16 @@ int flb_tls_destroy(struct flb_tls *tls) return 0; } +int flb_tls_set_alpn(struct flb_tls *tls, const char *alpn) +{ + if (tls->ctx) { + return tls->api->context_alpn_set(tls->ctx, alpn); + } + + return 0; +} + + int flb_tls_net_read(struct flb_tls_session *session, void *buf, size_t len) { time_t timeout_timestamp; diff --git a/src/tls/openssl.c b/src/tls/openssl.c index 341cf698bbf..14b77e09c6a 100644 --- a/src/tls/openssl.c +++ b/src/tls/openssl.c @@ -26,6 +26,11 @@ #include #include +#ifdef FLB_SYSTEM_WINDOWS + #define strtok_r(str, delimiter, context) \ + strtok_s(str, delimiter, context) +#endif + /* * OPENSSL_VERSION_NUMBER has the following semantics * @@ -39,6 +44,7 @@ struct tls_context { int debug_level; SSL_CTX *ctx; int mode; + char *alpn; pthread_mutex_t mutex; }; @@ -122,11 +128,113 @@ static void tls_context_destroy(void *ctx_backend) pthread_mutex_lock(&ctx->mutex); SSL_CTX_free(ctx->ctx); + if (ctx->alpn != NULL) { + flb_free(ctx->alpn); + } pthread_mutex_unlock(&ctx->mutex); flb_free(ctx); } +int tls_context_alpn_set(void *ctx_backend, const char *alpn) +{ + size_t wire_format_alpn_index; + char *alpn_token_context; + char *alpn_working_copy; + char *wire_format_alpn; + char *alpn_token; + int result; + struct tls_context *ctx; + + ctx = (struct tls_context *) ctx_backend; + + result = 0; + + if (alpn != NULL) { + wire_format_alpn = flb_calloc(strlen(alpn), + sizeof(char) + 1); + + if (wire_format_alpn == NULL) { + return -1; + } + + alpn_working_copy = strdup(alpn); + + if (alpn_working_copy == NULL) { + flb_free(wire_format_alpn); + + return -1; + } + + wire_format_alpn_index = 1; + alpn_token_context = NULL; + + alpn_token = strtok_r(alpn_working_copy, + ",", + &alpn_token_context); + + while (alpn_token != NULL) { + wire_format_alpn[wire_format_alpn_index] = \ + (char) strlen(alpn_token); + + strcpy(&wire_format_alpn[wire_format_alpn_index + 1], + alpn_token); + + wire_format_alpn_index += strlen(alpn_token) + 1; + + alpn_token = strtok_r(NULL, + ",", + &alpn_token_context); + } + + if (wire_format_alpn_index > 1) { + wire_format_alpn[0] = (char) wire_format_alpn_index - 1; + ctx->alpn = wire_format_alpn; + } + + free(alpn_working_copy); + } + + if (result != 0) { + result = -1; + } + + return result; +} + +static int tls_context_server_alpn_select_callback(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) +{ + int result; + struct tls_context *ctx; + + ctx = (struct tls_context *) arg; + + result = SSL_TLSEXT_ERR_NOACK; + + if (ctx->alpn != NULL) { + result = SSL_select_next_proto(out, + outlen, + &ctx->alpn[1], + (unsigned int) ctx->alpn[0], + in, + inlen); + + if (result == OPENSSL_NPN_NEGOTIATED) { + result = SSL_TLSEXT_ERR_OK; + } + else if (result == OPENSSL_NPN_NO_OVERLAP) { + result = SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + + return result; +} + #ifdef _MSC_VER static int windows_load_system_certificates(struct tls_context *ctx) { @@ -196,7 +304,7 @@ static int load_system_certificates(struct tls_context *ctx) } return 0; } - + static void *tls_context_create(int verify, int debug, int mode, @@ -242,6 +350,7 @@ static void *tls_context_create(int verify, ssl_ctx = SSL_CTX_new(TLS_client_method()); } #endif + if (!ssl_ctx) { flb_error("[openssl] could not create context"); return NULL; @@ -254,9 +363,16 @@ static void *tls_context_create(int verify, } ctx->ctx = ssl_ctx; ctx->mode = mode; + ctx->alpn = NULL; ctx->debug_level = debug; pthread_mutex_init(&ctx->mutex, NULL); + if (mode == FLB_TLS_SERVER_MODE) { + SSL_CTX_set_alpn_select_cb(ssl_ctx, + tls_context_server_alpn_select_callback, + ctx); + } + /* Verify peer: by default OpenSSL always verify peer */ if (verify == FLB_FALSE) { SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); @@ -388,6 +504,7 @@ static int tls_session_destroy(void *session) if (flb_socket_error(ptr->fd) == 0) { SSL_shutdown(ptr->ssl); } + SSL_free(ptr->ssl); flb_free(ptr); @@ -608,6 +725,7 @@ static struct flb_tls_backend tls_openssl = { .name = "openssl", .context_create = tls_context_create, .context_destroy = tls_context_destroy, + .context_alpn_set = tls_context_alpn_set, .session_create = tls_session_create, .session_destroy = tls_session_destroy, .net_read = tls_net_read, diff --git a/tests/internal/http_client.c b/tests/internal/http_client.c index 56c52b9653c..45e77e1b2ce 100644 --- a/tests/internal/http_client.c +++ b/tests/internal/http_client.c @@ -82,7 +82,7 @@ void test_http_buffer_increase() size_t s; struct test_ctx *ctx; struct flb_http_client *c; - struct flb_http_response *resp; + struct flb_http_client_response *resp; ctx = test_ctx_create(); if (!TEST_CHECK(ctx != NULL)) {