diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 07:32:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 07:32:08 +0000 |
commit | 328ad0a41c6bdf596224ff2e9ab9c0fabde8634d (patch) | |
tree | 973585a56cea8664b4be63b5bb737b443e4e2b76 | |
parent | Initial commit. (diff) | |
download | nghttp3-upstream.tar.xz nghttp3-upstream.zip |
Adding upstream version 0.8.0.upstream/0.8.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
133 files changed, 36260 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b4380bc --- /dev/null +++ b/.clang-format @@ -0,0 +1,191 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: NextLine +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: AfterHash +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000..86aa048 --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,5 @@ +FROM gcr.io/oss-fuzz-base/base-builder:v1 +RUN apt-get update && apt-get install -y make autoconf automake libtool pkg-config +COPY . $SRC/nghttp3 +WORKDIR nghttp3 +COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100755 index 0000000..5c68ce5 --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash -eu + +autoreconf -i +./configure +make -j$(nproc) + +$CXX $CXXFLAGS -std=c++17 -Ilib/includes \ + fuzz/fuzz_http3serverreq.cc -o $OUT/fuzz_http3serverreq \ + $LIB_FUZZING_ENGINE lib/.libs/libnghttp3.a + +$CXX $CXXFLAGS -std=c++17 -Ilib/includes \ + fuzz/fuzz_qpackdecoder.cc -o $OUT/fuzz_qpackdecoder \ + $LIB_FUZZING_ENGINE lib/.libs/libnghttp3.a + +zip -j $OUT/fuzz_http3serverreq_seed_corpus.zip fuzz/corpus/fuzz_http3serverreq/* +zip -j $OUT/fuzz_qpackdecoder_seed_corpus.zip fuzz/corpus/fuzz_qpackdecoder/* diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 0000000..b478801 --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1 @@ +language: c++ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a250041 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,153 @@ +name: build + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-22.04, macos-11] + compiler: [gcc, clang] + buildtool: [autotools, distcheck, cmake] + exclude: + - os: macos-11 + buildtool: distcheck + - compiler: gcc + buildtool: distcheck + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + - name: Linux setup + if: runner.os == 'Linux' + run: | + sudo apt-get install \ + g++-12 \ + clang-14 \ + autoconf \ + automake \ + autotools-dev \ + libtool \ + pkg-config \ + libcunit1-dev \ + cmake \ + cmake-data + - name: MacOS setup + if: runner.os == 'macOS' + run: | + brew install cunit autoconf automake pkg-config libtool + - name: Setup clang (Linux) + if: runner.os == 'Linux' && matrix.compiler == 'clang' + run: | + echo 'CC=clang-14' >> $GITHUB_ENV + echo 'CXX=clang++-14' >> $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: Configure autotools + if: matrix.buildtool == 'autotools' + run: | + autoreconf -i && ./configure --enable-werror + - name: Configure distcheck + if: matrix.buildtool == 'distcheck' + run: | + autoreconf -i && ./configure + - name: Configure cmake + if: matrix.buildtool == 'cmake' + run: | + autoreconf -i && ./configure + make dist + + VERSION=$(grep PACKAGE_VERSION config.h | cut -d' ' -f3 | tr -d '"') + tar xf nghttp3-$VERSION.tar.gz + cd nghttp3-$VERSION + mkdir build + cd build + + echo 'NGHTTP3_BUILD_DIR='"$PWD" >> $GITHUB_ENV + + cmake $CMAKE_OPTS .. + - name: Build nghttp3 + if: matrix.buildtool != 'distcheck' + run: | + [ -n "$NGHTTP3_BUILD_DIR" ] && cd "$NGHTTP3_BUILD_DIR" + make + make check + - name: Build nghttp3 with distcheck + if: matrix.buildtool == 'distcheck' + run: | + make distcheck + + 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@v3 + - 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" + - name: Build nghttp3 + 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: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + - name: Configure cmake + run: | + mkdir build + cd build + cmake -DENABLE_LIB_ONLY=ON .. + - name: Build nghttp3 + run: | + cmake --build build diff --git a/.github/workflows/cflite_batch.yaml b/.github/workflows/cflite_batch.yaml new file mode 100644 index 0000000..781b83e --- /dev/null +++ b/.github/workflows/cflite_batch.yaml @@ -0,0 +1,29 @@ +name: ClusterFuzzLite batch fuzzing +on: + schedule: + - cron: '0 0/6 * * *' +permissions: read-all +jobs: + BatchFuzzing: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: + - address + - undefined + - memory + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + sanitizer: ${{ matrix.sanitizer }} + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 3600 + mode: 'batch' + sanitizer: ${{ matrix.sanitizer }} diff --git a/.github/workflows/cflite_cron.yaml b/.github/workflows/cflite_cron.yaml new file mode 100644 index 0000000..d46a4da --- /dev/null +++ b/.github/workflows/cflite_cron.yaml @@ -0,0 +1,19 @@ +name: ClusterFuzzLite cron tasks +on: + schedule: + - cron: '0 0 * * *' +permissions: read-all +jobs: + Pruning: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + - name: Run Fuzzers + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 600 + mode: 'prune' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81b3ee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# 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 + +# test logs generated by `make check` +*.log +*.trs + +# autotools derivatives +/lib/includes/nghttp3/version.h +/lib/libnghttp3.pc @@ -0,0 +1,17 @@ +Alexis La Goutte +Amir Livneh +Bryan Call +Daniel Bevenius +Daniel Stenberg +Dimitris Apostolou +Don +James M Snell +Javier Blazquez +Li Xinwei +Ondřej Koláček +Peter Wu +Tatsuhiro Tsujikawa +Tim Gates +Toni Uhlig +Valère Plantevin +Viktor Szakats diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0bd6c81 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,279 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2016 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. + +cmake_minimum_required(VERSION 3.1) +# XXX using 0.1.90 instead of 0.2.0-DEV +project(nghttp3 VERSION 0.8.0) + +# See versioning rule: +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +set(LT_CURRENT 3) +set(LT_REVISION 2) +set(LT_AGE 0) + +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +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) + +include(CMakeOptions.txt) + +# 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() + +find_package(CUnit 2.1) +enable_testing() +set(HAVE_CUNIT ${CUNIT_FOUND}) +if(HAVE_CUNIT) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}) +endif() + +# Checks for header files. +include(CheckIncludeFile) +check_include_file("arpa/inet.h" HAVE_ARPA_INET_H) +check_include_file("stddef.h" HAVE_STDDEF_H) +check_include_file("stdint.h" HAVE_STDINT_H) +check_include_file("stdlib.h" HAVE_STDLIB_H) +check_include_file("string.h" HAVE_STRING_H) +check_include_file("unistd.h" HAVE_UNISTD_H) +check_include_file("sys/endian.h" HAVE_SYS_ENDIAN_H) +check_include_file("endian.h" HAVE_ENDIAN_H) +check_include_file("byteswap.h" HAVE_BYTESWAP_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 a pointer-size int. + set(ssize_t ptrdiff_t) +endif() + +# Checks for symbols. +include(CheckSymbolExists) +if(HAVE_ENDIAN_H) + check_symbol_exists(be64toh "endian.h" HAVE_BE64TOH) +endif() +if(HAVE_SYS_ENDIAN_H) + check_symbol_exists(be64toh "sys/endian.h" HAVE_BE64TOH) +endif() + +check_symbol_exists(bswap_64 "byteswap.h" HAVE_BSWAP_64) + +include(ExtractValidFlags) +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) + extract_valid_c_flags(WARNCFLAGS -Werror) + extract_valid_c_flags(WARNCXXFLAGS -Werror) + endif() + + # For C compiler + extract_valid_c_flags(WARNCFLAGS + -Wall + -Wextra + -Wmissing-prototypes + -Wstrict-prototypes + -Wmissing-declarations + -Wpointer-arith + -Wdeclaration-after-statement + -Wformat-security + -Wwrite-strings + -Wshadow + -Winline + -Wnested-externs + -Wfloat-equal + -Wundef + -Wendif-labels + -Wempty-body + -Wcast-align + -Wclobbered + -Wvla + -Wpragmas + -Wunreachable-code + -Waddress + -Wattributes + -Wdiv-by-zero + -Wshorten-64-to-32 + + -Wconversion + -Wextended-offsetof + -Wformat-nonliteral + -Wlanguage-extension-token + -Wmissing-field-initializers + -Wmissing-noreturn + -Wmissing-variable-declarations + # Not used because we cannot change public structs + # -Wpadded + -Wsign-conversion + # Not used because this basically disallows default case + # -Wswitch-enum + -Wunreachable-code-break + -Wunused-macros + -Wunused-parameter + -Wredundant-decls + # Only work with Clang for the moment + -Wheader-guard + -Wsometimes-uninitialized + + # Only work with gcc7 for the moment + -Wduplicated-branches + # This is required because we pass format string as "const char*. + -Wno-format-nonliteral + ) + + extract_valid_cxx_flags(WARNCXXFLAGS + # For C++ compiler + -Wall + -Wformat-security + -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. + -Wno-noexcept-type + ) + + if(ENABLE_ASAN) + include(CMakePushCheckState) + cmake_push_check_state() + set(CMAKE_REQUIRED_LIBRARIES "-fsanitize=address") + check_c_compiler_flag(-fsanitize=address C__fsanitize_address_VALID) + check_cxx_compiler_flag(-fsanitize=address CXX__fsanitize_address_VALID) + cmake_pop_check_state() + if(NOT C__fsanitize_address_VALID OR NOT CXX__fsanitize_address_VALID) + message(WARNING "ENABLE_ASAN was requested, but not supported!") + else() + set(CMAKE_C_FLAGS "-fsanitize=address ${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "-fsanitize=address ${CMAKE_CXX_FLAGS}") + endif() + endif() +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() + +if(ENABLE_LIB_ONLY) + set(ENABLE_EXAMPLES 0) +else() + set(ENABLE_EXAMPLES 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}") +# libnghttp3.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/libnghttp3.pc + lib/includes/nghttp3/version.h +) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +include_directories( + "${CMAKE_CURRENT_BINARY_DIR}" # for config.h +) +# For use in src/CMakeLists.txt +set(PKGDATADIR "${CMAKE_INSTALL_FULL_DATADIR}/${CMAKE_PROJECT_NAME}") + +install(FILES README.rst DESTINATION "${CMAKE_INSTALL_DOCDIR}") + +add_subdirectory(lib) +add_subdirectory(tests) +add_subdirectory(examples) + + +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} + WARNCXXFLAGS: ${WARNCXXFLAGS} + Library: + Shared: ${ENABLE_SHARED_LIB} + Static: ${ENABLE_STATIC_LIB} + Test: + CUnit: ${HAVE_CUNIT} (LIBS='${CUNIT_LIBRARIES}') + Library only: ${ENABLE_LIB_ONLY} + Examples: ${ENABLE_EXAMPLES} +") diff --git a/CMakeOptions.txt b/CMakeOptions.txt new file mode 100644 index 0000000..7f0a2e1 --- /dev/null +++ b/CMakeOptions.txt @@ -0,0 +1,11 @@ +# Features that can be enabled for cmake (see CMakeLists.txt) + +option(ENABLE_WERROR "Make compiler warnings fatal" OFF) +option(ENABLE_DEBUG "Turn on debug output") +option(ENABLE_ASAN "Enable AddressSanitizer (ASAN)" OFF) +option(ENABLE_LIB_ONLY "Build libnghttp3 only" OFF) +option(ENABLE_STATIC_LIB "Build libnghttp3 as a static library" ON) +option(ENABLE_SHARED_LIB "Build libnghttp3 as a shared library" ON) +option(ENABLE_STATIC_CRT "Build libnghttp3 against the MS LIBCMT[d]") + +# vim: ft=cmake: @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2019 nghttp3 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/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ChangeLog diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..ce15bcb --- /dev/null +++ b/Makefile.am @@ -0,0 +1,45 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2016 ngtcp2 contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = lib tests doc examples + +ACLOCAL_AMFLAGS = -I m4 + +dist_doc_DATA = README.rst + +EXTRA_DIST = \ + cmakeconfig.h.in \ + CMakeLists.txt \ + CMakeOptions.txt \ + cmake/ExtractValidFlags.cmake \ + cmake/FindCUnit.cmake \ + cmake/Version.cmake + +# 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} tests/*.{c,h} lib/includes/nghttp3/*.h \ + examples/*.{cc,h} @@ -0,0 +1 @@ +See README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..7170da6 --- /dev/null +++ b/README.rst @@ -0,0 +1,42 @@ +nghttp3 +======= + +nghttp3 is an implementation of `RFC 9114 +<https://datatracker.ietf.org/doc/html/rfc9114>`_ HTTP/3 mapping over +QUIC and `RFC 9204 <https://datatracker.ietf.org/doc/html/rfc9204>`_ +QPACK in C. + +It does not depend on any particular QUIC transport implementation. + +Documentation +------------- + +`Online documentation <https://nghttp2.org/nghttp3/>`_ is available. + +HTTP/3 +------ + +This library implements `RFC 9114 +<https://datatracker.ietf.org/doc/html/rfc9114>`_ HTTP/3. It does not +support server push. + +The following extensions have been implemented: + +- `Extensible Prioritization Scheme for HTTP + <https://datatracker.ietf.org/doc/html/rfc9218>`_ +- `Bootstrapping WebSockets with HTTP/3 + <https://datatracker.ietf.org/doc/html/rfc9220>`_ + +QPACK +----- + +This library implements `RFC 9204 +<https://datatracker.ietf.org/doc/html/rfc9204>`_ QPACK. It supports +dynamic table. + +License +------- + +The MIT License + +Copyright (c) 2019 nghttp3 contributors diff --git a/cmake/ExtractValidFlags.cmake b/cmake/ExtractValidFlags.cmake new file mode 100644 index 0000000..ccd57dc --- /dev/null +++ b/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/cmake/FindCUnit.cmake b/cmake/FindCUnit.cmake new file mode 100644 index 0000000..ada87c1 --- /dev/null +++ b/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/cmake/Version.cmake b/cmake/Version.cmake new file mode 100644 index 0000000..8ac4849 --- /dev/null +++ b/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/cmakeconfig.h.in b/cmakeconfig.h.in new file mode 100644 index 0000000..41fbb0b --- /dev/null +++ b/cmakeconfig.h.in @@ -0,0 +1,33 @@ + +/* Define to `int' if <sys/types.h> does not define. */ +#cmakedefine ssize_t @ssize_t@ + +/* Define to 1 to enable debug output. */ +#cmakedefine DEBUGBUILD 1 + +/* Define to 1 if you have the <arpa/inet.h> header file. */ +#cmakedefine HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the <stddef.h> header file. */ +#cmakedefine HAVE_STDDEF_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#cmakedefine HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#cmakedefine HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#cmakedefine HAVE_STRING_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the <sys/endian.h> header file. */ +#cmakedefine HAVE_SYS_ENDIAN_H 1 + +/* Define to 1 if you have the <endian.h> header file. */ +#cmakedefine HAVE_ENDIAN_H 1 + +/* Define to 1 if you have the `be64toh' function. */ +#cmakedefine HAVE_BE64TOH 1 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..a4ff7ad --- /dev/null +++ b/configure.ac @@ -0,0 +1,353 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2016 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. +AC_PREREQ(2.61) +AC_INIT([nghttp3], [0.8.0], [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])]) + +# See versioning rule: +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +AC_SUBST(LT_CURRENT, 3) +AC_SUBST(LT_REVISION, 2) +AC_SUBST(LT_AGE, 0) + +# from nghttp2 +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) + +# 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]) + +if test "x${debug}" = "xyes"; then + DEBUGCFLAGS="-O0 -g3" + AC_SUBST([DEBUGCFLAGS]) + AC_DEFINE([DEBUGBUILD], [1], [Define to 1 to enable debug output.]) +fi + +AC_ARG_ENABLE([memdebug], + [AS_HELP_STRING([--enable-memdebug], + [Turn on memory allocation debug output])], + [memdebug=$enableval], [memdebug=no]) + +AC_ARG_ENABLE(asan, + AS_HELP_STRING([--enable-asan], + [Enable AddressSanitizer (ASAN)]), + [asan=$enableval], [asan=no]) + +AC_ARG_ENABLE([lib-only], + [AS_HELP_STRING([--enable-lib-only], + [Build libnghttp3 only.])], + [lib_only=$enableval], [lib_only=no]) + +AC_ARG_WITH([cunit], + [AS_HELP_STRING([--with-cunit], + [Use cunit [default=check]])], + [request_cunit=$withval], [request_cunit=check]) + +# 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]) + +AX_CXX_COMPILE_STDCXX([17], [noext], [optional]) + +# Checks for libraries. + +# 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" ]) + +enable_examples=yes +if test "x${lib_only}" = "xyes"; then + enable_examples=no +fi + +AM_CONDITIONAL([ENABLE_EXAMPLES], [ test "x${enable_examples}" = "xyes" ]) + +# Checks for header files. +AC_CHECK_HEADERS([ \ + arpa/inet.h \ + stddef.h \ + stdint.h \ + stdlib.h \ + string.h \ + unistd.h \ + sys/endian.h \ + endian.h \ + byteswap.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 + +# Checks for library functions. +AC_CHECK_FUNCS([ \ + memmove \ + memset \ +]) + +# Checks for symbols. +AC_CHECK_DECLS([be64toh], [], [], [[ +#ifdef HAVE_ENDIAN_H +# include <endian.h> +#endif +#ifdef HAVE_SYS_ENDIAN_H +# include <sys/endian.h> +#endif +]]) + +AC_CHECK_DECLS([bswap_64], [], [], [[ +#include <byteswap.h> +]]) + +# More compiler flags from nghttp2. +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"]) + + # Only work with gcc7 for the moment + AX_CHECK_COMPILE_FLAG([-Wduplicated-branches], [CFLAGS="$CFLAGS -Wduplicated-branches"]) + + # 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"]) + # TODO separate option for -Werror and warnings? + #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]) + +if test "x$asan" != "xno"; then + # Assume both C and C++ compiler either support ASAN or not. + LDFLAGS_saved="$LDFLAGS" + LDFLAGS="$LDFLAGS -fsanitize=address" + AX_CHECK_COMPILE_FLAG([-fsanitize=address], + [CFLAGS="$CFLAGS -fsanitize=address"; CXXFLAGS="$CXXFLAGS -fsanitize=address"], + [LDFLAGS="$LDFLAGS_saved"]) +fi + +if test "x${memdebug}" = "xyes"; then + AC_DEFINE([MEMDEBUG], [1], + [Define to 1 to enable memory allocation debug output.]) +fi + +# extra flags for API function visibility +EXTRACFLAG= +AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [EXTRACFLAG="-fvisibility=hidden"]) + +AC_SUBST([EXTRACFLAG]) + +AC_CONFIG_FILES([ + Makefile + lib/Makefile + lib/libnghttp3.pc + lib/includes/Makefile + lib/includes/nghttp3/version.h + tests/Makefile + doc/Makefile + doc/source/conf.py + examples/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 preprocessor: ${CPP} + CPPFLAGS: ${CPPFLAGS} + C compiler: ${CC} + CFLAGS: ${CFLAGS} + C++ compiler: ${CXX} + CXXFLAGS: ${CXXFLAGS} + LDFLAGS: ${LDFLAGS} + WARNCFLAGS: ${WARNCFLAGS} + WARNCXXFLAGS: ${WARNCXXFLAGS} + EXTRACFLAG: ${EXTRACFLAG} + LIBS: ${LIBS} + Library: + Shared: ${enable_shared} + Static: ${enable_static} + Test: + CUnit: ${have_cunit} (CFLAGS='${CUNIT_CFLAGS}' LIBS='${CUNIT_LIBS}') + Debug: + Debug: ${debug} (CFLAGS='${DEBUGCFLAGS}') + Library only: ${lib_only} + Examples: ${enable_examples} +]) diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +/build diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..6ede162 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,51 @@ +# nghttp3 + +# Copyright (c) 2020 nghttp3 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. + +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help html apiref + +apiref: mkapiref.py \ + $(top_builddir)/lib/includes/nghttp3/version.h \ + $(top_srcdir)/lib/includes/nghttp3/nghttp3.h + $(top_srcdir)/doc/mkapiref.py \ + source/apiref.rst source/macros.rst source/enums.rst source/types.rst \ + source $(wordlist 2, $(words $^), $^) + +html-local: apiref + @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean-local: + -rm -rf "$(BUILDDIR)" diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..6247f7e --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/doc/mkapiref.py b/doc/mkapiref.py new file mode 100755 index 0000000..71337a7 --- /dev/null +++ b/doc/mkapiref.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2020 nghttp3 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'(nghttp3_[^ )]+)\(', 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 <nghttp3/{filename}>* + +'''.format(funcname=doc.funcname, secul='='*len(doc.funcname), + filename=doc.filename)) + doc.write(f) + +def process_macro(infile): + content = read_content(infile) + lines = [] + while True: + line = infile.readline() + if not line: + break + line = line.rstrip() + lines.append(line.rstrip('\\')) + if not line.endswith('\\'): + break + + macro_name = re.sub(r'#define ', '', ''.join(lines)) + m = re.match(r'^[^( ]+(:?\(.*?\))?', macro_name) + macro_name = m.group(0) + 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'int (settings|callbacks)_version,', + '', func_proto) + func_proto = re.sub(r'_versioned\(', '(', func_proto) + func_proto = re.sub(r';\n$', '', func_proto) + func_proto = re.sub(r'\s+', ' ', func_proto) + func_proto = re.sub(r'NGHTTP3_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) + content = re.sub(r':enum:', ':macro:', 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/doc/source/.gitignore b/doc/source/.gitignore new file mode 100644 index 0000000..a1a2e88 --- /dev/null +++ b/doc/source/.gitignore @@ -0,0 +1,5 @@ +/conf.py +/apiref.rst +/enums.rst +/macros.rst +/types.rst diff --git a/doc/source/conf.py.in b/doc/source/conf.py.in new file mode 100644 index 0000000..171595f --- /dev/null +++ b/doc/source/conf.py.in @@ -0,0 +1,94 @@ +# nghttp3 + +# Copyright (c) 2020 nghttp3 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. + +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# 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. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'nghttp3' +copyright = '2020, nghttp3 contributors' +author = 'nghttp3 contributors' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- 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 = 'alabaster' + +# 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 = ['_static'] + +# The reST default role (used for this markup: `text`) to use for all documents. +default_role = 'c:func' +primary_domain = 'c' + +# manpage URL pattern +manpages_url = 'https://man7.org/linux/man-pages/man{section}/{page}.{section}.html' + +# The default language to highlight source code in. +highlight_language = 'c' + +# 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@' diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..2ba7331 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,22 @@ +.. nghttp3 documentation master file, created by + sphinx-quickstart on Mon Nov 30 22:15:12 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to nghttp3's documentation! +=================================== + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + programmers-guide + qpack-howto + apiref + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/programmers-guide.rst b/doc/source/programmers-guide.rst new file mode 100644 index 0000000..d47d19c --- /dev/null +++ b/doc/source/programmers-guide.rst @@ -0,0 +1,207 @@ +The nghttp3 programmers' guide +============================== + +This document describes a basic usage of nghttp3 library and common +pitfalls which programmers might encounter. + +Assumptions +----------- + +nghttp3 is a thin HTTP/3 layer over an underlying QUIC stack. It +relies on an underlying QUIC stack for flow control and connection +management. Although nghttp3 is QUIC stack agnostic, it expects some +particular interfaces from QUIC stack. We will describe them below. + +QPACK operations are done behind the scenes. Application can use +:type:`nghttp3_settings` to change the behaviour of QPACK +encoder/decoder. + +We define some keywords to avoid ambiguity in this document: + +* HTTP payload: HTTP request/response body +* HTTP stream data: Series of HTTP header fields, HTTP payload, and + HTTP trailer fields, serialized into HTTP/3 wire format, which is + passed to or received from QUIC stack. + +Initialization +-------------- + +The :type:`nghttp3_conn` is a basic building block of nghttp3 library. +It is created per HTTP/3 connection. If an endpoint is a client, use +`nghttp3_conn_client_new` to initialize it as client. If it is a +server, use `nghttp3_conn_server_new` to initialize it as server. + +Those initialization functions take :type:`nghttp3_callbacks`. All +callbacks are optional, but setting no callback functions makes +nghttp3 library useless for the most cases. We list callbacks which +effectively required to do HTTP/3 transaction below: + +* :member:`acked_stream_data <nghttp3_callbacks.acked_stream_data>`: + Application has to retain HTTP payload (HTTP request/response body) + until they are no longer used by :type:`nghttp3_conn`. This + callback functions tells the largest offset of HTTP payload + acknowledged by a remote endpoint, and no longer used. +* :member:`stream_close <nghttp3_callbacks.stream_close>`: It is + called when a stream is closed. It is useful to free resources + allocated for a stream. +* :member:`recv_data <nghttp3_callbacks.recv_data>`: It is called when + HTTP payload (HTTP request/response body) is received. +* :member:`deferred_consume <nghttp3_callbacks.deferred_consume>`: It + is called when :type:`nghttp3_conn` consumed HTTP stream data which + had been blocked for synchronization between streams. Application + has to tell QUIC stack the number of bytes consumed which affects + flow control. We will discuss more about this callback later when + explaining `nghttp3_conn_read_stream`. +* :member:`recv_header <nghttp3_callbacks.recv_header>`: It is called + when an HTTP header field is received. +* :member:`send_stop_sending <nghttp3_callbacks.send_stop_sending>`: + It is called when QUIC STOP_SENDING frame must be sent for a + particular stream. Sending STOP_SENDING frame means that + :type:`nghttp3_conn` no longer reads an incoming data for a + particular stream. Application has to tell QUIC stack to send + STOP_SENDING frame. +* :member:`reset_stream <nghttp3_callbacks.reset_stream>`: It is + called when QUIC RESET_STREAM frame must be sent for a particular + stream. Sending RESET_STREAM frame means that :type:`nghttp3_conn` + stops sending any HTTP stream data to a particular stream. + Application has to tell QUIC stack to send RESET_STREAM frame. + +The initialization functions also takes :type:`nghttp3_settings` which +is a set of options to tweak HTTP3/ connection settings. +`nghttp3_settings_default` fills the default values. + +The *user_data* parameter to the initialization function is an opaque +pointer and it is passed to callback functions. + +Binding control streams +----------------------- + +HTTP/3 requires at least 3 local unidirectional streams for a control +stream and QPACK encoder/decoder streams. + +Use the following functions to bind those streams to their purposes: + +* `nghttp3_conn_bind_control_stream`: Bind a given stream ID to a HTTP + control stream. +* `nghttp3_conn_bind_qpack_streams`: Bind given 2 stream IDs to QPACK + encoder and decoder streams. + +Reading HTTP stream data +------------------------ + +`nghttp3_conn_read_stream` reads HTTP stream data from a particular +stream. It returns the number of bytes "consumed". "Consumed" means +that the those bytes are completely processed and QUIC stack can +increase the flow control credit of both stream and connection by that +amount. + +The HTTP payload notified by :member:`nghttp3_callbacks.recv_data` is +not included in the return value. This is because the consumption of +those data is done by application and nghttp3 library does not know +when that happens. + +Some HTTP stream data might be consumed later because of +synchronization between streams. In this case, those bytes are +notified by :member:`nghttp3_callbacks.deferred_consume`. + +In every case, the number of consumed HTTP stream data must be +notified to QUIC stack so that it can extend flow control limits. + +Writing HTTP stream data +------------------------ + +`nghttp3_conn_writev_stream` writes HTTP stream data to a particular +stream. The order of streams to produce HTTP stream data is +determined by the nghttp3 library. In general, the control streams +have higher priority. The regular HTTP streams are ordered by +header-based HTTP priority (see +https://tools.ietf.org/html/draft-ietf-httpbis-priority-03). + +When HTTP stream data is generated, its stream ID is assigned to +*\*pstream_id*. The pointer to HTTP stream data is assigned to *vec*, +and the function returns the number of *vec* it filled. If the +generated data is the final part of the stream, *\*pfin* gets nonzero +value. If no HTTP stream data is generated, the function returns 0 +and *\*pstream_id* gets -1. + +The function might return 0 and *\*pstream_id* has proper stream ID +and *\*pfin* set to nonzero. In this case, no data is written, but it +signals the end of the stream. Even though no data is written, QUIC +stack should be notified of the end of the stream. + +The produced HTTP stream data is passed to QUIC stack. Then call +`nghttp3_conn_add_write_offset` with the number of bytes accepted by +QUIC stack. This must be done even when the written data is 0 bytes +with fin (refer to the previous paragraph for this corner case). + +If QUIC stack indicates that a stream is blocked by stream level flow +control limit, call `nghttp3_conn_block_stream`. It makes the library +not to generate HTTP stream data for the stream. Call +`nghttp3_conn_unblock_stream` when stream level flow control limit is +increased. + +If QUIC stack indicates that the write side of stream is closed, call +`nghttp3_conn_shutdown_stream_write` instead of +`nghttp3_conn_block_stream` so that the stream never be scheduled in +the future. + +Creating HTTP request or response +--------------------------------- + +In order to create HTTP request, client application calls +`nghttp3_conn_submit_request`. :type:`nghttp3_data_reader` is used to +send HTTP payload (HTTP request body). + +Similarly, server application calls `nghttp3_conn_submit_response` to +create HTTP response. :type:`nghttp3_data_reader` is also used to +send HTTP payload (HTTP response body). + +In both cases, if :type:`nghttp3_data_reader` is not provided, no HTTP +payload is generated. + +The :member:`nghttp3_data_reader.read_data` is a callback function to +generate HTTP payload. Application must retain the data passed to the +library until those data are acknowledged by +:member:`nghttp3_callbacks.acked_stream_data`. When no data is +available but will become available in the future, application returns +:macro:`NGHTTP3_ERR_WOULDBLOCK` from this callback. Then the callback +is not called for the particular stream until +`nghttp3_conn_resume_stream` is called. + +Reading HTTP request or response +-------------------------------- + +The :member:`nghttp3_callbacks.recv_header` is called when an HTTP +header field is received. + +The :member:`nghttp3_callbacks.recv_data` is called when HTTP payload +is received. + +Acknowledgement of HTTP stream data +----------------------------------- + +QUIC stack must provide an interface to notify the amount of data +acknowledged by a remote endpoint. `nghttp3_conn_add_ack_offset` must +be called with the largest offset of acknowledged HTTP stream data. + +Handling QUIC stream events +--------------------------- + +If underlying QUIC stream is closed, call `nghttp3_conn_close_stream`. + +If underlying QUIC stream is reset by a remote endpoint (that is when +RESET_STREAM is received) or no longer read by a local endpoint (that +is when STOP_SENDING is sent), call +`nghttp3_conn_shutdown_stream_read`. + +Closing HTTP/3 connection gracefully +------------------------------------ + +`nghttp3_conn_submit_shutdown_notice` creates a message to a remote +endpoint that HTTP/3 connection is going down. The receiving endpoint +should stop sending HTTP request after reading this signal. After a +couple of RTTs, call `nghttp3_conn_submit_shutdown` to start graceful +shutdown. After calling this function, the local endpoint starts +rejecting new incoming streams. The existing streams are processed +normally. When all those streams are completely processed, the +connection can be closed. diff --git a/doc/source/qpack-howto.rst b/doc/source/qpack-howto.rst new file mode 100644 index 0000000..a38d285 --- /dev/null +++ b/doc/source/qpack-howto.rst @@ -0,0 +1,85 @@ +QPACK How-To +============ + +Using QPACK encoder +------------------- + +Firstly, create QPACK encoder by calling `nghttp3_qpack_encoder_new`. +It requires *hard_max_dtable_size* parameter. When in doubt, pass +4096 for this tutorial. Optionally, call +`nghttp3_qpack_encoder_set_max_dtable_capacity` to set the maximum +size of dynamic table. You can also call +`nghttp3_qpack_encoder_set_max_blocked_streams` to set the maximum +number of streams that can be blocked. + +In order to encode HTTP header fields, they must be stored in an array +of :type:`nghttp3_nv`. Then call `nghttp3_qpack_encoder_encode`. It +writes 3 buffers; *pbuf*, *rbuf*, and *ebuf*. They are a header block +prefix, request stream, and encoder stream respectively. A header +block prefix and request stream must be sent in this order to a stream +denoted by *stream_id* passed to the function. Encoder stream must be +sent to the encoder stream you setup. + +In order to read decoder stream, call +`nghttp3_qpack_encoder_read_decoder`. + +Once QPACK encoder is no longer used, call `nghttp3_qpack_encoder_del` +to free up memory allocated for it. + +Using QPACK decoder +------------------- + +`nghttp3_qpack_decoder_new` will create new QPACK decoder. It +requires *hard_max_dtable_size* and *max_blocked* parameters. When in +doubt, pass 4096 and 0 respectively for this tutorial. + +In order to read encoder stream, call +`nghttp3_qpack_decoder_read_encoder`. This might update dynamic +table, but does not emit any header fields. + +In order to read request stream, call +`nghttp3_qpack_decoder_read_request`. This is the function to emit +header fields. *sctx* stores a per-stream decoder state and must be +created by `nghttp3_qpack_stream_context_new`. It identifies a single +encoded header block in a particular request stream. *fin* must be +nonzero if and only if a passed data contains the last part of encoded +header block. + +The scope of :type:`nghttp3_qpack_stream_context` is per header block, +but `nghttp3_qpack_stream_context_reset` resets its state and can be +reused for an another header block in the same stream. In general, +you can reset it when you see that +:macro:`NGHTTP3_QPACK_DECODE_FLAG_FINAL` is set in *\*pflags*. When +:type:`nghttp3_qpack_stream_context` is no longer necessary, call +`nghttp3_qpack_stream_context_del` to free up its resource. + +`nghttp3_qpack_decoder_read_request` succeeds, *\*pflags* is assigned. +If it has :macro:`NGHTTP3_QPACK_DECODE_FLAG_EMIT` set, a header field +is emitted and stored in the buffer pointed by *nv*. If *\*pflags* +has :macro:`NGHTTP3_QPACK_DECODE_FLAG_FINAL` set, all header fields +have been successfully decoded. If *\*pflags* has +:macro:`NGHTTP3_QPACK_DECODE_FLAG_BLOCKED` set, decoding is blocked +due to required insert count, which means that more data must be read +by `nghttp3_qpack_decoder_read_encoder`. + +`nghttp3_qpack_decoder_read_request` returns the number of bytes read. +When a header field is emitted, it might read data partially. +Applciation has to call the function repeatedly by adjusting the +pointer to data and its length until the function consumes all data or +:macro:`NGHTTP3_QPACK_DECODE_FLAG_BLOCKED` is set in *\*pflags*. + +If *nv* is assigned, its :member:`nv->name <nghttp3_qpack_nv.name>` +and :member:`nv->value <nghttp3_qpack_nv.value>` are reference counted +and already incremented by 1. If application finishes processing +these values, it must call `nghttp3_rcbuf_decref(nv->name) +<nghttp3_rcbuf_decref>` and `nghttp3_rcbuf_decref(nv->value) +<nghttp3_rcbuf_decref>`. + +If an application has no interest to decode header fields for a +particular stream, call `nghttp3_qpack_decoder_cancel_stream`. + +In order to tell decoding state to an encoder, QPACK decoder has to +write decoder stream by calling `nghttp3_qpack_decoder_write_decoder`. + +Once QPACK decoder is no longer used, call `nghttp3_qpack_decoder_del` +to free up memory allocated for it. diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..20a9df3 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +/qpack diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..31ec1aa --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,50 @@ +# ngtcp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2017 ngtcp2 contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if(ENABLE_EXAMPLES) + include_directories( + ${CMAKE_SOURCE_DIR}/lib/includes + ${CMAKE_BINARY_DIR}/lib/includes + ) + + link_libraries( + nghttp3 + ) + + set(qpack_SOURCES + qpack.cc + qpack_encode.cc + qpack_decode.cc + util.cc + ) + + add_executable(qpack ${qpack_SOURCES}) + set_target_properties(qpack PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS}" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + ) + + # TODO prevent qpack example from being installed? +endif() diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..8c90892 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,46 @@ +# ngtcp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2017 ngtcp2 contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +if ENABLE_EXAMPLES + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) +AM_CXXFLAGS = $(WARNCXXFLAGS) $(DEBUGCFLAGS) +AM_CPPFLAGS = \ + -I$(top_srcdir)/lib/includes \ + -I$(top_builddir)/lib/includes \ + @DEFS@ +AM_LDFLAGS = -no-install +LDADD = $(top_builddir)/lib/libnghttp3.la + +noinst_PROGRAMS = qpack + +qpack_SOURCES = \ + qpack.cc qpack.h \ + qpack_encode.cc qpack_encode.h \ + qpack_decode.cc qpack_decode.h \ + template.h \ + util.cc util.h + +endif # ENABLE_EXAMPLES diff --git a/examples/qpack.cc b/examples/qpack.cc new file mode 100644 index 0000000..1353ad9 --- /dev/null +++ b/examples/qpack.cc @@ -0,0 +1,143 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "qpack.h" + +#include <cstring> +#include <iostream> +#include <string> + +#include <getopt.h> + +#include "qpack_encode.h" +#include "qpack_decode.h" + +namespace nghttp3 { + +Config config{}; + +namespace { +void print_usage() { + std::cerr << "Usage: qpack [OPTIONS] <COMMAND> <INFILE> <OUTFILE>" + << std::endl; +} +} // namespace + +namespace { +void print_help() { + print_usage(); + + std::cerr << R"( + <COMMAND> "encode" or "decode" + <INFILE> Path to an input file + <OUTFILE> Path to an output file +Options: + -h, --help Display this help and exit. + -m, --max-blocked=<N> + The maximum number of streams which are permitted to be blocked. + -s, --max-dtable-size=<N> + The maximum size of dynamic table. + -a, --immediate-ack + Turn on immediate acknowlegement. +)"; +} +} // namespace + +int main(int argc, char **argv) { + for (;;) { + static int flag = 0; + (void)flag; + constexpr static option long_opts[] = { + {"help", no_argument, nullptr, 'h'}, + {"max-blocked", required_argument, nullptr, 'm'}, + {"max-dtable-size", required_argument, nullptr, 's'}, + {"immediate-ack", no_argument, nullptr, 'a'}, + {nullptr, 0, nullptr, 0}, + }; + + auto optidx = 0; + auto c = getopt_long(argc, argv, "hm:s:a", long_opts, &optidx); + if (c == -1) { + break; + } + switch (c) { + case 'h': + // --help + print_help(); + exit(EXIT_SUCCESS); + case 'm': { + // --max-blocked + config.max_blocked = strtoul(optarg, nullptr, 10); + break; + } + case 's': { + // --max-dtable-size + config.max_dtable_size = strtoul(optarg, nullptr, 10); + break; + } + case 'a': + // --immediate-ack + config.immediate_ack = true; + break; + case '?': + print_usage(); + exit(EXIT_FAILURE); + case 0: + break; + default: + break; + }; + } + + if (argc - optind < 3) { + std::cerr << "Too few arguments" << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + auto command = std::string_view(argv[optind++]); + auto infile = std::string_view(argv[optind++]); + auto outfile = std::string_view(argv[optind++]); + + int rv; + if (command == "encode") { + rv = encode(outfile, infile); + } else if (command == "decode") { + rv = decode(outfile, infile); + } else { + std::cerr << "Unrecognized command: " << command << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + if (rv != 0) { + exit(EXIT_FAILURE); + } + + return 0; +} + +} // namespace nghttp3 + +int main(int argc, char **argv) { return nghttp3::main(argc, argv); } diff --git a/examples/qpack.h b/examples/qpack.h new file mode 100644 index 0000000..d0b0ce6 --- /dev/null +++ b/examples/qpack.h @@ -0,0 +1,44 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 QPACK_H +#define QPACK_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +namespace nghttp3 { + +struct Config { + size_t max_blocked; + size_t max_dtable_size; + bool immediate_ack; +}; + +} // namespace nghttp3 + +#endif // QPACK_H diff --git a/examples/qpack_decode.cc b/examples/qpack_decode.cc new file mode 100644 index 0000000..22e44c8 --- /dev/null +++ b/examples/qpack_decode.cc @@ -0,0 +1,299 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "qpack_decode.h" + +#include <arpa/inet.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/mman.h> + +#include <cassert> +#include <cstring> +#include <cerrno> +#include <iostream> +#include <fstream> + +#include "qpack.h" +#include "template.h" +#include "util.h" + +namespace nghttp3 { + +extern Config config; + +Request::Request(int64_t stream_id, const nghttp3_buf *buf) + : buf(*buf), stream_id(stream_id) { + auto mem = nghttp3_mem_default(); + nghttp3_qpack_stream_context_new(&sctx, stream_id, mem); +} + +Request::~Request() { nghttp3_qpack_stream_context_del(sctx); } + +Decoder::Decoder(size_t max_dtable_size, size_t max_blocked) + : mem_(nghttp3_mem_default()), + dec_(nullptr), + max_dtable_size_(max_dtable_size), + max_blocked_(max_blocked) {} + +Decoder::~Decoder() { nghttp3_qpack_decoder_del(dec_); } + +int Decoder::init() { + if (auto rv = nghttp3_qpack_decoder_new(&dec_, max_dtable_size_, max_blocked_, + mem_); + rv != 0) { + std::cerr << "nghttp3_qpack_decoder_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (auto rv = + nghttp3_qpack_decoder_set_max_dtable_capacity(dec_, max_dtable_size_); + rv != 0) { + std::cerr << "nghttp3_qpack_decoder_set_max_dtable_capacity: " + << nghttp3_strerror(rv) << std::endl; + return -1; + } + + return 0; +} + +int Decoder::read_encoder(nghttp3_buf *buf) { + auto nread = + nghttp3_qpack_decoder_read_encoder(dec_, buf->pos, nghttp3_buf_len(buf)); + if (nread < 0) { + std::cerr << "nghttp3_qpack_decoder_read_encoder: " + << nghttp3_strerror(nread) << std::endl; + return -1; + } + + assert(static_cast<size_t>(nread) == nghttp3_buf_len(buf)); + + return 0; +} + +std::tuple<Headers, int> Decoder::read_request(nghttp3_buf *buf, + int64_t stream_id) { + auto req = std::make_shared<Request>(stream_id, buf); + + auto [headers, rv] = read_request(*req); + if (rv == -1) { + return {Headers{}, -1}; + } + if (rv == 1) { + if (blocked_reqs_.size() >= max_blocked_) { + std::cerr << "Too many blocked streams: max_blocked=" << max_blocked_ + << std::endl; + return {Headers{}, -1}; + } + blocked_reqs_.emplace(std::move(req)); + return {Headers{}, 1}; + } + return {headers, 0}; +} + +std::tuple<Headers, int> Decoder::read_request(Request &req) { + nghttp3_qpack_nv nv; + uint8_t flags; + Headers headers; + + for (;;) { + auto nread = nghttp3_qpack_decoder_read_request( + dec_, req.sctx, &nv, &flags, req.buf.pos, nghttp3_buf_len(&req.buf), 1); + if (nread < 0) { + std::cerr << "nghttp3_qpack_decoder_read_request: " + << nghttp3_strerror(nread) << std::endl; + return {Headers{}, -1}; + } + + req.buf.pos += nread; + + if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) { + break; + } + if (flags & NGHTTP3_QPACK_DECODE_FLAG_BLOCKED) { + return {Headers{}, 1}; + } + if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) { + auto name = nghttp3_rcbuf_get_buf(nv.name); + auto value = nghttp3_rcbuf_get_buf(nv.value); + headers.emplace_back(std::string{name.base, name.base + name.len}, + std::string{value.base, value.base + value.len}); + nghttp3_rcbuf_decref(nv.name); + nghttp3_rcbuf_decref(nv.value); + } + } + + return {headers, 0}; +} + +std::tuple<int64_t, Headers, int> Decoder::process_blocked() { + if (!blocked_reqs_.empty()) { + auto &top = blocked_reqs_.top(); + if (nghttp3_qpack_stream_context_get_ricnt(top->sctx) > + nghttp3_qpack_decoder_get_icnt(dec_)) { + return {-1, {}, 0}; + } + + auto req = top; + blocked_reqs_.pop(); + + auto [headers, rv] = read_request(*req); + if (rv < 0) { + return {-1, {}, -1}; + } + assert(rv == 0); + + return {req->stream_id, headers, 0}; + } + return {-1, {}, 0}; +} + +size_t Decoder::get_num_blocked() const { return blocked_reqs_.size(); } + +namespace { +void write_header( + std::ostream &out, + const std::vector<std::pair<std::string, std::string>> &headers) { + for (auto &nv : headers) { + out.write(nv.first.c_str(), nv.first.size()); + out.put('\t'); + out.write(nv.second.c_str(), nv.second.size()); + out.put('\n'); + } + out.put('\n'); +} +} // namespace + +int decode(const std::string_view &outfile, const std::string_view &infile) { + auto fd = open(infile.data(), O_RDONLY); + if (fd == -1) { + std::cerr << "Could not open " << infile << ": " << strerror(errno) + << std::endl; + return -1; + } + + auto fd_closer = defer(close, fd); + + struct stat st; + if (fstat(fd, &st) == -1) { + std::cerr << "fstat: " << strerror(errno) << std::endl; + return -1; + } + + auto out = std::ofstream(outfile.data(), std::ios::trunc | std::ios::binary); + if (!out) { + std::cerr << "Could not open file " << outfile << ": " << strerror(errno) + << std::endl; + return -1; + } + + auto in = reinterpret_cast<uint8_t *>( + mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0)); + if (in == MAP_FAILED) { + std::cerr << "mmap: " << strerror(errno) << std::endl; + return -1; + } + + auto unmapper = defer(munmap, in, st.st_size); + + auto dec = Decoder(config.max_dtable_size, config.max_blocked); + if (auto rv = dec.init(); rv != 0) { + return rv; + } + + for (auto p = in, end = in + st.st_size; p != end;) { + int64_t stream_id; + uint32_t size; + + if (static_cast<size_t>(end - p) < sizeof(stream_id) + sizeof(size)) { + std::cerr << "Could not read stream ID and size" << std::endl; + return -1; + } + + memcpy(&stream_id, p, sizeof(stream_id)); + stream_id = nghttp3_ntohl64(stream_id); + p += sizeof(stream_id); + + memcpy(&size, p, sizeof(size)); + size = ntohl(size); + p += sizeof(size); + + if ((size_t)(end - p) < size) { + std::cerr << "Insufficient input: require " << size << " but " + << (end - p) << " is available" << std::endl; + return -1; + } + + nghttp3_buf buf; + buf.begin = buf.pos = p; + buf.end = buf.last = p + size; + + p += size; + + if (stream_id == 0) { + if (auto rv = dec.read_encoder(&buf); rv != 0) { + return rv; + } + + for (;;) { + auto [stream_id, headers, rv] = dec.process_blocked(); + if (rv != 0) { + return rv; + } + + if (stream_id == -1) { + break; + } + + write_header(out, headers); + } + + continue; + } + + auto [headers, rv] = dec.read_request(&buf, stream_id); + if (rv == -1) { + return rv; + } + if (rv == 1) { + // Stream blocked + continue; + } + + write_header(out, headers); + } + + if (auto n = dec.get_num_blocked(); n) { + std::cerr << "Still " << n << " stream(s) blocked" << std::endl; + return -1; + } + + return 0; +} + +} // namespace nghttp3 diff --git a/examples/qpack_decode.h b/examples/qpack_decode.h new file mode 100644 index 0000000..8641b3a --- /dev/null +++ b/examples/qpack_decode.h @@ -0,0 +1,93 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 QPACK_DECODE_H +#define QPACK_DECODE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include <vector> +#include <queue> +#include <functional> +#include <utility> +#include <memory> +#include <string> + +namespace nghttp3 { +struct Request { + Request(int64_t stream_id, const nghttp3_buf *buf); + ~Request(); + + nghttp3_buf buf; + nghttp3_qpack_stream_context *sctx; + int64_t stream_id; +}; +} // namespace nghttp3 + +namespace std { +template <> struct greater<std::shared_ptr<nghttp3::Request>> { + bool operator()(const std::shared_ptr<nghttp3::Request> &lhs, + const std::shared_ptr<nghttp3::Request> &rhs) const { + return nghttp3_qpack_stream_context_get_ricnt(lhs->sctx) > + nghttp3_qpack_stream_context_get_ricnt(rhs->sctx); + } +}; +} // namespace std + +namespace nghttp3 { + +using Headers = std::vector<std::pair<std::string, std::string>>; + +class Decoder { +public: + Decoder(size_t max_dtable_size, size_t max_blocked); + ~Decoder(); + + int init(); + int read_encoder(nghttp3_buf *buf); + std::tuple<Headers, int> read_request(nghttp3_buf *buf, int64_t stream_id); + std::tuple<Headers, int> read_request(Request &req); + std::tuple<int64_t, Headers, int> process_blocked(); + size_t get_num_blocked() const; + +private: + const nghttp3_mem *mem_; + nghttp3_qpack_decoder *dec_; + std::priority_queue<std::shared_ptr<Request>, + std::vector<std::shared_ptr<Request>>, + std::greater<std::shared_ptr<Request>>> + blocked_reqs_; + size_t max_dtable_size_; + size_t max_blocked_; +}; + +int decode(const std::string_view &outfile, const std::string_view &infile); + +} // namespace nghttp3 + +#endif // QPACK_ENCODE_H diff --git a/examples/qpack_encode.cc b/examples/qpack_encode.cc new file mode 100644 index 0000000..867a085 --- /dev/null +++ b/examples/qpack_encode.cc @@ -0,0 +1,221 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "qpack_encode.h" + +#include <arpa/inet.h> + +#include <cerrno> +#include <cstring> +#include <cassert> +#include <iostream> +#include <fstream> +#include <algorithm> +#include <array> +#include <iomanip> +#include <vector> + +#include "qpack.h" +#include "template.h" +#include "util.h" + +namespace nghttp3 { + +extern Config config; + +Encoder::Encoder(size_t max_dtable_size, size_t max_blocked, bool immediate_ack) + : mem_(nghttp3_mem_default()), + enc_(nullptr), + max_dtable_size_(max_dtable_size), + max_blocked_(max_blocked), + immediate_ack_(immediate_ack) {} + +Encoder::~Encoder() { nghttp3_qpack_encoder_del(enc_); } + +int Encoder::init() { + int rv; + + rv = nghttp3_qpack_encoder_new(&enc_, max_dtable_size_, mem_); + if (rv != 0) { + std::cerr << "nghttp3_qpack_encoder_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + nghttp3_qpack_encoder_set_max_dtable_capacity(enc_, max_dtable_size_); + nghttp3_qpack_encoder_set_max_blocked_streams(enc_, max_blocked_); + + return 0; +} + +int Encoder::encode(nghttp3_buf *pbuf, nghttp3_buf *rbuf, nghttp3_buf *ebuf, + int64_t stream_id, const nghttp3_nv *nva, size_t len) { + auto rv = + nghttp3_qpack_encoder_encode(enc_, pbuf, rbuf, ebuf, stream_id, nva, len); + if (rv != 0) { + std::cerr << "nghttp3_qpack_encoder_encode: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + if (immediate_ack_) { + nghttp3_qpack_encoder_ack_everything(enc_); + } + return 0; +} + +namespace { +void write_encoder_stream(std::ostream &out, nghttp3_buf *ebuf) { + uint64_t stream_id = 0; + out.write(reinterpret_cast<char *>(&stream_id), sizeof(stream_id)); + uint32_t size = htonl(nghttp3_buf_len(ebuf)); + out.write(reinterpret_cast<char *>(&size), sizeof(size)); + out.write(reinterpret_cast<char *>(ebuf->pos), nghttp3_buf_len(ebuf)); +} +} // namespace + +namespace { +void write_request_stream(std::ostream &out, int64_t stream_id, + nghttp3_buf *pbuf, nghttp3_buf *rbuf) { + stream_id = nghttp3_htonl64(stream_id); + out.write(reinterpret_cast<char *>(&stream_id), sizeof(stream_id)); + uint32_t size = htonl(nghttp3_buf_len(pbuf) + nghttp3_buf_len(rbuf)); + out.write(reinterpret_cast<char *>(&size), sizeof(size)); + out.write(reinterpret_cast<char *>(pbuf->pos), nghttp3_buf_len(pbuf)); + out.write(reinterpret_cast<char *>(rbuf->pos), nghttp3_buf_len(rbuf)); +} +} // namespace + +int encode(const std::string_view &outfile, const std::string_view &infile) { + auto in = std::ifstream(infile.data(), std::ios::binary); + + if (!in) { + std::cerr << "Could not open file " << infile << ": " << strerror(errno) + << std::endl; + return -1; + } + + auto out = std::ofstream(outfile.data(), std::ios::trunc | std::ios::binary); + if (!out) { + std::cerr << "Could not open file " << outfile << ": " << strerror(errno) + << std::endl; + return -1; + } + + auto enc = + Encoder(config.max_dtable_size, config.max_blocked, config.immediate_ack); + if (enc.init() != 0) { + return -1; + } + + nghttp3_buf pbuf, rbuf, ebuf; + nghttp3_buf_init(&pbuf); + nghttp3_buf_init(&rbuf); + nghttp3_buf_init(&ebuf); + + auto mem = nghttp3_mem_default(); + auto pbufd = defer(nghttp3_buf_free, &pbuf, mem); + auto rbufd = defer(nghttp3_buf_free, &rbuf, mem); + auto ebufd = defer(nghttp3_buf_free, &ebuf, mem); + + int64_t stream_id = 1; + std::array<std::string, 1024> sarray; + + size_t srclen = 0; + size_t enclen = 0; + size_t rslen = 0; + size_t eslen = 0; + + for (; in;) { + auto nva = std::vector<nghttp3_nv>(); + for (std::string line; std::getline(in, line);) { + if (line == "") { + break; + } + + if (sarray.size() == nva.size()) { + std::cerr << "Too many headers: " << nva.size() << std::endl; + return -1; + } + + sarray[nva.size()] = line; + const auto &s = sarray[nva.size()]; + + auto d = s.find('\t'); + if (d == std::string_view::npos) { + std::cerr << "Could not find TAB in " << s << std::endl; + return -1; + } + auto name = std::string_view(s.c_str(), d); + auto value = std::string_view(s.c_str() + d + 1, s.size() - d - 1); + value.remove_prefix(std::min(value.find_first_not_of(" "), value.size())); + + srclen += name.size() + value.size(); + + nva.emplace_back(nghttp3_nv{ + const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(name.data())), + const_cast<uint8_t *>( + reinterpret_cast<const uint8_t *>(value.data())), + name.size(), value.size()}); + } + + if (nva.empty()) { + break; + } + + if (auto rv = + enc.encode(&pbuf, &rbuf, &ebuf, stream_id, nva.data(), nva.size()); + rv != 0) { + return -1; + } + + enclen += nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf) + + nghttp3_buf_len(&ebuf); + + if (nghttp3_buf_len(&ebuf)) { + write_encoder_stream(out, &ebuf); + } + write_request_stream(out, stream_id, &pbuf, &rbuf); + + rslen += nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf); + eslen += nghttp3_buf_len(&ebuf); + + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + ++stream_id; + } + + if (srclen == 0) { + std::cerr << "No header field processed" << std::endl; + } else { + std::cerr << srclen << " -> " << enclen << " (r:" << rslen + << " + e:" << eslen << ") " << std::fixed << std::setprecision(2) + << (1. - (static_cast<double>(enclen) / srclen)) * 100 + << "% compressed" << std::endl; + } + return 0; +} + +} // namespace nghttp3 diff --git a/examples/qpack_encode.h b/examples/qpack_encode.h new file mode 100644 index 0000000..b310ecd --- /dev/null +++ b/examples/qpack_encode.h @@ -0,0 +1,59 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 QPACK_ENCODE_H +#define QPACK_ENCODE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include <string> + +namespace nghttp3 { + +class Encoder { +public: + Encoder(size_t max_dtable_size, size_t max_blocked, bool immediate_ack); + ~Encoder(); + + int init(); + int encode(nghttp3_buf *pbuf, nghttp3_buf *rbuf, nghttp3_buf *ebuf, + int64_t stream_id, const nghttp3_nv *nva, size_t len); + +private: + const nghttp3_mem *mem_; + nghttp3_qpack_encoder *enc_; + size_t max_dtable_size_; + size_t max_blocked_; + bool immediate_ack_; +}; + +int encode(const std::string_view &outfile, const std::string_view &infile); + +} // namespace nghttp3 + +#endif // QPACK_ENCODE_H diff --git a/examples/template.h b/examples/template.h new file mode 100644 index 0000000..1f758b4 --- /dev/null +++ b/examples/template.h @@ -0,0 +1,77 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2015 ngttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TEMPLATE_H +#define TEMPLATE_H + +#include <functional> +#include <utility> +#include <type_traits> + +namespace nghttp3 { + +// inspired by <http://blog.korfuri.fr/post/go-defer-in-cpp/>, but our +// template can take functions returning other than void. +template <typename F, typename... T> struct Defer { + Defer(F &&f, T &&...t) + : f(std::bind(std::forward<F>(f), std::forward<T>(t)...)) {} + Defer(Defer &&o) noexcept : f(std::move(o.f)) {} + ~Defer() { f(); } + + using ResultType = typename std::result_of<typename std::decay<F>::type( + typename std::decay<T>::type...)>::type; + std::function<ResultType()> f; +}; + +template <typename F, typename... T> Defer<F, T...> defer(F &&f, T &&...t) { + return Defer<F, T...>(std::forward<F>(f), std::forward<T>(t)...); +} + +template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) { + return N; +} + +template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) { + return N - 1; +} + +// User-defined literals for K, M, and G (powers of 1024) + +constexpr unsigned long long operator"" _k(unsigned long long k) { + return k * 1024; +} + +constexpr unsigned long long operator"" _m(unsigned long long m) { + return m * 1024 * 1024; +} + +constexpr unsigned long long operator"" _g(unsigned long long g) { + return g * 1024 * 1024 * 1024; +} + +} // namespace nghttp3 + +#endif // TEMPLATE_H diff --git a/examples/util.cc b/examples/util.cc new file mode 100644 index 0000000..8973825 --- /dev/null +++ b/examples/util.cc @@ -0,0 +1,27 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util.h" + +namespace nghttp3 {} // namespace nghttp3 diff --git a/examples/util.h b/examples/util.h new file mode 100644 index 0000000..8e06086 --- /dev/null +++ b/examples/util.h @@ -0,0 +1,64 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef UTIL_H +#define UTIL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdint.h> + +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif /* HAVE_ARPA_INET_H */ + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif /* HAVE_NETINET_IN_H */ + +#ifdef HAVE_ENDIAN_H +# include <endian.h> +#endif /* HAVE_ENDIAN_H */ + +#ifdef HAVE_SYS_ENDIAN_H +# include <sys/endian.h> +#endif /* HAVE_SYS_ENDIAN_H */ + +namespace nghttp3 { + +#if defined HAVE_BE64TOH || HAVE_DECL_BE64TOH +# define nghttp3_ntohl64(N) be64toh(N) +# define nghttp3_htonl64(N) htobe64(N) +#else /* !HAVE_BE64TOH */ +# define nghttp3_bswap64(N) \ + ((uint64_t)(ntohl((uint32_t)(N))) << 32 | ntohl((uint32_t)((N) >> 32))) +# define nghttp3_ntohl64(N) nghttp3_bswap64(N) +# define nghttp3_htonl64(N) nghttp3_bswap64(N) +#endif /* !HAVE_BE64TOH */ + +} // namespace nghttp3 + +#endif // UTIL_H diff --git a/fuzz/corpus/fuzz_http3serverreq/curl b/fuzz/corpus/fuzz_http3serverreq/curl Binary files differnew file mode 100644 index 0000000..15aa688 --- /dev/null +++ b/fuzz/corpus/fuzz_http3serverreq/curl diff --git a/fuzz/corpus/fuzz_qpackdecoder/netbsd-hq.out.256.100.1 b/fuzz/corpus/fuzz_qpackdecoder/netbsd-hq.out.256.100.1 Binary files differnew file mode 100644 index 0000000..d55ee30 --- /dev/null +++ b/fuzz/corpus/fuzz_qpackdecoder/netbsd-hq.out.256.100.1 diff --git a/fuzz/fuzz_http3serverreq.cc b/fuzz/fuzz_http3serverreq.cc new file mode 100644 index 0000000..98c82f0 --- /dev/null +++ b/fuzz/fuzz_http3serverreq.cc @@ -0,0 +1,67 @@ +#include <array> + +#include <nghttp3/nghttp3.h> + +static int send_data(nghttp3_conn *conn) { + std::array<nghttp3_vec, 16> vec; + int64_t stream_id; + int fin; + + for (;;) { + auto veccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec.data(), + vec.size()); + if (veccnt < 0) { + return 0; + } + + if (veccnt || fin) { + auto ndatalen = nghttp3_vec_len(vec.data(), veccnt); + + if (nghttp3_conn_add_write_offset(conn, stream_id, ndatalen) < 0) { + return 0; + } + + if (nghttp3_conn_add_ack_offset(conn, stream_id, ndatalen) < 0) { + return 0; + } + } else { + return 0; + } + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + nghttp3_callbacks callbacks{}; + nghttp3_settings settings; + + nghttp3_settings_default(&settings); + + nghttp3_conn *conn; + auto rv = + nghttp3_conn_server_new(&conn, &callbacks, &settings, nullptr, nullptr); + if (rv != 0) { + return 0; + } + + nghttp3_conn_set_max_client_streams_bidi(conn, 100); + + nghttp3_ssize nread; + + if (send_data(conn) != 0) { + goto fin; + } + + nread = nghttp3_conn_read_stream(conn, 0, data, size, 0); + if (nread < 0) { + goto fin; + } + + if (send_data(conn) != 0) { + goto fin; + } + +fin: + nghttp3_conn_del(conn); + + return 0; +} diff --git a/fuzz/fuzz_qpackdecoder.cc b/fuzz/fuzz_qpackdecoder.cc new file mode 100644 index 0000000..43bbe50 --- /dev/null +++ b/fuzz/fuzz_qpackdecoder.cc @@ -0,0 +1,250 @@ +#include <arpa/inet.h> + +#include <cassert> +#include <cstring> +#include <vector> +#include <queue> +#include <functional> +#include <utility> +#include <memory> +#include <string> + +#include <nghttp3/nghttp3.h> + +#define nghttp3_ntohl64(N) be64toh(N) + +struct Request { + Request(int64_t stream_id, const nghttp3_buf *buf); + ~Request(); + + nghttp3_buf buf; + nghttp3_qpack_stream_context *sctx; + int64_t stream_id; +}; + +namespace std { +template <> struct greater<std::shared_ptr<Request>> { + bool operator()(const std::shared_ptr<Request> &lhs, + const std::shared_ptr<Request> &rhs) const { + return nghttp3_qpack_stream_context_get_ricnt(lhs->sctx) > + nghttp3_qpack_stream_context_get_ricnt(rhs->sctx); + } +}; +} // namespace std + +using Headers = std::vector<std::pair<std::string, std::string>>; + +class Decoder { +public: + Decoder(size_t max_dtable_size, size_t max_blocked); + ~Decoder(); + + int init(); + int read_encoder(nghttp3_buf *buf); + std::tuple<Headers, int> read_request(nghttp3_buf *buf, int64_t stream_id); + std::tuple<Headers, int> read_request(Request &req); + std::tuple<int64_t, Headers, int> process_blocked(); + size_t get_num_blocked() const; + +private: + const nghttp3_mem *mem_; + nghttp3_qpack_decoder *dec_; + std::priority_queue<std::shared_ptr<Request>, + std::vector<std::shared_ptr<Request>>, + std::greater<std::shared_ptr<Request>>> + blocked_reqs_; + size_t max_dtable_size_; + size_t max_blocked_; +}; + +Request::Request(int64_t stream_id, const nghttp3_buf *buf) + : buf(*buf), stream_id(stream_id) { + auto mem = nghttp3_mem_default(); + nghttp3_qpack_stream_context_new(&sctx, stream_id, mem); +} + +Request::~Request() { nghttp3_qpack_stream_context_del(sctx); } + +Decoder::Decoder(size_t max_dtable_size, size_t max_blocked) + : mem_(nghttp3_mem_default()), + dec_(nullptr), + max_dtable_size_(max_dtable_size), + max_blocked_(max_blocked) {} + +Decoder::~Decoder() { nghttp3_qpack_decoder_del(dec_); } + +int Decoder::init() { + if (auto rv = nghttp3_qpack_decoder_new(&dec_, max_dtable_size_, max_blocked_, + mem_); + rv != 0) { + return -1; + } + + nghttp3_qpack_decoder_set_max_dtable_capacity(dec_, max_dtable_size_); + + return 0; +} + +int Decoder::read_encoder(nghttp3_buf *buf) { + auto nread = + nghttp3_qpack_decoder_read_encoder(dec_, buf->pos, nghttp3_buf_len(buf)); + if (nread < 0) { + return -1; + } + + assert(static_cast<size_t>(nread) == nghttp3_buf_len(buf)); + + return 0; +} + +std::tuple<Headers, int> Decoder::read_request(nghttp3_buf *buf, + int64_t stream_id) { + auto req = std::make_shared<Request>(stream_id, buf); + + auto [headers, rv] = read_request(*req); + if (rv == -1) { + return {Headers{}, -1}; + } + if (rv == 1) { + if (blocked_reqs_.size() >= max_blocked_) { + return {Headers{}, -1}; + } + blocked_reqs_.emplace(std::move(req)); + return {Headers{}, 1}; + } + return {headers, 0}; +} + +std::tuple<Headers, int> Decoder::read_request(Request &req) { + nghttp3_qpack_nv nv; + uint8_t flags; + Headers headers; + + for (;;) { + auto nread = nghttp3_qpack_decoder_read_request( + dec_, req.sctx, &nv, &flags, req.buf.pos, nghttp3_buf_len(&req.buf), 1); + if (nread < 0) { + return {Headers{}, -1}; + } + + req.buf.pos += nread; + + if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) { + break; + } + if (flags & NGHTTP3_QPACK_DECODE_FLAG_BLOCKED) { + return {Headers{}, 1}; + } + if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) { + auto name = nghttp3_rcbuf_get_buf(nv.name); + auto value = nghttp3_rcbuf_get_buf(nv.value); + headers.emplace_back(std::string{name.base, name.base + name.len}, + std::string{value.base, value.base + value.len}); + nghttp3_rcbuf_decref(nv.name); + nghttp3_rcbuf_decref(nv.value); + } + } + + return {headers, 0}; +} + +std::tuple<int64_t, Headers, int> Decoder::process_blocked() { + if (!blocked_reqs_.empty()) { + auto &top = blocked_reqs_.top(); + if (nghttp3_qpack_stream_context_get_ricnt(top->sctx) > + nghttp3_qpack_decoder_get_icnt(dec_)) { + return {-1, {}, 0}; + } + + auto req = top; + blocked_reqs_.pop(); + + auto [headers, rv] = read_request(*req); + if (rv < 0) { + return {-1, {}, -1}; + } + assert(rv == 0); + + return {req->stream_id, headers, 0}; + } + return {-1, {}, 0}; +} + +size_t Decoder::get_num_blocked() const { return blocked_reqs_.size(); } + +int decode(const uint8_t *data, size_t datalen) { + auto dec = Decoder(256, 100); + if (auto rv = dec.init(); rv != 0) { + return rv; + } + + for (auto p = data, end = data + datalen; p != end;) { + int64_t stream_id; + uint32_t size; + + if (static_cast<size_t>(end - p) < sizeof(stream_id) + sizeof(size)) { + return -1; + } + + memcpy(&stream_id, p, sizeof(stream_id)); + stream_id = nghttp3_ntohl64(stream_id); + p += sizeof(stream_id); + + memcpy(&size, p, sizeof(size)); + size = ntohl(size); + p += sizeof(size); + + if ((size_t)(end - p) < size) { + return -1; + } + + nghttp3_buf buf; + buf.begin = buf.pos = const_cast<uint8_t *>(p); + buf.end = buf.last = const_cast<uint8_t *>(p) + size; + + p += size; + + if (stream_id == 0) { + if (auto rv = dec.read_encoder(&buf); rv != 0) { + return rv; + } + + for (;;) { + auto [stream_id, headers, rv] = dec.process_blocked(); + if (rv != 0) { + return rv; + } + + if (stream_id == -1) { + break; + } + + (void)headers; + } + + continue; + } + + auto [headers, rv] = dec.read_request(&buf, stream_id); + if (rv == -1) { + return rv; + } + if (rv == 1) { + // Stream blocked + continue; + } + + (void)headers; + } + + if (auto n = dec.get_num_blocked(); n) { + return -1; + } + + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + decode(data, size); + return 0; +} diff --git a/genchartbl.py b/genchartbl.py new file mode 100755 index 0000000..3529ede --- /dev/null +++ b/genchartbl.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +import sys +import string + +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] + if i == 0x7f: + return 'DEL ' + +def gentbl(tblname, pred): + sys.stdout.write('''\ +/* Generated by genchartbl.py */ +static const int {}[] = {{ +'''.format(tblname)) + + for i in range(256): + if pred(chr(i)): + v = 1 + else: + v = 0 + + if 0x21 <= i and i < 0x7f: + sys.stdout.write('{} /* {} */, '.format(v, chr(i))) + elif 0x80 <= i: + sys.stdout.write('{} /* {} */, '.format(v, hex(i))) + else: + sys.stdout.write('{} /* {} */, '.format(v, name(i))) + if (i + 1)%4 == 0: + sys.stdout.write('\n') + + sys.stdout.write('};\n') + +def sf_key(): + gentbl('SF_KEY_CHARS', lambda c: c in string.ascii_lowercase or + c in string.digits or c in '_-.*') + +def sf_dquote(): + gentbl('SF_DQUOTE_CHARS', lambda c: (0x20 <= ord(c) and ord(c) <= 0x21) or + (0x23 <= ord(c) and ord(c) <= 0x5b) or + (0x5d <= ord(c) and ord(c) <= 0x7e)) + +def sf_token(): + gentbl('SF_TOKEN_CHARS', lambda c: c in "!#$%&'*+-.^_`|~:/" or + c in string.digits or c in string.ascii_letters) + +def sf_byteseq(): + gentbl('SF_BYTESEQ_CHARS', lambda c: c in string.ascii_letters or + c in string.digits or c in '+/=') + +sf_key() +sys.stdout.write('\n') + +sf_dquote() +sys.stdout.write('\n') + +sf_token() +sys.stdout.write('\n') + +sf_byteseq() +sys.stdout.write('\n') diff --git a/genlibtokenlookup.py b/genlibtokenlookup.py new file mode 100755 index 0000000..cd6163f --- /dev/null +++ b/genlibtokenlookup.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python + +HEADERS = [ + (':authority', 0), + (':path', 1), + ('age', 2), + ('content-disposition', 3), + ('content-length', 4), + ('cookie', 5), + ('date', 6), + ('etag', 7), + ('if-modified-since', 8), + ('if-none-match', 9), + ('last-modified', 10), + ('link', 11), + ('location', 12), + ('referer', 13), + ('set-cookie', 14), + (':method', 15), + (':method', 16), + (':method', 17), + (':method', 18), + (':method', 19), + (':method', 20), + (':method', 21), + (':scheme', 22), + (':scheme', 23), + (':status', 24), + (':status', 25), + (':status', 26), + (':status', 27), + (':status', 28), + ('accept', 29), + ('accept', 30), + ('accept-encoding', 31), + ('accept-ranges', 32), + ('access-control-allow-headers', 33), + ('access-control-allow-headers', 34), + ('access-control-allow-origin', 35), + ('cache-control', 36), + ('cache-control', 37), + ('cache-control', 38), + ('cache-control', 39), + ('cache-control', 40), + ('cache-control', 41), + ('content-encoding', 42), + ('content-encoding', 43), + ('content-type', 44), + ('content-type', 45), + ('content-type', 46), + ('content-type', 47), + ('content-type', 48), + ('content-type', 49), + ('content-type', 50), + ('content-type', 51), + ('content-type', 52), + ('content-type', 53), + ('content-type', 54), + ('range', 55), + ('strict-transport-security', 56), + ('strict-transport-security', 57), + ('strict-transport-security', 58), + ('vary', 59), + ('vary', 60), + ('x-content-type-options', 61), + ('x-xss-protection', 62), + (':status', 63), + (':status', 64), + (':status', 65), + (':status', 66), + (':status', 67), + (':status', 68), + (':status', 69), + (':status', 70), + (':status', 71), + ('accept-language', 72), + ('access-control-allow-credentials', 73), + ('access-control-allow-credentials', 74), + ('access-control-allow-headers', 75), + ('access-control-allow-methods', 76), + ('access-control-allow-methods', 77), + ('access-control-allow-methods', 78), + ('access-control-expose-headers', 79), + ('access-control-request-headers', 80), + ('access-control-request-method', 81), + ('access-control-request-method', 82), + ('alt-svc', 83), + ('authorization', 84), + ('content-security-policy', 85), + ('early-data', 86), + ('expect-ct', 87), + ('forwarded', 88), + ('if-range', 89), + ('origin', 90), + ('purpose', 91), + ('server', 92), + ('timing-allow-origin', 93), + ('upgrade-insecure-requests', 94), + ('user-agent', 95), + ('x-forwarded-for', 96), + ('x-frame-options', 97), + ('x-frame-options', 98), + # Additional header fields for HTTP messaging validation + ('host', None), + ('connection', None), + ('keep-alive', None), + ('proxy-connection', None), + ('transfer-encoding', None), + ('upgrade', None), + ('te', None), + (':protocol', None), + ('priority', None), +] + +def to_enum_hd(k): + res = 'NGHTTP3_QPACK_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 '} nghttp3_qpack_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__': + print '''/* Don't use nghttp3_qpack_token below. Use mkstatichdtbl.py instead */''' + gen_enum() + print '' + gen_index_header() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..8022467 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,97 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 +# Copyright (c) 2016 ngtcp2 +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +add_subdirectory(includes) + +include_directories( + "${CMAKE_CURRENT_SOURCE_DIR}/includes" + "${CMAKE_CURRENT_BINARY_DIR}/includes" +) + +add_definitions(-DBUILDING_NGHTTP3) + +set(nghttp3_SOURCES + nghttp3_rcbuf.c + nghttp3_mem.c + nghttp3_str.c + nghttp3_conv.c + nghttp3_buf.c + nghttp3_ringbuf.c + nghttp3_pq.c + nghttp3_map.c + nghttp3_ksl.c + nghttp3_qpack.c + nghttp3_qpack_huffman.c + nghttp3_qpack_huffman_data.c + nghttp3_err.c + nghttp3_debug.c + nghttp3_conn.c + nghttp3_stream.c + nghttp3_frame.c + nghttp3_tnode.c + nghttp3_vec.c + nghttp3_gaptr.c + nghttp3_idtr.c + nghttp3_range.c + nghttp3_http.c + nghttp3_version.c + nghttp3_balloc.c + nghttp3_opl.c + nghttp3_objalloc.c + nghttp3_unreachable.c +) + +# Public shared library +if(ENABLE_SHARED_LIB) + add_library(nghttp3 SHARED ${nghttp3_SOURCES}) + set_target_properties(nghttp3 PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + C_VISIBILITY_PRESET hidden + ) + + install(TARGETS nghttp3 + 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(nghttp3_static STATIC ${nghttp3_SOURCES}) + set_target_properties(nghttp3_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + ARCHIVE_OUTPUT_NAME nghttp3${STATIC_LIB_SUFFIX} + ) + target_compile_definitions(nghttp3_static PUBLIC "-DNGHTTP3_STATICLIB") + if(ENABLE_STATIC_LIB) + install(TARGETS nghttp3_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + endif() +endif() + + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnghttp3.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..c9cc14b --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,97 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 +# Copyright (c) 2016 ngtcp2 +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = includes + +EXTRA_DIST = CMakeLists.txt + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes -DBUILDING_NGHTTP3 + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libnghttp3.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LTLIBRARIES = libnghttp3.la + +OBJECTS = \ + nghttp3_rcbuf.c \ + nghttp3_mem.c \ + nghttp3_str.c \ + nghttp3_conv.c \ + nghttp3_buf.c \ + nghttp3_ringbuf.c \ + nghttp3_pq.c \ + nghttp3_map.c \ + nghttp3_ksl.c \ + nghttp3_qpack.c \ + nghttp3_qpack_huffman.c \ + nghttp3_qpack_huffman_data.c \ + nghttp3_err.c \ + nghttp3_debug.c \ + nghttp3_conn.c \ + nghttp3_stream.c \ + nghttp3_frame.c \ + nghttp3_tnode.c \ + nghttp3_vec.c \ + nghttp3_gaptr.c \ + nghttp3_idtr.c \ + nghttp3_range.c \ + nghttp3_http.c \ + nghttp3_version.c \ + nghttp3_balloc.c \ + nghttp3_opl.c \ + nghttp3_objalloc.c \ + nghttp3_unreachable.c +HFILES = \ + nghttp3_rcbuf.h \ + nghttp3_mem.h \ + nghttp3_str.h \ + nghttp3_conv.h \ + nghttp3_buf.h \ + nghttp3_ringbuf.h \ + nghttp3_pq.h \ + nghttp3_map.h \ + nghttp3_ksl.h \ + nghttp3_qpack.h \ + nghttp3_qpack_huffman.h \ + nghttp3_err.h \ + nghttp3_debug.h \ + nghttp3_conn.h \ + nghttp3_stream.h \ + nghttp3_frame.h \ + nghttp3_tnode.h \ + nghttp3_vec.h \ + nghttp3_gaptr.h \ + nghttp3_idtr.h \ + nghttp3_range.h \ + nghttp3_http.h \ + nghttp3_balloc.h \ + nghttp3_opl.h \ + nghttp3_objalloc.h \ + nghttp3_unreachable.h \ + nghttp3_macro.h + +libnghttp3_la_SOURCES = $(HFILES) $(OBJECTS) +libnghttp3_la_LDFLAGS = -no-undefined \ + -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) diff --git a/lib/includes/CMakeLists.txt b/lib/includes/CMakeLists.txt new file mode 100644 index 0000000..76d7364 --- /dev/null +++ b/lib/includes/CMakeLists.txt @@ -0,0 +1,4 @@ +install(FILES + nghttp3/nghttp3.h + "${CMAKE_CURRENT_BINARY_DIR}/nghttp3/version.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/nghttp3") diff --git a/lib/includes/Makefile.am b/lib/includes/Makefile.am new file mode 100644 index 0000000..b69d2fc --- /dev/null +++ b/lib/includes/Makefile.am @@ -0,0 +1,26 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2016 ngtcp2 contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +nobase_include_HEADERS = nghttp3/nghttp3.h nghttp3/version.h diff --git a/lib/includes/nghttp3/nghttp3.h b/lib/includes/nghttp3/nghttp3.h new file mode 100644 index 0000000..7986ea8 --- /dev/null +++ b/lib/includes/nghttp3/nghttp3.h @@ -0,0 +1,2646 @@ +/* + * nghttp3 + * + * Copyright (c) 2018 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2017 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_H +#define NGHTTP3_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 + +#include <stdlib.h> +#if defined(_MSC_VER) && (_MSC_VER < 1800) +/* MSVC < 2013 does not have inttypes.h because it is not C99 + compliant. See compiler macros and version number in + https://sourceforge.net/p/predef/wiki/Compilers/ */ +# include <stdint.h> +#else /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +# include <inttypes.h> +#endif /* !defined(_MSC_VER) || (_MSC_VER >= 1800) */ +#include <sys/types.h> +#include <stdarg.h> +#include <stddef.h> + +#include <nghttp3/version.h> + +#ifdef NGHTTP3_STATICLIB +# define NGHTTP3_EXTERN +#elif defined(WIN32) +# ifdef BUILDING_NGHTTP3 +# define NGHTTP3_EXTERN __declspec(dllexport) +# else /* !BUILDING_NGHTTP3 */ +# define NGHTTP3_EXTERN __declspec(dllimport) +# endif /* !BUILDING_NGHTTP3 */ +#else /* !defined(WIN32) */ +# ifdef BUILDING_NGHTTP3 +# define NGHTTP3_EXTERN __attribute__((visibility("default"))) +# else /* !BUILDING_NGHTTP3 */ +# define NGHTTP3_EXTERN +# endif /* !BUILDING_NGHTTP3 */ +#endif /* !defined(WIN32) */ + +/** + * @typedef + * + * :type:`nghttp3_ssize` is signed counterpart of size_t. + */ +typedef ptrdiff_t nghttp3_ssize; + +/** + * @macro + * + * :macro:`NGHTTP3_ALPN_H3` is a serialized form of HTTP/3 ALPN + * protocol identifier this library supports. Notice that the first + * byte is the length of the following protocol identifier. + */ +#define NGHTTP3_ALPN_H3 "\x2h3" + +/** + * @macrosection + * + * nghttp3 library error codes + */ + +/** + * @macro + * + * :macro:`NGHTTP3_ERR_INVALID_ARGUMENT` indicates that a passed + * argument is invalid. + */ +#define NGHTTP3_ERR_INVALID_ARGUMENT -101 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_NOBUF` indicates that a provided buffer does + * not have enough space to store data. + */ +#define NGHTTP3_ERR_NOBUF -102 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_INVALID_STATE` indicates that a requested + * operation is not allowed at the current connection state. + */ +#define NGHTTP3_ERR_INVALID_STATE -103 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_WOULDBLOCK` indicates that an operation might + * block. + */ +#define NGHTTP3_ERR_WOULDBLOCK -104 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_STREAM_IN_USE` indicates that a stream ID is + * already in use. + */ +#define NGHTTP3_ERR_STREAM_IN_USE -105 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_MALFORMED_HTTP_HEADER` indicates that an HTTP + * header field is malformed. + */ +#define NGHTTP3_ERR_MALFORMED_HTTP_HEADER -107 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_REMOVE_HTTP_HEADER` indicates that an HTTP + * header field is discarded. + */ +#define NGHTTP3_ERR_REMOVE_HTTP_HEADER -108 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING` indicates that HTTP + * messaging is malformed. + */ +#define NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING -109 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_QPACK_FATAL` indicates that a fatal error is + * occurred during QPACK processing and it cannot be recoverable. + */ +#define NGHTTP3_ERR_QPACK_FATAL -111 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE` indicates that a header + * field is too large to process. + */ +#define NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE -112 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_STREAM_NOT_FOUND` indicates that a stream is + * not found. + */ +#define NGHTTP3_ERR_STREAM_NOT_FOUND -114 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_CONN_CLOSING` indicates that a connection is + * closing state. + */ +#define NGHTTP3_ERR_CONN_CLOSING -116 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_STREAM_DATA_OVERFLOW` indicates that the length + * of stream data is too long and causes overflow. + */ +#define NGHTTP3_ERR_STREAM_DATA_OVERFLOW -117 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED` indicates that a + * QPACK decompression failed. + */ +#define NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED -402 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR` indicates that an + * error occurred while reading QPACK encoder stream. + */ +#define NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR -403 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR` indicates that an + * error occurred while reading QPACK decoder stream. + */ +#define NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR -404 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_FRAME_UNEXPECTED` indicates that an + * unexpected HTTP/3 frame is received. + */ +#define NGHTTP3_ERR_H3_FRAME_UNEXPECTED -408 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_FRAME_ERROR` indicates that an HTTP/3 frame + * is malformed. + */ +#define NGHTTP3_ERR_H3_FRAME_ERROR -409 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_MISSING_SETTINGS` indicates that an HTTP/3 + * SETTINGS frame is missing. + */ +#define NGHTTP3_ERR_H3_MISSING_SETTINGS -665 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_INTERNAL_ERROR` indicates an internal error. + */ +#define NGHTTP3_ERR_H3_INTERNAL_ERROR -667 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM` indicates that a + * critical stream is closed. + */ +#define NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM -668 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR` indicates a general + * protocol error. This is typically a catch-all error. + */ +#define NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR -669 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_ID_ERROR` indicates that an ID related error + * occurred. + */ +#define NGHTTP3_ERR_H3_ID_ERROR -670 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_SETTINGS_ERROR` indicates that an HTTP/3 + * SETTINGS frame is malformed. + */ +#define NGHTTP3_ERR_H3_SETTINGS_ERROR -671 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_H3_STREAM_CREATION_ERROR` indicates that a + * remote endpoint attempts to create a new stream which is not + * allowed. + */ +#define NGHTTP3_ERR_H3_STREAM_CREATION_ERROR -672 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_FATAL` indicates that error codes less than + * this value is fatal error. When this error is returned, an + * endpoint should drop connection immediately. + */ +#define NGHTTP3_ERR_FATAL -900 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_NOMEM` indicates out of memory. + */ +#define NGHTTP3_ERR_NOMEM -901 +/** + * @macro + * + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` indicates that user defined + * callback function failed. + */ +#define NGHTTP3_ERR_CALLBACK_FAILURE -902 + +/** + * @macrosection + * + * HTTP/3 application error code + */ + +/** + * @macro + * + * :macro:`NGHTTP3_H3_NO_ERROR` is HTTP/3 application error code + * ``H3_NO_ERROR``. + */ +#define NGHTTP3_H3_NO_ERROR 0x0100 +/** + * @macro + * + * :macro:`NGHTTP3_H3_GENERAL_PROTOCOL_ERROR` is HTTP/3 application + * error code ``H3_GENERAL_PROTOCOL_ERROR``. + */ +#define NGHTTP3_H3_GENERAL_PROTOCOL_ERROR 0x0101 +/** + * @macro + * + * :macro:`NGHTTP3_H3_INTERNAL_ERROR` is HTTP/3 application error code + * ``H3_INTERNAL_ERROR``. + */ +#define NGHTTP3_H3_INTERNAL_ERROR 0x0102 +/** + * @macro + * + * :macro:`NGHTTP3_H3_STREAM_CREATION_ERROR` is HTTP/3 application + * error code ``H3_STREAM_CREATION_ERROR``. + */ +#define NGHTTP3_H3_STREAM_CREATION_ERROR 0x0103 +/** + * @macro + * + * :macro:`NGHTTP3_H3_CLOSED_CRITICAL_STREAM` is HTTP/3 application + * error code ``H3_CLOSED_CRITICAL_STREAM``. + */ +#define NGHTTP3_H3_CLOSED_CRITICAL_STREAM 0x0104 +/** + * @macro + * + * :macro:`NGHTTP3_H3_FRAME_UNEXPECTED` is HTTP/3 application error + * code ``H3_FRAME_UNEXPECTED``. + */ +#define NGHTTP3_H3_FRAME_UNEXPECTED 0x0105 +/** + * @macro + * + * :macro:`NGHTTP3_H3_FRAME_ERROR` is HTTP/3 application error code + * ``H3_FRAME_ERROR``. + */ +#define NGHTTP3_H3_FRAME_ERROR 0x0106 +/** + * @macro + * + * :macro:`NGHTTP3_H3_EXCESSIVE_LOAD` is HTTP/3 application error code + * ``H3_EXCESSIVE_LOAD``. + */ +#define NGHTTP3_H3_EXCESSIVE_LOAD 0x0107 +/** + * @macro + * + * :macro:`NGHTTP3_H3_ID_ERROR` is HTTP/3 application error code + * ``H3_ID_ERROR``. + */ +#define NGHTTP3_H3_ID_ERROR 0x0108 +/** + * @macro + * + * :macro:`NGHTTP3_H3_SETTINGS_ERROR` is HTTP/3 application error code + * ``H3_SETTINGS_ERROR``. + */ +#define NGHTTP3_H3_SETTINGS_ERROR 0x0109 +/** + * @macro + * + * :macro:`NGHTTP3_H3_MISSING_SETTINGS` is HTTP/3 application error + * code ``H3_MISSING_SETTINGS``. + */ +#define NGHTTP3_H3_MISSING_SETTINGS 0x010a +/** + * @macro + * + * :macro:`NGHTTP3_H3_REQUEST_REJECTED` is HTTP/3 application error + * code ``H3_REQUEST_REJECTED``. + */ +#define NGHTTP3_H3_REQUEST_REJECTED 0x010b +/** + * @macro + * + * :macro:`NGHTTP3_H3_REQUEST_CANCELLED` is HTTP/3 application error + * code ``H3_REQUEST_CANCELLED``. + */ +#define NGHTTP3_H3_REQUEST_CANCELLED 0x010c +/** + * @macro + * + * :macro:`NGHTTP3_H3_REQUEST_INCOMPLETE` is HTTP/3 application error + * code ``H3_REQUEST_INCOMPLETE``. + */ +#define NGHTTP3_H3_REQUEST_INCOMPLETE 0x010d +/** + * @macro + * + * :macro:`NGHTTP3_H3_MESSAGE_ERROR` is HTTP/3 application error code + * ``H3_MESSAGE_ERROR``. + */ +#define NGHTTP3_H3_MESSAGE_ERROR 0x010e +/** + * @macro + * + * :macro:`NGHTTP3_H3_CONNECT_ERROR` is HTTP/3 application error code + * ``H3_CONNECT_ERROR``. + */ +#define NGHTTP3_H3_CONNECT_ERROR 0x010f +/** + * @macro + * + * :macro:`NGHTTP3_H3_VERSION_FALLBACK` is HTTP/3 application error + * code ``H3_VERSION_FALLBACK``. + */ +#define NGHTTP3_H3_VERSION_FALLBACK 0x0110 +/** + * @macro + * + * :macro:`NGHTTP3_QPACK_DECOMPRESSION_FAILED` is HTTP/3 application + * error code ``QPACK_DECOMPRESSION_FAILED``. + */ +#define NGHTTP3_QPACK_DECOMPRESSION_FAILED 0x0200 +/** + * @macro + * + * :macro:`NGHTTP3_QPACK_ENCODER_STREAM_ERROR` is HTTP/3 application + * error code ``QPACK_ENCODER_STREAM_ERROR``. + */ +#define NGHTTP3_QPACK_ENCODER_STREAM_ERROR 0x0201 +/** + * @macro + * + * :macro:`NGHTTP3_QPACK_DECODER_STREAM_ERROR` is HTTP/3 application + * error code ``QPACK_DECODER_STREAM_ERROR``. + */ +#define NGHTTP3_QPACK_DECODER_STREAM_ERROR 0x0202 + +/** + * @functypedef + * + * :type:`nghttp3_malloc` is a custom memory allocator to replace + * :manpage:`malloc(3)`. The |user_data| is the + * :member:`nghttp3_mem.user_data`. + */ +typedef void *(*nghttp3_malloc)(size_t size, void *user_data); + +/** + * @functypedef + * + * :type:`nghttp3_free` is a custom memory allocator to replace + * :manpage:`free(3)`. The |user_data| is the + * :member:`nghttp3_mem.user_data`. + */ +typedef void (*nghttp3_free)(void *ptr, void *user_data); + +/** + * @functypedef + * + * :type:`nghttp3_calloc` is a custom memory allocator to replace + * :manpage:`calloc(3)`. The |user_data| is the + * :member:`nghttp3_mem.user_data`. + */ +typedef void *(*nghttp3_calloc)(size_t nmemb, size_t size, void *user_data); + +/** + * @functypedef + * + * :type:`nghttp3_realloc` is a custom memory allocator to replace + * :manpage:`realloc(3)`. The |user_data| is the + * :member:`nghttp3_mem.user_data`. + */ +typedef void *(*nghttp3_realloc)(void *ptr, size_t size, void *user_data); + +/** + * @struct + * + * :type:`nghttp3_mem` is a custom memory allocator functions and user + * defined pointer. The :member:`user_data` field 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 :manpage:`malloc(3)`, :manpage:`free(3)`, + * :manpage:`calloc(3)` and :manpage:`realloc(3)` respectively:: + * + * void *my_malloc_cb(size_t size, void *user_data) { + * (void)user_data; + * return my_malloc(size); + * } + * + * void my_free_cb(void *ptr, void *user_data) { + * (void)user_data; + * my_free(ptr); + * } + * + * void *my_calloc_cb(size_t nmemb, size_t size, void *user_data) { + * (void)user_data; + * return my_calloc(nmemb, size); + * } + * + * void *my_realloc_cb(void *ptr, size_t size, void *user_data) { + * (void)user_data; + * return my_realloc(ptr, size); + * } + * + * void conn_new() { + * nghttp3_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb, + * my_realloc_cb}; + * + * ... + * } + */ +typedef struct nghttp3_mem { + /** + * :member:`user_data` is an arbitrary user supplied data. This + * is passed to each allocator function. + */ + void *user_data; + /** + * :member:`malloc` is a custom allocator function to replace + * :manpage:`malloc(3)`. + */ + nghttp3_malloc malloc; + /** + * :member:`free` is a custom allocator function to replace + * :manpage:`free(3)`. + */ + nghttp3_free free; + /** + * :member:`calloc` is a custom allocator function to replace + * :manpage:`calloc(3)`. + */ + nghttp3_calloc calloc; + /** + * :member:`realloc` is a custom allocator function to replace + * :manpage:`realloc(3)`. + */ + nghttp3_realloc realloc; +} nghttp3_mem; + +/** + * @function + * + * `nghttp3_mem_default` returns the default memory allocator which + * uses malloc/calloc/realloc/free. + */ +NGHTTP3_EXTERN const nghttp3_mem *nghttp3_mem_default(void); + +/** + * @struct + * + * :type:`nghttp3_vec` is ``struct iovec`` compatible structure to + * reference arbitrary array of bytes. + */ +typedef struct nghttp3_vec { + /** + * :member:`base` points to the data. + */ + uint8_t *base; + /** + * :member:`len` is the number of bytes which the buffer pointed by + * base contains. + */ + size_t len; +} nghttp3_vec; + +/** + * @struct + * + * :type:`nghttp3_rcbuf` is the object representing reference counted + * buffer. The details of this structure are intentionally hidden + * from the public API. + */ +typedef struct nghttp3_rcbuf nghttp3_rcbuf; + +/** + * @function + * + * `nghttp3_rcbuf_incref` increments the reference count of |rcbuf| by + * 1. + */ +NGHTTP3_EXTERN void nghttp3_rcbuf_incref(nghttp3_rcbuf *rcbuf); + +/** + * @function + * + * `nghttp3_rcbuf_decref` 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. + */ +NGHTTP3_EXTERN void nghttp3_rcbuf_decref(nghttp3_rcbuf *rcbuf); + +/** + * @function + * + * `nghttp3_rcbuf_get_buf` returns the underlying buffer managed by + * |rcbuf|. + */ +NGHTTP3_EXTERN nghttp3_vec nghttp3_rcbuf_get_buf(const nghttp3_rcbuf *rcbuf); + +/** + * @function + * + * `nghttp3_rcbuf_is_static` 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. + */ +NGHTTP3_EXTERN int nghttp3_rcbuf_is_static(const nghttp3_rcbuf *rcbuf); + +/** + * @struct + * + * :type:`nghttp3_buf` is the variable size buffer. + */ +typedef struct nghttp3_buf { + /** + * :member:`begin` points to the beginning of the buffer. + */ + uint8_t *begin; + /** + * :member:`end` points to the one beyond of the last byte of the + * buffer + */ + uint8_t *end; + /** + * :member:`pos` pointers to the start of data. Typically, this + * points to the point that next data should be read. Initially, it + * points to :member:`begin`. + */ + uint8_t *pos; + /** + * :member:`last` points to the one beyond of the last data of the + * buffer. Typically, new data is written at this point. + * Initially, it points to :member:`begin`. + */ + uint8_t *last; +} nghttp3_buf; + +/** + * @function + * + * `nghttp3_buf_init` initializes empty |buf|. + */ +NGHTTP3_EXTERN void nghttp3_buf_init(nghttp3_buf *buf); + +/** + * @function + * + * `nghttp3_buf_free` frees resources allocated for |buf| using |mem| + * as memory allocator. :member:`buf->begin <nghttp3_buf.begin>` must + * be a heap buffer allocated by |mem|. + */ +NGHTTP3_EXTERN void nghttp3_buf_free(nghttp3_buf *buf, const nghttp3_mem *mem); + +/** + * @function + * + * `nghttp3_buf_left` returns the number of additional bytes which can + * be written to the underlying buffer. In other words, it returns + * :member:`buf->end <nghttp3_buf.end>` - :member:`buf->last + * <nghttp3_buf.last>`. + */ +NGHTTP3_EXTERN size_t nghttp3_buf_left(const nghttp3_buf *buf); + +/** + * @function + * + * `nghttp3_buf_len` returns the number of bytes left to read. In + * other words, it returns :member:`buf->last <nghttp3_buf.last>` - + * :member:`buf->pos <nghttp3_buf.pos>`. + */ +NGHTTP3_EXTERN size_t nghttp3_buf_len(const nghttp3_buf *buf); + +/** + * @function + * + * `nghttp3_buf_reset` sets :member:`buf->pos <nghttp3_buf.pos>` and + * :member:`buf->last <nghttp3_buf.last>` to :member:`buf->begin + * <nghttp3_buf.begin>`. + */ +NGHTTP3_EXTERN void nghttp3_buf_reset(nghttp3_buf *buf); + +/** + * @macrosection + * + * Flags for header field name/value pair + */ + +/** + * @macro + * + * :macro:`NGHTTP3_NV_FLAG_NONE` indicates no flag set. + */ +#define NGHTTP3_NV_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGHTTP3_NV_FLAG_NEVER_INDEX` indicates that this name/value + * pair must not be indexed. Other implementation calls this bit as + * "sensitive". + */ +#define NGHTTP3_NV_FLAG_NEVER_INDEX 0x01u + +/** + * @macro + * + * :macro:`NGHTTP3_NV_FLAG_NO_COPY_NAME` 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. + */ +#define NGHTTP3_NV_FLAG_NO_COPY_NAME 0x02u + +/** + * @macro + * + * :macro:`NGHTTP3_NV_FLAG_NO_COPY_VALUE` 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. + */ +#define NGHTTP3_NV_FLAG_NO_COPY_VALUE 0x04u + +/** + * @struct + * + * :type:`nghttp3_nv` is the name/value pair, which mainly used to + * represent header fields. + */ +typedef struct nghttp3_nv { + /** + * :member:`name` is the header field name. + */ + uint8_t *name; + /** + * :member:`value` is the header field value. + */ + uint8_t *value; + /** + * :member:`namelen` is the length of the |name|, excluding + * terminating NULL. + */ + size_t namelen; + /** + * :member:`valuelen` is the length of the |value|, excluding + * terminating NULL. + */ + size_t valuelen; + /** + * :member:`flags` is bitwise OR of one or more of + * :macro:`NGHTTP3_NV_FLAG_* <NGHTTP3_NV_FLAG_NONE>`. + */ + uint8_t flags; +} nghttp3_nv; + +/* Generated by mkstatichdtbl.py */ +/** + * @enum + * + * :type:`nghttp3_qpack_token` defines HTTP header field name tokens + * to identify field name quickly. It appears in + * :member:`nghttp3_qpack_nv.token`. + */ +typedef enum nghttp3_qpack_token { + /** + * :enum:`NGHTTP3_QPACK_TOKEN__AUTHORITY` is a token for + * ``:authority``. + */ + NGHTTP3_QPACK_TOKEN__AUTHORITY = 0, + /** + * :enum:`NGHTTP3_QPACK_TOKEN__PATH` is a token for ``:path``. + */ + NGHTTP3_QPACK_TOKEN__PATH = 8, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_AGE` is a token for ``age``. + */ + NGHTTP3_QPACK_TOKEN_AGE = 43, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_CONTENT_DISPOSITION` is a token for + * ``content-disposition``. + */ + NGHTTP3_QPACK_TOKEN_CONTENT_DISPOSITION = 52, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_CONTENT_LENGTH` is a token for + * ``content-length``. + */ + NGHTTP3_QPACK_TOKEN_CONTENT_LENGTH = 55, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_COOKIE` is a token for ``cookie``. + */ + NGHTTP3_QPACK_TOKEN_COOKIE = 68, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_DATE` is a token for ``date``. + */ + NGHTTP3_QPACK_TOKEN_DATE = 69, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ETAG` is a token for ``etag``. + */ + NGHTTP3_QPACK_TOKEN_ETAG = 71, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_IF_MODIFIED_SINCE` is a token for + * ``if-modified-since``. + */ + NGHTTP3_QPACK_TOKEN_IF_MODIFIED_SINCE = 74, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_IF_NONE_MATCH` is a token for + * ``if-none-match``. + */ + NGHTTP3_QPACK_TOKEN_IF_NONE_MATCH = 75, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_LAST_MODIFIED` is a token for + * ``last-modified``. + */ + NGHTTP3_QPACK_TOKEN_LAST_MODIFIED = 77, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_LINK` is a token for ``link``. + */ + NGHTTP3_QPACK_TOKEN_LINK = 78, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_LOCATION` is a token for ``location``. + */ + NGHTTP3_QPACK_TOKEN_LOCATION = 79, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_REFERER` is a token for ``referer``. + */ + NGHTTP3_QPACK_TOKEN_REFERER = 83, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_SET_COOKIE` is a token for + * ``set-cookie``. + */ + NGHTTP3_QPACK_TOKEN_SET_COOKIE = 85, + /** + * :enum:`NGHTTP3_QPACK_TOKEN__METHOD` is a token for ``:method``. + */ + NGHTTP3_QPACK_TOKEN__METHOD = 1, + /** + * :enum:`NGHTTP3_QPACK_TOKEN__SCHEME` is a token for ``:scheme``. + */ + NGHTTP3_QPACK_TOKEN__SCHEME = 9, + /** + * :enum:`NGHTTP3_QPACK_TOKEN__STATUS` is a token for ``:status``. + */ + NGHTTP3_QPACK_TOKEN__STATUS = 11, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCEPT` is a token for ``accept``. + */ + NGHTTP3_QPACK_TOKEN_ACCEPT = 25, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCEPT_ENCODING` is a token for + * ``accept-encoding``. + */ + NGHTTP3_QPACK_TOKEN_ACCEPT_ENCODING = 27, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCEPT_RANGES` is a token for + * ``accept-ranges``. + */ + NGHTTP3_QPACK_TOKEN_ACCEPT_RANGES = 29, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS` is a + * token for ``access-control-allow-headers``. + */ + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS = 32, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN` is a + * token for ``access-control-allow-origin``. + */ + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN = 38, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_CACHE_CONTROL` is a token for + * ``cache-control``. + */ + NGHTTP3_QPACK_TOKEN_CACHE_CONTROL = 46, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_CONTENT_ENCODING` is a token for + * ``content-encoding``. + */ + NGHTTP3_QPACK_TOKEN_CONTENT_ENCODING = 53, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_CONTENT_TYPE` is a token for + * ``content-type``. + */ + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE = 57, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_RANGE` is a token for ``range``. + */ + NGHTTP3_QPACK_TOKEN_RANGE = 82, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY` is a token + * for ``strict-transport-security``. + */ + NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY = 86, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_VARY` is a token for ``vary``. + */ + NGHTTP3_QPACK_TOKEN_VARY = 92, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_X_CONTENT_TYPE_OPTIONS` is a token for + * ``x-content-type-options``. + */ + NGHTTP3_QPACK_TOKEN_X_CONTENT_TYPE_OPTIONS = 94, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_X_XSS_PROTECTION` is a token for + * ``x-xss-protection``. + */ + NGHTTP3_QPACK_TOKEN_X_XSS_PROTECTION = 98, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCEPT_LANGUAGE` is a token for + * ``accept-language``. + */ + NGHTTP3_QPACK_TOKEN_ACCEPT_LANGUAGE = 28, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_CREDENTIALS` is a + * token for ``access-control-allow-credentials``. + */ + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_CREDENTIALS = 30, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS` is a + * token for ``access-control-allow-methods``. + */ + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS = 35, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_EXPOSE_HEADERS` is a + * token for ``access-control-expose-headers``. + */ + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_EXPOSE_HEADERS = 39, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_HEADERS` is a + * token for ``access-control-request-headers``. + */ + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_HEADERS = 40, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_METHOD` is a + * token for ``access-control-request-method``. + */ + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_METHOD = 41, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ALT_SVC` is a token for ``alt-svc``. + */ + NGHTTP3_QPACK_TOKEN_ALT_SVC = 44, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_AUTHORIZATION` is a token for + * ``authorization``. + */ + NGHTTP3_QPACK_TOKEN_AUTHORIZATION = 45, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_CONTENT_SECURITY_POLICY` is a token + * for ``content-security-policy``. + */ + NGHTTP3_QPACK_TOKEN_CONTENT_SECURITY_POLICY = 56, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_EARLY_DATA` is a token for + * ``early-data``. + */ + NGHTTP3_QPACK_TOKEN_EARLY_DATA = 70, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_EXPECT_CT` is a token for + * ``expect-ct``. + */ + NGHTTP3_QPACK_TOKEN_EXPECT_CT = 72, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_FORWARDED` is a token for + * ``forwarded``. + */ + NGHTTP3_QPACK_TOKEN_FORWARDED = 73, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_IF_RANGE` is a token for ``if-range``. + */ + NGHTTP3_QPACK_TOKEN_IF_RANGE = 76, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_ORIGIN` is a token for ``origin``. + */ + NGHTTP3_QPACK_TOKEN_ORIGIN = 80, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_PURPOSE` is a token for ``purpose``. + */ + NGHTTP3_QPACK_TOKEN_PURPOSE = 81, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_SERVER` is a token for ``server``. + */ + NGHTTP3_QPACK_TOKEN_SERVER = 84, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_TIMING_ALLOW_ORIGIN` is a token for + * ``timing-allow-origin``. + */ + NGHTTP3_QPACK_TOKEN_TIMING_ALLOW_ORIGIN = 89, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_UPGRADE_INSECURE_REQUESTS` is a token + * for ``upgrade-insecure-requests``. + */ + NGHTTP3_QPACK_TOKEN_UPGRADE_INSECURE_REQUESTS = 90, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_USER_AGENT` is a token for + * ``user-agent``. + */ + NGHTTP3_QPACK_TOKEN_USER_AGENT = 91, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_X_FORWARDED_FOR` is a token for + * ``x-forwarded-for``. + */ + NGHTTP3_QPACK_TOKEN_X_FORWARDED_FOR = 95, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_X_FRAME_OPTIONS` is a token for + * ``x-frame-options``. + */ + NGHTTP3_QPACK_TOKEN_X_FRAME_OPTIONS = 96, + + /* Additional header fields for HTTP messaging validation */ + + /** + * :enum:`NGHTTP3_QPACK_TOKEN_HOST` is a token for ``host``. + */ + NGHTTP3_QPACK_TOKEN_HOST = 1000, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_CONNECTION` is a token for + * ``connection``. + */ + NGHTTP3_QPACK_TOKEN_CONNECTION, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_KEEP_ALIVE` is a token for + * ``keep-alive``. + */ + NGHTTP3_QPACK_TOKEN_KEEP_ALIVE, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_PROXY_CONNECTION` is a token for + * ``proxy-connection``. + */ + NGHTTP3_QPACK_TOKEN_PROXY_CONNECTION, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_TRANSFER_ENCODING` is a token for + * ``transfer-encoding``. + */ + NGHTTP3_QPACK_TOKEN_TRANSFER_ENCODING, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_UPGRADE` is a token for ``upgrade``. + */ + NGHTTP3_QPACK_TOKEN_UPGRADE, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_TE` is a token for ``te``. + */ + NGHTTP3_QPACK_TOKEN_TE, + /** + * :enum:`NGHTTP3_QPACK_TOKEN__PROTOCOL` is a token for + * ``:protocol``. + */ + NGHTTP3_QPACK_TOKEN__PROTOCOL, + /** + * :enum:`NGHTTP3_QPACK_TOKEN_PRIORITY` is a token for ``priority``. + */ + NGHTTP3_QPACK_TOKEN_PRIORITY +} nghttp3_qpack_token; + +/** + * @struct + * + * :type:`nghttp3_qpack_nv` represents header field name/value pair + * just like :type:`nghttp3_nv`. It is an extended version of + * :type:`nghttp3_nv` and has reference counted buffers and tokens + * which might be useful for applications. + */ +typedef struct nghttp3_qpack_nv { + /** + * :member:`name` is the buffer containing header field name. + * NULL-termination is guaranteed. + */ + nghttp3_rcbuf *name; + /** + * :member:`value` is the buffer containing header field value. + * NULL-termination is guaranteed. + */ + nghttp3_rcbuf *value; + /** + * :member:`token` is :type:`nghttp3_qpack_token` value of + * :member:`name`. It could be -1 if we have no token for that + * header field name. + */ + int32_t token; + /** + * :member:`flags` is a bitwise OR of one or more of + * :macro:`NGHTTP3_NV_FLAG_* <NGHTTP3_NV_FLAG_NONE>`. + */ + uint8_t flags; +} nghttp3_qpack_nv; + +/** + * @struct + * + * :type:`nghttp3_qpack_encoder` is QPACK encoder. + */ +typedef struct nghttp3_qpack_encoder nghttp3_qpack_encoder; + +/** + * @function + * + * `nghttp3_qpack_encoder_new` initializes QPACK encoder. |pencoder| + * must be non-NULL pointer. |hard_max_dtable_capacity| is the upper + * bound of the dynamic table capacity. |mem| is a memory allocator. + * This function allocates memory for :type:`nghttp3_qpack_encoder` + * itself and assigns its pointer to |*pencoder| if it succeeds. + * + * The maximum dynamic table capacity is still 0. In order to change + * the maximum dynamic table capacity, call + * `nghttp3_qpack_encoder_set_max_dtable_capacity`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory. + */ +NGHTTP3_EXTERN int nghttp3_qpack_encoder_new(nghttp3_qpack_encoder **pencoder, + size_t hard_max_dtable_capacity, + const nghttp3_mem *mem); + +/** + * @function + * + * `nghttp3_qpack_encoder_del` frees memory allocated for |encoder|. + * This function frees memory pointed by |encoder| itself. + */ +NGHTTP3_EXTERN void nghttp3_qpack_encoder_del(nghttp3_qpack_encoder *encoder); + +/** + * @function + * + * `nghttp3_qpack_encoder_encode` encodes the list of header fields + * |nva|. |nvlen| is the length of |nva|. |stream_id| is the + * identifier of the stream which this header fields belong to. This + * function writes header block prefix, encoded header fields, and + * encoder stream to |pbuf|, |rbuf|, and |ebuf| respectively. The + * :member:`nghttp3_buf.last` will be adjusted when data is written. + * An application should write |pbuf| and |rbuf| to the request stream + * in this order. + * + * The buffer pointed by |pbuf|, |rbuf|, and |ebuf| can be empty + * buffer. It is fine to pass a buffer initialized by + * `nghttp3_buf_init(buf) <nghttp3_buf_init>`. This function + * allocates memory for these buffers as necessary. In particular, it + * frees and expands buffer if the current capacity of buffer is not + * enough. If :member:`nghttp3_buf.begin` of any buffer is not NULL, + * it must be allocated by the same memory allocator passed to + * `nghttp3_qpack_encoder_new()`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory + * :macro:`NGHTTP3_ERR_QPACK_FATAL` + * |encoder| is in unrecoverable error state and cannot be used + * anymore. + */ +NGHTTP3_EXTERN int nghttp3_qpack_encoder_encode( + nghttp3_qpack_encoder *encoder, nghttp3_buf *pbuf, nghttp3_buf *rbuf, + nghttp3_buf *ebuf, int64_t stream_id, const nghttp3_nv *nva, size_t nvlen); + +/** + * @function + * + * `nghttp3_qpack_encoder_read_decoder` reads decoder stream. The + * buffer pointed by |src| of length |srclen| contains decoder stream. + * + * This function returns the number of bytes read, or one of the + * following negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory + * :macro:`NGHTTP3_ERR_QPACK_FATAL` + * |encoder| is in unrecoverable error state and cannot be used + * anymore. + * :macro:`NGHTTP3_ERR_QPACK_DECODER_STREAM` + * |encoder| is unable to process input because it is malformed. + */ +NGHTTP3_EXTERN nghttp3_ssize nghttp3_qpack_encoder_read_decoder( + nghttp3_qpack_encoder *encoder, const uint8_t *src, size_t srclen); + +/** + * @function + * + * `nghttp3_qpack_encoder_set_max_dtable_capacity` sets max dynamic + * table capacity to |max_dtable_capacity|. If |max_dtable_capacity| is + * larger than ``hard_max_dtable_capacity`` parameter of + * `nghttp3_qpack_encoder_new`, it is truncated to the latter. + */ +NGHTTP3_EXTERN void +nghttp3_qpack_encoder_set_max_dtable_capacity(nghttp3_qpack_encoder *encoder, + size_t max_dtable_capacity); + +/** + * @function + * + * `nghttp3_qpack_encoder_set_max_blocked_streams` sets the number of + * streams which can be blocked to |max_blocked_streams|. + */ +NGHTTP3_EXTERN void +nghttp3_qpack_encoder_set_max_blocked_streams(nghttp3_qpack_encoder *encoder, + size_t max_blocked_streams); + +/** + * @function + * + * `nghttp3_qpack_encoder_ack_everything` tells |encoder| that all + * encoded header blocks are acknowledged. This function is provided + * for debugging purpose only. In HTTP/3, |encoder| knows this by + * reading decoder stream with `nghttp3_qpack_encoder_read_decoder()`. + */ +NGHTTP3_EXTERN void +nghttp3_qpack_encoder_ack_everything(nghttp3_qpack_encoder *encoder); + +/** + * @function + * + * `nghttp3_qpack_encoder_get_num_blocked_streams` returns the number + * of streams which are potentially blocked at decoder side. + */ +NGHTTP3_EXTERN size_t +nghttp3_qpack_encoder_get_num_blocked_streams(nghttp3_qpack_encoder *encoder); + +/** + * @struct + * + * :type:`nghttp3_qpack_stream_context` is a decoder context for an + * individual stream. Its state is per header block. In order to + * reuse this object for another header block, call + * `nghttp3_qpack_stream_context_reset`. + */ +typedef struct nghttp3_qpack_stream_context nghttp3_qpack_stream_context; + +/** + * @function + * + * `nghttp3_qpack_stream_context_new` initializes stream context. + * |psctx| must be non-NULL pointer. |stream_id| is stream ID. |mem| + * is a memory allocator. This function allocates memory for + * :type:`nghttp3_qpack_stream_context` itself and assigns its pointer + * to |*psctx| if it succeeds. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory. + */ +NGHTTP3_EXTERN int +nghttp3_qpack_stream_context_new(nghttp3_qpack_stream_context **psctx, + int64_t stream_id, const nghttp3_mem *mem); + +/** + * @function + * + * `nghttp3_qpack_stream_context_del` frees memory allocated for + * |sctx|. This function frees memory pointed by |sctx| itself. + */ +NGHTTP3_EXTERN void +nghttp3_qpack_stream_context_del(nghttp3_qpack_stream_context *sctx); + +/** + * @function + * + * `nghttp3_qpack_stream_context_get_ricnt` returns required insert + * count. + */ +NGHTTP3_EXTERN uint64_t +nghttp3_qpack_stream_context_get_ricnt(nghttp3_qpack_stream_context *sctx); + +/** + * @function + * + * `nghttp3_qpack_stream_context_reset` resets the state of |sctx|. + * Then it can be reused for an another header block in the same + * stream. + */ +NGHTTP3_EXTERN +void nghttp3_qpack_stream_context_reset(nghttp3_qpack_stream_context *sctx); + +/** + * @struct + * + * :type:`nghttp3_qpack_decoder` is QPACK decoder. + */ +typedef struct nghttp3_qpack_decoder nghttp3_qpack_decoder; + +/** + * @function + * + * `nghttp3_qpack_decoder_new` initializes QPACK decoder. |pdecoder| + * must be non-NULL pointer. |hard_max_dtable_capacity| is the upper + * bound of the dynamic table capacity. |max_blocked_streams| is the + * maximum number of streams which can be blocked. |mem| is a memory + * allocator. This function allocates memory for + * :type:`nghttp3_qpack_decoder` itself and assigns its pointer to + * |*pdecoder| if it succeeds. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory. + */ +NGHTTP3_EXTERN int nghttp3_qpack_decoder_new(nghttp3_qpack_decoder **pdecoder, + size_t hard_max_dtable_capacity, + size_t max_blocked_streams, + const nghttp3_mem *mem); + +/** + * @function + * + * `nghttp3_qpack_decoder_del` frees memory allocated for |decoder|. + * This function frees memory pointed by |decoder| itself. + */ +NGHTTP3_EXTERN void nghttp3_qpack_decoder_del(nghttp3_qpack_decoder *decoder); + +/** + * @function + * + * `nghttp3_qpack_decoder_read_encoder` reads encoder stream. The + * buffer pointed by |src| of length |srclen| contains encoder stream. + * + * This function returns the number of bytes read, or one of the + * following negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory. + * :macro:`NGHTTP3_ERR_QPACK_FATAL` + * |decoder| is in unrecoverable error state and cannot be used + * anymore. + * :macro:`NGHTTP3_ERR_QPACK_ENCODER_STREAM` + * Could not interpret encoder stream instruction. + */ +NGHTTP3_EXTERN nghttp3_ssize nghttp3_qpack_decoder_read_encoder( + nghttp3_qpack_decoder *decoder, const uint8_t *src, size_t srclen); + +/** + * @function + * + * `nghttp3_qpack_decoder_get_icnt` returns insert count. + */ +NGHTTP3_EXTERN uint64_t +nghttp3_qpack_decoder_get_icnt(const nghttp3_qpack_decoder *decoder); + +/** + * @macrosection + * + * Flags for QPACK decoder + */ + +/** + * @macro + * + * :macro:`NGHTTP3_QPACK_DECODE_FLAG_NONE` indicates that no flag set. + */ +#define NGHTTP3_QPACK_DECODE_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGHTTP3_QPACK_DECODE_FLAG_EMIT` indicates that a header + * field is successfully decoded. + */ +#define NGHTTP3_QPACK_DECODE_FLAG_EMIT 0x01u + +/** + * @macro + * + * :macro:`NGHTTP3_QPACK_DECODE_FLAG_FINAL` indicates that all header + * fields have been decoded. + */ +#define NGHTTP3_QPACK_DECODE_FLAG_FINAL 0x02u + +/** + * @macro + * + * :macro:`NGHTTP3_QPACK_DECODE_FLAG_BLOCKED` indicates that decoding + * has been blocked. + */ +#define NGHTTP3_QPACK_DECODE_FLAG_BLOCKED 0x04u + +/** + * @function + * + * `nghttp3_qpack_decoder_read_request` reads request stream. The + * request stream is given as the buffer pointed by |src| of length + * |srclen|. |sctx| is the stream context and it must be created by + * `nghttp3_qpack_stream_context_new()`. |*pflags| must be non-NULL + * pointer. |nv| must be non-NULL pointer. + * + * If this function succeeds, it assigns flags to |*pflags|. If + * |*pflags| has :macro:`NGHTTP3_QPACK_DECODE_FLAG_EMIT` set, a + * decoded header field is assigned to |nv|. If |*pflags| has + * :macro:`NGHTTP3_QPACK_DECODE_FLAG_FINAL` set, all header fields + * have been successfully decoded. If |*pflags| has + * :macro:`NGHTTP3_QPACK_DECODE_FLAG_BLOCKED` set, decoding is blocked + * due to required insert count. + * + * When a header field is decoded, an application receives it in |nv|. + * :member:`nv->name <nghttp3_qpack_nv.name>` and :member:`nv->value + * <nghttp3_qpack_nv.value>` are reference counted buffer, and their + * reference counts are already incremented for application use. + * Therefore, when application finishes processing the header field, + * it must call `nghttp3_rcbuf_decref(nv->name) + * <nghttp3_rcbuf_decref>` and `nghttp3_rcbuf_decref(nv->value) + * <nghttp3_rcbuf_decref>` or memory leak might occur. These + * :type:`nghttp3_rcbuf` objects hold the pointer to + * :type:`nghttp3_mem` that is passed to `nghttp3_qpack_decoder_new` + * (or either `nghttp3_conn_client_new` or `nghttp3_conn_server_new` + * if it is used indirectly). As long as these objects are alive, the + * pointed :type:`nghttp3_mem` object must be available. Otherwise, + * `nghttp3_rcbuf_decref` will cause undefined behavior. + * + * This function returns the number of bytes read, or one of the + * following negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory. + * :macro:`NGHTTP3_ERR_QPACK_FATAL` + * |decoder| is in unrecoverable error state and cannot be used + * anymore. + * :macro:`NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED` + * Could not interpret header block instruction. + * :macro:`NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE` + * Header field is too large. + */ +NGHTTP3_EXTERN nghttp3_ssize nghttp3_qpack_decoder_read_request( + nghttp3_qpack_decoder *decoder, nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv, uint8_t *pflags, const uint8_t *src, size_t srclen, + int fin); + +/** + * @function + * + * `nghttp3_qpack_decoder_write_decoder` writes decoder stream into + * |dbuf|. + * + * The caller must ensure that `nghttp3_buf_left(dbuf) + * <nghttp3_buf_left>` >= + * `nghttp3_qpack_decoder_get_decoder_streamlen(decoder) + * <nghttp3_qpack_decoder_get_decoder_streamlen>`. + */ +NGHTTP3_EXTERN void +nghttp3_qpack_decoder_write_decoder(nghttp3_qpack_decoder *decoder, + nghttp3_buf *dbuf); + +/** + * @function + * + * `nghttp3_qpack_decoder_get_decoder_streamlen` returns the length of + * decoder stream. + */ +NGHTTP3_EXTERN size_t +nghttp3_qpack_decoder_get_decoder_streamlen(nghttp3_qpack_decoder *decoder); + +/** + * @function + * + * `nghttp3_qpack_decoder_cancel_stream` cancels header decoding for + * stream denoted by |stream_id|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory. + * :macro:`NGHTTP3_ERR_QPACK_FATAL` + * Decoder stream overflow. + */ +NGHTTP3_EXTERN int +nghttp3_qpack_decoder_cancel_stream(nghttp3_qpack_decoder *decoder, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_qpack_decoder_set_max_dtable_capacity` sets + * |max_dtable_capacity| as maximum dynamic table size. + * |max_dtable_capacity| must be equal to or smaller than + * ``hard_max_dtable_capacity`` parameter of + * `nghttp3_qpack_decoder_new`. Normally, the maximum capacity is + * communicated in encoder stream. This function is provided for + * debugging and testing purpose. + * + * This function returns 0 if it succeeds, or one of the + * following negative error codes: + * + * :macro:`NGHTTP3_ERR_INVALID_ARGUMENT` + * |max_dtable_capacity| exceeds the upper bound of the dynamic + * table capacity. + */ +NGHTTP3_EXTERN int +nghttp3_qpack_decoder_set_max_dtable_capacity(nghttp3_qpack_decoder *decoder, + size_t max_dtable_capacity); + +/** + * @function + * + * `nghttp3_qpack_decoder_set_max_concurrent_streams` tells |decoder| + * the maximum number of concurrent streams that a remote endpoint can + * open, including both bidirectional and unidirectional streams which + * potentially receive QPACK encoded HEADERS frame. This value is + * used as a hint to limit the length of decoder stream. + */ +NGHTTP3_EXTERN void +nghttp3_qpack_decoder_set_max_concurrent_streams(nghttp3_qpack_decoder *decoder, + size_t max_concurrent_streams); + +/** + * @function + * + * `nghttp3_strerror` returns textual representation of |liberr|. + */ +NGHTTP3_EXTERN const char *nghttp3_strerror(int liberr); + +/** + * @function + * + * `nghttp3_err_infer_quic_app_error_code` returns a QUIC application + * error code which corresponds to |liberr|. + */ +NGHTTP3_EXTERN uint64_t nghttp3_err_infer_quic_app_error_code(int liberr); + +/** + * @functypedef + * + * :type:`nghttp3_debug_vprintf_callback` is a callback function + * invoked when the library outputs debug logging. The function is + * called with arguments suitable for :manpage:`vfprintf(3)`. + * + * The debug output is only enabled if the library is built with + * :macro:`DEBUGBUILD` macro defined. + */ +typedef void (*nghttp3_debug_vprintf_callback)(const char *format, + va_list args); + +/** + * @function + * + * `nghttp3_set_debug_vprintf_callback` sets a debug output callback + * called by the library when built with :macro:`DEBUGBUILD` macro + * defined. If this option is not used, debug log is written into + * standard error output. + * + * For builds without :macro:`DEBUGBUILD` macro defined, this function + * is noop. + * + * Note that building with :macro:`DEBUGBUILD` may cause significant + * performance penalty to libnghttp3 because of extra processing. It + * should be used for debugging purpose only. + * + * .. Warning:: + * + * Building with :macro:`DEBUGBUILD` may cause significant + * performance penalty to libnghttp3 because of extra processing. + * It should be used for debugging purpose only. We write this two + * times because this is important. + */ +NGHTTP3_EXTERN void nghttp3_set_debug_vprintf_callback( + nghttp3_debug_vprintf_callback debug_vprintf_callback); + +/** + * @macrosection + * + * Shutdown related constants + */ + +/** + * @macro + * + * :macro:`NGHTTP3_SHUTDOWN_NOTICE_STREAM_ID` specifies stream id sent + * by a server when it initiates graceful shutdown of the connection + * via `nghttp3_conn_submit_shutdown_notice`. + */ +#define NGHTTP3_SHUTDOWN_NOTICE_STREAM_ID ((1ull << 62) - 4) + +/** + * @macro + * + * :macro:`NGHTTP3_SHUTDOWN_NOTICE_PUSH_ID` specifies push id sent + * by a client when it initiates graceful shutdown of the connection + * via `nghttp3_conn_submit_shutdown_notice`. + */ +#define NGHTTP3_SHUTDOWN_NOTICE_PUSH_ID ((1ull << 62) - 1) + +/** + * @struct + * + * :type:`nghttp3_conn` represents a single HTTP/3 connection. + */ +typedef struct nghttp3_conn nghttp3_conn; + +/** + * @functypedef + * + * :type:`nghttp3_acked_stream_data` is a callback function which is + * invoked when data sent on stream denoted by |stream_id| supplied + * from application is acknowledged by remote endpoint. The number of + * bytes acknowledged is given in |datalen|. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_acked_stream_data)(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_conn_stream_close` is a callback function which is + * invoked when a stream identified by |stream_id| is closed. + * |app_error_code| indicates the reason of this closure. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_stream_close)(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, + void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_recv_data` is a callback function which is invoked + * when a part of request or response body on stream identified by + * |stream_id| is received. |data| points to the received data and + * its length is |datalen|. + * + * The application is responsible for increasing flow control credit + * by |datalen| bytes. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_recv_data)(nghttp3_conn *conn, int64_t stream_id, + const uint8_t *data, size_t datalen, + void *conn_user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_deferred_consume` is a callback function which is + * invoked when the library consumed |consumed| bytes for a stream + * identified by |stream_id|. This callback is used to notify the + * consumed bytes for stream blocked by QPACK decoder. The + * application is responsible for increasing flow control credit by + * |consumed| bytes. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_deferred_consume)(nghttp3_conn *conn, int64_t stream_id, + size_t consumed, void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_begin_headers` is a callback function which is + * invoked when an incoming header block section is started on a + * stream denoted by |stream_id|. Each header field is passed to + * application by :type:`nghttp3_recv_header` callback. And then + * :type:`nghttp3_end_headers` is called when a whole header block is + * processed. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_begin_headers)(nghttp3_conn *conn, int64_t stream_id, + void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_recv_header` is a callback function which is invoked + * when a header field is received on a stream denoted by |stream_id|. + * |name| contains a field name and |value| contains a field value. + * |token| is one of token defined in :type:`nghttp3_qpack_token` or + * -1 if no token is defined for |name|. |flags| is bitwise OR of + * zero or more of :macro:`NGHTTP3_NV_FLAG_* <NGHTTP3_NV_FLAG_NONE>`. + * + * The buffers for |name| and |value| are reference counted. If + * application needs to keep them, increment the reference count with + * `nghttp3_rcbuf_incref`. When they are no longer used, call + * `nghttp3_rcbuf_decref`. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_recv_header)(nghttp3_conn *conn, int64_t stream_id, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_end_headers` is a callback function which is invoked + * when an incoming header block has ended. + * + * If the stream ends with this header block, |fin| is set to nonzero. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_end_headers)(nghttp3_conn *conn, int64_t stream_id, + int fin, void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_end_stream` is a callback function which is invoked + * when the receiving side of stream is closed. For server, this + * callback function is invoked when HTTP request is received + * completely. For client, this callback function is invoked when + * HTTP response is received completely. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_end_stream)(nghttp3_conn *conn, int64_t stream_id, + void *conn_user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_stop_sending` is a callback function which is + * invoked when the library asks application to send STOP_SENDING to + * the stream identified by |stream_id|. |app_error_code| indicates + * the reason for this action. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_stop_sending)(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, + void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_reset_stream` is a callback function which is + * invoked when the library asks application to reset stream + * identified by |stream_id|. |app_error_code| indicates the reason + * for this action. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_reset_stream)(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, + void *conn_user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`nghttp3_shutdown` is a callback function which is invoked + * when a shutdown is initiated by the remote endpoint. For client, + * |id| contains a stream id of a client initiated stream, for server, + * it contains a push id. All client streams with stream id or pushes + * with push id equal to or larger than |id| are guaranteed to not be + * processed by the remote endpoint. + * + * Parameter |id| for client can contain a special value + * :macro:`NGHTTP3_SHUTDOWN_NOTICE_STREAM_ID` and for server it can + * contain special value + * :macro:`NGHTTP3_SHUTDOWN_NOTICE_PUSH_ID`. These values signal + * request for graceful shutdown of the connection, triggered by + * remote endpoint's invocation of + * `nghttp3_conn_submit_shutdown_notice`. + * + * It is possible that this callback is invoked multiple times on a + * single connection, however the |id| can only stay the same or + * decrease, never increase. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` will return to the + * caller immediately. Any values other than 0 is treated as + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp3_shutdown)(nghttp3_conn *conn, int64_t id, + void *conn_user_data); + +#define NGHTTP3_CALLBACKS_VERSION_V1 1 +#define NGHTTP3_CALLBACKS_VERSION NGHTTP3_CALLBACKS_VERSION_V1 + +/** + * @struct + * + * :type:`nghttp3_callbacks` holds a set of callback functions. + */ +typedef struct nghttp3_callbacks { + /** + * :member:`acked_stream_data` is a callback function which is + * invoked when data sent on a particular stream have been + * acknowledged by a remote endpoint. + */ + nghttp3_acked_stream_data acked_stream_data; + /** + * :member:`stream_close` is a callback function which is invoked + * when a particular stream has closed. + */ + nghttp3_stream_close stream_close; + /** + * :member:`recv_data` is a callback function which is invoked when + * stream data is received. + */ + nghttp3_recv_data recv_data; + /** + * :member:`deferred_consume` is a callback function which is + * invoked when the library consumed data for a particular stream + * which had been blocked for synchronization between streams. + */ + nghttp3_deferred_consume deferred_consume; + /** + * :member:`begin_headers` is a callback function which is invoked + * when a header block has started on a particular stream. + */ + nghttp3_begin_headers begin_headers; + /** + * :member:`recv_header` is a callback function which is invoked + * when a single header field is received on a particular stream. + */ + nghttp3_recv_header recv_header; + /** + * :member:`end_headers` is a callback function which is invoked + * when a header block has ended on a particular stream. + */ + nghttp3_end_headers end_headers; + /** + * :member:`begin_trailers` is a callback function which is invoked + * when a trailer block has started on a particular stream. + */ + nghttp3_begin_headers begin_trailers; + /** + * :member:`recv_trailer` is a callback function which is invoked + * when a single trailer field is received on a particular stream. + */ + nghttp3_recv_header recv_trailer; + /** + * :member:`end_trailers` is a callback function which is invoked + * when a trailer block has ended on a particular stream. + */ + nghttp3_end_headers end_trailers; + /** + * :member:`stop_sending` is a callback function which is invoked + * when the library asks application to send STOP_SENDING to a + * particular stream. + */ + nghttp3_stop_sending stop_sending; + /** + * :member:`end_stream` is a callback function which is invoked when + * a receiving side of stream has been closed. + */ + nghttp3_end_stream end_stream; + /** + * :member:`reset_stream` is a callback function which is invoked + * when the library asks application to reset stream (by sending + * RESET_STREAM). + */ + nghttp3_reset_stream reset_stream; + /** + * :member:`shutdown` is a callback function which is invoked when + * the remote endpoint has signalled initiation of connection shutdown. + */ + nghttp3_shutdown shutdown; +} nghttp3_callbacks; + +#define NGHTTP3_SETTINGS_VERSION_V1 1 +#define NGHTTP3_SETTINGS_VERSION NGHTTP3_SETTINGS_VERSION_V1 + +/** + * @struct + * + * :type:`nghttp3_settings` defines HTTP/3 settings. + */ +typedef struct nghttp3_settings { + /** + * :member:`max_field_section_size` specifies the maximum header + * section (block) size. + */ + uint64_t max_field_section_size; + /** + * :member:`qpack_max_dtable_capacity` is the maximum size of QPACK + * dynamic table. + */ + size_t qpack_max_dtable_capacity; + /** + * :member:`qpack_encoder_max_dtable_capacity` is the upper bound of + * QPACK dynamic table capacity that the QPACK encoder is willing to + * use. The effective maximum dynamic table capacity is the minimum + * of this field and the value of the received + * SETTINGS_QPACK_MAX_TABLE_CAPACITY. If this field is set to 0, + * the encoder does not use the dynamic table. + */ + size_t qpack_encoder_max_dtable_capacity; + /** + * :member:`qpack_blocked_streams` is the maximum number of streams + * which can be blocked while they are being decoded. + */ + size_t qpack_blocked_streams; + /** + * :member:`enable_connect_protocol`, if set to nonzero, enables + * Extended CONNECT Method (see + * https://datatracker.ietf.org/doc/html/rfc9220). Client ignores + * this field. + */ + int enable_connect_protocol; +} nghttp3_settings; + +/** + * @function + * + * `nghttp3_settings_default` fills |settings| with the default + * values. + * + * - :member:`max_field_section_size + * <nghttp3_settings.max_field_section_size>` = :expr:`((1ull << 62) - 1)` + * - :member:`qpack_max_dtable_capacity + * <nghttp3_settings.qpack_max_dtable_capacity>` = 0 + * - :member:`qpack_encoder_max_dtable_capacity + * <nghttp3_settings.qpack_encoder_max_dtable_capacity>` = 4096 + * - :member:`qpack_blocked_streams + * <nghttp3_settings.qpack_blocked_streams>` = 0 + * - :member:`enable_connect_protocol + * <nghttp3_settings.enable_connect_protocol>` = 0 + */ +NGHTTP3_EXTERN void +nghttp3_settings_default_versioned(int settings_version, + nghttp3_settings *settings); + +/** + * @function + * + * `nghttp3_conn_client_new` creates :type:`nghttp3_conn` and + * initializes it for client use. The pointer to the object is stored + * in |*pconn|. If |mem| is ``NULL``, the memory allocator returned + * by `nghttp3_mem_default` is used. + */ +NGHTTP3_EXTERN int +nghttp3_conn_client_new_versioned(nghttp3_conn **pconn, int callbacks_version, + const nghttp3_callbacks *callbacks, + int settings_version, + const nghttp3_settings *settings, + const nghttp3_mem *mem, void *conn_user_data); + +/** + * @function + * + * `nghttp3_conn_server_new` creates :type:`nghttp3_conn` and + * initializes it for server use. The pointer to the object is stored + * in |*pconn|. If |mem| is ``NULL``, the memory allocator returned + * by `nghttp3_mem_default` is used. + */ +NGHTTP3_EXTERN int +nghttp3_conn_server_new_versioned(nghttp3_conn **pconn, int callbacks_version, + const nghttp3_callbacks *callbacks, + int settings_version, + const nghttp3_settings *settings, + const nghttp3_mem *mem, void *conn_user_data); + +/** + * @function + * + * `nghttp3_conn_del` frees resources allocated for |conn|. + */ +NGHTTP3_EXTERN void nghttp3_conn_del(nghttp3_conn *conn); + +/** + * @function + * + * `nghttp3_conn_bind_control_stream` binds stream denoted by + * |stream_id| to outgoing unidirectional control stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_INVALID_STATE` + * Control stream has already corresponding stream ID. + * + * TBD + */ +NGHTTP3_EXTERN int nghttp3_conn_bind_control_stream(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_conn_bind_qpack_streams` binds stream denoted by + * |qenc_stream_id| to outgoing QPACK encoder stream and stream + * denoted by |qdec_stream_id| to outgoing QPACK encoder stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_INVALID_STATE` + * QPACK encoder/decoder stream have already corresponding stream + * IDs. + * + * TBD + */ +NGHTTP3_EXTERN int nghttp3_conn_bind_qpack_streams(nghttp3_conn *conn, + int64_t qenc_stream_id, + int64_t qdec_stream_id); + +/** + * @function + * + * `nghttp3_conn_read_stream` reads data |src| of length |srclen| on + * stream identified by |stream_id|. It returns the number of bytes + * consumed. The "consumed" means that application can increase flow + * control credit (both stream and connection) of underlying QUIC + * connection by that amount. It does not include the amount of data + * carried by DATA frame which contains application data (excluding + * any control or QPACK unidirectional streams) . See + * :type:`nghttp3_recv_data` to handle those bytes. If |fin| is + * nonzero, this is the last data from remote endpoint in this stream. + */ +NGHTTP3_EXTERN nghttp3_ssize nghttp3_conn_read_stream(nghttp3_conn *conn, + int64_t stream_id, + const uint8_t *src, + size_t srclen, int fin); + +/** + * @function + * + * `nghttp3_conn_writev_stream` stores stream data to send to |vec| of + * length |veccnt| and returns the number of nghttp3_vec object in + * which it stored data. It stores stream ID to |*pstream_id|. An + * application has to call `nghttp3_conn_add_write_offset` to inform + * |conn| of the actual number of bytes that underlying QUIC stack + * accepted. |*pfin| will be nonzero if this is the last data to + * send. If there is no stream to write data or send fin, this + * function returns 0, and -1 is assigned to |*pstream_id|. This + * function may return 0 and |*pstream_id| is not -1 and |*pfin| is + * nonzero. It means 0 length data to |*pstream_id| and it is the + * last data to the stream. They must be passed to QUIC stack, and + * they are accepted, the application has to call + * `nghttp3_conn_add_write_offset`. + */ +NGHTTP3_EXTERN nghttp3_ssize nghttp3_conn_writev_stream(nghttp3_conn *conn, + int64_t *pstream_id, + int *pfin, + nghttp3_vec *vec, + size_t veccnt); + +/** + * @function + * + * `nghttp3_conn_add_write_offset` tells |conn| the number of bytes + * |n| for stream denoted by |stream_id| QUIC stack accepted. + * + * If stream has no data to send but just sends fin (closing the write + * side of a stream), the number of bytes sent is 0. It is important + * to call this function even if |n| is 0 in this case. It is safe to + * call this function if |n| is 0. + * + * `nghttp3_conn_writev_stream` must be called before calling this + * function to get data to send, and those data must be fed into QUIC + * stack. + */ +NGHTTP3_EXTERN int nghttp3_conn_add_write_offset(nghttp3_conn *conn, + int64_t stream_id, size_t n); + +/** + * @function + * + * `nghttp3_conn_add_ack_offset` tells |conn| the number of bytes |n| + * for stream denoted by |stream_id| QUIC stack has acknowledged. + */ +NGHTTP3_EXTERN int nghttp3_conn_add_ack_offset(nghttp3_conn *conn, + int64_t stream_id, uint64_t n); + +/** + * @function + * + * `nghttp3_conn_block_stream` tells the library that stream + * identified by |stream_id| is blocked due to QUIC flow control. + */ +NGHTTP3_EXTERN void nghttp3_conn_block_stream(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_conn_unblock_stream` tells the library that stream + * identified by |stream_id| which was blocked by QUIC flow control is + * unblocked. + */ +NGHTTP3_EXTERN int nghttp3_conn_unblock_stream(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_conn_is_stream_writable` returns nonzero if a stream + * identified by |stream_id| is writable. It is not writable if: + * + * - the stream does not exist; or, + * - the stream is closed (e.g., `nghttp3_conn_close_stream` is + * called); or, + * - the stream is QUIC flow control blocked (e.g., + * `nghttp3_conn_block_stream` is called); or, + * - the stream is input data blocked (e.g., + * :macro:`NGHTTP3_ERR_WOULDBLOCK` is returned from + * :type:`nghttp3_read_data_callback`); or, + * - the stream is half-closed local (e.g., + * `nghttp3_conn_shutdown_stream_write` is called). + */ +NGHTTP3_EXTERN int nghttp3_conn_is_stream_writable(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_conn_shutdown_stream_write` tells the library that any + * further write operation to stream identified by |stream_id| is + * prohibited. This works like `nghttp3_conn_block_stream`, but it + * cannot be unblocked by `nghttp3_conn_unblock_stream`. + */ +NGHTTP3_EXTERN void nghttp3_conn_shutdown_stream_write(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_conn_shutdown_stream_read` tells the library that + * read-side of stream denoted by |stream_id| is abruptly closed and + * any further incoming data and pending stream data should be + * discarded. + */ +NGHTTP3_EXTERN int nghttp3_conn_shutdown_stream_read(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_conn_resume_stream` resumes stream identified by + * |stream_id| which was previously unable to provide data. + */ +NGHTTP3_EXTERN int nghttp3_conn_resume_stream(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_conn_close_stream` closes stream identified by + * |stream_id|. |app_error_code| is the reason of the closure. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_STREAM_NOT_FOUND` + * Stream not found. + * :macro:`NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM` + * A critical stream is closed. + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE` + * User callback failed + */ +NGHTTP3_EXTERN int nghttp3_conn_close_stream(nghttp3_conn *conn, + int64_t stream_id, + uint64_t app_error_code); + +/** + * @macrosection + * + * Data flags + */ + +/** + * @macro + * + * :macro:`NGHTTP3_DATA_FLAG_NONE` indicates no flag set. + */ +#define NGHTTP3_DATA_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGHTTP3_DATA_FLAG_EOF` indicates that all request or + * response body has been provided to the library. It also indicates + * that sending side of stream is closed unless + * :macro:`NGHTTP3_DATA_FLAG_NO_END_STREAM` is given at the same time. + */ +#define NGHTTP3_DATA_FLAG_EOF 0x01u + +/** + * @macro + * + * :macro:`NGHTTP3_DATA_FLAG_NO_END_STREAM` indicates that sending + * side of stream is not closed even if :macro:`NGHTTP3_DATA_FLAG_EOF` + * is set. Usually this flag is used to send trailer fields with + * `nghttp3_conn_submit_trailers()`. If + * `nghttp3_conn_submit_trailers()` has been called, regardless of + * this flag, the submitted trailer fields are sent. + */ +#define NGHTTP3_DATA_FLAG_NO_END_STREAM 0x02u + +/** + * @function + * + * `nghttp3_conn_set_max_client_streams_bidi` tells |conn| the + * cumulative number of bidirectional streams that client can open. + */ +NGHTTP3_EXTERN void +nghttp3_conn_set_max_client_streams_bidi(nghttp3_conn *conn, + uint64_t max_streams); + +/** + * @function + * + * `nghttp3_conn_set_max_concurrent_streams` tells |conn| the maximum + * number of concurrent streams that a remote endpoint can open, + * including both bidirectional and unidirectional streams which + * potentially receive QPACK encoded HEADERS frame. This value is + * used as a hint to limit the internal resource consumption. + */ +NGHTTP3_EXTERN void +nghttp3_conn_set_max_concurrent_streams(nghttp3_conn *conn, + size_t max_concurrent_streams); + +/** + * @functypedef + * + * :type:`nghttp3_read_data_callback` is a callback function invoked + * when the library asks an application to provide stream data for a + * stream denoted by |stream_id|. + * + * The library provides |vec| of length |veccnt| to the application. + * The application should fill data and its length to |vec|. It has + * to return the number of the filled objects. The application must + * retain data until they are safe to free. It is notified by + * :type:`nghttp3_acked_stream_data` callback. + * + * If this is the last data to send (or there is no data to send + * because all data have been sent already), set + * :macro:`NGHTTP3_DATA_FLAG_EOF` to |*pflags|. + * + * If the application is unable to provide data temporarily, return + * :macro:`NGHTTP3_ERR_WOULDBLOCK`. When it is ready to provide + * data, call `nghttp3_conn_resume_stream()`. + * + * The callback should return the number of objects in |vec| that the + * application filled if it succeeds, or + * :macro:`NGHTTP3_ERR_CALLBACK_FAILURE`. + * + * TODO Add NGHTTP3_ERR_TEMPORAL_CALLBACK_FAILURE to reset just this + * stream. + */ +typedef nghttp3_ssize (*nghttp3_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); + +/** + * @struct + * + * :type:`nghttp3_data_reader` specifies the way how to generate + * request or response body. + */ +typedef struct nghttp3_data_reader { + /** + * :member:`read_data` is a callback function to generate body. + */ + nghttp3_read_data_callback read_data; +} nghttp3_data_reader; + +/** + * @function + * + * `nghttp3_conn_submit_request` submits HTTP request header fields + * and body on the stream identified by |stream_id|. |stream_id| must + * be a client initiated bidirectional stream. Only client can submit + * HTTP request. |nva| of length |nvlen| specifies HTTP request + * header fields. |dr| specifies a request body. If there is no + * request body, specify NULL. If |dr| is NULL, it implies the end of + * stream. |stream_user_data| is an opaque pointer attached to the + * stream. + */ +NGHTTP3_EXTERN int nghttp3_conn_submit_request( + nghttp3_conn *conn, int64_t stream_id, const nghttp3_nv *nva, size_t nvlen, + const nghttp3_data_reader *dr, void *stream_user_data); + +/** + * @function + * + * `nghttp3_conn_submit_info` submits HTTP non-final response header + * fields on the stream identified by |stream_id|. |nva| of length + * |nvlen| specifies HTTP response header fields. + */ +NGHTTP3_EXTERN int nghttp3_conn_submit_info(nghttp3_conn *conn, + int64_t stream_id, + const nghttp3_nv *nva, + size_t nvlen); + +/** + * @function + * + * `nghttp3_conn_submit_response` submits HTTP response header fields + * and body on the stream identified by |stream_id|. |nva| of length + * |nvlen| specifies HTTP response header fields. |dr| specifies a + * response body. If there is no response body, specify NULL. If + * |dr| is NULL, it implies the end of stream. + */ +NGHTTP3_EXTERN int nghttp3_conn_submit_response(nghttp3_conn *conn, + int64_t stream_id, + const nghttp3_nv *nva, + size_t nvlen, + const nghttp3_data_reader *dr); + +/** + * @function + * + * `nghttp3_conn_submit_trailers` submits HTTP trailer fields on the + * stream identified by |stream_id|. |nva| of length |nvlen| + * specifies HTTP trailer fields. Calling this function implies the + * end of stream. + */ +NGHTTP3_EXTERN int nghttp3_conn_submit_trailers(nghttp3_conn *conn, + int64_t stream_id, + const nghttp3_nv *nva, + size_t nvlen); + +/** + * @function + * + * `nghttp3_conn_submit_shutdown_notice` notifies the other endpoint + * to stop creating new stream. After a couple of RTTs later, call + * `nghttp3_conn_shutdown` to start graceful shutdown. + */ +NGHTTP3_EXTERN int nghttp3_conn_submit_shutdown_notice(nghttp3_conn *conn); + +/** + * @function + * + * `nghttp3_conn_shutdown` starts graceful shutdown. It should be + * called after `nghttp3_conn_submit_shutdown_notice` and a couple of + * RTT. After calling this function, the local endpoint starts + * rejecting new incoming streams. The existing streams are processed + * normally. + */ +NGHTTP3_EXTERN int nghttp3_conn_shutdown(nghttp3_conn *conn); + +/** + * @function + * + * `nghttp3_conn_set_stream_user_data` sets |stream_user_data| to the + * stream identified by |stream_id|. + */ +NGHTTP3_EXTERN int nghttp3_conn_set_stream_user_data(nghttp3_conn *conn, + int64_t stream_id, + void *stream_user_data); + +/** + * @function + * + * `nghttp3_conn_get_frame_payload_left` returns the number of bytes + * left to read current frame payload for a stream denoted by + * |stream_id|. If no such stream is found, it returns 0. + */ +NGHTTP3_EXTERN uint64_t nghttp3_conn_get_frame_payload_left(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @macrosection + * + * HTTP stream priority flags + */ + +/** + * @macro + * + * :macro:`NGHTTP3_DEFAULT_URGENCY` is the default urgency level. + */ +#define NGHTTP3_DEFAULT_URGENCY 3 + +/** + * @macro + * + * :macro:`NGHTTP3_URGENCY_HIGH` is the highest urgency level. + */ +#define NGHTTP3_URGENCY_HIGH 0 + +/** + * @macro + * + * :macro:`NGHTTP3_URGENCY_LOW` is the lowest urgency level. + */ +#define NGHTTP3_URGENCY_LOW 7 + +/** + * @macro + * + * :macro:`NGHTTP3_URGENCY_LEVELS` is the number of urgency levels. + */ +#define NGHTTP3_URGENCY_LEVELS (NGHTTP3_URGENCY_LOW + 1) + +/** + * @struct + * + * :type:`nghttp3_pri` represents HTTP priority. + */ +typedef struct nghttp3_pri { + /** + * :member:`urgency` is the urgency of a stream, it must be in + * [:macro:`NGHTTP3_URGENCY_HIGH`, :macro:`NGHTTP3_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; +} nghttp3_pri; + +/** + * @function + * + * `nghttp3_conn_get_stream_priority` stores stream priority of a + * stream denoted by |stream_id| into |*dest|. |stream_id| must + * identify client initiated bidirectional stream. Only server can + * use this function. + * + * This function must not be called if |conn| is initialized as + * client. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_STREAM_NOT_FOUND` + * Stream not found. + */ +NGHTTP3_EXTERN int nghttp3_conn_get_stream_priority(nghttp3_conn *conn, + nghttp3_pri *dest, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_conn_set_stream_priority` updates priority of a stream + * denoted by |stream_id| with the value pointed by |pri|. + * |stream_id| must identify client initiated bidirectional stream. + * + * Both client and server can update stream priority with this + * function. + * + * If server updates stream priority with this function, it completely + * overrides stream priority set by client and the attempts to update + * priority by client are ignored. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_INVALID_ARGUMENT` + * |stream_id| is not a client initiated bidirectional stream ID. + * :macro:`NGHTTP3_ERR_STREAM_NOT_FOUND` + * Stream not found. + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory. + */ +NGHTTP3_EXTERN int nghttp3_conn_set_stream_priority(nghttp3_conn *conn, + int64_t stream_id, + const nghttp3_pri *pri); + +/** + * @function + * + * `nghttp3_conn_is_remote_qpack_encoder_stream` returns nonzero if a + * stream denoted by |stream_id| is QPACK encoder stream of a remote + * endpoint. + */ +NGHTTP3_EXTERN int +nghttp3_conn_is_remote_qpack_encoder_stream(nghttp3_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `nghttp3_vec_len` returns the sum of length in |vec| of |cnt| + * elements. + */ +NGHTTP3_EXTERN uint64_t nghttp3_vec_len(const nghttp3_vec *vec, size_t cnt); + +/** + * @function + * + * `nghttp3_check_header_name` returns nonzero if HTTP header field + * name |name| of length |len| is valid according to + * :rfc:`7230#section-3.2`. + * + * Because this is a header field name in HTTP/3, the upper cased + * alphabet is treated as error. + */ +NGHTTP3_EXTERN int nghttp3_check_header_name(const uint8_t *name, size_t len); + +/** + * @function + * + * `nghttp3_check_header_value` returns nonzero if HTTP header field + * value |value| of length |len| is valid according to + * :rfc:`7230#section-3.2`. + */ +NGHTTP3_EXTERN int nghttp3_check_header_value(const uint8_t *value, size_t len); + +/** + * @function + * + * `nghttp3_http_parse_priority` parses priority HTTP header field + * stored in the buffer pointed by |value| of length |len|. If it + * successfully processed header field value, it stores the result + * into |*dest|. This function just overwrites what it sees in the + * header field value and does not initialize any field in |*dest|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_INVALID_ARGUMENT` + * The function could not parse the provided value. + */ +NGHTTP3_EXTERN int nghttp3_http_parse_priority(nghttp3_pri *dest, + const uint8_t *value, + size_t len); + +/** + * @macrosection + * + * nghttp3_info flags + */ + +/** + * @macro + * + * :macro:`NGHTTP3_VERSION_AGE` is the age of :type:`nghttp3_info`. + */ +#define NGHTTP3_VERSION_AGE 1 + +/** + * @struct + * + * :type:`nghttp3_info` is what `nghttp3_version()` returns. It holds + * information about the particular nghttp3 version. + */ +typedef struct nghttp3_info { + /** + * :member:`age` is the age of this struct. This instance of + * nghttp3 sets it to :macro:`NGHTTP3_VERSION_AGE` but a future + * version may bump it and add more struct fields at the bottom + */ + int age; + /** + * :member:`version_num` is the :macro:`NGHTTP3_VERSION_NUM` number + * (since age ==1) + */ + int version_num; + /** + * :member:`version_str` points to the :macro:`NGHTTP3_VERSION` + * string (since age ==1) + */ + const char *version_str; + /* -------- the above fields all exist when age == 1 */ +} nghttp3_info; + +/** + * @function + * + * `nghttp3_version` returns a pointer to a :type:`nghttp3_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. + */ +NGHTTP3_EXTERN const nghttp3_info *nghttp3_version(int least_version); + +/** + * @function + * + * `nghttp3_err_is_fatal` returns nonzero if |liberr| is a fatal + * error. |liberr| must be one of nghttp3 library error codes (which + * is defined as NGHTTP3_ERR_* macro, such as + * :macro:`NGHTTP3_ERR_NOMEM`). + */ +NGHTTP3_EXTERN int nghttp3_err_is_fatal(int liberr); + +/* + * Versioned function wrappers + */ + +/* + * `nghttp3_settings_default` is a wrapper around + * `nghttp3_settings_default_versioned` to set the correct struct + * version. + */ +#define nghttp3_settings_default(SETTINGS) \ + nghttp3_settings_default_versioned(NGHTTP3_SETTINGS_VERSION, (SETTINGS)) + +/* + * `nghttp3_conn_client_new` is a wrapper around + * `nghttp3_conn_client_new_versioned` to set the correct struct + * version. + */ +#define nghttp3_conn_client_new(PCONN, CALLBACKS, SETTINGS, MEM, USER_DATA) \ + nghttp3_conn_client_new_versioned((PCONN), NGHTTP3_CALLBACKS_VERSION, \ + (CALLBACKS), NGHTTP3_SETTINGS_VERSION, \ + (SETTINGS), (MEM), (USER_DATA)) + +/* + * `nghttp3_conn_server_new` is a wrapper around + * `nghttp3_conn_server_new_versioned` to set the correct struct + * version. + */ +#define nghttp3_conn_server_new(PCONN, CALLBACKS, SETTINGS, MEM, USER_DATA) \ + nghttp3_conn_server_new_versioned((PCONN), NGHTTP3_CALLBACKS_VERSION, \ + (CALLBACKS), NGHTTP3_SETTINGS_VERSION, \ + (SETTINGS), (MEM), (USER_DATA)) + +#ifdef __cplusplus +} +#endif + +#endif /* NGHTTP3_H */ diff --git a/lib/includes/nghttp3/version.h.in b/lib/includes/nghttp3/version.h.in new file mode 100644 index 0000000..1d86147 --- /dev/null +++ b/lib/includes/nghttp3/version.h.in @@ -0,0 +1,46 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2016 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_VERSION_H +#define NGHTTP3_VERSION_H + +/** + * @macro + * + * Version number of the nghttp3 library release. + */ +#define NGHTTP3_VERSION "@PACKAGE_VERSION@" + +/** + * @macro + * + * Numerical representation of the version number of the nghttp3 + * 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 NGHTTP3_VERSION_NUM @PACKAGE_VERSION_NUM@ + +#endif /* NGHTTP3_VERSION_H */ diff --git a/lib/libnghttp3.pc.in b/lib/libnghttp3.pc.in new file mode 100644 index 0000000..c109b3e --- /dev/null +++ b/lib/libnghttp3.pc.in @@ -0,0 +1,34 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2016 ngtcp2 contributors +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libnghttp3 +Description: nghttp3 library +URL: https://github.com/ngtcp2/nghttp3 +Version: @VERSION@ +Libs: -L${libdir} -lnghttp3 +Cflags: -I${includedir} diff --git a/lib/nghttp3_balloc.c b/lib/nghttp3_balloc.c new file mode 100644 index 0000000..e134d0f --- /dev/null +++ b/lib/nghttp3_balloc.c @@ -0,0 +1,91 @@ +/* + * nghttp3 + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_balloc.h" + +#include <assert.h> + +#include "nghttp3_mem.h" + +void nghttp3_balloc_init(nghttp3_balloc *balloc, size_t blklen, + const nghttp3_mem *mem) { + assert((blklen & 0xfu) == 0); + + balloc->mem = mem; + balloc->blklen = blklen; + balloc->head = NULL; + nghttp3_buf_wrap_init(&balloc->buf, (void *)"", 0); +} + +void nghttp3_balloc_free(nghttp3_balloc *balloc) { + if (balloc == NULL) { + return; + } + + nghttp3_balloc_clear(balloc); +} + +void nghttp3_balloc_clear(nghttp3_balloc *balloc) { + nghttp3_memblock_hd *p, *next; + + for (p = balloc->head; p; p = next) { + next = p->next; + nghttp3_mem_free(balloc->mem, p); + } + + balloc->head = NULL; + nghttp3_buf_wrap_init(&balloc->buf, (void *)"", 0); +} + +int nghttp3_balloc_get(nghttp3_balloc *balloc, void **pbuf, size_t n) { + uint8_t *p; + nghttp3_memblock_hd *hd; + + assert(n <= balloc->blklen); + + if (nghttp3_buf_left(&balloc->buf) < n) { + p = nghttp3_mem_malloc(balloc->mem, sizeof(nghttp3_memblock_hd) + 0x10u + + balloc->blklen); + if (p == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + hd = (nghttp3_memblock_hd *)(void *)p; + hd->next = balloc->head; + balloc->head = hd; + nghttp3_buf_wrap_init( + &balloc->buf, + (uint8_t *)(((uintptr_t)p + sizeof(nghttp3_memblock_hd) + 0xfu) & + ~(uintptr_t)0xfu), + balloc->blklen); + } + + assert(((uintptr_t)balloc->buf.last & 0xfu) == 0); + + *pbuf = balloc->buf.last; + balloc->buf.last += (n + 0xfu) & ~(uintptr_t)0xfu; + + return 0; +} diff --git a/lib/nghttp3_balloc.h b/lib/nghttp3_balloc.h new file mode 100644 index 0000000..e02f61d --- /dev/null +++ b/lib/nghttp3_balloc.h @@ -0,0 +1,92 @@ +/* + * nghttp3 + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_BALLOC_H +#define NGHTTP3_BALLOC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_buf.h" + +typedef struct nghttp3_memblock_hd nghttp3_memblock_hd; + +/* + * nghttp3_memblock_hd is the header of memory block. + */ +struct nghttp3_memblock_hd { + nghttp3_memblock_hd *next; +}; + +/* + * nghttp3_balloc is a custom memory allocator. It allocates |blklen| + * bytes of memory at once on demand, and returns its slice when the + * allocation is requested. + */ +typedef struct nghttp3_balloc { + /* mem is the underlying memory allocator. */ + const nghttp3_mem *mem; + /* blklen is the size of memory block. */ + size_t blklen; + /* head points to the list of memory block allocated so far. */ + nghttp3_memblock_hd *head; + /* buf wraps the current memory block for allocation requests. */ + nghttp3_buf buf; +} nghttp3_balloc; + +/* + * nghttp3_balloc_init initializes |balloc| with |blklen| which is the + * size of memory block. + */ +void nghttp3_balloc_init(nghttp3_balloc *balloc, size_t blklen, + const nghttp3_mem *mem); + +/* + * nghttp3_balloc_free releases all allocated memory blocks. + */ +void nghttp3_balloc_free(nghttp3_balloc *balloc); + +/* + * nghttp3_balloc_get allocates |n| bytes of memory and assigns its + * pointer to |*pbuf|. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_balloc_get(nghttp3_balloc *balloc, void **pbuf, size_t n); + +/* + * nghttp3_balloc_clear releases all allocated memory blocks and + * initializes its state. + */ +void nghttp3_balloc_clear(nghttp3_balloc *balloc); + +#endif /* NGHTTP3_BALLOC_H */ diff --git a/lib/nghttp3_buf.c b/lib/nghttp3_buf.c new file mode 100644 index 0000000..aae075a --- /dev/null +++ b/lib/nghttp3_buf.c @@ -0,0 +1,90 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_buf.h" + +void nghttp3_buf_init(nghttp3_buf *buf) { + buf->begin = buf->end = buf->pos = buf->last = NULL; +} + +void nghttp3_buf_wrap_init(nghttp3_buf *buf, uint8_t *src, size_t len) { + buf->begin = buf->pos = buf->last = src; + buf->end = buf->begin + len; +} + +void nghttp3_buf_free(nghttp3_buf *buf, const nghttp3_mem *mem) { + nghttp3_mem_free(mem, buf->begin); +} + +size_t nghttp3_buf_left(const nghttp3_buf *buf) { + return (size_t)(buf->end - buf->last); +} + +size_t nghttp3_buf_len(const nghttp3_buf *buf) { + return (size_t)(buf->last - buf->pos); +} + +size_t nghttp3_buf_cap(const nghttp3_buf *buf) { + return (size_t)(buf->end - buf->begin); +} + +void nghttp3_buf_reset(nghttp3_buf *buf) { buf->pos = buf->last = buf->begin; } + +int nghttp3_buf_reserve(nghttp3_buf *buf, size_t size, const nghttp3_mem *mem) { + uint8_t *p; + nghttp3_ssize pos_offset, last_offset; + + if ((size_t)(buf->end - buf->begin) >= size) { + return 0; + } + + pos_offset = buf->pos - buf->begin; + last_offset = buf->last - buf->begin; + + p = nghttp3_mem_realloc(mem, buf->begin, size); + if (p == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + buf->begin = p; + buf->end = p + size; + buf->pos = p + pos_offset; + buf->last = p + last_offset; + + return 0; +} + +void nghttp3_buf_swap(nghttp3_buf *a, nghttp3_buf *b) { + nghttp3_buf c = *a; + + *a = *b; + *b = c; +} + +void nghttp3_typed_buf_init(nghttp3_typed_buf *tbuf, const nghttp3_buf *buf, + nghttp3_buf_type type) { + tbuf->buf = *buf; + tbuf->type = type; +} diff --git a/lib/nghttp3_buf.h b/lib/nghttp3_buf.h new file mode 100644 index 0000000..472a4b7 --- /dev/null +++ b/lib/nghttp3_buf.h @@ -0,0 +1,74 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_BUF_H +#define NGHTTP3_BUF_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_mem.h" + +void nghttp3_buf_wrap_init(nghttp3_buf *buf, uint8_t *src, size_t len); + +/* + * nghttp3_buf_cap returns the capacity of the buffer. In other + * words, it returns buf->end - buf->begin. + */ +size_t nghttp3_buf_cap(const nghttp3_buf *buf); + +int nghttp3_buf_reserve(nghttp3_buf *buf, size_t size, const nghttp3_mem *mem); + +/* + * nghttp3_buf_swap swaps |a| and |b|. + */ +void nghttp3_buf_swap(nghttp3_buf *a, nghttp3_buf *b); + +typedef enum nghttp3_buf_type { + /* NGHTTP3_BUF_TYPE_PRIVATE indicates that memory is allocated for + this buffer only and should be freed after its use. */ + NGHTTP3_BUF_TYPE_PRIVATE, + /* NGHTTP3_BUF_TYPE_SHARED indicates that buffer points to shared + memory. */ + NGHTTP3_BUF_TYPE_SHARED, + /* NGHTTP3_BUF_TYPE_ALIEN indicates that the buffer points to a + memory which comes from outside of the library. */ + NGHTTP3_BUF_TYPE_ALIEN, +} nghttp3_buf_type; + +typedef struct nghttp3_typed_buf { + nghttp3_buf buf; + nghttp3_buf_type type; +} nghttp3_typed_buf; + +void nghttp3_typed_buf_init(nghttp3_typed_buf *tbuf, const nghttp3_buf *buf, + nghttp3_buf_type type); + +void nghttp3_typed_buf_free(nghttp3_typed_buf *tbuf); + +#endif /* NGHTTP3_BUF_H */ diff --git a/lib/nghttp3_conn.c b/lib/nghttp3_conn.c new file mode 100644 index 0000000..c52be96 --- /dev/null +++ b/lib/nghttp3_conn.c @@ -0,0 +1,2532 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "nghttp3_conn.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +#include "nghttp3_mem.h" +#include "nghttp3_macro.h" +#include "nghttp3_err.h" +#include "nghttp3_conv.h" +#include "nghttp3_http.h" +#include "nghttp3_unreachable.h" + +/* NGHTTP3_QPACK_ENCODER_MAX_DTABLE_CAPACITY is the upper bound of the + dynamic table capacity that QPACK encoder is willing to use. */ +#define NGHTTP3_QPACK_ENCODER_MAX_DTABLE_CAPACITY 4096 + +/* + * conn_remote_stream_uni returns nonzero if |stream_id| is remote + * unidirectional stream ID. + */ +static int conn_remote_stream_uni(nghttp3_conn *conn, int64_t stream_id) { + if (conn->server) { + return (stream_id & 0x03) == 0x02; + } + return (stream_id & 0x03) == 0x03; +} + +static int conn_call_begin_headers(nghttp3_conn *conn, nghttp3_stream *stream) { + int rv; + + if (!conn->callbacks.begin_headers) { + return 0; + } + + rv = conn->callbacks.begin_headers(conn, stream->node.id, conn->user_data, + stream->user_data); + if (rv != 0) { + /* TODO Allow ignore headers */ + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_end_headers(nghttp3_conn *conn, nghttp3_stream *stream, + int fin) { + int rv; + + if (!conn->callbacks.end_headers) { + return 0; + } + + rv = conn->callbacks.end_headers(conn, stream->node.id, fin, conn->user_data, + stream->user_data); + if (rv != 0) { + /* TODO Allow ignore headers */ + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_begin_trailers(nghttp3_conn *conn, + nghttp3_stream *stream) { + int rv; + + if (!conn->callbacks.begin_trailers) { + return 0; + } + + rv = conn->callbacks.begin_trailers(conn, stream->node.id, conn->user_data, + stream->user_data); + if (rv != 0) { + /* TODO Allow ignore headers */ + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_end_trailers(nghttp3_conn *conn, nghttp3_stream *stream, + int fin) { + int rv; + + if (!conn->callbacks.end_trailers) { + return 0; + } + + rv = conn->callbacks.end_trailers(conn, stream->node.id, fin, conn->user_data, + stream->user_data); + if (rv != 0) { + /* TODO Allow ignore headers */ + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_end_stream(nghttp3_conn *conn, nghttp3_stream *stream) { + int rv; + + if (!conn->callbacks.end_stream) { + return 0; + } + + rv = conn->callbacks.end_stream(conn, stream->node.id, conn->user_data, + stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_stop_sending(nghttp3_conn *conn, nghttp3_stream *stream, + uint64_t app_error_code) { + int rv; + + if (!conn->callbacks.stop_sending) { + return 0; + } + + rv = conn->callbacks.stop_sending(conn, stream->node.id, app_error_code, + conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_reset_stream(nghttp3_conn *conn, nghttp3_stream *stream, + uint64_t app_error_code) { + int rv; + + if (!conn->callbacks.reset_stream) { + return 0; + } + + rv = conn->callbacks.reset_stream(conn, stream->node.id, app_error_code, + conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_deferred_consume(nghttp3_conn *conn, + nghttp3_stream *stream, + size_t nconsumed) { + int rv; + + if (nconsumed == 0 || !conn->callbacks.deferred_consume) { + return 0; + } + + rv = conn->callbacks.deferred_consume(conn, stream->node.id, nconsumed, + conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int ricnt_less(const nghttp3_pq_entry *lhsx, + const nghttp3_pq_entry *rhsx) { + nghttp3_stream *lhs = + nghttp3_struct_of(lhsx, nghttp3_stream, qpack_blocked_pe); + nghttp3_stream *rhs = + nghttp3_struct_of(rhsx, nghttp3_stream, qpack_blocked_pe); + + return lhs->qpack_sctx.ricnt < rhs->qpack_sctx.ricnt; +} + +static int cycle_less(const nghttp3_pq_entry *lhsx, + const nghttp3_pq_entry *rhsx) { + const nghttp3_tnode *lhs = nghttp3_struct_of(lhsx, nghttp3_tnode, pe); + const nghttp3_tnode *rhs = nghttp3_struct_of(rhsx, nghttp3_tnode, pe); + + if (lhs->cycle == rhs->cycle) { + return lhs->id < rhs->id; + } + + return rhs->cycle - lhs->cycle <= NGHTTP3_TNODE_MAX_CYCLE_GAP; +} + +static int conn_new(nghttp3_conn **pconn, int server, int callbacks_version, + const nghttp3_callbacks *callbacks, int settings_version, + const nghttp3_settings *settings, const nghttp3_mem *mem, + void *user_data) { + int rv; + nghttp3_conn *conn; + size_t i; + (void)callbacks_version; + (void)settings_version; + + if (mem == NULL) { + mem = nghttp3_mem_default(); + } + + conn = nghttp3_mem_calloc(mem, 1, sizeof(nghttp3_conn)); + if (conn == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + nghttp3_objalloc_init(&conn->out_chunk_objalloc, + NGHTTP3_STREAM_MIN_CHUNK_SIZE * 16, mem); + nghttp3_objalloc_stream_init(&conn->stream_objalloc, 64, mem); + + nghttp3_map_init(&conn->streams, mem); + + rv = nghttp3_qpack_decoder_init(&conn->qdec, + settings->qpack_max_dtable_capacity, + settings->qpack_blocked_streams, mem); + if (rv != 0) { + goto qdec_init_fail; + } + + rv = nghttp3_qpack_encoder_init( + &conn->qenc, settings->qpack_encoder_max_dtable_capacity, mem); + if (rv != 0) { + goto qenc_init_fail; + } + + nghttp3_pq_init(&conn->qpack_blocked_streams, ricnt_less, mem); + + for (i = 0; i < NGHTTP3_URGENCY_LEVELS; ++i) { + nghttp3_pq_init(&conn->sched[i].spq, cycle_less, mem); + } + + nghttp3_idtr_init(&conn->remote.bidi.idtr, server, mem); + + conn->callbacks = *callbacks; + conn->local.settings = *settings; + if (!server) { + conn->local.settings.enable_connect_protocol = 0; + } + nghttp3_settings_default(&conn->remote.settings); + conn->mem = mem; + conn->user_data = user_data; + conn->server = server; + conn->rx.goaway_id = NGHTTP3_VARINT_MAX + 1; + conn->tx.goaway_id = NGHTTP3_VARINT_MAX + 1; + conn->rx.max_stream_id_bidi = -4; + + *pconn = conn; + + return 0; + +qenc_init_fail: + nghttp3_qpack_decoder_free(&conn->qdec); +qdec_init_fail: + nghttp3_map_free(&conn->streams); + nghttp3_objalloc_free(&conn->stream_objalloc); + nghttp3_objalloc_free(&conn->out_chunk_objalloc); + nghttp3_mem_free(mem, conn); + + return rv; +} + +int nghttp3_conn_client_new_versioned(nghttp3_conn **pconn, + int callbacks_version, + const nghttp3_callbacks *callbacks, + int settings_version, + const nghttp3_settings *settings, + const nghttp3_mem *mem, void *user_data) { + int rv; + + rv = conn_new(pconn, /* server = */ 0, callbacks_version, callbacks, + settings_version, settings, mem, user_data); + if (rv != 0) { + return rv; + } + + return 0; +} + +int nghttp3_conn_server_new_versioned(nghttp3_conn **pconn, + int callbacks_version, + const nghttp3_callbacks *callbacks, + int settings_version, + const nghttp3_settings *settings, + const nghttp3_mem *mem, void *user_data) { + int rv; + + rv = conn_new(pconn, /* server = */ 1, callbacks_version, callbacks, + settings_version, settings, mem, user_data); + if (rv != 0) { + return rv; + } + + return 0; +} + +static int free_stream(void *data, void *ptr) { + nghttp3_stream *stream = data; + + (void)ptr; + + nghttp3_stream_del(stream); + + return 0; +} + +void nghttp3_conn_del(nghttp3_conn *conn) { + size_t i; + + if (conn == NULL) { + return; + } + + nghttp3_buf_free(&conn->tx.qpack.ebuf, conn->mem); + nghttp3_buf_free(&conn->tx.qpack.rbuf, conn->mem); + + nghttp3_idtr_free(&conn->remote.bidi.idtr); + + for (i = 0; i < NGHTTP3_URGENCY_LEVELS; ++i) { + nghttp3_pq_free(&conn->sched[i].spq); + } + + nghttp3_pq_free(&conn->qpack_blocked_streams); + + nghttp3_qpack_encoder_free(&conn->qenc); + nghttp3_qpack_decoder_free(&conn->qdec); + + nghttp3_map_each_free(&conn->streams, free_stream, NULL); + nghttp3_map_free(&conn->streams); + + nghttp3_objalloc_free(&conn->stream_objalloc); + nghttp3_objalloc_free(&conn->out_chunk_objalloc); + + nghttp3_mem_free(conn->mem, conn); +} + +static int conn_bidi_idtr_open(nghttp3_conn *conn, int64_t stream_id) { + int rv; + + rv = nghttp3_idtr_open(&conn->remote.bidi.idtr, stream_id); + if (rv != 0) { + return rv; + } + + if (nghttp3_ksl_len(&conn->remote.bidi.idtr.gap.gap) > 32) { + nghttp3_gaptr_drop_first_gap(&conn->remote.bidi.idtr.gap); + } + + return 0; +} + +nghttp3_ssize nghttp3_conn_read_stream(nghttp3_conn *conn, int64_t stream_id, + const uint8_t *src, size_t srclen, + int fin) { + nghttp3_stream *stream; + size_t bidi_nproc; + int rv; + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream == NULL) { + /* TODO Assert idtr */ + /* QUIC transport ensures that this is new stream. */ + if (conn->server) { + if (nghttp3_client_stream_bidi(stream_id)) { + rv = conn_bidi_idtr_open(conn, stream_id); + if (rv != 0) { + if (nghttp3_err_is_fatal(rv)) { + return rv; + } + + /* Ignore return value. We might drop the first gap if there + are many gaps if QUIC stack allows too many holes in stream + ID space. idtr is used to decide whether PRIORITY_UPDATE + frame should be ignored or not and the frame is optional. + Ignoring them causes no harm. */ + } + + conn->rx.max_stream_id_bidi = + nghttp3_max(conn->rx.max_stream_id_bidi, stream_id); + rv = nghttp3_conn_create_stream(conn, &stream, stream_id); + if (rv != 0) { + return rv; + } + + if ((conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_QUEUED) && + conn->tx.goaway_id <= stream_id) { + stream->rstate.state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + + rv = nghttp3_conn_reject_stream(conn, stream); + if (rv != 0) { + return rv; + } + } + } else { + /* unidirectional stream */ + if (srclen == 0 && fin) { + return 0; + } + + rv = nghttp3_conn_create_stream(conn, &stream, stream_id); + if (rv != 0) { + return rv; + } + } + + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_INITIAL; + stream->tx.hstate = NGHTTP3_HTTP_STATE_REQ_INITIAL; + } else if (nghttp3_stream_uni(stream_id)) { + if (srclen == 0 && fin) { + return 0; + } + + rv = nghttp3_conn_create_stream(conn, &stream, stream_id); + if (rv != 0) { + return rv; + } + + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + stream->tx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + } else { + /* client doesn't expect to receive new bidirectional stream + from server. */ + return NGHTTP3_ERR_H3_STREAM_CREATION_ERROR; + } + } else if (conn->server) { + if (nghttp3_client_stream_bidi(stream_id)) { + if (stream->rx.hstate == NGHTTP3_HTTP_STATE_NONE) { + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_INITIAL; + stream->tx.hstate = NGHTTP3_HTTP_STATE_REQ_INITIAL; + } + } + } + + if (srclen == 0 && !fin) { + return 0; + } + + if (nghttp3_stream_uni(stream_id)) { + return nghttp3_conn_read_uni(conn, stream, src, srclen, fin); + } + + if (fin) { + stream->flags |= NGHTTP3_STREAM_FLAG_READ_EOF; + } + return nghttp3_conn_read_bidi(conn, &bidi_nproc, stream, src, srclen, fin); +} + +static nghttp3_ssize conn_read_type(nghttp3_conn *conn, nghttp3_stream *stream, + const uint8_t *src, size_t srclen, + int fin) { + nghttp3_stream_read_state *rstate = &stream->rstate; + nghttp3_varint_read_state *rvint = &rstate->rvint; + nghttp3_ssize nread; + int64_t stream_type; + + assert(srclen); + + nread = nghttp3_read_varint(rvint, src, srclen, fin); + if (nread < 0) { + return NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR; + } + + if (rvint->left) { + return nread; + } + + stream_type = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + switch (stream_type) { + case NGHTTP3_STREAM_TYPE_CONTROL: + if (conn->flags & NGHTTP3_CONN_FLAG_CONTROL_OPENED) { + return NGHTTP3_ERR_H3_STREAM_CREATION_ERROR; + } + conn->flags |= NGHTTP3_CONN_FLAG_CONTROL_OPENED; + stream->type = NGHTTP3_STREAM_TYPE_CONTROL; + rstate->state = NGHTTP3_CTRL_STREAM_STATE_FRAME_TYPE; + break; + case NGHTTP3_STREAM_TYPE_PUSH: + return NGHTTP3_ERR_H3_STREAM_CREATION_ERROR; + case NGHTTP3_STREAM_TYPE_QPACK_ENCODER: + if (conn->flags & NGHTTP3_CONN_FLAG_QPACK_ENCODER_OPENED) { + return NGHTTP3_ERR_H3_STREAM_CREATION_ERROR; + } + conn->flags |= NGHTTP3_CONN_FLAG_QPACK_ENCODER_OPENED; + stream->type = NGHTTP3_STREAM_TYPE_QPACK_ENCODER; + break; + case NGHTTP3_STREAM_TYPE_QPACK_DECODER: + if (conn->flags & NGHTTP3_CONN_FLAG_QPACK_DECODER_OPENED) { + return NGHTTP3_ERR_H3_STREAM_CREATION_ERROR; + } + conn->flags |= NGHTTP3_CONN_FLAG_QPACK_DECODER_OPENED; + stream->type = NGHTTP3_STREAM_TYPE_QPACK_DECODER; + break; + default: + stream->type = NGHTTP3_STREAM_TYPE_UNKNOWN; + break; + } + + stream->flags |= NGHTTP3_STREAM_FLAG_TYPE_IDENTIFIED; + + return nread; +} + +static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream); + +nghttp3_ssize nghttp3_conn_read_uni(nghttp3_conn *conn, nghttp3_stream *stream, + const uint8_t *src, size_t srclen, + int fin) { + nghttp3_ssize nread = 0; + nghttp3_ssize nconsumed = 0; + int rv; + + assert(srclen || fin); + + if (!(stream->flags & NGHTTP3_STREAM_FLAG_TYPE_IDENTIFIED)) { + if (srclen == 0 && fin) { + /* Ignore stream if it is closed before reading stream header. + If it is closed while reading it, return error, making it + consistent in our code base. */ + if (stream->rstate.rvint.left) { + return NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR; + } + + rv = conn_delete_stream(conn, stream); + assert(0 == rv); + + return 0; + } + nread = conn_read_type(conn, stream, src, srclen, fin); + if (nread < 0) { + return (int)nread; + } + if (!(stream->flags & NGHTTP3_STREAM_FLAG_TYPE_IDENTIFIED)) { + assert((size_t)nread == srclen); + return (nghttp3_ssize)srclen; + } + + src += nread; + srclen -= (size_t)nread; + + if (srclen == 0) { + return nread; + } + } + + switch (stream->type) { + case NGHTTP3_STREAM_TYPE_CONTROL: + if (fin) { + return NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM; + } + nconsumed = nghttp3_conn_read_control(conn, stream, src, srclen); + break; + case NGHTTP3_STREAM_TYPE_QPACK_ENCODER: + if (fin) { + return NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM; + } + nconsumed = nghttp3_conn_read_qpack_encoder(conn, src, srclen); + break; + case NGHTTP3_STREAM_TYPE_QPACK_DECODER: + if (fin) { + return NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM; + } + nconsumed = nghttp3_conn_read_qpack_decoder(conn, src, srclen); + break; + case NGHTTP3_STREAM_TYPE_UNKNOWN: + nconsumed = (nghttp3_ssize)srclen; + + rv = conn_call_stop_sending(conn, stream, NGHTTP3_H3_STREAM_CREATION_ERROR); + if (rv != 0) { + return rv; + } + break; + default: + nghttp3_unreachable(); + } + + if (nconsumed < 0) { + return nconsumed; + } + + return nread + nconsumed; +} + +static int frame_fin(nghttp3_stream_read_state *rstate, size_t len) { + return (int64_t)len >= rstate->left; +} + +nghttp3_ssize nghttp3_conn_read_control(nghttp3_conn *conn, + nghttp3_stream *stream, + const uint8_t *src, size_t srclen) { + const uint8_t *p = src, *end = src + srclen; + int rv; + nghttp3_stream_read_state *rstate = &stream->rstate; + nghttp3_varint_read_state *rvint = &rstate->rvint; + nghttp3_ssize nread; + size_t nconsumed = 0; + int busy = 0; + size_t len; + const uint8_t *pri_field_value = NULL; + size_t pri_field_valuelen = 0; + + assert(srclen); + + for (; p != end || busy;) { + busy = 0; + switch (rstate->state) { + case NGHTTP3_CTRL_STREAM_STATE_FRAME_TYPE: + assert(end - p > 0); + nread = nghttp3_read_varint(rvint, p, (size_t)(end - p), /* fin = */ 0); + if (nread < 0) { + return NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + if (rvint->left) { + return (nghttp3_ssize)nconsumed; + } + + rstate->fr.hd.type = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + rstate->state = NGHTTP3_CTRL_STREAM_STATE_FRAME_LENGTH; + if (p == end) { + break; + } + /* Fall through */ + case NGHTTP3_CTRL_STREAM_STATE_FRAME_LENGTH: + assert(end - p > 0); + nread = nghttp3_read_varint(rvint, p, (size_t)(end - p), /* fin = */ 0); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + if (rvint->left) { + return (nghttp3_ssize)nconsumed; + } + + rstate->left = rstate->fr.hd.length = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + if (!(conn->flags & NGHTTP3_CONN_FLAG_SETTINGS_RECVED)) { + if (rstate->fr.hd.type != NGHTTP3_FRAME_SETTINGS) { + return NGHTTP3_ERR_H3_MISSING_SETTINGS; + } + conn->flags |= NGHTTP3_CONN_FLAG_SETTINGS_RECVED; + } else if (rstate->fr.hd.type == NGHTTP3_FRAME_SETTINGS) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + + switch (rstate->fr.hd.type) { + case NGHTTP3_FRAME_SETTINGS: + /* SETTINGS frame might be empty. */ + if (rstate->left == 0) { + nghttp3_stream_read_state_reset(rstate); + break; + } + rstate->state = NGHTTP3_CTRL_STREAM_STATE_SETTINGS; + break; + case NGHTTP3_FRAME_GOAWAY: + if (rstate->left == 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + rstate->state = NGHTTP3_CTRL_STREAM_STATE_GOAWAY; + break; + case NGHTTP3_FRAME_MAX_PUSH_ID: + if (!conn->server) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + if (rstate->left == 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + rstate->state = NGHTTP3_CTRL_STREAM_STATE_MAX_PUSH_ID; + break; + case NGHTTP3_FRAME_PRIORITY_UPDATE: + if (!conn->server) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + if (rstate->left == 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + rstate->state = NGHTTP3_CTRL_STREAM_STATE_PRIORITY_UPDATE_PRI_ELEM_ID; + break; + case NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID: + /* We do not support push */ + return NGHTTP3_ERR_H3_ID_ERROR; + case NGHTTP3_FRAME_CANCEL_PUSH: /* We do not support push */ + case NGHTTP3_FRAME_DATA: + case NGHTTP3_FRAME_HEADERS: + case NGHTTP3_FRAME_PUSH_PROMISE: + case NGHTTP3_H2_FRAME_PRIORITY: + case NGHTTP3_H2_FRAME_PING: + case NGHTTP3_H2_FRAME_WINDOW_UPDATE: + case NGHTTP3_H2_FRAME_CONTINUATION: + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + default: + /* TODO Handle reserved frame type */ + busy = 1; + rstate->state = NGHTTP3_CTRL_STREAM_STATE_IGN_FRAME; + break; + } + break; + case NGHTTP3_CTRL_STREAM_STATE_SETTINGS: + for (; p != end;) { + if (rstate->left == 0) { + nghttp3_stream_read_state_reset(rstate); + break; + } + /* Read Identifier */ + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + assert(len > 0); + nread = nghttp3_read_varint(rvint, p, len, frame_fin(rstate, len)); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + rstate->left -= nread; + if (rvint->left) { + rstate->state = NGHTTP3_CTRL_STREAM_STATE_SETTINGS_ID; + return (nghttp3_ssize)nconsumed; + } + rstate->fr.settings.iv[0].id = (uint64_t)rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + /* Read Value */ + if (rstate->left == 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + len -= (size_t)nread; + if (len == 0) { + rstate->state = NGHTTP3_CTRL_STREAM_STATE_SETTINGS_VALUE; + break; + } + + nread = nghttp3_read_varint(rvint, p, len, frame_fin(rstate, len)); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + rstate->left -= nread; + if (rvint->left) { + rstate->state = NGHTTP3_CTRL_STREAM_STATE_SETTINGS_VALUE; + return (nghttp3_ssize)nconsumed; + } + rstate->fr.settings.iv[0].value = (uint64_t)rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + rv = + nghttp3_conn_on_settings_entry_received(conn, &rstate->fr.settings); + if (rv != 0) { + return rv; + } + } + break; + case NGHTTP3_CTRL_STREAM_STATE_SETTINGS_ID: + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + assert(len > 0); + nread = nghttp3_read_varint(rvint, p, len, frame_fin(rstate, len)); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + rstate->left -= nread; + if (rvint->left) { + return (nghttp3_ssize)nconsumed; + } + rstate->fr.settings.iv[0].id = (uint64_t)rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + if (rstate->left == 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + rstate->state = NGHTTP3_CTRL_STREAM_STATE_SETTINGS_VALUE; + + if (p == end) { + return (nghttp3_ssize)nconsumed; + } + /* Fall through */ + case NGHTTP3_CTRL_STREAM_STATE_SETTINGS_VALUE: + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + assert(len > 0); + nread = nghttp3_read_varint(rvint, p, len, frame_fin(rstate, len)); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + rstate->left -= nread; + if (rvint->left) { + return (nghttp3_ssize)nconsumed; + } + rstate->fr.settings.iv[0].value = (uint64_t)rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + rv = nghttp3_conn_on_settings_entry_received(conn, &rstate->fr.settings); + if (rv != 0) { + return rv; + } + + if (rstate->left) { + rstate->state = NGHTTP3_CTRL_STREAM_STATE_SETTINGS; + break; + } + + nghttp3_stream_read_state_reset(rstate); + break; + case NGHTTP3_CTRL_STREAM_STATE_GOAWAY: + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + assert(len > 0); + nread = nghttp3_read_varint(rvint, p, len, frame_fin(rstate, len)); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + rstate->left -= nread; + if (rvint->left) { + return (nghttp3_ssize)nconsumed; + } + + if (!conn->server && !nghttp3_client_stream_bidi(rvint->acc)) { + return NGHTTP3_ERR_H3_ID_ERROR; + } + if (conn->rx.goaway_id < rvint->acc) { + return NGHTTP3_ERR_H3_ID_ERROR; + } + + conn->flags |= NGHTTP3_CONN_FLAG_GOAWAY_RECVED; + conn->rx.goaway_id = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + if (conn->callbacks.shutdown) { + rv = + conn->callbacks.shutdown(conn, conn->rx.goaway_id, conn->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + + nghttp3_stream_read_state_reset(rstate); + break; + case NGHTTP3_CTRL_STREAM_STATE_MAX_PUSH_ID: + /* server side only */ + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + assert(len > 0); + nread = nghttp3_read_varint(rvint, p, len, frame_fin(rstate, len)); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + rstate->left -= nread; + if (rvint->left) { + return (nghttp3_ssize)nconsumed; + } + + if (conn->local.uni.max_pushes > (uint64_t)rvint->acc + 1) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + conn->local.uni.max_pushes = (uint64_t)rvint->acc + 1; + nghttp3_varint_read_state_reset(rvint); + + nghttp3_stream_read_state_reset(rstate); + break; + case NGHTTP3_CTRL_STREAM_STATE_PRIORITY_UPDATE_PRI_ELEM_ID: + /* server side only */ + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + assert(len > 0); + nread = nghttp3_read_varint(rvint, p, len, frame_fin(rstate, len)); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + rstate->left -= nread; + if (rvint->left) { + return (nghttp3_ssize)nconsumed; + } + + rstate->fr.priority_update.pri_elem_id = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + if (rstate->left == 0) { + rstate->fr.priority_update.pri.urgency = NGHTTP3_DEFAULT_URGENCY; + rstate->fr.priority_update.pri.inc = 0; + + rv = nghttp3_conn_on_priority_update(conn, &rstate->fr.priority_update); + if (rv != 0) { + return rv; + } + + nghttp3_stream_read_state_reset(rstate); + break; + } + + rstate->state = NGHTTP3_CTRL_STREAM_STATE_PRIORITY_UPDATE; + + /* Fall through */ + case NGHTTP3_CTRL_STREAM_STATE_PRIORITY_UPDATE: + /* We need to buffer Priority Field Value because it might be + fragmented. */ + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + assert(len > 0); + if (conn->rx.pri_fieldbuflen == 0 && rstate->left == (int64_t)len) { + /* Everything is in the input buffer. Apply same length + limit we impose when buffering the field. */ + if (len > sizeof(conn->rx.pri_fieldbuf)) { + busy = 1; + rstate->state = NGHTTP3_CTRL_STREAM_STATE_IGN_FRAME; + break; + } + + pri_field_value = p; + pri_field_valuelen = len; + } else if (len + conn->rx.pri_fieldbuflen > + sizeof(conn->rx.pri_fieldbuf)) { + busy = 1; + rstate->state = NGHTTP3_CTRL_STREAM_STATE_IGN_FRAME; + break; + } else { + memcpy(conn->rx.pri_fieldbuf + conn->rx.pri_fieldbuflen, p, len); + conn->rx.pri_fieldbuflen += len; + + if (rstate->left == (int64_t)len) { + pri_field_value = conn->rx.pri_fieldbuf; + pri_field_valuelen = conn->rx.pri_fieldbuflen; + } + } + + p += len; + nconsumed += len; + rstate->left -= (int64_t)len; + + if (rstate->left) { + return (nghttp3_ssize)nconsumed; + } + + rstate->fr.priority_update.pri.urgency = NGHTTP3_DEFAULT_URGENCY; + rstate->fr.priority_update.pri.inc = 0; + + if (nghttp3_http_parse_priority(&rstate->fr.priority_update.pri, + pri_field_value, + pri_field_valuelen) != 0) { + return NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR; + } + + rv = nghttp3_conn_on_priority_update(conn, &rstate->fr.priority_update); + if (rv != 0) { + return rv; + } + + conn->rx.pri_fieldbuflen = 0; + + nghttp3_stream_read_state_reset(rstate); + break; + case NGHTTP3_CTRL_STREAM_STATE_IGN_FRAME: + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + p += len; + nconsumed += len; + rstate->left -= (int64_t)len; + + if (rstate->left) { + return (nghttp3_ssize)nconsumed; + } + + nghttp3_stream_read_state_reset(rstate); + break; + default: + nghttp3_unreachable(); + } + } + + return (nghttp3_ssize)nconsumed; +} + +static int conn_delete_stream(nghttp3_conn *conn, nghttp3_stream *stream) { + int bidi = nghttp3_client_stream_bidi(stream->node.id); + int rv; + + rv = conn_call_deferred_consume(conn, stream, + nghttp3_stream_get_buffered_datalen(stream)); + if (rv != 0) { + return rv; + } + + if (bidi && conn->callbacks.stream_close) { + rv = conn->callbacks.stream_close(conn, stream->node.id, stream->error_code, + conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + + rv = + nghttp3_map_remove(&conn->streams, (nghttp3_map_key_type)stream->node.id); + + assert(0 == rv); + + nghttp3_stream_del(stream); + + return 0; +} + +static int conn_process_blocked_stream_data(nghttp3_conn *conn, + nghttp3_stream *stream) { + nghttp3_buf *buf; + size_t nproc; + nghttp3_ssize nconsumed; + int rv; + size_t len; + + assert(nghttp3_client_stream_bidi(stream->node.id)); + + for (;;) { + len = nghttp3_ringbuf_len(&stream->inq); + if (len == 0) { + break; + } + + buf = nghttp3_ringbuf_get(&stream->inq, 0); + + nconsumed = nghttp3_conn_read_bidi( + conn, &nproc, stream, buf->pos, nghttp3_buf_len(buf), + len == 1 && (stream->flags & NGHTTP3_STREAM_FLAG_READ_EOF)); + if (nconsumed < 0) { + return (int)nconsumed; + } + + buf->pos += nproc; + + rv = conn_call_deferred_consume(conn, stream, (size_t)nconsumed); + if (rv != 0) { + return 0; + } + + if (nghttp3_buf_len(buf) == 0) { + nghttp3_buf_free(buf, stream->mem); + nghttp3_ringbuf_pop_front(&stream->inq); + } + + if (stream->flags & NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED) { + break; + } + } + + if (!(stream->flags & NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED) && + (stream->flags & NGHTTP3_STREAM_FLAG_CLOSED)) { + assert(stream->qpack_blocked_pe.index == NGHTTP3_PQ_BAD_INDEX); + + rv = conn_delete_stream(conn, stream); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +nghttp3_ssize nghttp3_conn_read_qpack_encoder(nghttp3_conn *conn, + const uint8_t *src, + size_t srclen) { + nghttp3_ssize nconsumed = + nghttp3_qpack_decoder_read_encoder(&conn->qdec, src, srclen); + nghttp3_stream *stream; + int rv; + + if (nconsumed < 0) { + return nconsumed; + } + + for (; !nghttp3_pq_empty(&conn->qpack_blocked_streams);) { + stream = nghttp3_struct_of(nghttp3_pq_top(&conn->qpack_blocked_streams), + nghttp3_stream, qpack_blocked_pe); + if (nghttp3_qpack_stream_context_get_ricnt(&stream->qpack_sctx) > + nghttp3_qpack_decoder_get_icnt(&conn->qdec)) { + break; + } + + nghttp3_conn_qpack_blocked_streams_pop(conn); + stream->qpack_blocked_pe.index = NGHTTP3_PQ_BAD_INDEX; + stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED; + + rv = conn_process_blocked_stream_data(conn, stream); + if (rv != 0) { + return rv; + } + } + + return nconsumed; +} + +nghttp3_ssize nghttp3_conn_read_qpack_decoder(nghttp3_conn *conn, + const uint8_t *src, + size_t srclen) { + return nghttp3_qpack_encoder_read_decoder(&conn->qenc, src, srclen); +} + +static nghttp3_tnode *stream_get_sched_node(nghttp3_stream *stream) { + return &stream->node; +} + +static int conn_update_stream_priority(nghttp3_conn *conn, + nghttp3_stream *stream, uint8_t pri) { + assert(nghttp3_client_stream_bidi(stream->node.id)); + + if (stream->node.pri == pri) { + return 0; + } + + nghttp3_conn_unschedule_stream(conn, stream); + + stream->node.pri = pri; + + if (nghttp3_stream_require_schedule(stream)) { + return nghttp3_conn_schedule_stream(conn, stream); + } + + return 0; +} + +nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, + nghttp3_stream *stream, const uint8_t *src, + size_t srclen, int fin) { + const uint8_t *p = src, *end = src ? src + srclen : src; + int rv; + nghttp3_stream_read_state *rstate = &stream->rstate; + nghttp3_varint_read_state *rvint = &rstate->rvint; + nghttp3_ssize nread; + size_t nconsumed = 0; + int busy = 0; + size_t len; + + if (stream->flags & NGHTTP3_STREAM_FLAG_SHUT_RD) { + *pnproc = srclen; + + return (nghttp3_ssize)srclen; + } + + if (stream->flags & NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED) { + *pnproc = 0; + + if (srclen == 0) { + return 0; + } + + rv = nghttp3_stream_buffer_data(stream, p, (size_t)(end - p)); + if (rv != 0) { + return rv; + } + return 0; + } + + for (; p != end || busy;) { + busy = 0; + switch (rstate->state) { + case NGHTTP3_REQ_STREAM_STATE_FRAME_TYPE: + assert(end - p > 0); + nread = nghttp3_read_varint(rvint, p, (size_t)(end - p), fin); + if (nread < 0) { + return NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + if (rvint->left) { + goto almost_done; + } + + rstate->fr.hd.type = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + rstate->state = NGHTTP3_REQ_STREAM_STATE_FRAME_LENGTH; + if (p == end) { + goto almost_done; + } + /* Fall through */ + case NGHTTP3_REQ_STREAM_STATE_FRAME_LENGTH: + assert(end - p > 0); + nread = nghttp3_read_varint(rvint, p, (size_t)(end - p), fin); + if (nread < 0) { + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + + p += nread; + nconsumed += (size_t)nread; + if (rvint->left) { + goto almost_done; + } + + rstate->left = rstate->fr.hd.length = rvint->acc; + nghttp3_varint_read_state_reset(rvint); + + switch (rstate->fr.hd.type) { + case NGHTTP3_FRAME_DATA: + rv = nghttp3_stream_transit_rx_http_state( + stream, NGHTTP3_HTTP_EVENT_DATA_BEGIN); + if (rv != 0) { + return rv; + } + /* DATA frame might be empty. */ + if (rstate->left == 0) { + rv = nghttp3_stream_transit_rx_http_state( + stream, NGHTTP3_HTTP_EVENT_DATA_END); + assert(0 == rv); + + nghttp3_stream_read_state_reset(rstate); + break; + } + rstate->state = NGHTTP3_REQ_STREAM_STATE_DATA; + break; + case NGHTTP3_FRAME_HEADERS: + rv = nghttp3_stream_transit_rx_http_state( + stream, NGHTTP3_HTTP_EVENT_HEADERS_BEGIN); + if (rv != 0) { + return rv; + } + if (rstate->left == 0) { + rv = nghttp3_stream_empty_headers_allowed(stream); + if (rv != 0) { + return rv; + } + + rv = nghttp3_stream_transit_rx_http_state( + stream, NGHTTP3_HTTP_EVENT_HEADERS_END); + assert(0 == rv); + + nghttp3_stream_read_state_reset(rstate); + break; + } + + switch (stream->rx.hstate) { + case NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN: + case NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN: + rv = conn_call_begin_headers(conn, stream); + break; + case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN: + case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN: + rv = conn_call_begin_trailers(conn, stream); + break; + default: + nghttp3_unreachable(); + } + + if (rv != 0) { + return rv; + } + + rstate->state = NGHTTP3_REQ_STREAM_STATE_HEADERS; + break; + case NGHTTP3_FRAME_PUSH_PROMISE: /* We do not support push */ + case NGHTTP3_FRAME_CANCEL_PUSH: + case NGHTTP3_FRAME_SETTINGS: + case NGHTTP3_FRAME_GOAWAY: + case NGHTTP3_FRAME_MAX_PUSH_ID: + case NGHTTP3_FRAME_PRIORITY_UPDATE: + case NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID: + case NGHTTP3_H2_FRAME_PRIORITY: + case NGHTTP3_H2_FRAME_PING: + case NGHTTP3_H2_FRAME_WINDOW_UPDATE: + case NGHTTP3_H2_FRAME_CONTINUATION: + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + default: + /* TODO Handle reserved frame type */ + busy = 1; + rstate->state = NGHTTP3_REQ_STREAM_STATE_IGN_FRAME; + break; + } + break; + case NGHTTP3_REQ_STREAM_STATE_DATA: + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + rv = nghttp3_conn_on_data(conn, stream, p, len); + if (rv != 0) { + return rv; + } + p += len; + rstate->left -= (int64_t)len; + + if (rstate->left) { + goto almost_done; + } + + rv = nghttp3_stream_transit_rx_http_state(stream, + NGHTTP3_HTTP_EVENT_DATA_END); + assert(0 == rv); + + nghttp3_stream_read_state_reset(rstate); + break; + case NGHTTP3_REQ_STREAM_STATE_HEADERS: + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + nread = nghttp3_conn_on_headers(conn, stream, p, len, + (int64_t)len == rstate->left); + if (nread < 0) { + if (nread == NGHTTP3_ERR_MALFORMED_HTTP_HEADER) { + goto http_header_error; + } + + return nread; + } + + p += nread; + nconsumed += (size_t)nread; + rstate->left -= nread; + + if (stream->flags & NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED) { + if (p != end && nghttp3_stream_get_buffered_datalen(stream) == 0) { + rv = nghttp3_stream_buffer_data(stream, p, (size_t)(end - p)); + if (rv != 0) { + return rv; + } + } + *pnproc = (size_t)(p - src); + return (nghttp3_ssize)nconsumed; + } + + if (rstate->left) { + goto almost_done; + } + + switch (stream->rx.hstate) { + case NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN: + rv = nghttp3_http_on_request_headers(&stream->rx.http); + break; + case NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN: + rv = nghttp3_http_on_response_headers(&stream->rx.http); + break; + case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN: + case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN: + rv = 0; + break; + default: + nghttp3_unreachable(); + } + + if (rv != 0) { + if (rv == NGHTTP3_ERR_MALFORMED_HTTP_HEADER) { + goto http_header_error; + } + + return rv; + } + + switch (stream->rx.hstate) { + case NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN: + /* Only server utilizes priority information to schedule + streams. */ + if (conn->server && + (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_PRIORITY) && + !(stream->flags & NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED) && + !(stream->flags & NGHTTP3_STREAM_FLAG_SERVER_PRIORITY_SET)) { + rv = conn_update_stream_priority(conn, stream, stream->rx.http.pri); + if (rv != 0) { + return rv; + } + } + /* fall through */ + case NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN: + rv = conn_call_end_headers(conn, stream, p == end && fin); + break; + case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN: + case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN: + rv = conn_call_end_trailers(conn, stream, p == end && fin); + break; + default: + nghttp3_unreachable(); + } + + if (rv != 0) { + return rv; + } + + rv = nghttp3_stream_transit_rx_http_state(stream, + NGHTTP3_HTTP_EVENT_HEADERS_END); + assert(0 == rv); + + nghttp3_stream_read_state_reset(rstate); + + break; + + http_header_error: + stream->flags |= NGHTTP3_STREAM_FLAG_HTTP_ERROR; + + busy = 1; + rstate->state = NGHTTP3_REQ_STREAM_STATE_IGN_REST; + + rv = conn_call_stop_sending(conn, stream, NGHTTP3_H3_MESSAGE_ERROR); + if (rv != 0) { + return rv; + } + + rv = conn_call_reset_stream(conn, stream, NGHTTP3_H3_MESSAGE_ERROR); + if (rv != 0) { + return rv; + } + + break; + case NGHTTP3_REQ_STREAM_STATE_IGN_FRAME: + len = (size_t)nghttp3_min(rstate->left, (int64_t)(end - p)); + p += len; + nconsumed += len; + rstate->left -= (int64_t)len; + + if (rstate->left) { + goto almost_done; + } + + nghttp3_stream_read_state_reset(rstate); + break; + case NGHTTP3_REQ_STREAM_STATE_IGN_REST: + nconsumed += (size_t)(end - p); + *pnproc = (size_t)(end - src); + return (nghttp3_ssize)nconsumed; + } + } + +almost_done: + if (fin) { + switch (rstate->state) { + case NGHTTP3_REQ_STREAM_STATE_FRAME_TYPE: + if (rvint->left) { + return NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR; + } + rv = nghttp3_stream_transit_rx_http_state(stream, + NGHTTP3_HTTP_EVENT_MSG_END); + if (rv != 0) { + return rv; + } + rv = conn_call_end_stream(conn, stream); + if (rv != 0) { + return rv; + } + break; + case NGHTTP3_REQ_STREAM_STATE_IGN_REST: + break; + default: + return NGHTTP3_ERR_H3_FRAME_ERROR; + } + } + + *pnproc = (size_t)(p - src); + return (nghttp3_ssize)nconsumed; +} + +int nghttp3_conn_on_data(nghttp3_conn *conn, nghttp3_stream *stream, + const uint8_t *data, size_t datalen) { + int rv; + + rv = nghttp3_http_on_data_chunk(stream, datalen); + if (rv != 0) { + return rv; + } + + if (!conn->callbacks.recv_data) { + return 0; + } + + rv = conn->callbacks.recv_data(conn, stream->node.id, data, datalen, + conn->user_data, stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static nghttp3_pq *conn_get_sched_pq(nghttp3_conn *conn, nghttp3_tnode *tnode) { + uint32_t urgency = nghttp3_pri_uint8_urgency(tnode->pri); + + assert(urgency < NGHTTP3_URGENCY_LEVELS); + + return &conn->sched[urgency].spq; +} + +static nghttp3_ssize conn_decode_headers(nghttp3_conn *conn, + nghttp3_stream *stream, + const uint8_t *src, size_t srclen, + int fin) { + nghttp3_ssize nread; + int rv; + nghttp3_qpack_decoder *qdec = &conn->qdec; + nghttp3_qpack_nv nv; + uint8_t flags; + nghttp3_buf buf; + nghttp3_recv_header recv_header = NULL; + nghttp3_http_state *http; + int request = 0; + int trailers = 0; + + switch (stream->rx.hstate) { + case NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN: + request = 1; + /* Fall through */ + case NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN: + recv_header = conn->callbacks.recv_header; + break; + case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN: + request = 1; + /* Fall through */ + case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN: + trailers = 1; + recv_header = conn->callbacks.recv_trailer; + break; + default: + nghttp3_unreachable(); + } + http = &stream->rx.http; + + nghttp3_buf_wrap_init(&buf, (uint8_t *)src, srclen); + buf.last = buf.end; + + for (;;) { + nread = nghttp3_qpack_decoder_read_request(qdec, &stream->qpack_sctx, &nv, + &flags, buf.pos, + nghttp3_buf_len(&buf), fin); + + if (nread < 0) { + return (int)nread; + } + + buf.pos += nread; + + if (flags & NGHTTP3_QPACK_DECODE_FLAG_BLOCKED) { + if (conn->local.settings.qpack_blocked_streams <= + nghttp3_pq_size(&conn->qpack_blocked_streams)) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + + stream->flags |= NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED; + rv = nghttp3_conn_qpack_blocked_streams_push(conn, stream); + if (rv != 0) { + return rv; + } + break; + } + + if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) { + nghttp3_qpack_stream_context_reset(&stream->qpack_sctx); + break; + } + + if (nread == 0) { + break; + } + + if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) { + rv = nghttp3_http_on_header( + http, &nv, request, trailers, + conn->server && conn->local.settings.enable_connect_protocol); + switch (rv) { + case NGHTTP3_ERR_MALFORMED_HTTP_HEADER: + break; + case NGHTTP3_ERR_REMOVE_HTTP_HEADER: + rv = 0; + break; + case 0: + if (recv_header) { + rv = recv_header(conn, stream->node.id, nv.token, nv.name, nv.value, + nv.flags, conn->user_data, stream->user_data); + if (rv != 0) { + rv = NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + break; + default: + nghttp3_unreachable(); + } + + nghttp3_rcbuf_decref(nv.name); + nghttp3_rcbuf_decref(nv.value); + + if (rv != 0) { + return rv; + } + } + } + + return buf.pos - src; +} + +nghttp3_ssize nghttp3_conn_on_headers(nghttp3_conn *conn, + nghttp3_stream *stream, + const uint8_t *src, size_t srclen, + int fin) { + if (srclen == 0 && !fin) { + return 0; + } + + return conn_decode_headers(conn, stream, src, srclen, fin); +} + +int nghttp3_conn_on_settings_entry_received(nghttp3_conn *conn, + const nghttp3_frame_settings *fr) { + const nghttp3_settings_entry *ent = &fr->iv[0]; + nghttp3_settings *dest = &conn->remote.settings; + + /* TODO Check for duplicates */ + switch (ent->id) { + case NGHTTP3_SETTINGS_ID_MAX_FIELD_SECTION_SIZE: + dest->max_field_section_size = ent->value; + break; + case NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY: + if (dest->qpack_max_dtable_capacity != 0) { + return NGHTTP3_ERR_H3_SETTINGS_ERROR; + } + + if (ent->value == 0) { + break; + } + + dest->qpack_max_dtable_capacity = (size_t)ent->value; + + nghttp3_qpack_encoder_set_max_dtable_capacity(&conn->qenc, + (size_t)ent->value); + break; + case NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS: + if (dest->qpack_blocked_streams != 0) { + return NGHTTP3_ERR_H3_SETTINGS_ERROR; + } + + if (ent->value == 0) { + break; + } + + dest->qpack_blocked_streams = (size_t)ent->value; + + nghttp3_qpack_encoder_set_max_blocked_streams( + &conn->qenc, (size_t)nghttp3_min(100, ent->value)); + break; + case NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL: + if (!conn->server) { + break; + } + + switch (ent->value) { + case 0: + if (dest->enable_connect_protocol) { + return NGHTTP3_ERR_H3_SETTINGS_ERROR; + } + + break; + case 1: + break; + default: + return NGHTTP3_ERR_H3_SETTINGS_ERROR; + } + + dest->enable_connect_protocol = (int)ent->value; + break; + case NGHTTP3_H2_SETTINGS_ID_ENABLE_PUSH: + case NGHTTP3_H2_SETTINGS_ID_MAX_CONCURRENT_STREAMS: + case NGHTTP3_H2_SETTINGS_ID_INITIAL_WINDOW_SIZE: + case NGHTTP3_H2_SETTINGS_ID_MAX_FRAME_SIZE: + return NGHTTP3_ERR_H3_SETTINGS_ERROR; + default: + /* Ignore unknown settings ID */ + break; + } + + return 0; +} + +static int +conn_on_priority_update_stream(nghttp3_conn *conn, + const nghttp3_frame_priority_update *fr) { + int64_t stream_id = fr->pri_elem_id; + nghttp3_stream *stream; + int rv; + + if (!nghttp3_client_stream_bidi(stream_id) || + nghttp3_ord_stream_id(stream_id) > conn->remote.bidi.max_client_streams) { + return NGHTTP3_ERR_H3_ID_ERROR; + } + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream == NULL) { + if ((conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_QUEUED) && + conn->tx.goaway_id <= stream_id) { + /* Connection is going down. Ignore priority signal. */ + return 0; + } + + rv = conn_bidi_idtr_open(conn, stream_id); + if (rv != 0) { + if (nghttp3_err_is_fatal(rv)) { + return rv; + } + + assert(rv == NGHTTP3_ERR_STREAM_IN_USE); + + /* The stream is gone. Just ignore. */ + return 0; + } + + conn->rx.max_stream_id_bidi = + nghttp3_max(conn->rx.max_stream_id_bidi, stream_id); + rv = nghttp3_conn_create_stream(conn, &stream, stream_id); + if (rv != 0) { + return rv; + } + + stream->node.pri = nghttp3_pri_to_uint8(&fr->pri); + stream->flags |= NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED; + + return 0; + } + + if (stream->flags & NGHTTP3_STREAM_FLAG_SERVER_PRIORITY_SET) { + return 0; + } + + stream->flags |= NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED; + + return conn_update_stream_priority(conn, stream, + nghttp3_pri_to_uint8(&fr->pri)); +} + +int nghttp3_conn_on_priority_update(nghttp3_conn *conn, + const nghttp3_frame_priority_update *fr) { + assert(conn->server); + assert(fr->hd.type == NGHTTP3_FRAME_PRIORITY_UPDATE); + + return conn_on_priority_update_stream(conn, fr); +} + +static int conn_stream_acked_data(nghttp3_stream *stream, int64_t stream_id, + uint64_t datalen, void *user_data) { + nghttp3_conn *conn = stream->conn; + int rv; + + if (!conn->callbacks.acked_stream_data) { + return 0; + } + + rv = conn->callbacks.acked_stream_data(conn, stream_id, datalen, + conn->user_data, user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int nghttp3_conn_create_stream(nghttp3_conn *conn, nghttp3_stream **pstream, + int64_t stream_id) { + nghttp3_stream *stream; + int rv; + nghttp3_stream_callbacks callbacks = { + conn_stream_acked_data, + }; + + rv = nghttp3_stream_new(&stream, stream_id, &callbacks, + &conn->out_chunk_objalloc, &conn->stream_objalloc, + conn->mem); + if (rv != 0) { + return rv; + } + + stream->conn = conn; + + rv = nghttp3_map_insert(&conn->streams, (nghttp3_map_key_type)stream->node.id, + stream); + if (rv != 0) { + nghttp3_stream_del(stream); + return rv; + } + + *pstream = stream; + + return 0; +} + +nghttp3_stream *nghttp3_conn_find_stream(nghttp3_conn *conn, + int64_t stream_id) { + return nghttp3_map_find(&conn->streams, (nghttp3_map_key_type)stream_id); +} + +int nghttp3_conn_bind_control_stream(nghttp3_conn *conn, int64_t stream_id) { + nghttp3_stream *stream; + nghttp3_frame_entry frent; + int rv; + + assert(!conn->server || nghttp3_server_stream_uni(stream_id)); + assert(conn->server || nghttp3_client_stream_uni(stream_id)); + + if (conn->tx.ctrl) { + return NGHTTP3_ERR_INVALID_STATE; + } + + rv = nghttp3_conn_create_stream(conn, &stream, stream_id); + if (rv != 0) { + return rv; + } + + stream->type = NGHTTP3_STREAM_TYPE_CONTROL; + + conn->tx.ctrl = stream; + + rv = nghttp3_stream_write_stream_type(stream); + if (rv != 0) { + return rv; + } + + frent.fr.hd.type = NGHTTP3_FRAME_SETTINGS; + frent.aux.settings.local_settings = &conn->local.settings; + + return nghttp3_stream_frq_add(stream, &frent); +} + +int nghttp3_conn_bind_qpack_streams(nghttp3_conn *conn, int64_t qenc_stream_id, + int64_t qdec_stream_id) { + nghttp3_stream *stream; + int rv; + + assert(!conn->server || nghttp3_server_stream_uni(qenc_stream_id)); + assert(!conn->server || nghttp3_server_stream_uni(qdec_stream_id)); + assert(conn->server || nghttp3_client_stream_uni(qenc_stream_id)); + assert(conn->server || nghttp3_client_stream_uni(qdec_stream_id)); + + if (conn->tx.qenc || conn->tx.qdec) { + return NGHTTP3_ERR_INVALID_STATE; + } + + rv = nghttp3_conn_create_stream(conn, &stream, qenc_stream_id); + if (rv != 0) { + return rv; + } + + stream->type = NGHTTP3_STREAM_TYPE_QPACK_ENCODER; + + conn->tx.qenc = stream; + + rv = nghttp3_stream_write_stream_type(stream); + if (rv != 0) { + return rv; + } + + rv = nghttp3_conn_create_stream(conn, &stream, qdec_stream_id); + if (rv != 0) { + return rv; + } + + stream->type = NGHTTP3_STREAM_TYPE_QPACK_DECODER; + + conn->tx.qdec = stream; + + return nghttp3_stream_write_stream_type(stream); +} + +static nghttp3_ssize conn_writev_stream(nghttp3_conn *conn, int64_t *pstream_id, + int *pfin, nghttp3_vec *vec, + size_t veccnt, nghttp3_stream *stream) { + int rv; + nghttp3_ssize n; + + assert(veccnt > 0); + + /* If stream is blocked by read callback, don't attempt to fill + more. */ + if (!(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED)) { + rv = nghttp3_stream_fill_outq(stream); + if (rv != 0) { + return rv; + } + } + + if (!nghttp3_stream_uni(stream->node.id) && conn->tx.qenc && + !nghttp3_stream_is_blocked(conn->tx.qenc)) { + n = nghttp3_stream_writev(conn->tx.qenc, pfin, vec, veccnt); + if (n < 0) { + return n; + } + if (n) { + *pstream_id = conn->tx.qenc->node.id; + return n; + } + } + + n = nghttp3_stream_writev(stream, pfin, vec, veccnt); + if (n < 0) { + return n; + } + /* We might just want to write stream fin without sending any stream + data. */ + if (n == 0 && *pfin == 0) { + return 0; + } + + *pstream_id = stream->node.id; + + return n; +} + +nghttp3_ssize nghttp3_conn_writev_stream(nghttp3_conn *conn, + int64_t *pstream_id, int *pfin, + nghttp3_vec *vec, size_t veccnt) { + nghttp3_ssize ncnt; + nghttp3_stream *stream; + int rv; + + *pstream_id = -1; + *pfin = 0; + + if (veccnt == 0) { + return 0; + } + + if (conn->tx.ctrl && !nghttp3_stream_is_blocked(conn->tx.ctrl)) { + ncnt = + conn_writev_stream(conn, pstream_id, pfin, vec, veccnt, conn->tx.ctrl); + if (ncnt) { + return ncnt; + } + } + + if (conn->tx.qdec && !nghttp3_stream_is_blocked(conn->tx.qdec)) { + rv = nghttp3_stream_write_qpack_decoder_stream(conn->tx.qdec); + if (rv != 0) { + return rv; + } + + ncnt = + conn_writev_stream(conn, pstream_id, pfin, vec, veccnt, conn->tx.qdec); + if (ncnt) { + return ncnt; + } + } + + if (conn->tx.qenc && !nghttp3_stream_is_blocked(conn->tx.qenc)) { + ncnt = + conn_writev_stream(conn, pstream_id, pfin, vec, veccnt, conn->tx.qenc); + if (ncnt) { + return ncnt; + } + } + + stream = nghttp3_conn_get_next_tx_stream(conn); + if (stream == NULL) { + return 0; + } + + ncnt = conn_writev_stream(conn, pstream_id, pfin, vec, veccnt, stream); + if (ncnt < 0) { + return ncnt; + } + + if (nghttp3_client_stream_bidi(stream->node.id) && + !nghttp3_stream_require_schedule(stream)) { + nghttp3_conn_unschedule_stream(conn, stream); + } + + return ncnt; +} + +nghttp3_stream *nghttp3_conn_get_next_tx_stream(nghttp3_conn *conn) { + size_t i; + nghttp3_tnode *tnode; + nghttp3_pq *pq; + + for (i = 0; i < NGHTTP3_URGENCY_LEVELS; ++i) { + pq = &conn->sched[i].spq; + if (nghttp3_pq_empty(pq)) { + continue; + } + + tnode = nghttp3_struct_of(nghttp3_pq_top(pq), nghttp3_tnode, pe); + + return nghttp3_struct_of(tnode, nghttp3_stream, node); + } + + return NULL; +} + +int nghttp3_conn_add_write_offset(nghttp3_conn *conn, int64_t stream_id, + size_t n) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + int rv; + + if (stream == NULL) { + return 0; + } + + rv = nghttp3_stream_add_outq_offset(stream, n); + if (rv != 0) { + return rv; + } + + stream->unscheduled_nwrite += n; + + if (!nghttp3_client_stream_bidi(stream->node.id)) { + return 0; + } + + if (!nghttp3_stream_require_schedule(stream)) { + nghttp3_conn_unschedule_stream(conn, stream); + return 0; + } + + if (stream->unscheduled_nwrite < NGHTTP3_STREAM_MIN_WRITELEN) { + return 0; + } + + return nghttp3_conn_schedule_stream(conn, stream); +} + +int nghttp3_conn_add_ack_offset(nghttp3_conn *conn, int64_t stream_id, + uint64_t n) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return 0; + } + + return nghttp3_stream_add_ack_offset(stream, n); +} + +static int conn_submit_headers_data(nghttp3_conn *conn, nghttp3_stream *stream, + const nghttp3_nv *nva, size_t nvlen, + const nghttp3_data_reader *dr) { + int rv; + nghttp3_nv *nnva; + nghttp3_frame_entry frent; + + rv = nghttp3_nva_copy(&nnva, nva, nvlen, conn->mem); + if (rv != 0) { + return rv; + } + + frent.fr.hd.type = NGHTTP3_FRAME_HEADERS; + frent.fr.headers.nva = nnva; + frent.fr.headers.nvlen = nvlen; + + rv = nghttp3_stream_frq_add(stream, &frent); + if (rv != 0) { + nghttp3_nva_del(nnva, conn->mem); + return rv; + } + + if (dr) { + frent.fr.hd.type = NGHTTP3_FRAME_DATA; + frent.aux.data.dr = *dr; + + rv = nghttp3_stream_frq_add(stream, &frent); + if (rv != 0) { + return rv; + } + } + + if (nghttp3_stream_require_schedule(stream)) { + return nghttp3_conn_schedule_stream(conn, stream); + } + + return 0; +} + +int nghttp3_conn_schedule_stream(nghttp3_conn *conn, nghttp3_stream *stream) { + /* Assume that stream stays on the same urgency level */ + nghttp3_tnode *node = stream_get_sched_node(stream); + int rv; + + rv = nghttp3_tnode_schedule(node, conn_get_sched_pq(conn, node), + stream->unscheduled_nwrite); + if (rv != 0) { + return rv; + } + + stream->unscheduled_nwrite = 0; + + return 0; +} + +int nghttp3_conn_ensure_stream_scheduled(nghttp3_conn *conn, + nghttp3_stream *stream) { + if (nghttp3_tnode_is_scheduled(stream_get_sched_node(stream))) { + return 0; + } + + return nghttp3_conn_schedule_stream(conn, stream); +} + +void nghttp3_conn_unschedule_stream(nghttp3_conn *conn, + nghttp3_stream *stream) { + nghttp3_tnode *node = stream_get_sched_node(stream); + + nghttp3_tnode_unschedule(node, conn_get_sched_pq(conn, node)); +} + +int nghttp3_conn_submit_request(nghttp3_conn *conn, int64_t stream_id, + const nghttp3_nv *nva, size_t nvlen, + const nghttp3_data_reader *dr, + void *stream_user_data) { + nghttp3_stream *stream; + int rv; + + assert(!conn->server); + assert(conn->tx.qenc); + + assert(nghttp3_client_stream_bidi(stream_id)); + + /* TODO Should we check that stream_id is client stream_id? */ + /* TODO Check GOAWAY last stream ID */ + if (nghttp3_stream_uni(stream_id)) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + if (conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_RECVED) { + return NGHTTP3_ERR_CONN_CLOSING; + } + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream != NULL) { + return NGHTTP3_ERR_STREAM_IN_USE; + } + + rv = nghttp3_conn_create_stream(conn, &stream, stream_id); + if (rv != 0) { + return rv; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + stream->tx.hstate = NGHTTP3_HTTP_STATE_REQ_END; + stream->user_data = stream_user_data; + + nghttp3_http_record_request_method(stream, nva, nvlen); + + if (dr == NULL) { + stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM; + } + + return conn_submit_headers_data(conn, stream, nva, nvlen, dr); +} + +int nghttp3_conn_submit_info(nghttp3_conn *conn, int64_t stream_id, + const nghttp3_nv *nva, size_t nvlen) { + nghttp3_stream *stream; + + /* TODO Verify that it is allowed to send info (non-final response) + now. */ + assert(conn->server); + assert(conn->tx.qenc); + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream == NULL) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + return conn_submit_headers_data(conn, stream, nva, nvlen, NULL); +} + +int nghttp3_conn_submit_response(nghttp3_conn *conn, int64_t stream_id, + const nghttp3_nv *nva, size_t nvlen, + const nghttp3_data_reader *dr) { + nghttp3_stream *stream; + + /* TODO Verify that it is allowed to send response now. */ + assert(conn->server); + assert(conn->tx.qenc); + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream == NULL) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + if (dr == NULL) { + stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM; + } + + return conn_submit_headers_data(conn, stream, nva, nvlen, dr); +} + +int nghttp3_conn_submit_trailers(nghttp3_conn *conn, int64_t stream_id, + const nghttp3_nv *nva, size_t nvlen) { + nghttp3_stream *stream; + + /* TODO Verify that it is allowed to send trailer now. */ + assert(conn->tx.qenc); + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream == NULL) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + if (stream->flags & NGHTTP3_STREAM_FLAG_WRITE_END_STREAM) { + return NGHTTP3_ERR_INVALID_STATE; + } + + stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM; + + return conn_submit_headers_data(conn, stream, nva, nvlen, NULL); +} + +int nghttp3_conn_submit_shutdown_notice(nghttp3_conn *conn) { + nghttp3_frame_entry frent; + int rv; + + assert(conn->tx.ctrl); + + frent.fr.hd.type = NGHTTP3_FRAME_GOAWAY; + frent.fr.goaway.id = conn->server ? NGHTTP3_SHUTDOWN_NOTICE_STREAM_ID + : NGHTTP3_SHUTDOWN_NOTICE_PUSH_ID; + + assert(frent.fr.goaway.id <= conn->tx.goaway_id); + + rv = nghttp3_stream_frq_add(conn->tx.ctrl, &frent); + if (rv != 0) { + return rv; + } + + conn->tx.goaway_id = frent.fr.goaway.id; + conn->flags |= NGHTTP3_CONN_FLAG_GOAWAY_QUEUED; + + return 0; +} + +int nghttp3_conn_shutdown(nghttp3_conn *conn) { + nghttp3_frame_entry frent; + int rv; + + assert(conn->tx.ctrl); + + frent.fr.hd.type = NGHTTP3_FRAME_GOAWAY; + if (conn->server) { + frent.fr.goaway.id = + nghttp3_min((1ll << 62) - 4, conn->rx.max_stream_id_bidi + 4); + } else { + frent.fr.goaway.id = 0; + } + + assert(frent.fr.goaway.id <= conn->tx.goaway_id); + + rv = nghttp3_stream_frq_add(conn->tx.ctrl, &frent); + if (rv != 0) { + return rv; + } + + conn->tx.goaway_id = frent.fr.goaway.id; + conn->flags |= NGHTTP3_CONN_FLAG_GOAWAY_QUEUED; + + return 0; +} + +int nghttp3_conn_reject_stream(nghttp3_conn *conn, nghttp3_stream *stream) { + int rv; + + rv = conn_call_stop_sending(conn, stream, NGHTTP3_H3_REQUEST_REJECTED); + if (rv != 0) { + return rv; + } + + return conn_call_reset_stream(conn, stream, NGHTTP3_H3_REQUEST_REJECTED); +} + +void nghttp3_conn_block_stream(nghttp3_conn *conn, int64_t stream_id) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return; + } + + stream->flags |= NGHTTP3_STREAM_FLAG_FC_BLOCKED; + stream->unscheduled_nwrite = 0; + + if (nghttp3_client_stream_bidi(stream->node.id)) { + nghttp3_conn_unschedule_stream(conn, stream); + } +} + +void nghttp3_conn_shutdown_stream_write(nghttp3_conn *conn, int64_t stream_id) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return; + } + + stream->flags |= NGHTTP3_STREAM_FLAG_SHUT_WR; + stream->unscheduled_nwrite = 0; + + if (nghttp3_client_stream_bidi(stream->node.id)) { + nghttp3_conn_unschedule_stream(conn, stream); + } +} + +int nghttp3_conn_unblock_stream(nghttp3_conn *conn, int64_t stream_id) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return 0; + } + + stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_FC_BLOCKED; + + if (nghttp3_client_stream_bidi(stream->node.id) && + nghttp3_stream_require_schedule(stream)) { + return nghttp3_conn_ensure_stream_scheduled(conn, stream); + } + + return 0; +} + +int nghttp3_conn_is_stream_writable(nghttp3_conn *conn, int64_t stream_id) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return 0; + } + + return (stream->flags & + (NGHTTP3_STREAM_FLAG_FC_BLOCKED | + NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED | NGHTTP3_STREAM_FLAG_SHUT_WR | + NGHTTP3_STREAM_FLAG_CLOSED)) == 0; +} + +int nghttp3_conn_resume_stream(nghttp3_conn *conn, int64_t stream_id) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return 0; + } + + stream->flags &= (uint16_t)~NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED; + + if (nghttp3_client_stream_bidi(stream->node.id) && + nghttp3_stream_require_schedule(stream)) { + return nghttp3_conn_ensure_stream_scheduled(conn, stream); + } + + return 0; +} + +int nghttp3_conn_close_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + if (nghttp3_stream_uni(stream_id) && + stream->type != NGHTTP3_STREAM_TYPE_UNKNOWN) { + return NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM; + } + + stream->error_code = app_error_code; + + nghttp3_conn_unschedule_stream(conn, stream); + + if (stream->qpack_blocked_pe.index == NGHTTP3_PQ_BAD_INDEX) { + return conn_delete_stream(conn, stream); + } + + stream->flags |= NGHTTP3_STREAM_FLAG_CLOSED; + return 0; +} + +int nghttp3_conn_shutdown_stream_read(nghttp3_conn *conn, int64_t stream_id) { + nghttp3_stream *stream; + + if (!nghttp3_client_stream_bidi(stream_id)) { + return 0; + } + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream) { + if (stream->flags & NGHTTP3_STREAM_FLAG_SHUT_RD) { + return 0; + } + + stream->flags |= NGHTTP3_STREAM_FLAG_SHUT_RD; + } + + return nghttp3_qpack_decoder_cancel_stream(&conn->qdec, stream_id); +} + +int nghttp3_conn_qpack_blocked_streams_push(nghttp3_conn *conn, + nghttp3_stream *stream) { + assert(stream->qpack_blocked_pe.index == NGHTTP3_PQ_BAD_INDEX); + + return nghttp3_pq_push(&conn->qpack_blocked_streams, + &stream->qpack_blocked_pe); +} + +void nghttp3_conn_qpack_blocked_streams_pop(nghttp3_conn *conn) { + assert(!nghttp3_pq_empty(&conn->qpack_blocked_streams)); + nghttp3_pq_pop(&conn->qpack_blocked_streams); +} + +void nghttp3_conn_set_max_client_streams_bidi(nghttp3_conn *conn, + uint64_t max_streams) { + assert(conn->server); + assert(conn->remote.bidi.max_client_streams <= max_streams); + + conn->remote.bidi.max_client_streams = max_streams; +} + +void nghttp3_conn_set_max_concurrent_streams(nghttp3_conn *conn, + size_t max_concurrent_streams) { + nghttp3_qpack_decoder_set_max_concurrent_streams(&conn->qdec, + max_concurrent_streams); +} + +int nghttp3_conn_set_stream_user_data(nghttp3_conn *conn, int64_t stream_id, + void *stream_user_data) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + stream->user_data = stream_user_data; + + return 0; +} + +uint64_t nghttp3_conn_get_frame_payload_left(nghttp3_conn *conn, + int64_t stream_id) { + nghttp3_stream *stream = nghttp3_conn_find_stream(conn, stream_id); + + if (stream == NULL) { + return 0; + } + + return (uint64_t)stream->rstate.left; +} + +int nghttp3_conn_get_stream_priority(nghttp3_conn *conn, nghttp3_pri *dest, + int64_t stream_id) { + nghttp3_stream *stream; + + assert(conn->server); + + if (!nghttp3_client_stream_bidi(stream_id)) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream == NULL) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + dest->urgency = nghttp3_pri_uint8_urgency(stream->node.pri); + dest->inc = nghttp3_pri_uint8_inc(stream->node.pri); + + return 0; +} + +int nghttp3_conn_set_stream_priority(nghttp3_conn *conn, int64_t stream_id, + const nghttp3_pri *pri) { + nghttp3_stream *stream; + nghttp3_frame_entry frent; + + assert(pri->urgency < NGHTTP3_URGENCY_LEVELS); + assert(pri->inc == 0 || pri->inc == 1); + + if (!nghttp3_client_stream_bidi(stream_id)) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + stream = nghttp3_conn_find_stream(conn, stream_id); + if (stream == NULL) { + return NGHTTP3_ERR_STREAM_NOT_FOUND; + } + + if (conn->server) { + stream->flags |= NGHTTP3_STREAM_FLAG_SERVER_PRIORITY_SET; + + return conn_update_stream_priority(conn, stream, nghttp3_pri_to_uint8(pri)); + } + + frent.fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE; + frent.fr.priority_update.pri_elem_id = stream_id; + frent.fr.priority_update.pri = *pri; + + return nghttp3_stream_frq_add(conn->tx.ctrl, &frent); +} + +int nghttp3_conn_is_remote_qpack_encoder_stream(nghttp3_conn *conn, + int64_t stream_id) { + nghttp3_stream *stream; + + if (!conn_remote_stream_uni(conn, stream_id)) { + return 0; + } + + stream = nghttp3_conn_find_stream(conn, stream_id); + return stream && stream->type == NGHTTP3_STREAM_TYPE_QPACK_ENCODER; +} + +void nghttp3_settings_default_versioned(int settings_version, + nghttp3_settings *settings) { + (void)settings_version; + + memset(settings, 0, sizeof(nghttp3_settings)); + settings->max_field_section_size = NGHTTP3_VARINT_MAX; + settings->qpack_encoder_max_dtable_capacity = + NGHTTP3_QPACK_ENCODER_MAX_DTABLE_CAPACITY; +} diff --git a/lib/nghttp3_conn.h b/lib/nghttp3_conn.h new file mode 100644 index 0000000..a3f904c --- /dev/null +++ b/lib/nghttp3_conn.h @@ -0,0 +1,200 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 NGHTTP3_CONN_H +#define NGHTTP3_CONN_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_stream.h" +#include "nghttp3_map.h" +#include "nghttp3_qpack.h" +#include "nghttp3_tnode.h" +#include "nghttp3_idtr.h" +#include "nghttp3_gaptr.h" + +#define NGHTTP3_VARINT_MAX ((1ull << 62) - 1) + +/* NGHTTP3_QPACK_ENCODER_MAX_TABLE_CAPACITY is the maximum dynamic + table size for QPACK encoder. */ +#define NGHTTP3_QPACK_ENCODER_MAX_TABLE_CAPACITY 16384 + +/* NGHTTP3_QPACK_ENCODER_MAX_BLOCK_STREAMS is the maximum number of + blocked streams for QPACK encoder. */ +#define NGHTTP3_QPACK_ENCODER_MAX_BLOCK_STREAMS 100 + +/* NGHTTP3_CONN_FLAG_NONE indicates that no flag is set. */ +#define NGHTTP3_CONN_FLAG_NONE 0x0000u +/* NGHTTP3_CONN_FLAG_SETTINGS_RECVED is set when SETTINGS frame has + been received. */ +#define NGHTTP3_CONN_FLAG_SETTINGS_RECVED 0x0001u +/* NGHTTP3_CONN_FLAG_CONTROL_OPENED is set when a control stream has + opened. */ +#define NGHTTP3_CONN_FLAG_CONTROL_OPENED 0x0002u +/* NGHTTP3_CONN_FLAG_QPACK_ENCODER_OPENED is set when a QPACK encoder + stream has opened. */ +#define NGHTTP3_CONN_FLAG_QPACK_ENCODER_OPENED 0x0004u +/* NGHTTP3_CONN_FLAG_QPACK_DECODER_OPENED is set when a QPACK decoder + stream has opened. */ +#define NGHTTP3_CONN_FLAG_QPACK_DECODER_OPENED 0x0008u +/* NGHTTP3_CONN_FLAG_GOAWAY_RECVED indicates that GOAWAY frame has + received. */ +#define NGHTTP3_CONN_FLAG_GOAWAY_RECVED 0x0020u +/* NGHTTP3_CONN_FLAG_GOAWAY_QUEUED indicates that GOAWAY frame has + been submitted for transmission. */ +#define NGHTTP3_CONN_FLAG_GOAWAY_QUEUED 0x0040u + +typedef struct nghttp3_chunk { + nghttp3_opl_entry oplent; +} nghttp3_chunk; + +nghttp3_objalloc_def(chunk, nghttp3_chunk, oplent); + +struct nghttp3_conn { + nghttp3_objalloc out_chunk_objalloc; + nghttp3_objalloc stream_objalloc; + nghttp3_callbacks callbacks; + nghttp3_map streams; + nghttp3_qpack_decoder qdec; + nghttp3_qpack_encoder qenc; + nghttp3_pq qpack_blocked_streams; + struct { + nghttp3_pq spq; + } sched[NGHTTP3_URGENCY_LEVELS]; + const nghttp3_mem *mem; + void *user_data; + int server; + uint16_t flags; + + struct { + nghttp3_settings settings; + struct { + /* max_pushes is the number of push IDs that local endpoint can + issue. This field is used by server only and used just for + validation */ + uint64_t max_pushes; + } uni; + } local; + + struct { + struct { + nghttp3_idtr idtr; + /* max_client_streams is the cumulative number of client + initiated bidirectional stream ID the remote endpoint can + issue. This field is used on server side only. */ + uint64_t max_client_streams; + } bidi; + nghttp3_settings settings; + } remote; + + struct { + /* goaway_id is the latest ID received in GOAWAY frame. */ + int64_t goaway_id; + + int64_t max_stream_id_bidi; + + /* pri_fieldbuf is a buffer to store incoming Priority Field Value + in PRIORITY_UPDATE frame. */ + uint8_t pri_fieldbuf[8]; + /* pri_fieldlen is the number of bytes written into + pri_fieldbuf. */ + size_t pri_fieldbuflen; + } rx; + + struct { + struct { + nghttp3_buf rbuf; + nghttp3_buf ebuf; + } qpack; + nghttp3_stream *ctrl; + nghttp3_stream *qenc; + nghttp3_stream *qdec; + /* goaway_id is the latest ID sent in GOAWAY frame. */ + int64_t goaway_id; + } tx; +}; + +nghttp3_stream *nghttp3_conn_find_stream(nghttp3_conn *conn, int64_t stream_id); + +int nghttp3_conn_create_stream(nghttp3_conn *conn, nghttp3_stream **pstream, + int64_t stream_id); + +nghttp3_ssize nghttp3_conn_read_bidi(nghttp3_conn *conn, size_t *pnproc, + nghttp3_stream *stream, const uint8_t *src, + size_t srclen, int fin); + +nghttp3_ssize nghttp3_conn_read_uni(nghttp3_conn *conn, nghttp3_stream *stream, + const uint8_t *src, size_t srclen, int fin); + +nghttp3_ssize nghttp3_conn_read_control(nghttp3_conn *conn, + nghttp3_stream *stream, + const uint8_t *src, size_t srclen); + +nghttp3_ssize nghttp3_conn_read_qpack_encoder(nghttp3_conn *conn, + const uint8_t *src, + size_t srclen); + +nghttp3_ssize nghttp3_conn_read_qpack_decoder(nghttp3_conn *conn, + const uint8_t *src, + size_t srclen); + +int nghttp3_conn_on_data(nghttp3_conn *conn, nghttp3_stream *stream, + const uint8_t *data, size_t datalen); + +int nghttp3_conn_on_priority_update(nghttp3_conn *conn, + const nghttp3_frame_priority_update *fr); + +nghttp3_ssize nghttp3_conn_on_headers(nghttp3_conn *conn, + nghttp3_stream *stream, + const uint8_t *data, size_t datalen, + int fin); + +int nghttp3_conn_on_settings_entry_received(nghttp3_conn *conn, + const nghttp3_frame_settings *fr); + +int nghttp3_conn_qpack_blocked_streams_push(nghttp3_conn *conn, + nghttp3_stream *stream); + +void nghttp3_conn_qpack_blocked_streams_pop(nghttp3_conn *conn); + +int nghttp3_conn_schedule_stream(nghttp3_conn *conn, nghttp3_stream *stream); + +int nghttp3_conn_ensure_stream_scheduled(nghttp3_conn *conn, + nghttp3_stream *stream); + +void nghttp3_conn_unschedule_stream(nghttp3_conn *conn, nghttp3_stream *stream); + +int nghttp3_conn_reject_stream(nghttp3_conn *conn, nghttp3_stream *stream); + +/* + * nghttp3_conn_get_next_tx_stream returns next stream to send. It + * returns NULL if there is no such stream. + */ +nghttp3_stream *nghttp3_conn_get_next_tx_stream(nghttp3_conn *conn); + +#endif /* NGHTTP3_CONN_H */ diff --git a/lib/nghttp3_conv.c b/lib/nghttp3_conv.c new file mode 100644 index 0000000..f7853d9 --- /dev/null +++ b/lib/nghttp3_conv.c @@ -0,0 +1,125 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_conv.h" + +#include <string.h> +#include <assert.h> + +#include "nghttp3_str.h" +#include "nghttp3_unreachable.h" + +int64_t nghttp3_get_varint(size_t *plen, const uint8_t *p) { + union { + char b[8]; + uint16_t n16; + uint32_t n32; + uint64_t n64; + } n; + + *plen = 1u << (*p >> 6); + + switch (*plen) { + case 1: + return (int64_t)*p; + case 2: + memcpy(&n, p, 2); + n.b[0] &= 0x3f; + return (int64_t)ntohs(n.n16); + case 4: + memcpy(&n, p, 4); + n.b[0] &= 0x3f; + return (int64_t)ntohl(n.n32); + case 8: + memcpy(&n, p, 8); + n.b[0] &= 0x3f; + return (int64_t)nghttp3_ntohl64(n.n64); + } + + nghttp3_unreachable(); +} + +int64_t nghttp3_get_varint_fb(const uint8_t *p) { return *p & 0x3f; } + +size_t nghttp3_get_varintlen(const uint8_t *p) { return 1u << (*p >> 6); } + +uint8_t *nghttp3_put_uint64be(uint8_t *p, uint64_t n) { + n = nghttp3_htonl64(n); + return nghttp3_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *nghttp3_put_uint32be(uint8_t *p, uint32_t n) { + n = htonl(n); + return nghttp3_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *nghttp3_put_uint16be(uint8_t *p, uint16_t n) { + n = htons(n); + return nghttp3_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *nghttp3_put_varint(uint8_t *p, int64_t n) { + uint8_t *rv; + if (n < 64) { + *p++ = (uint8_t)n; + return p; + } + if (n < 16384) { + rv = nghttp3_put_uint16be(p, (uint16_t)n); + *p |= 0x40; + return rv; + } + if (n < 1073741824) { + rv = nghttp3_put_uint32be(p, (uint32_t)n); + *p |= 0x80; + return rv; + } + assert(n < 4611686018427387904LL); + rv = nghttp3_put_uint64be(p, (uint64_t)n); + *p |= 0xc0; + return rv; +} + +size_t nghttp3_put_varintlen(int64_t n) { + if (n < 64) { + return 1; + } + if (n < 16384) { + return 2; + } + if (n < 1073741824) { + return 4; + } + assert(n < 4611686018427387904LL); + return 8; +} + +uint64_t nghttp3_ord_stream_id(int64_t stream_id) { + return (uint64_t)(stream_id >> 2) + 1; +} + +uint8_t nghttp3_pri_to_uint8(const nghttp3_pri *pri) { + return (uint8_t)((uint32_t)pri->inc << 7 | pri->urgency); +} diff --git a/lib/nghttp3_conv.h b/lib/nghttp3_conv.h new file mode 100644 index 0000000..516013a --- /dev/null +++ b/lib/nghttp3_conv.h @@ -0,0 +1,207 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_CONV_H +#define NGHTTP3_CONV_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif /* HAVE_ARPA_INET_H */ + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif /* HAVE_NETINET_IN_H */ + +#ifdef HAVE_BYTESWAP_H +# include <byteswap.h> +#endif /* HAVE_BYTESWAP_H */ + +#ifdef HAVE_ENDIAN_H +# include <endian.h> +#endif /* HAVE_ENDIAN_H */ + +#ifdef HAVE_SYS_ENDIAN_H +# include <sys/endian.h> +#endif /* HAVE_SYS_ENDIAN_H */ + +#include <nghttp3/nghttp3.h> + +#if defined(HAVE_BSWAP_64) || \ + (defined(HAVE_DECL_BSWAP_64) && HAVE_DECL_BSWAP_64 > 0) +# define nghttp3_bswap64 bswap_64 +#else /* !HAVE_BSWAP_64 */ +# define nghttp3_bswap64(N) \ + ((uint64_t)(ntohl((uint32_t)(N))) << 32 | ntohl((uint32_t)((N) >> 32))) +#endif /* !HAVE_BSWAP_64 */ + +#if defined(HAVE_BE64TOH) || \ + (defined(HAVE_DECL_BE64TOH) && HAVE_DECL_BE64TOH > 0) +# define nghttp3_ntohl64(N) be64toh(N) +# define nghttp3_htonl64(N) htobe64(N) +#else /* !HAVE_BE64TOH */ +# if defined(WORDS_BIGENDIAN) +# define nghttp3_ntohl64(N) (N) +# define nghttp3_htonl64(N) (N) +# else /* !WORDS_BIGENDIAN */ +# define nghttp3_ntohl64(N) nghttp3_bswap64(N) +# define nghttp3_htonl64(N) nghttp3_bswap64(N) +# endif /* !WORDS_BIGENDIAN */ +#endif /* !HAVE_BE64TOH */ + +#if defined(WIN32) +/* Windows requires ws2_32 library for ntonl family of functions. We + define inline functions for those functions 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 = *p++ << 24; + res += *p++ << 16; + res += *p++ << 8; + res += *p; + return res; +} + +STIN uint16_t ntohs(uint16_t netshort) { + uint16_t res; + unsigned char *p = (unsigned char *)&netshort; + res = *p++ << 8; + res += *p; + return res; +} + +#endif /* WIN32 */ + +/* + * nghttp3_get_varint reads variable-length integer from |p|, and + * returns it in host byte order. The number of bytes read is stored + * in |*plen|. + */ +int64_t nghttp3_get_varint(size_t *plen, const uint8_t *p); + +/* + * nghttp3_get_varint_fb reads first byte of encoded variable-length + * integer from |p|. + */ +int64_t nghttp3_get_varint_fb(const uint8_t *p); + +/* + * nghttp3_get_varintlen returns the required number of bytes to read + * variable-length integer starting at |p|. + */ +size_t nghttp3_get_varintlen(const uint8_t *p); + +/* + * nghttp3_put_uint64be writes |n| in host byte order in |p| in + * network byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *nghttp3_put_uint64be(uint8_t *p, uint64_t n); + +/* + * nghttp3_put_uint32be writes |n| in host byte order in |p| in + * network byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *nghttp3_put_uint32be(uint8_t *p, uint32_t n); + +/* + * nghttp3_put_uint16be writes |n| in host byte order in |p| in + * network byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *nghttp3_put_uint16be(uint8_t *p, uint16_t n); + +/* + * nghttp3_put_varint writes |n| in |p| using variable-length integer + * encoding. It returns the one beyond of the last written position. + */ +uint8_t *nghttp3_put_varint(uint8_t *p, int64_t n); + +/* + * nghttp3_put_varintlen returns the required number of bytes to + * encode |n|. + */ +size_t nghttp3_put_varintlen(int64_t n); + +/* + * nghttp3_ord_stream_id returns the ordinal number of |stream_id|. + */ +uint64_t nghttp3_ord_stream_id(int64_t stream_id); + +/* + * NGHTTP3_PRI_INC_MASK is a bit mask to retrieve incremental bit from + * a value produced by nghttp3_pri_to_uint8. + */ +#define NGHTTP3_PRI_INC_MASK (1 << 7) + +/* + * nghttp3_pri_to_uint8 encodes |pri| into uint8_t variable. + */ +uint8_t nghttp3_pri_to_uint8(const nghttp3_pri *pri); + +/* + * nghttp3_pri_uint8_urgency extracts urgency from |PRI| which is + * supposed to be constructed by nghttp3_pri_to_uint8. + */ +#define nghttp3_pri_uint8_urgency(PRI) \ + ((uint32_t)((PRI) & ~NGHTTP3_PRI_INC_MASK)) + +/* + * nghttp3_pri_uint8_inc extracts inc from |PRI| which is supposed to + * be constructed by nghttp3_pri_to_uint8. + */ +#define nghttp3_pri_uint8_inc(PRI) (((PRI)&NGHTTP3_PRI_INC_MASK) != 0) + +#endif /* NGHTTP3_CONV_H */ diff --git a/lib/nghttp3_debug.c b/lib/nghttp3_debug.c new file mode 100644 index 0000000..4021b0d --- /dev/null +++ b/lib/nghttp3_debug.c @@ -0,0 +1,61 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2016 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_debug.h" + +#include <stdio.h> + +#ifdef DEBUGBUILD + +static void nghttp3_default_debug_vfprintf_callback(const char *fmt, + va_list args) { + vfprintf(stderr, fmt, args); +} + +static nghttp3_debug_vprintf_callback static_debug_vprintf_callback = + nghttp3_default_debug_vfprintf_callback; + +void nghttp3_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 nghttp3_set_debug_vprintf_callback( + nghttp3_debug_vprintf_callback debug_vprintf_callback) { + static_debug_vprintf_callback = debug_vprintf_callback; +} + +#else /* !DEBUGBUILD */ + +void nghttp3_set_debug_vprintf_callback( + nghttp3_debug_vprintf_callback debug_vprintf_callback) { + (void)debug_vprintf_callback; +} + +#endif /* !DEBUGBUILD */ diff --git a/lib/nghttp3_debug.h b/lib/nghttp3_debug.h new file mode 100644 index 0000000..01ed918 --- /dev/null +++ b/lib/nghttp3_debug.h @@ -0,0 +1,44 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2016 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_DEBUG_H +#define NGHTTP3_DEBUG_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#ifdef DEBUGBUILD +# define DEBUGF(...) nghttp3_debug_vprintf(__VA_ARGS__) +void nghttp3_debug_vprintf(const char *format, ...); +#else +# define DEBUGF(...) \ + do { \ + } while (0) +#endif + +#endif /* NGHTTP3_DEBUG_H */ diff --git a/lib/nghttp3_err.c b/lib/nghttp3_err.c new file mode 100644 index 0000000..5cf94db --- /dev/null +++ b/lib/nghttp3_err.c @@ -0,0 +1,126 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "nghttp3_err.h" + +const char *nghttp3_strerror(int liberr) { + switch (liberr) { + case NGHTTP3_ERR_INVALID_ARGUMENT: + return "ERR_INVALID_ARGUMENT"; + case NGHTTP3_ERR_NOBUF: + return "ERR_NOBUF"; + case NGHTTP3_ERR_INVALID_STATE: + return "ERR_INVALID_STATE"; + case NGHTTP3_ERR_WOULDBLOCK: + return "ERR_WOULDBLOCK"; + case NGHTTP3_ERR_STREAM_IN_USE: + return "ERR_STREAM_IN_USE"; + case NGHTTP3_ERR_MALFORMED_HTTP_HEADER: + return "ERR_MALFORMED_HTTP_HEADER"; + case NGHTTP3_ERR_REMOVE_HTTP_HEADER: + return "ERR_REMOVE_HTTP_HEADER"; + case NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING: + return "ERR_MALFORMED_HTTP_MESSAGING"; + case NGHTTP3_ERR_QPACK_FATAL: + return "ERR_QPACK_FATAL"; + case NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE: + return "ERR_QPACK_HEADER_TOO_LARGE"; + case NGHTTP3_ERR_STREAM_NOT_FOUND: + return "ERR_STREAM_NOT_FOUND"; + case NGHTTP3_ERR_CONN_CLOSING: + return "ERR_CONN_CLOSING"; + case NGHTTP3_ERR_STREAM_DATA_OVERFLOW: + return "ERR_STREAM_DATA_OVERFLOW"; + case NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED: + return "ERR_QPACK_DECOMPRESSION_FAILED"; + case NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR: + return "ERR_QPACK_ENCODER_STREAM_ERROR"; + case NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR: + return "ERR_QPACK_DECODER_STREAM_ERROR"; + case NGHTTP3_ERR_H3_FRAME_UNEXPECTED: + return "ERR_H3_FRAME_UNEXPECTED"; + case NGHTTP3_ERR_H3_FRAME_ERROR: + return "ERR_H3_FRAME_ERROR"; + case NGHTTP3_ERR_H3_MISSING_SETTINGS: + return "ERR_H3_MISSING_SETTINGS"; + case NGHTTP3_ERR_H3_INTERNAL_ERROR: + return "ERR_H3_INTERNAL_ERROR"; + case NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM: + return "ERR_CLOSED_CRITICAL_STREAM"; + case NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR: + return "ERR_H3_GENERAL_PROTOCOL_ERROR"; + case NGHTTP3_ERR_H3_ID_ERROR: + return "ERR_H3_ID_ERROR"; + case NGHTTP3_ERR_H3_SETTINGS_ERROR: + return "ERR_H3_SETTINGS_ERROR"; + case NGHTTP3_ERR_H3_STREAM_CREATION_ERROR: + return "ERR_H3_STREAM_CREATION_ERROR"; + case NGHTTP3_ERR_NOMEM: + return "ERR_NOMEM"; + case NGHTTP3_ERR_CALLBACK_FAILURE: + return "ERR_CALLBACK_FAILURE"; + default: + return "(unknown)"; + } +} + +uint64_t nghttp3_err_infer_quic_app_error_code(int liberr) { + switch (liberr) { + case 0: + return NGHTTP3_H3_NO_ERROR; + case NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED: + return NGHTTP3_QPACK_DECOMPRESSION_FAILED; + case NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR: + return NGHTTP3_QPACK_ENCODER_STREAM_ERROR; + case NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR: + return NGHTTP3_QPACK_DECODER_STREAM_ERROR; + case NGHTTP3_ERR_H3_FRAME_UNEXPECTED: + return NGHTTP3_H3_FRAME_UNEXPECTED; + case NGHTTP3_ERR_H3_FRAME_ERROR: + return NGHTTP3_H3_FRAME_ERROR; + case NGHTTP3_ERR_H3_MISSING_SETTINGS: + return NGHTTP3_H3_MISSING_SETTINGS; + case NGHTTP3_ERR_H3_INTERNAL_ERROR: + case NGHTTP3_ERR_NOMEM: + case NGHTTP3_ERR_CALLBACK_FAILURE: + return NGHTTP3_H3_INTERNAL_ERROR; + case NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM: + return NGHTTP3_H3_CLOSED_CRITICAL_STREAM; + case NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR: + return NGHTTP3_H3_GENERAL_PROTOCOL_ERROR; + case NGHTTP3_ERR_H3_ID_ERROR: + return NGHTTP3_H3_ID_ERROR; + case NGHTTP3_ERR_H3_SETTINGS_ERROR: + return NGHTTP3_H3_SETTINGS_ERROR; + case NGHTTP3_ERR_H3_STREAM_CREATION_ERROR: + return NGHTTP3_H3_STREAM_CREATION_ERROR; + case NGHTTP3_ERR_MALFORMED_HTTP_HEADER: + case NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING: + return NGHTTP3_H3_MESSAGE_ERROR; + default: + return NGHTTP3_H3_GENERAL_PROTOCOL_ERROR; + } +} + +int nghttp3_err_is_fatal(int liberr) { return liberr < NGHTTP3_ERR_FATAL; } diff --git a/lib/nghttp3_err.h b/lib/nghttp3_err.h new file mode 100644 index 0000000..2fa914f --- /dev/null +++ b/lib/nghttp3_err.h @@ -0,0 +1,34 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 NGHTTP3_ERR_H +#define NGHTTP3_ERR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#endif /* NGHTTP3_ERR_H */ diff --git a/lib/nghttp3_frame.c b/lib/nghttp3_frame.c new file mode 100644 index 0000000..e7d64fb --- /dev/null +++ b/lib/nghttp3_frame.c @@ -0,0 +1,204 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2013 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_frame.h" + +#include <string.h> +#include <assert.h> + +#include "nghttp3_conv.h" +#include "nghttp3_str.h" + +uint8_t *nghttp3_frame_write_hd(uint8_t *p, const nghttp3_frame_hd *hd) { + p = nghttp3_put_varint(p, hd->type); + p = nghttp3_put_varint(p, hd->length); + return p; +} + +size_t nghttp3_frame_write_hd_len(const nghttp3_frame_hd *hd) { + return nghttp3_put_varintlen(hd->type) + nghttp3_put_varintlen(hd->length); +} + +uint8_t *nghttp3_frame_write_settings(uint8_t *p, + const nghttp3_frame_settings *fr) { + size_t i; + + p = nghttp3_frame_write_hd(p, &fr->hd); + + for (i = 0; i < fr->niv; ++i) { + p = nghttp3_put_varint(p, (int64_t)fr->iv[i].id); + p = nghttp3_put_varint(p, (int64_t)fr->iv[i].value); + } + + return p; +} + +size_t nghttp3_frame_write_settings_len(int64_t *ppayloadlen, + const nghttp3_frame_settings *fr) { + size_t payloadlen = 0; + size_t i; + + for (i = 0; i < fr->niv; ++i) { + payloadlen += nghttp3_put_varintlen((int64_t)fr->iv[i].id) + + nghttp3_put_varintlen((int64_t)fr->iv[i].value); + } + + *ppayloadlen = (int64_t)payloadlen; + + return nghttp3_put_varintlen(NGHTTP3_FRAME_SETTINGS) + + nghttp3_put_varintlen((int64_t)payloadlen) + payloadlen; +} + +uint8_t *nghttp3_frame_write_goaway(uint8_t *p, + const nghttp3_frame_goaway *fr) { + p = nghttp3_frame_write_hd(p, &fr->hd); + p = nghttp3_put_varint(p, fr->id); + + return p; +} + +size_t nghttp3_frame_write_goaway_len(int64_t *ppayloadlen, + const nghttp3_frame_goaway *fr) { + size_t payloadlen = nghttp3_put_varintlen(fr->id); + + *ppayloadlen = (int64_t)payloadlen; + + return nghttp3_put_varintlen(NGHTTP3_FRAME_GOAWAY) + + nghttp3_put_varintlen((int64_t)payloadlen) + payloadlen; +} + +uint8_t * +nghttp3_frame_write_priority_update(uint8_t *p, + const nghttp3_frame_priority_update *fr) { + p = nghttp3_frame_write_hd(p, &fr->hd); + p = nghttp3_put_varint(p, fr->pri_elem_id); + + assert(fr->pri.urgency <= NGHTTP3_URGENCY_LOW); + + *p++ = 'u'; + *p++ = '='; + *p++ = (uint8_t)('0' + fr->pri.urgency); + + if (fr->pri.inc) { +#define NGHTTP3_PRIORITY_INCREMENTAL ", i" + p = nghttp3_cpymem(p, (const uint8_t *)NGHTTP3_PRIORITY_INCREMENTAL, + sizeof(NGHTTP3_PRIORITY_INCREMENTAL) - 1); + } + + return p; +} + +size_t nghttp3_frame_write_priority_update_len( + int64_t *ppayloadlen, const nghttp3_frame_priority_update *fr) { + size_t payloadlen = nghttp3_put_varintlen(fr->pri_elem_id) + sizeof("u=U") - + 1 + (fr->pri.inc ? sizeof(", i") - 1 : 0); + + *ppayloadlen = (int64_t)payloadlen; + + return nghttp3_put_varintlen(fr->hd.type) + + nghttp3_put_varintlen((int64_t)payloadlen) + payloadlen; +} + +int nghttp3_nva_copy(nghttp3_nv **pnva, const nghttp3_nv *nva, size_t nvlen, + const nghttp3_mem *mem) { + size_t i; + uint8_t *data = NULL; + size_t buflen = 0; + nghttp3_nv *p; + + if (nvlen == 0) { + *pnva = NULL; + + return 0; + } + + for (i = 0; i < nvlen; ++i) { + /* + 1 for null-termination */ + if ((nva[i].flags & NGHTTP3_NV_FLAG_NO_COPY_NAME) == 0) { + buflen += nva[i].namelen + 1; + } + if ((nva[i].flags & NGHTTP3_NV_FLAG_NO_COPY_VALUE) == 0) { + buflen += nva[i].valuelen + 1; + } + } + + buflen += sizeof(nghttp3_nv) * nvlen; + + *pnva = nghttp3_mem_malloc(mem, buflen); + + if (*pnva == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + p = *pnva; + data = (uint8_t *)(*pnva) + sizeof(nghttp3_nv) * nvlen; + + for (i = 0; i < nvlen; ++i) { + p->flags = nva[i].flags; + + if (nva[i].flags & NGHTTP3_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'; + nghttp3_downcase(p->name, p->namelen); + data += nva[i].namelen + 1; + } + + if (nva[i].flags & NGHTTP3_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; +} + +void nghttp3_nva_del(nghttp3_nv *nva, const nghttp3_mem *mem) { + nghttp3_mem_free(mem, nva); +} + +void nghttp3_frame_headers_free(nghttp3_frame_headers *fr, + const nghttp3_mem *mem) { + if (fr == NULL) { + return; + } + + nghttp3_nva_del(fr->nva, mem); +} diff --git a/lib/nghttp3_frame.h b/lib/nghttp3_frame.h new file mode 100644 index 0000000..4ee161d --- /dev/null +++ b/lib/nghttp3_frame.h @@ -0,0 +1,214 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2013 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_FRAME_H +#define NGHTTP3_FRAME_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_buf.h" + +typedef enum nghttp3_frame_type { + NGHTTP3_FRAME_DATA = 0x00, + NGHTTP3_FRAME_HEADERS = 0x01, + NGHTTP3_FRAME_CANCEL_PUSH = 0x03, + NGHTTP3_FRAME_SETTINGS = 0x04, + NGHTTP3_FRAME_PUSH_PROMISE = 0x05, + NGHTTP3_FRAME_GOAWAY = 0x07, + NGHTTP3_FRAME_MAX_PUSH_ID = 0x0d, + /* PRIORITY_UPDATE: https://datatracker.ietf.org/doc/html/rfc9218 */ + NGHTTP3_FRAME_PRIORITY_UPDATE = 0x0f0700, + NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID = 0x0f0701, +} nghttp3_frame_type; + +typedef enum nghttp3_h2_reserved_type { + NGHTTP3_H2_FRAME_PRIORITY = 0x02, + NGHTTP3_H2_FRAME_PING = 0x06, + NGHTTP3_H2_FRAME_WINDOW_UPDATE = 0x08, + NGHTTP3_H2_FRAME_CONTINUATION = 0x9, +} nghttp3_h2_reserved_type; + +typedef struct nghttp3_frame_hd { + int64_t type; + int64_t length; +} nghttp3_frame_hd; + +typedef struct nghttp3_frame_data { + nghttp3_frame_hd hd; +} nghttp3_frame_data; + +typedef struct nghttp3_frame_headers { + nghttp3_frame_hd hd; + nghttp3_nv *nva; + size_t nvlen; +} nghttp3_frame_headers; + +#define NGHTTP3_SETTINGS_ID_MAX_FIELD_SECTION_SIZE 0x06 +#define NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY 0x01 +#define NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS 0x07 +#define NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL 0x08 + +#define NGHTTP3_H2_SETTINGS_ID_ENABLE_PUSH 0x2 +#define NGHTTP3_H2_SETTINGS_ID_MAX_CONCURRENT_STREAMS 0x3 +#define NGHTTP3_H2_SETTINGS_ID_INITIAL_WINDOW_SIZE 0x4 +#define NGHTTP3_H2_SETTINGS_ID_MAX_FRAME_SIZE 0x5 + +typedef struct nghttp3_settings_entry { + uint64_t id; + uint64_t value; +} nghttp3_settings_entry; + +typedef struct nghttp3_frame_settings { + nghttp3_frame_hd hd; + size_t niv; + nghttp3_settings_entry iv[1]; +} nghttp3_frame_settings; + +typedef struct nghttp3_frame_goaway { + nghttp3_frame_hd hd; + int64_t id; +} nghttp3_frame_goaway; + +typedef struct nghttp3_frame_priority_update { + nghttp3_frame_hd hd; + /* pri_elem_id is stream ID if hd.type == + NGHTTP3_FRAME_PRIORITY_UPDATE. It is push ID if hd.type == + NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID. It is undefined + otherwise. */ + int64_t pri_elem_id; + nghttp3_pri pri; +} nghttp3_frame_priority_update; + +typedef union nghttp3_frame { + nghttp3_frame_hd hd; + nghttp3_frame_data data; + nghttp3_frame_headers headers; + nghttp3_frame_settings settings; + nghttp3_frame_goaway goaway; + nghttp3_frame_priority_update priority_update; +} nghttp3_frame; + +/* + * nghttp3_frame_write_hd writes frame header |hd| to |dest|. This + * function assumes that |dest| has enough space to write |hd|. + * + * This function returns |dest| plus the number of bytes written. + */ +uint8_t *nghttp3_frame_write_hd(uint8_t *dest, const nghttp3_frame_hd *hd); + +/* + * nghttp3_frame_write_hd_len returns the number of bytes required to + * write |hd|. hd->length must be set. + */ +size_t nghttp3_frame_write_hd_len(const nghttp3_frame_hd *hd); + +/* + * nghttp3_frame_write_settings writes SETTINGS frame |fr| to |dest|. + * This function assumes that |dest| has enough space to write |fr|. + * + * This function returns |dest| plus the number of bytes written. + */ +uint8_t *nghttp3_frame_write_settings(uint8_t *dest, + const nghttp3_frame_settings *fr); + +/* + * nghttp3_frame_write_settings_len returns the number of bytes + * required to write |fr|. fr->hd.length is ignored. This function + * stores payload length in |*ppayloadlen|. + */ +size_t nghttp3_frame_write_settings_len(int64_t *pppayloadlen, + const nghttp3_frame_settings *fr); + +/* + * nghttp3_frame_write_goaway writes GOAWAY frame |fr| to |dest|. + * This function assumes that |dest| has enough space to write |fr|. + * + * This function returns |dest| plus the number of bytes written. + */ +uint8_t *nghttp3_frame_write_goaway(uint8_t *dest, + const nghttp3_frame_goaway *fr); + +/* + * nghttp3_frame_write_goaway_len returns the number of bytes required + * to write |fr|. fr->hd.length is ignored. This function stores + * payload length in |*ppayloadlen|. + */ +size_t nghttp3_frame_write_goaway_len(int64_t *ppayloadlen, + const nghttp3_frame_goaway *fr); + +/* + * nghttp3_frame_write_priority_update writes PRIORITY_UPDATE frame + * |fr| to |dest|. This function assumes that |dest| has enough space + * to write |fr|. + * + * This function returns |dest| plus the number of bytes written; + */ +uint8_t * +nghttp3_frame_write_priority_update(uint8_t *dest, + const nghttp3_frame_priority_update *fr); + +/* + * nghttp3_frame_write_priority_update_len returns the number of bytes + * required to write |fr|. fr->hd.length is ignored. This function + * stores payload length in |*ppayloadlen|. + */ +size_t nghttp3_frame_write_priority_update_len( + int64_t *ppayloadlen, const nghttp3_frame_priority_update *fr); + +/* + * nghttp3_nva_copy 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 |*pnva| must be freed using nghttp3_nva_del(). + * + * This function returns 0 if it succeeds or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_nva_copy(nghttp3_nv **pnva, const nghttp3_nv *nva, size_t nvlen, + const nghttp3_mem *mem); + +/* + * nghttp3_nva_del frees |nva|. + */ +void nghttp3_nva_del(nghttp3_nv *nva, const nghttp3_mem *mem); + +/* + * nghttp3_frame_headers_free frees memory allocated for |fr|. It + * assumes that fr->nva is created by nghttp3_nva_copy() or NULL. + */ +void nghttp3_frame_headers_free(nghttp3_frame_headers *fr, + const nghttp3_mem *mem); + +#endif /* NGHTTP3_FRAME_H */ diff --git a/lib/nghttp3_gaptr.c b/lib/nghttp3_gaptr.c new file mode 100644 index 0000000..88cb49a --- /dev/null +++ b/lib/nghttp3_gaptr.c @@ -0,0 +1,169 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_gaptr.h" + +#include <string.h> +#include <assert.h> + +void nghttp3_gaptr_init(nghttp3_gaptr *gaptr, const nghttp3_mem *mem) { + nghttp3_ksl_init(&gaptr->gap, nghttp3_ksl_range_compar, sizeof(nghttp3_range), + mem); + + gaptr->mem = mem; +} + +static int gaptr_gap_init(nghttp3_gaptr *gaptr) { + nghttp3_range range = {0, UINT64_MAX}; + int rv; + + rv = nghttp3_ksl_insert(&gaptr->gap, NULL, &range, NULL); + if (rv != 0) { + return rv; + } + + return 0; +} + +void nghttp3_gaptr_free(nghttp3_gaptr *gaptr) { + if (gaptr == NULL) { + return; + } + + nghttp3_ksl_free(&gaptr->gap); +} + +int nghttp3_gaptr_push(nghttp3_gaptr *gaptr, uint64_t offset, + uint64_t datalen) { + int rv; + nghttp3_range k, m, l, r, q = {offset, offset + datalen}; + nghttp3_ksl_it it; + + if (nghttp3_ksl_len(&gaptr->gap) == 0) { + rv = gaptr_gap_init(gaptr); + if (rv != 0) { + return rv; + } + } + + it = nghttp3_ksl_lower_bound_compar(&gaptr->gap, &q, + nghttp3_ksl_range_exclusive_compar); + + for (; !nghttp3_ksl_it_end(&it);) { + k = *(nghttp3_range *)nghttp3_ksl_it_key(&it); + m = nghttp3_range_intersect(&q, &k); + if (!nghttp3_range_len(&m)) { + break; + } + + if (nghttp3_range_eq(&k, &m)) { + nghttp3_ksl_remove_hint(&gaptr->gap, &it, &it, &k); + continue; + } + nghttp3_range_cut(&l, &r, &k, &m); + if (nghttp3_range_len(&l)) { + nghttp3_ksl_update_key(&gaptr->gap, &k, &l); + + if (nghttp3_range_len(&r)) { + rv = nghttp3_ksl_insert(&gaptr->gap, &it, &r, NULL); + if (rv != 0) { + return rv; + } + } + } else if (nghttp3_range_len(&r)) { + nghttp3_ksl_update_key(&gaptr->gap, &k, &r); + } + nghttp3_ksl_it_next(&it); + } + return 0; +} + +uint64_t nghttp3_gaptr_first_gap_offset(nghttp3_gaptr *gaptr) { + nghttp3_ksl_it it; + nghttp3_range r; + + if (nghttp3_ksl_len(&gaptr->gap) == 0) { + return 0; + } + + it = nghttp3_ksl_begin(&gaptr->gap); + r = *(nghttp3_range *)nghttp3_ksl_it_key(&it); + + return r.begin; +} + +nghttp3_range nghttp3_gaptr_get_first_gap_after(nghttp3_gaptr *gaptr, + uint64_t offset) { + nghttp3_range q = {offset, offset + 1}; + nghttp3_ksl_it it; + + if (nghttp3_ksl_len(&gaptr->gap) == 0) { + nghttp3_range r = {0, UINT64_MAX}; + return r; + } + + it = nghttp3_ksl_lower_bound_compar(&gaptr->gap, &q, + nghttp3_ksl_range_exclusive_compar); + + assert(!nghttp3_ksl_it_end(&it)); + + return *(nghttp3_range *)nghttp3_ksl_it_key(&it); +} + +int nghttp3_gaptr_is_pushed(nghttp3_gaptr *gaptr, uint64_t offset, + uint64_t datalen) { + nghttp3_range q = {offset, offset + datalen}; + nghttp3_ksl_it it; + nghttp3_range k; + nghttp3_range m; + + if (nghttp3_ksl_len(&gaptr->gap) == 0) { + return 0; + } + + it = nghttp3_ksl_lower_bound_compar(&gaptr->gap, &q, + nghttp3_ksl_range_exclusive_compar); + k = *(nghttp3_range *)nghttp3_ksl_it_key(&it); + m = nghttp3_range_intersect(&q, &k); + + return nghttp3_range_len(&m) == 0; +} + +void nghttp3_gaptr_drop_first_gap(nghttp3_gaptr *gaptr) { + nghttp3_ksl_it it; + nghttp3_range r; + + if (nghttp3_ksl_len(&gaptr->gap) == 0) { + return; + } + + it = nghttp3_ksl_begin(&gaptr->gap); + + assert(!nghttp3_ksl_it_end(&it)); + + r = *(nghttp3_range *)nghttp3_ksl_it_key(&it); + + nghttp3_ksl_remove_hint(&gaptr->gap, NULL, &it, &r); +} diff --git a/lib/nghttp3_gaptr.h b/lib/nghttp3_gaptr.h new file mode 100644 index 0000000..7c83c84 --- /dev/null +++ b/lib/nghttp3_gaptr.h @@ -0,0 +1,99 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_GAPTR_H +#define NGHTTP3_GAPTR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_mem.h" +#include "nghttp3_ksl.h" +#include "nghttp3_range.h" + +/* + * nghttp3_gaptr maintains the gap in the range [0, UINT64_MAX). + */ +typedef struct nghttp3_gaptr { + /* gap maintains the range of offset which is not received + yet. Initially, its range is [0, UINT64_MAX). */ + nghttp3_ksl gap; + /* mem is custom memory allocator */ + const nghttp3_mem *mem; +} nghttp3_gaptr; + +/* + * nghttp3_gaptr_init initializes |gaptr|. + */ +void nghttp3_gaptr_init(nghttp3_gaptr *gaptr, const nghttp3_mem *mem); + +/* + * nghttp3_gaptr_free frees resources allocated for |gaptr|. + */ +void nghttp3_gaptr_free(nghttp3_gaptr *gaptr); + +/* + * nghttp3_gaptr_push adds new data of length |datalen| at the stream + * offset |offset|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory + */ +int nghttp3_gaptr_push(nghttp3_gaptr *gaptr, uint64_t offset, uint64_t datalen); + +/* + * nghttp3_gaptr_first_gap_offset returns the offset to the first gap. + * If there is no gap, it returns UINT64_MAX. + */ +uint64_t nghttp3_gaptr_first_gap_offset(nghttp3_gaptr *gaptr); + +/* + * nghttp3_gaptr_get_first_gap_after returns the first gap which + * overlaps or comes after |offset|. + */ +nghttp3_range nghttp3_gaptr_get_first_gap_after(nghttp3_gaptr *gaptr, + uint64_t offset); + +/* + * nghttp3_gaptr_is_pushed returns nonzero if range [offset, offset + + * datalen) is completely pushed into this object. + */ +int nghttp3_gaptr_is_pushed(nghttp3_gaptr *gaptr, uint64_t offset, + uint64_t datalen); + +/* + * nghttp3_gaptr_drop_first_gap deletes the first gap entirely as if + * the range is pushed. This function assumes that at least one gap + * exists. + */ +void nghttp3_gaptr_drop_first_gap(nghttp3_gaptr *gaptr); + +#endif /* NGHTTP3_GAPTR_H */ diff --git a/lib/nghttp3_http.c b/lib/nghttp3_http.c new file mode 100644 index 0000000..29af976 --- /dev/null +++ b/lib/nghttp3_http.c @@ -0,0 +1,1654 @@ +/* + * nghttp3 + * + * 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 "nghttp3_http.h" + +#include <string.h> +#include <assert.h> + +#include "nghttp3_stream.h" +#include "nghttp3_macro.h" +#include "nghttp3_conv.h" +#include "nghttp3_unreachable.h" + +static uint8_t downcase(uint8_t c) { + return 'A' <= c && c <= 'Z' ? (uint8_t)(c - 'A' + 'a') : c; +} + +/* + * memieq returns 1 if the data pointed by |a| of length |n| equals to + * |b| of the same length in case-insensitive manner. The data + * pointed by |a| must not include upper cased letters (A-Z). + */ +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 (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(nghttp3_http_state *http, + const nghttp3_qpack_nv *nv, uint32_t flag) { + if ((http->flags & flag) || nv->value->len == 0) { + return 0; + } + http->flags |= flag; + return 1; +} + +static int expect_response_body(nghttp3_http_state *http) { + return (http->flags & NGHTTP3_HTTP_FLAG_METH_HEAD) == 0 && + http->status_code / 100 != 1 && http->status_code != 304 && + http->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_flags(nghttp3_http_state *http) { + return (http->flags & NGHTTP3_HTTP_FLAG_SCHEME_HTTP) == 0 || + ((http->flags & NGHTTP3_HTTP_FLAG_PATH_REGULAR) || + ((http->flags & NGHTTP3_HTTP_FLAG_METH_OPTIONS) && + (http->flags & NGHTTP3_HTTP_FLAG_PATH_ASTERISK))); +} + +/* Generated by genchartbl.py */ +static const int SF_KEY_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 */, 0 /* ! */, 0 /* " */, + 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */, + 0 /* ( */, 0 /* ) */, 1 /* * */, 0 /* + */, 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 /* ] */, 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 /* } */, 0 /* ~ */, 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 */, +}; + +static nghttp3_ssize sf_parse_key(const uint8_t *begin, const uint8_t *end) { + const uint8_t *p = begin; + + if ((*p < 'a' || 'z' < *p) && *p != '*') { + return -1; + } + + for (; p != end && SF_KEY_CHARS[*p]; ++p) + ; + + return p - begin; +} + +static nghttp3_ssize sf_parse_integer_or_decimal(nghttp3_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + int sign = 1; + int64_t value = 0; + int type = NGHTTP3_SF_VALUE_TYPE_INTEGER; + size_t len = 0; + size_t fpos = 0; + size_t i; + + if (*p == '-') { + if (++p == end) { + return -1; + } + + sign = -1; + } + + if (*p < '0' || '9' < *p) { + return -1; + } + + for (; p != end; ++p) { + switch (*p) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + value *= 10; + value += *p - '0'; + + if (++len > 15) { + return -1; + } + + break; + case '.': + if (type != NGHTTP3_SF_VALUE_TYPE_INTEGER) { + goto fin; + } + + if (len > 12) { + return -1; + } + fpos = len; + type = NGHTTP3_SF_VALUE_TYPE_DECIMAL; + + break; + default: + goto fin; + }; + } + +fin: + switch (type) { + case NGHTTP3_SF_VALUE_TYPE_INTEGER: + if (dest) { + dest->type = (uint8_t)type; + dest->i = value * sign; + } + + return p - begin; + case NGHTTP3_SF_VALUE_TYPE_DECIMAL: + if (fpos == len || len - fpos > 3) { + return -1; + } + + if (dest) { + dest->type = (uint8_t)type; + dest->d = (double)value; + for (i = len - fpos; i > 0; --i) { + dest->d /= (double)10; + } + dest->d *= sign; + } + + return p - begin; + default: + nghttp3_unreachable(); + } +} + +/* Generated by genchartbl.py */ +static const int SF_DQUOTE_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 */, 1 /* SPC */, 1 /* ! */, 0 /* " */, + 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 /* [ */, 0 /* \ */, 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 */, 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 */, +}; + +static nghttp3_ssize sf_parse_string(nghttp3_sf_value *dest, + const uint8_t *begin, const uint8_t *end) { + const uint8_t *p = begin; + + if (*p++ != '"') { + return -1; + } + + for (; p != end; ++p) { + switch (*p) { + case '\\': + if (++p == end) { + return -1; + } + + switch (*p) { + case '"': + case '\\': + break; + default: + return -1; + } + + break; + case '"': + if (dest) { + dest->type = NGHTTP3_SF_VALUE_TYPE_STRING; + dest->s.base = begin + 1; + dest->s.len = (size_t)(p - dest->s.base); + } + + ++p; + + return p - begin; + default: + if (!SF_DQUOTE_CHARS[*p]) { + return -1; + } + } + } + + return -1; +} + +/* Generated by genchartbl.py */ +static const int SF_TOKEN_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 /* . */, 1 /* / */, 1 /* 0 */, 1 /* 1 */, + 1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, + 1 /* 7 */, 1 /* 8 */, 1 /* 9 */, 1 /* : */, 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 */, +}; + +static nghttp3_ssize sf_parse_token(nghttp3_sf_value *dest, + const uint8_t *begin, const uint8_t *end) { + const uint8_t *p = begin; + + if ((*p < 'A' || 'Z' < *p) && (*p < 'a' || 'z' < *p) && *p != '*') { + return -1; + } + + for (; p != end && SF_TOKEN_CHARS[*p]; ++p) + ; + + if (dest) { + dest->type = NGHTTP3_SF_VALUE_TYPE_TOKEN; + dest->s.base = begin; + dest->s.len = (size_t)(p - begin); + } + + return p - begin; +} + +/* Generated by genchartbl.py */ +static const int SF_BYTESEQ_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 */, 0 /* ! */, 0 /* " */, + 0 /* # */, 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */, + 0 /* ( */, 0 /* ) */, 0 /* * */, 1 /* + */, 0 /* , */, + 0 /* - */, 0 /* . */, 1 /* / */, 1 /* 0 */, 1 /* 1 */, + 1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, 1 /* 6 */, + 1 /* 7 */, 1 /* 8 */, 1 /* 9 */, 0 /* : */, 0 /* ; */, + 0 /* < */, 1 /* = */, 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 /* ] */, 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 /* } */, 0 /* ~ */, 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 */, +}; + +static nghttp3_ssize sf_parse_byteseq(nghttp3_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + + if (*p++ != ':') { + return -1; + } + + for (; p != end; ++p) { + switch (*p) { + case ':': + if (dest) { + dest->type = NGHTTP3_SF_VALUE_TYPE_BYTESEQ; + dest->s.base = begin + 1; + dest->s.len = (size_t)(p - dest->s.base); + } + + ++p; + + return p - begin; + default: + if (!SF_BYTESEQ_CHARS[*p]) { + return -1; + } + } + } + + return -1; +} + +static nghttp3_ssize sf_parse_boolean(nghttp3_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + int b; + + if (*p++ != '?') { + return -1; + } + + if (p == end) { + return -1; + } + + switch (*p++) { + case '0': + b = 0; + break; + case '1': + b = 1; + break; + default: + return -1; + } + + if (dest) { + dest->type = NGHTTP3_SF_VALUE_TYPE_BOOLEAN; + dest->b = b; + } + + return p - begin; +} + +static nghttp3_ssize sf_parse_bare_item(nghttp3_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + switch (*begin) { + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return sf_parse_integer_or_decimal(dest, begin, end); + case '"': + return sf_parse_string(dest, begin, end); + case '*': + return sf_parse_token(dest, begin, end); + case ':': + return sf_parse_byteseq(dest, begin, end); + case '?': + return sf_parse_boolean(dest, begin, end); + default: + if (('A' <= *begin && *begin <= 'Z') || ('a' <= *begin && *begin <= 'z')) { + return sf_parse_token(dest, begin, end); + } + return -1; + } +} + +#define sf_discard_sp_end_err(BEGIN, END, ERR) \ + for (;; ++(BEGIN)) { \ + if ((BEGIN) == (END)) { \ + return (ERR); \ + } \ + if (*(BEGIN) != ' ') { \ + break; \ + } \ + } + +static nghttp3_ssize sf_parse_params(const uint8_t *begin, const uint8_t *end) { + const uint8_t *p = begin; + nghttp3_ssize slen; + + for (; p != end && *p == ';';) { + ++p; + + sf_discard_sp_end_err(p, end, -1); + + slen = sf_parse_key(p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + if (p == end || *p != '=') { + /* Boolean true */ + } else if (++p == end) { + return -1; + } else { + slen = sf_parse_bare_item(NULL, p, end); + if (slen < 0) { + return -1; + } + + p += slen; + } + } + + return p - begin; +} + +static nghttp3_ssize sf_parse_item(nghttp3_sf_value *dest, const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + nghttp3_ssize slen; + + slen = sf_parse_bare_item(dest, p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + slen = sf_parse_params(p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + return p - begin; +} + +nghttp3_ssize nghttp3_sf_parse_item(nghttp3_sf_value *dest, + const uint8_t *begin, const uint8_t *end) { + return sf_parse_item(dest, begin, end); +} + +static nghttp3_ssize sf_parse_inner_list(nghttp3_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + const uint8_t *p = begin; + nghttp3_ssize slen; + + if (*p++ != '(') { + return -1; + } + + for (;;) { + sf_discard_sp_end_err(p, end, -1); + + if (*p == ')') { + ++p; + + slen = sf_parse_params(p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + if (dest) { + dest->type = NGHTTP3_SF_VALUE_TYPE_INNER_LIST; + } + + return p - begin; + } + + slen = sf_parse_item(NULL, p, end); + if (slen < 0) { + return -1; + } + + p += slen; + + if (p == end || (*p != ' ' && *p != ')')) { + return -1; + } + } +} + +nghttp3_ssize nghttp3_sf_parse_inner_list(nghttp3_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + return sf_parse_inner_list(dest, begin, end); +} + +static nghttp3_ssize sf_parse_item_or_inner_list(nghttp3_sf_value *dest, + const uint8_t *begin, + const uint8_t *end) { + if (*begin == '(') { + return sf_parse_inner_list(dest, begin, end); + } + + return sf_parse_item(dest, begin, end); +} + +#define sf_discard_ows(BEGIN, END) \ + for (;; ++(BEGIN)) { \ + if ((BEGIN) == (END)) { \ + goto fin; \ + } \ + if (*(BEGIN) != ' ' && *(BEGIN) != '\t') { \ + break; \ + } \ + } + +#define sf_discard_ows_end_err(BEGIN, END, ERR) \ + for (;; ++(BEGIN)) { \ + if ((BEGIN) == (END)) { \ + return (ERR); \ + } \ + if (*(BEGIN) != ' ' && *(BEGIN) != '\t') { \ + break; \ + } \ + } + +int nghttp3_http_parse_priority(nghttp3_pri *dest, const uint8_t *value, + size_t valuelen) { + const uint8_t *p = value, *end = value + valuelen; + nghttp3_ssize slen; + nghttp3_sf_value val; + nghttp3_pri pri = *dest; + const uint8_t *key; + size_t keylen; + + for (; p != end && *p == ' '; ++p) + ; + + for (; p != end;) { + slen = sf_parse_key(p, end); + if (slen < 0) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + key = p; + keylen = (size_t)slen; + + p += slen; + + if (p == end || *p != '=') { + /* Boolean true */ + val.type = NGHTTP3_SF_VALUE_TYPE_BOOLEAN; + val.b = 1; + + slen = sf_parse_params(p, end); + if (slen < 0) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + } else if (++p == end) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } else { + slen = sf_parse_item_or_inner_list(&val, p, end); + if (slen < 0) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + } + + p += slen; + + if (keylen == 1) { + switch (key[0]) { + case 'i': + if (val.type != NGHTTP3_SF_VALUE_TYPE_BOOLEAN) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + pri.inc = val.b; + + break; + case 'u': + if (val.type != NGHTTP3_SF_VALUE_TYPE_INTEGER || + val.i < NGHTTP3_URGENCY_HIGH || NGHTTP3_URGENCY_LOW < val.i) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + pri.urgency = (uint32_t)val.i; + + break; + } + } + + sf_discard_ows(p, end); + + if (*p++ != ',') { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + sf_discard_ows_end_err(p, end, NGHTTP3_ERR_INVALID_ARGUMENT); + } + +fin: + *dest = pri; + + return 0; +} + +static int http_request_on_header(nghttp3_http_state *http, + nghttp3_qpack_nv *nv, int trailers, + int connect_protocol) { + nghttp3_pri pri; + + if (nv->name->base[0] == ':') { + if (trailers || + (http->flags & NGHTTP3_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + } + + switch (nv->token) { + case NGHTTP3_QPACK_TOKEN__AUTHORITY: + if (!check_pseudo_header(http, nv, NGHTTP3_HTTP_FLAG__AUTHORITY)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + break; + case NGHTTP3_QPACK_TOKEN__METHOD: + if (!check_pseudo_header(http, nv, NGHTTP3_HTTP_FLAG__METHOD)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + switch (nv->value->len) { + case 4: + if (lstreq("HEAD", nv->value->base, nv->value->len)) { + http->flags |= NGHTTP3_HTTP_FLAG_METH_HEAD; + } + break; + case 7: + switch (nv->value->base[6]) { + case 'T': + if (lstreq("CONNECT", nv->value->base, nv->value->len)) { + http->flags |= NGHTTP3_HTTP_FLAG_METH_CONNECT; + } + break; + case 'S': + if (lstreq("OPTIONS", nv->value->base, nv->value->len)) { + http->flags |= NGHTTP3_HTTP_FLAG_METH_OPTIONS; + } + break; + } + break; + } + break; + case NGHTTP3_QPACK_TOKEN__PATH: + if (!check_pseudo_header(http, nv, NGHTTP3_HTTP_FLAG__PATH)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + if (nv->value->base[0] == '/') { + http->flags |= NGHTTP3_HTTP_FLAG_PATH_REGULAR; + } else if (nv->value->len == 1 && nv->value->base[0] == '*') { + http->flags |= NGHTTP3_HTTP_FLAG_PATH_ASTERISK; + } + break; + case NGHTTP3_QPACK_TOKEN__SCHEME: + if (!check_pseudo_header(http, nv, NGHTTP3_HTTP_FLAG__SCHEME)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + /* scheme is case-insensitive: + https://datatracker.ietf.org/doc/html/rfc3986#section-3.1 */ + if (lstrieq("http", nv->value->base, nv->value->len) || + lstrieq("https", nv->value->base, nv->value->len)) { + http->flags |= NGHTTP3_HTTP_FLAG_SCHEME_HTTP; + } + break; + case NGHTTP3_QPACK_TOKEN__PROTOCOL: + if (!connect_protocol) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + + if (!check_pseudo_header(http, nv, NGHTTP3_HTTP_FLAG__PROTOCOL)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + break; + case NGHTTP3_QPACK_TOKEN_HOST: + if (!check_pseudo_header(http, nv, NGHTTP3_HTTP_FLAG_HOST)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + break; + case NGHTTP3_QPACK_TOKEN_CONTENT_LENGTH: { + /* https://tools.ietf.org/html/rfc7230#section-4.1.2: A sender + MUST NOT generate a trailer that contains a field necessary for + message framing (e.g., Transfer-Encoding and Content-Length), + ... */ + if (trailers) { + return NGHTTP3_ERR_REMOVE_HTTP_HEADER; + } + if (http->content_length != -1) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + http->content_length = parse_uint(nv->value->base, nv->value->len); + if (http->content_length == -1) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + break; + } + /* disallowed header fields */ + case NGHTTP3_QPACK_TOKEN_CONNECTION: + case NGHTTP3_QPACK_TOKEN_KEEP_ALIVE: + case NGHTTP3_QPACK_TOKEN_PROXY_CONNECTION: + case NGHTTP3_QPACK_TOKEN_TRANSFER_ENCODING: + case NGHTTP3_QPACK_TOKEN_UPGRADE: + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + case NGHTTP3_QPACK_TOKEN_TE: + if (!lstrieq("trailers", nv->value->base, nv->value->len)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + break; + case NGHTTP3_QPACK_TOKEN_PRIORITY: + if (!trailers && !(http->flags & NGHTTP3_HTTP_FLAG_BAD_PRIORITY)) { + pri.urgency = nghttp3_pri_uint8_urgency(http->pri); + pri.inc = nghttp3_pri_uint8_inc(http->pri); + if (nghttp3_http_parse_priority(&pri, nv->value->base, nv->value->len) == + 0) { + http->pri = nghttp3_pri_to_uint8(&pri); + http->flags |= NGHTTP3_HTTP_FLAG_PRIORITY; + } else { + http->flags &= ~NGHTTP3_HTTP_FLAG_PRIORITY; + http->flags |= NGHTTP3_HTTP_FLAG_BAD_PRIORITY; + } + } + break; + default: + if (nv->name->base[0] == ':') { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + } + + return 0; +} + +static int http_response_on_header(nghttp3_http_state *http, + nghttp3_qpack_nv *nv, int trailers) { + if (nv->name->base[0] == ':') { + if (trailers || + (http->flags & NGHTTP3_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + } + + switch (nv->token) { + case NGHTTP3_QPACK_TOKEN__STATUS: { + if (!check_pseudo_header(http, nv, NGHTTP3_HTTP_FLAG__STATUS)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + if (nv->value->len != 3) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + http->status_code = (int16_t)parse_uint(nv->value->base, nv->value->len); + if (http->status_code < 100 || http->status_code == 101) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + break; + } + case NGHTTP3_QPACK_TOKEN_CONTENT_LENGTH: { + /* https://tools.ietf.org/html/rfc7230#section-4.1.2: A sender + MUST NOT generate a trailer that contains a field necessary for + message framing (e.g., Transfer-Encoding and Content-Length), + ... */ + if (trailers) { + return NGHTTP3_ERR_REMOVE_HTTP_HEADER; + } + if (http->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 (http->content_length != -1) { + /* Found multiple content-length field */ + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + if (!lstrieq("0", nv->value->base, nv->value->len)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + http->content_length = 0; + return NGHTTP3_ERR_REMOVE_HTTP_HEADER; + } + if (http->status_code / 100 == 1) { + return NGHTTP3_ERR_REMOVE_HTTP_HEADER; + } + /* https://tools.ietf.org/html/rfc7230#section-3.3.3 */ + if (http->status_code / 100 == 2 && + (http->flags & NGHTTP3_HTTP_FLAG_METH_CONNECT)) { + return NGHTTP3_ERR_REMOVE_HTTP_HEADER; + } + if (http->content_length != -1) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + http->content_length = parse_uint(nv->value->base, nv->value->len); + if (http->content_length == -1) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + break; + } + /* disallowed header fields */ + case NGHTTP3_QPACK_TOKEN_CONNECTION: + case NGHTTP3_QPACK_TOKEN_KEEP_ALIVE: + case NGHTTP3_QPACK_TOKEN_PROXY_CONNECTION: + case NGHTTP3_QPACK_TOKEN_TRANSFER_ENCODING: + case NGHTTP3_QPACK_TOKEN_UPGRADE: + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + case NGHTTP3_QPACK_TOKEN_TE: + if (!lstrieq("trailers", nv->value->base, nv->value->len)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + break; + default: + if (nv->name->base[0] == ':') { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + } + + return 0; +} + +/* Generated by genauthroitychartbl.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 */ +}; + +static int 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; +} + +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; +} + +/* 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 */ +}; + +static int 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 */ +}; + +static int 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; +} + +int nghttp3_http_on_header(nghttp3_http_state *http, nghttp3_qpack_nv *nv, + int request, int trailers, int connect_protocol) { + int rv; + size_t i; + uint8_t c; + + if (!nghttp3_check_header_name(nv->name->base, nv->name->len)) { + if (nv->name->len > 0 && nv->name->base[0] == ':') { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + /* header field name must be lower-cased without exception */ + for (i = 0; i < nv->name->len; ++i) { + c = nv->name->base[i]; + if ('A' <= c && c <= 'Z') { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + } + /* When ignoring regular header fields, we set this flag so that + we still enforce header field ordering rule for pseudo header + fields. */ + http->flags |= NGHTTP3_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + return NGHTTP3_ERR_REMOVE_HTTP_HEADER; + } + + assert(nv->name->len > 0); + + switch (nv->token) { + case NGHTTP3_QPACK_TOKEN__METHOD: + rv = check_method(nv->value->base, nv->value->len); + break; + case NGHTTP3_QPACK_TOKEN__SCHEME: + rv = check_scheme(nv->value->base, nv->value->len); + break; + case NGHTTP3_QPACK_TOKEN__AUTHORITY: + case NGHTTP3_QPACK_TOKEN_HOST: + if (request) { + rv = check_authority(nv->value->base, nv->value->len); + } else { + /* The use of host field in response field section is + undefined. */ + rv = nghttp3_check_header_value(nv->value->base, nv->value->len); + } + break; + case NGHTTP3_QPACK_TOKEN__PATH: + rv = check_path(nv->value->base, nv->value->len); + break; + default: + rv = nghttp3_check_header_value(nv->value->base, nv->value->len); + } + + if (rv == 0) { + if (nv->name->base[0] == ':') { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + /* When ignoring regular header fields, we set this flag so that + we still enforce header field ordering rule for pseudo header + fields. */ + http->flags |= NGHTTP3_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + return NGHTTP3_ERR_REMOVE_HTTP_HEADER; + } + + if (request) { + rv = http_request_on_header(http, nv, trailers, connect_protocol); + } else { + rv = http_response_on_header(http, nv, trailers); + } + + if (nv->name->base[0] != ':') { + switch (rv) { + case 0: + case NGHTTP3_ERR_REMOVE_HTTP_HEADER: + http->flags |= NGHTTP3_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED; + break; + } + } + + return rv; +} + +int nghttp3_http_on_request_headers(nghttp3_http_state *http) { + if (!(http->flags & NGHTTP3_HTTP_FLAG__PROTOCOL) && + (http->flags & NGHTTP3_HTTP_FLAG_METH_CONNECT)) { + if ((http->flags & (NGHTTP3_HTTP_FLAG__SCHEME | NGHTTP3_HTTP_FLAG__PATH)) || + (http->flags & NGHTTP3_HTTP_FLAG__AUTHORITY) == 0) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + http->content_length = -1; + } else { + if ((http->flags & NGHTTP3_HTTP_FLAG_REQ_HEADERS) != + NGHTTP3_HTTP_FLAG_REQ_HEADERS || + (http->flags & + (NGHTTP3_HTTP_FLAG__AUTHORITY | NGHTTP3_HTTP_FLAG_HOST)) == 0) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + if ((http->flags & NGHTTP3_HTTP_FLAG__PROTOCOL) && + ((http->flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) == 0 || + (http->flags & NGHTTP3_HTTP_FLAG__AUTHORITY) == 0)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + if (!check_path_flags(http)) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + } + + return 0; +} + +int nghttp3_http_on_response_headers(nghttp3_http_state *http) { + if ((http->flags & NGHTTP3_HTTP_FLAG__STATUS) == 0) { + return NGHTTP3_ERR_MALFORMED_HTTP_HEADER; + } + + if (http->status_code / 100 == 1) { + /* non-final response */ + http->flags = (http->flags & NGHTTP3_HTTP_FLAG_METH_ALL) | + NGHTTP3_HTTP_FLAG_EXPECT_FINAL_RESPONSE; + http->content_length = -1; + http->status_code = -1; + return 0; + } + + http->flags &= ~NGHTTP3_HTTP_FLAG_EXPECT_FINAL_RESPONSE; + + if (!expect_response_body(http)) { + http->content_length = 0; + } else if (http->flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) { + http->content_length = -1; + } + + return 0; +} + +int nghttp3_http_on_remote_end_stream(nghttp3_stream *stream) { + if ((stream->rx.http.flags & NGHTTP3_HTTP_FLAG_EXPECT_FINAL_RESPONSE) || + (stream->rx.http.content_length != -1 && + stream->rx.http.content_length != stream->rx.http.recv_content_length)) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + + return 0; +} + +int nghttp3_http_on_data_chunk(nghttp3_stream *stream, size_t n) { + stream->rx.http.recv_content_length += (int64_t)n; + + if ((stream->rx.http.flags & NGHTTP3_HTTP_FLAG_EXPECT_FINAL_RESPONSE) || + (stream->rx.http.content_length != -1 && + stream->rx.http.recv_content_length > stream->rx.http.content_length)) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + + return 0; +} + +void nghttp3_http_record_request_method(nghttp3_stream *stream, + const nghttp3_nv *nva, size_t nvlen) { + size_t i; + const nghttp3_nv *nv; + + /* TODO we should do this strictly. */ + for (i = 0; i < nvlen; ++i) { + 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->rx.http.flags |= NGHTTP3_HTTP_FLAG_METH_CONNECT; + return; + } + if (lstreq("HEAD", nv->value, nv->valuelen)) { + stream->rx.http.flags |= NGHTTP3_HTTP_FLAG_METH_HEAD; + return; + } + return; + } +} + +/* 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 nghttp3_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 nghttp3_check_header_value(const uint8_t *value, size_t len) { + const uint8_t *last; + + switch (len) { + case 0: + return 1; + case 1: + return !(*value == ' ' || *value == '\t'); + default: + if (*value == ' ' || *value == '\t' || *(value + len - 1) == ' ' || + *(value + len - 1) == '\t') { + return 0; + } + } + + for (last = value + len; value != last; ++value) { + if (!VALID_HD_VALUE_CHARS[*value]) { + return 0; + } + } + return 1; +} diff --git a/lib/nghttp3_http.h b/lib/nghttp3_http.h new file mode 100644 index 0000000..1617348 --- /dev/null +++ b/lib/nghttp3_http.h @@ -0,0 +1,202 @@ +/* + * nghttp3 + * + * 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 NGHTTP3_HTTP_H +#define NGHTTP3_HTTP_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +typedef struct nghttp3_stream nghttp3_stream; + +typedef struct nghttp3_http_state nghttp3_http_state; + +/* HTTP related flags to enforce HTTP semantics */ + +/* NGHTTP3_HTTP_FLAG_NONE indicates that no flag is set. */ +#define NGHTTP3_HTTP_FLAG_NONE 0x00u +/* header field seen so far */ +#define NGHTTP3_HTTP_FLAG__AUTHORITY 0x01u +#define NGHTTP3_HTTP_FLAG__PATH 0x02u +#define NGHTTP3_HTTP_FLAG__METHOD 0x04u +#define NGHTTP3_HTTP_FLAG__SCHEME 0x08u +/* host is not pseudo header, but we require either host or + :authority */ +#define NGHTTP3_HTTP_FLAG_HOST 0x10u +#define NGHTTP3_HTTP_FLAG__STATUS 0x20u +/* required header fields for HTTP request except for CONNECT + method. */ +#define NGHTTP3_HTTP_FLAG_REQ_HEADERS \ + (NGHTTP3_HTTP_FLAG__METHOD | NGHTTP3_HTTP_FLAG__PATH | \ + NGHTTP3_HTTP_FLAG__SCHEME) +#define NGHTTP3_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED 0x40u +/* HTTP method flags */ +#define NGHTTP3_HTTP_FLAG_METH_CONNECT 0x80u +#define NGHTTP3_HTTP_FLAG_METH_HEAD 0x0100u +#define NGHTTP3_HTTP_FLAG_METH_OPTIONS 0x0200u +#define NGHTTP3_HTTP_FLAG_METH_ALL \ + (NGHTTP3_HTTP_FLAG_METH_CONNECT | NGHTTP3_HTTP_FLAG_METH_HEAD | \ + NGHTTP3_HTTP_FLAG_METH_OPTIONS) +/* :path category */ +/* path starts with "/" */ +#define NGHTTP3_HTTP_FLAG_PATH_REGULAR 0x0400u +/* path "*" */ +#define NGHTTP3_HTTP_FLAG_PATH_ASTERISK 0x0800u +/* scheme */ +/* "http" or "https" scheme */ +#define NGHTTP3_HTTP_FLAG_SCHEME_HTTP 0x1000u +/* set if final response is expected */ +#define NGHTTP3_HTTP_FLAG_EXPECT_FINAL_RESPONSE 0x2000u +/* NGHTTP3_HTTP_FLAG__PROTOCOL is set when :protocol pseudo header + field is seen. */ +#define NGHTTP3_HTTP_FLAG__PROTOCOL 0x4000u +/* NGHTTP3_HTTP_FLAG_PRIORITY is set when priority header field is + processed. */ +#define NGHTTP3_HTTP_FLAG_PRIORITY 0x8000u +/* NGHTTP3_HTTP_FLAG_BAD_PRIORITY is set when an error is encountered + while parsing priority header field. */ +#define NGHTTP3_HTTP_FLAG_BAD_PRIORITY 0x010000u + +/* + * This function is called when HTTP header field |nv| received for + * |http|. This function will validate |nv| against the current state + * of stream. Pass nonzero if this is request headers. Pass nonzero + * to |trailers| if |nv| is included in trailers. |connect_protocol| + * is nonzero if Extended CONNECT Method is enabled. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_MALFORMED_HTTP_HEADER + * Invalid HTTP header field was received. + * NGHTTP3_ERR_REMOVE_HTTP_HEADER + * Invalid HTTP header field was received but it can be treated as + * if it was not received because of compatibility reasons. + */ +int nghttp3_http_on_header(nghttp3_http_state *http, nghttp3_qpack_nv *nv, + int request, int trailers, int connect_protocol); + +/* + * This function is called when request header is received. This + * function performs validation and returns 0 if it succeeds, or one + * of the following negative error codes: + * + * NGHTTP3_ERR_MALFORMED_HTTP_HEADER + * Required HTTP header field was not received; or an invalid + * header field was received. + */ +int nghttp3_http_on_request_headers(nghttp3_http_state *http); + +/* + * This function is called when response header is received. This + * function performs validation and returns 0 if it succeeds, or one + * of the following negative error codes: + * + * NGHTTP3_ERR_MALFORMED_HTTP_HEADER + * Required HTTP header field was not received; or an invalid + * header field was received. + */ +int nghttp3_http_on_response_headers(nghttp3_http_state *http); + +/* + * This function is called when read side stream is closed. This + * function performs validation and returns 0 if it succeeds, or one + * of the following negative error codes: + * + * NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING + * HTTP messaging is violated. + */ +int nghttp3_http_on_remote_end_stream(nghttp3_stream *stream); + +/* + * This function is called when chunk of data is received. This + * function performs validation and returns 0 if it succeeds, or one + * of the following negative error codes: + * + * NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING + * HTTP messaging is violated. + */ +int nghttp3_http_on_data_chunk(nghttp3_stream *stream, size_t n); + +/* + * This function inspects header fields in |nva| of length |nvlen| and + * records its method in stream->http_flags. + */ +void nghttp3_http_record_request_method(nghttp3_stream *stream, + const nghttp3_nv *nva, size_t nvlen); + +/* + * RFC 8941 Structured Field Values. + */ +typedef enum nghttp3_sf_value_type { + NGHTTP3_SF_VALUE_TYPE_BOOLEAN, + NGHTTP3_SF_VALUE_TYPE_INTEGER, + NGHTTP3_SF_VALUE_TYPE_DECIMAL, + NGHTTP3_SF_VALUE_TYPE_STRING, + NGHTTP3_SF_VALUE_TYPE_TOKEN, + NGHTTP3_SF_VALUE_TYPE_BYTESEQ, + NGHTTP3_SF_VALUE_TYPE_INNER_LIST, +} nghttp3_sf_value_type; + +/* + * nghttp3_sf_value stores Structured Field Values item. For Inner + * List, only type is set to NGHTTP3_SF_VALUE_TYPE_INNER_LIST. + */ +typedef struct nghttp3_sf_value { + uint8_t type; + union { + int b; + int64_t i; + double d; + struct { + const uint8_t *base; + size_t len; + } s; + }; +} nghttp3_sf_value; + +/* + * nghttp3_sf_parse_item parses the input sequence [|begin|, |end|) + * and stores the parsed an Item in |dest|. It returns the number of + * bytes consumed if it succeeds, or -1. This function is declared + * here for unit tests. + */ +nghttp3_ssize nghttp3_sf_parse_item(nghttp3_sf_value *dest, + const uint8_t *begin, const uint8_t *end); + +/* + * nghttp3_sf_parse_inner_list parses the input sequence [|begin|, |end|) + * and stores the parsed an Inner List in |dest|. It returns the number of + * bytes consumed if it succeeds, or -1. This function is declared + * here for unit tests. + */ +nghttp3_ssize nghttp3_sf_parse_inner_list(nghttp3_sf_value *dest, + const uint8_t *begin, + const uint8_t *end); + +#endif /* NGHTTP3_HTTP_H */ diff --git a/lib/nghttp3_idtr.c b/lib/nghttp3_idtr.c new file mode 100644 index 0000000..dc34841 --- /dev/null +++ b/lib/nghttp3_idtr.c @@ -0,0 +1,80 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_idtr.h" + +#include <assert.h> + +void nghttp3_idtr_init(nghttp3_idtr *idtr, int server, const nghttp3_mem *mem) { + nghttp3_gaptr_init(&idtr->gap, mem); + + idtr->server = server; +} + +void nghttp3_idtr_free(nghttp3_idtr *idtr) { + if (idtr == NULL) { + return; + } + + nghttp3_gaptr_free(&idtr->gap); +} + +/* + * id_from_stream_id translates |stream_id| to id space used by + * nghttp3_idtr. + */ +static uint64_t id_from_stream_id(int64_t stream_id) { + return (uint64_t)(stream_id >> 2); +} + +int nghttp3_idtr_open(nghttp3_idtr *idtr, int64_t stream_id) { + uint64_t q; + + assert((idtr->server && (stream_id % 2)) || + (!idtr->server && (stream_id % 2)) == 0); + + q = id_from_stream_id(stream_id); + + if (nghttp3_gaptr_is_pushed(&idtr->gap, q, 1)) { + return NGHTTP3_ERR_STREAM_IN_USE; + } + + return nghttp3_gaptr_push(&idtr->gap, q, 1); +} + +int nghttp3_idtr_is_open(nghttp3_idtr *idtr, int64_t stream_id) { + uint64_t q; + + assert((idtr->server && (stream_id % 2)) || + (!idtr->server && (stream_id % 2)) == 0); + + q = id_from_stream_id(stream_id); + + return nghttp3_gaptr_is_pushed(&idtr->gap, q, 1); +} + +uint64_t nghttp3_idtr_first_gap(nghttp3_idtr *idtr) { + return nghttp3_gaptr_first_gap_offset(&idtr->gap); +} diff --git a/lib/nghttp3_idtr.h b/lib/nghttp3_idtr.h new file mode 100644 index 0000000..ea3346c --- /dev/null +++ b/lib/nghttp3_idtr.h @@ -0,0 +1,90 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_IDTR_H +#define NGHTTP3_IDTR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_mem.h" +#include "nghttp3_gaptr.h" + +/* + * nghttp3_idtr tracks the usage of stream ID. + */ +typedef struct nghttp3_idtr { + /* gap maintains the range of ID which is not used yet. Initially, + its range is [0, UINT64_MAX). */ + nghttp3_gaptr gap; + /* server is nonzero if this object records server initiated stream + ID. */ + int server; +} nghttp3_idtr; + +/* + * nghttp3_idtr_init initializes |idtr|. + * + * If this object records server initiated ID (even number), set + * |server| to nonzero. + */ +void nghttp3_idtr_init(nghttp3_idtr *idtr, int server, const nghttp3_mem *mem); + +/* + * nghttp3_idtr_free frees resources allocated for |idtr|. + */ +void nghttp3_idtr_free(nghttp3_idtr *idtr); + +/* + * nghttp3_idtr_open claims that |stream_id| is in used. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGHTTP3_ERR_STREAM_IN_USE + * ID has already been used. + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_idtr_open(nghttp3_idtr *idtr, int64_t stream_id); + +/* + * nghttp3_idtr_open tells whether ID |stream_id| is in used or not. + * + * It returns nonzero if |stream_id| is used. + */ +int nghttp3_idtr_is_open(nghttp3_idtr *idtr, int64_t stream_id); + +/* + * nghttp3_idtr_first_gap returns the first id of first gap. If there + * is no gap, it returns UINT64_MAX. The returned id is an id space + * used in this object internally, and not stream ID. + */ +uint64_t nghttp3_idtr_first_gap(nghttp3_idtr *idtr); + +#endif /* NGHTTP3_IDTR_H */ diff --git a/lib/nghttp3_ksl.c b/lib/nghttp3_ksl.c new file mode 100644 index 0000000..adea677 --- /dev/null +++ b/lib/nghttp3_ksl.c @@ -0,0 +1,827 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_ksl.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "nghttp3_macro.h" +#include "nghttp3_mem.h" +#include "nghttp3_range.h" + +static nghttp3_ksl_blk null_blk = {{{NULL, NULL, 0, 0, {0}}}}; + +static size_t ksl_nodelen(size_t keylen) { + return (sizeof(nghttp3_ksl_node) + keylen - sizeof(uint64_t) + 0xfu) & + ~(uintptr_t)0xfu; +} + +static size_t ksl_blklen(size_t nodelen) { + return sizeof(nghttp3_ksl_blk) + nodelen * NGHTTP3_KSL_MAX_NBLK - + sizeof(uint64_t); +} + +/* + * ksl_node_set_key sets |key| to |node|. + */ +static void ksl_node_set_key(nghttp3_ksl *ksl, nghttp3_ksl_node *node, + const void *key) { + memcpy(node->key, key, ksl->keylen); +} + +void nghttp3_ksl_init(nghttp3_ksl *ksl, nghttp3_ksl_compar compar, + size_t keylen, const nghttp3_mem *mem) { + size_t nodelen = ksl_nodelen(keylen); + + nghttp3_objalloc_init(&ksl->blkalloc, + ((ksl_blklen(nodelen) + 0xfu) & ~(uintptr_t)0xfu) * 8, + mem); + + ksl->head = NULL; + ksl->front = ksl->back = NULL; + ksl->compar = compar; + ksl->keylen = keylen; + ksl->nodelen = nodelen; + ksl->n = 0; +} + +static nghttp3_ksl_blk *ksl_blk_objalloc_new(nghttp3_ksl *ksl) { + return nghttp3_objalloc_ksl_blk_len_get(&ksl->blkalloc, + ksl_blklen(ksl->nodelen)); +} + +static void ksl_blk_objalloc_del(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk) { + nghttp3_objalloc_ksl_blk_release(&ksl->blkalloc, blk); +} + +static int ksl_head_init(nghttp3_ksl *ksl) { + nghttp3_ksl_blk *head = ksl_blk_objalloc_new(ksl); + if (!head) { + return NGHTTP3_ERR_NOMEM; + } + + head->next = head->prev = NULL; + head->n = 0; + head->leaf = 1; + + ksl->head = head; + ksl->front = ksl->back = head; + + return 0; +} + +#ifdef NOMEMPOOL +/* + * ksl_free_blk frees |blk| recursively. + */ +static void ksl_free_blk(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk) { + size_t i; + + if (!blk->leaf) { + for (i = 0; i < blk->n; ++i) { + ksl_free_blk(ksl, nghttp3_ksl_nth_node(ksl, blk, i)->blk); + } + } + + ksl_blk_objalloc_del(ksl, blk); +} +#endif /* NOMEMPOOL */ + +void nghttp3_ksl_free(nghttp3_ksl *ksl) { + if (!ksl || !ksl->head) { + return; + } + +#ifdef NOMEMPOOL + ksl_free_blk(ksl, ksl->head); +#endif /* NOMEMPOOL */ + + nghttp3_objalloc_free(&ksl->blkalloc); +} + +/* + * ksl_split_blk splits |blk| into 2 nghttp3_ksl_blk objects. The new + * nghttp3_ksl_blk is always the "right" block. + * + * It returns the pointer to the nghttp3_ksl_blk created which is the + * located at the right of |blk|, or NULL which indicates out of + * memory error. + */ +static nghttp3_ksl_blk *ksl_split_blk(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk) { + nghttp3_ksl_blk *rblk; + + rblk = ksl_blk_objalloc_new(ksl); + if (rblk == NULL) { + return NULL; + } + + rblk->next = blk->next; + blk->next = rblk; + if (rblk->next) { + rblk->next->prev = rblk; + } else if (ksl->back == blk) { + ksl->back = rblk; + } + rblk->prev = blk; + rblk->leaf = blk->leaf; + + rblk->n = blk->n / 2; + + memcpy(rblk->nodes, blk->nodes + ksl->nodelen * (blk->n - rblk->n), + ksl->nodelen * rblk->n); + + blk->n -= rblk->n; + + assert(blk->n >= NGHTTP3_KSL_MIN_NBLK); + assert(rblk->n >= NGHTTP3_KSL_MIN_NBLK); + + return rblk; +} + +/* + * ksl_split_node splits a node included in |blk| at the position |i| + * into 2 adjacent nodes. The new node is always inserted at the + * position |i+1|. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +static int ksl_split_node(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, size_t i) { + nghttp3_ksl_node *node; + nghttp3_ksl_blk *lblk = nghttp3_ksl_nth_node(ksl, blk, i)->blk, *rblk; + + rblk = ksl_split_blk(ksl, lblk); + if (rblk == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + memmove(blk->nodes + (i + 2) * ksl->nodelen, + blk->nodes + (i + 1) * ksl->nodelen, + ksl->nodelen * (blk->n - (i + 1))); + + node = nghttp3_ksl_nth_node(ksl, blk, i + 1); + node->blk = rblk; + ++blk->n; + ksl_node_set_key(ksl, node, + nghttp3_ksl_nth_node(ksl, rblk, rblk->n - 1)->key); + + node = nghttp3_ksl_nth_node(ksl, blk, i); + ksl_node_set_key(ksl, node, + nghttp3_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); + + return 0; +} + +/* + * ksl_split_head splits a head (root) block. It increases the height + * of skip list by 1. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +static int ksl_split_head(nghttp3_ksl *ksl) { + nghttp3_ksl_blk *rblk = NULL, *lblk, *nhead = NULL; + nghttp3_ksl_node *node; + + rblk = ksl_split_blk(ksl, ksl->head); + if (rblk == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + lblk = ksl->head; + + nhead = ksl_blk_objalloc_new(ksl); + if (nhead == NULL) { + ksl_blk_objalloc_del(ksl, rblk); + return NGHTTP3_ERR_NOMEM; + } + nhead->next = nhead->prev = NULL; + nhead->n = 2; + nhead->leaf = 0; + + node = nghttp3_ksl_nth_node(ksl, nhead, 0); + ksl_node_set_key(ksl, node, + nghttp3_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); + node->blk = lblk; + + node = nghttp3_ksl_nth_node(ksl, nhead, 1); + ksl_node_set_key(ksl, node, + nghttp3_ksl_nth_node(ksl, rblk, rblk->n - 1)->key); + node->blk = rblk; + + ksl->head = nhead; + + return 0; +} + +/* + * insert_node inserts a node whose key is |key| with the associated + * |data| at the index of |i|. This function assumes that the number + * of nodes contained by |blk| is strictly less than + * NGHTTP3_KSL_MAX_NBLK. + */ +static void ksl_insert_node(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, size_t i, + const nghttp3_ksl_key *key, void *data) { + nghttp3_ksl_node *node; + + assert(blk->n < NGHTTP3_KSL_MAX_NBLK); + + memmove(blk->nodes + (i + 1) * ksl->nodelen, blk->nodes + i * ksl->nodelen, + ksl->nodelen * (blk->n - i)); + + node = nghttp3_ksl_nth_node(ksl, blk, i); + ksl_node_set_key(ksl, node, key); + node->data = data; + + ++blk->n; +} + +static size_t ksl_bsearch(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, + const nghttp3_ksl_key *key, + nghttp3_ksl_compar compar) { + size_t i; + nghttp3_ksl_node *node; + + for (i = 0, node = (nghttp3_ksl_node *)(void *)blk->nodes; + i < blk->n && compar((nghttp3_ksl_key *)node->key, key); + ++i, node = (nghttp3_ksl_node *)(void *)((uint8_t *)node + ksl->nodelen)) + ; + + return i; +} + +int nghttp3_ksl_insert(nghttp3_ksl *ksl, nghttp3_ksl_it *it, + const nghttp3_ksl_key *key, void *data) { + nghttp3_ksl_blk *blk; + nghttp3_ksl_node *node; + size_t i; + int rv; + + if (!ksl->head) { + rv = ksl_head_init(ksl); + if (rv != 0) { + return rv; + } + } + + blk = ksl->head; + + if (blk->n == NGHTTP3_KSL_MAX_NBLK) { + rv = ksl_split_head(ksl); + if (rv != 0) { + return rv; + } + blk = ksl->head; + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, ksl->compar); + + if (blk->leaf) { + if (i < blk->n && + !ksl->compar(key, nghttp3_ksl_nth_node(ksl, blk, i)->key)) { + if (it) { + *it = nghttp3_ksl_end(ksl); + } + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + ksl_insert_node(ksl, blk, i, key, data); + ++ksl->n; + if (it) { + nghttp3_ksl_it_init(it, ksl, blk, i); + } + return 0; + } + + if (i == blk->n) { + /* This insertion extends the largest key in this subtree. */ + for (; !blk->leaf;) { + node = nghttp3_ksl_nth_node(ksl, blk, blk->n - 1); + if (node->blk->n == NGHTTP3_KSL_MAX_NBLK) { + rv = ksl_split_node(ksl, blk, blk->n - 1); + if (rv != 0) { + return rv; + } + node = nghttp3_ksl_nth_node(ksl, blk, blk->n - 1); + } + ksl_node_set_key(ksl, node, key); + blk = node->blk; + } + ksl_insert_node(ksl, blk, blk->n, key, data); + ++ksl->n; + if (it) { + nghttp3_ksl_it_init(it, ksl, blk, blk->n - 1); + } + return 0; + } + + node = nghttp3_ksl_nth_node(ksl, blk, i); + + if (node->blk->n == NGHTTP3_KSL_MAX_NBLK) { + rv = ksl_split_node(ksl, blk, i); + if (rv != 0) { + return rv; + } + if (ksl->compar((nghttp3_ksl_key *)node->key, key)) { + node = nghttp3_ksl_nth_node(ksl, blk, i + 1); + if (ksl->compar((nghttp3_ksl_key *)node->key, key)) { + ksl_node_set_key(ksl, node, key); + } + } + } + + blk = node->blk; + } +} + +/* + * ksl_remove_node removes the node included in |blk| at the index of + * |i|. + */ +static void ksl_remove_node(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, size_t i) { + memmove(blk->nodes + i * ksl->nodelen, blk->nodes + (i + 1) * ksl->nodelen, + ksl->nodelen * (blk->n - (i + 1))); + + --blk->n; +} + +/* + * ksl_merge_node merges 2 nodes which are the nodes at the index of + * |i| and |i + 1|. + * + * If |blk| is the direct descendant of head (root) block and the head + * block contains just 2 nodes, the merged block becomes head block, + * which decreases the height of |ksl| by 1. + * + * This function returns the pointer to the merged block. + */ +static nghttp3_ksl_blk *ksl_merge_node(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, + size_t i) { + nghttp3_ksl_blk *lblk, *rblk; + + assert(i + 1 < blk->n); + + lblk = nghttp3_ksl_nth_node(ksl, blk, i)->blk; + rblk = nghttp3_ksl_nth_node(ksl, blk, i + 1)->blk; + + assert(lblk->n + rblk->n < NGHTTP3_KSL_MAX_NBLK); + + memcpy(lblk->nodes + ksl->nodelen * lblk->n, rblk->nodes, + ksl->nodelen * rblk->n); + + lblk->n += rblk->n; + lblk->next = rblk->next; + if (lblk->next) { + lblk->next->prev = lblk; + } else if (ksl->back == rblk) { + ksl->back = lblk; + } + + ksl_blk_objalloc_del(ksl, rblk); + + if (ksl->head == blk && blk->n == 2) { + ksl_blk_objalloc_del(ksl, ksl->head); + ksl->head = lblk; + } else { + ksl_remove_node(ksl, blk, i + 1); + ksl_node_set_key(ksl, nghttp3_ksl_nth_node(ksl, blk, i), + nghttp3_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); + } + + return lblk; +} + +/* + * ksl_shift_left moves the first nodes in blk->nodes[i]->blk->nodes + * to blk->nodes[i - 1]->blk->nodes in a manner that they have the + * same amount of nodes as much as possible. + */ +static void ksl_shift_left(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, size_t i) { + nghttp3_ksl_node *lnode, *rnode; + size_t n; + + assert(i > 0); + + lnode = nghttp3_ksl_nth_node(ksl, blk, i - 1); + rnode = nghttp3_ksl_nth_node(ksl, blk, i); + + assert(lnode->blk->n < NGHTTP3_KSL_MAX_NBLK); + assert(rnode->blk->n > NGHTTP3_KSL_MIN_NBLK); + + n = (lnode->blk->n + rnode->blk->n + 1) / 2 - lnode->blk->n; + + assert(n > 0); + assert(lnode->blk->n <= NGHTTP3_KSL_MAX_NBLK - n); + assert(rnode->blk->n >= NGHTTP3_KSL_MIN_NBLK + n); + + memcpy(lnode->blk->nodes + ksl->nodelen * lnode->blk->n, rnode->blk->nodes, + ksl->nodelen * n); + + lnode->blk->n += (uint32_t)n; + rnode->blk->n -= (uint32_t)n; + + ksl_node_set_key( + ksl, lnode, + nghttp3_ksl_nth_node(ksl, lnode->blk, lnode->blk->n - 1)->key); + + memmove(rnode->blk->nodes, rnode->blk->nodes + ksl->nodelen * n, + ksl->nodelen * rnode->blk->n); +} + +/* + * ksl_shift_right moves the last nodes in blk->nodes[i]->blk->nodes + * to blk->nodes[i + 1]->blk->nodes in a manner that they have the + * same amount of nodes as much as possible.. + */ +static void ksl_shift_right(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, size_t i) { + nghttp3_ksl_node *lnode, *rnode; + size_t n; + + assert(i < blk->n - 1); + + lnode = nghttp3_ksl_nth_node(ksl, blk, i); + rnode = nghttp3_ksl_nth_node(ksl, blk, i + 1); + + assert(lnode->blk->n > NGHTTP3_KSL_MIN_NBLK); + assert(rnode->blk->n < NGHTTP3_KSL_MAX_NBLK); + + n = (lnode->blk->n + rnode->blk->n + 1) / 2 - rnode->blk->n; + + assert(n > 0); + assert(lnode->blk->n >= NGHTTP3_KSL_MIN_NBLK + n); + assert(rnode->blk->n <= NGHTTP3_KSL_MAX_NBLK - n); + + memmove(rnode->blk->nodes + ksl->nodelen * n, rnode->blk->nodes, + ksl->nodelen * rnode->blk->n); + + rnode->blk->n += (uint32_t)n; + lnode->blk->n -= (uint32_t)n; + + memcpy(rnode->blk->nodes, lnode->blk->nodes + ksl->nodelen * lnode->blk->n, + ksl->nodelen * n); + + ksl_node_set_key( + ksl, lnode, + nghttp3_ksl_nth_node(ksl, lnode->blk, lnode->blk->n - 1)->key); +} + +/* + * key_equal returns nonzero if |lhs| and |rhs| are equal using the + * function |compar|. + */ +static int key_equal(nghttp3_ksl_compar compar, const nghttp3_ksl_key *lhs, + const nghttp3_ksl_key *rhs) { + return !compar(lhs, rhs) && !compar(rhs, lhs); +} + +int nghttp3_ksl_remove_hint(nghttp3_ksl *ksl, nghttp3_ksl_it *it, + const nghttp3_ksl_it *hint, + const nghttp3_ksl_key *key) { + nghttp3_ksl_blk *blk = hint->blk; + + assert(ksl->head); + + if (blk->n <= NGHTTP3_KSL_MIN_NBLK) { + return nghttp3_ksl_remove(ksl, it, key); + } + + ksl_remove_node(ksl, blk, hint->i); + + --ksl->n; + + if (it) { + if (hint->i == blk->n && blk->next) { + nghttp3_ksl_it_init(it, ksl, blk->next, 0); + } else { + nghttp3_ksl_it_init(it, ksl, blk, hint->i); + } + } + + return 0; +} + +int nghttp3_ksl_remove(nghttp3_ksl *ksl, nghttp3_ksl_it *it, + const nghttp3_ksl_key *key) { + nghttp3_ksl_blk *blk = ksl->head; + nghttp3_ksl_node *node; + size_t i; + + if (!ksl->head) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + if (!blk->leaf && blk->n == 2 && + nghttp3_ksl_nth_node(ksl, blk, 0)->blk->n == NGHTTP3_KSL_MIN_NBLK && + nghttp3_ksl_nth_node(ksl, blk, 1)->blk->n == NGHTTP3_KSL_MIN_NBLK) { + blk = ksl_merge_node(ksl, ksl->head, 0); + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, ksl->compar); + + if (i == blk->n) { + if (it) { + *it = nghttp3_ksl_end(ksl); + } + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + if (blk->leaf) { + if (ksl->compar(key, nghttp3_ksl_nth_node(ksl, blk, i)->key)) { + if (it) { + *it = nghttp3_ksl_end(ksl); + } + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + ksl_remove_node(ksl, blk, i); + --ksl->n; + if (it) { + if (blk->n == i && blk->next) { + nghttp3_ksl_it_init(it, ksl, blk->next, 0); + } else { + nghttp3_ksl_it_init(it, ksl, blk, i); + } + } + return 0; + } + + node = nghttp3_ksl_nth_node(ksl, blk, i); + + if (node->blk->n > NGHTTP3_KSL_MIN_NBLK) { + blk = node->blk; + continue; + } + + assert(node->blk->n == NGHTTP3_KSL_MIN_NBLK); + + if (i + 1 < blk->n && + nghttp3_ksl_nth_node(ksl, blk, i + 1)->blk->n > NGHTTP3_KSL_MIN_NBLK) { + ksl_shift_left(ksl, blk, i + 1); + blk = node->blk; + continue; + } + + if (i > 0 && + nghttp3_ksl_nth_node(ksl, blk, i - 1)->blk->n > NGHTTP3_KSL_MIN_NBLK) { + ksl_shift_right(ksl, blk, i - 1); + blk = node->blk; + continue; + } + + if (i + 1 < blk->n) { + blk = ksl_merge_node(ksl, blk, i); + continue; + } + + assert(i > 0); + + blk = ksl_merge_node(ksl, blk, i - 1); + } +} + +nghttp3_ksl_it nghttp3_ksl_lower_bound(nghttp3_ksl *ksl, + const nghttp3_ksl_key *key) { + nghttp3_ksl_blk *blk = ksl->head; + nghttp3_ksl_it it; + size_t i; + + if (!blk) { + nghttp3_ksl_it_init(&it, ksl, &null_blk, 0); + return it; + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, ksl->compar); + + if (blk->leaf) { + if (i == blk->n && blk->next) { + blk = blk->next; + i = 0; + } + nghttp3_ksl_it_init(&it, ksl, blk, i); + return it; + } + + if (i == blk->n) { + /* This happens if descendant has smaller key. Fast forward to + find last node in this subtree. */ + for (; !blk->leaf; blk = nghttp3_ksl_nth_node(ksl, blk, blk->n - 1)->blk) + ; + if (blk->next) { + blk = blk->next; + i = 0; + } else { + i = blk->n; + } + nghttp3_ksl_it_init(&it, ksl, blk, i); + return it; + } + blk = nghttp3_ksl_nth_node(ksl, blk, i)->blk; + } +} + +nghttp3_ksl_it nghttp3_ksl_lower_bound_compar(nghttp3_ksl *ksl, + const nghttp3_ksl_key *key, + nghttp3_ksl_compar compar) { + nghttp3_ksl_blk *blk = ksl->head; + nghttp3_ksl_it it; + size_t i; + + if (!blk) { + nghttp3_ksl_it_init(&it, ksl, &null_blk, 0); + return it; + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, compar); + + if (blk->leaf) { + if (i == blk->n && blk->next) { + blk = blk->next; + i = 0; + } + nghttp3_ksl_it_init(&it, ksl, blk, i); + return it; + } + + if (i == blk->n) { + /* This happens if descendant has smaller key. Fast forward to + find last node in this subtree. */ + for (; !blk->leaf; blk = nghttp3_ksl_nth_node(ksl, blk, blk->n - 1)->blk) + ; + if (blk->next) { + blk = blk->next; + i = 0; + } else { + i = blk->n; + } + nghttp3_ksl_it_init(&it, ksl, blk, i); + return it; + } + blk = nghttp3_ksl_nth_node(ksl, blk, i)->blk; + } +} + +void nghttp3_ksl_update_key(nghttp3_ksl *ksl, const nghttp3_ksl_key *old_key, + const nghttp3_ksl_key *new_key) { + nghttp3_ksl_blk *blk = ksl->head; + nghttp3_ksl_node *node; + size_t i; + + assert(ksl->head); + + for (;;) { + i = ksl_bsearch(ksl, blk, old_key, ksl->compar); + + assert(i < blk->n); + node = nghttp3_ksl_nth_node(ksl, blk, i); + + if (blk->leaf) { + assert(key_equal(ksl->compar, (nghttp3_ksl_key *)node->key, old_key)); + ksl_node_set_key(ksl, node, new_key); + return; + } + + if (key_equal(ksl->compar, (nghttp3_ksl_key *)node->key, old_key) || + ksl->compar((nghttp3_ksl_key *)node->key, new_key)) { + ksl_node_set_key(ksl, node, new_key); + } + + blk = node->blk; + } +} + +static void ksl_print(nghttp3_ksl *ksl, nghttp3_ksl_blk *blk, size_t level) { + size_t i; + nghttp3_ksl_node *node; + + fprintf(stderr, "LV=%zu n=%u\n", level, blk->n); + + if (blk->leaf) { + for (i = 0; i < blk->n; ++i) { + node = nghttp3_ksl_nth_node(ksl, blk, i); + fprintf(stderr, " %" PRId64, *(int64_t *)(void *)node->key); + } + fprintf(stderr, "\n"); + return; + } + + for (i = 0; i < blk->n; ++i) { + ksl_print(ksl, nghttp3_ksl_nth_node(ksl, blk, i)->blk, level + 1); + } +} + +size_t nghttp3_ksl_len(nghttp3_ksl *ksl) { return ksl->n; } + +void nghttp3_ksl_clear(nghttp3_ksl *ksl) { + if (!ksl->head) { + return; + } + +#ifdef NOMEMPOOL + ksl_free_blk(ksl, ksl->head); +#endif /* NOMEMPOOL */ + + ksl->front = ksl->back = ksl->head = NULL; + ksl->n = 0; + + nghttp3_objalloc_clear(&ksl->blkalloc); +} + +void nghttp3_ksl_print(nghttp3_ksl *ksl) { + if (!ksl->head) { + return; + } + + ksl_print(ksl, ksl->head, 0); +} + +nghttp3_ksl_it nghttp3_ksl_begin(const nghttp3_ksl *ksl) { + nghttp3_ksl_it it; + + if (ksl->head) { + nghttp3_ksl_it_init(&it, ksl, ksl->front, 0); + } else { + nghttp3_ksl_it_init(&it, ksl, &null_blk, 0); + } + + return it; +} + +nghttp3_ksl_it nghttp3_ksl_end(const nghttp3_ksl *ksl) { + nghttp3_ksl_it it; + + if (ksl->head) { + nghttp3_ksl_it_init(&it, ksl, ksl->back, ksl->back->n); + } else { + nghttp3_ksl_it_init(&it, ksl, &null_blk, 0); + } + + return it; +} + +void nghttp3_ksl_it_init(nghttp3_ksl_it *it, const nghttp3_ksl *ksl, + nghttp3_ksl_blk *blk, size_t i) { + it->ksl = ksl; + it->blk = blk; + it->i = i; +} + +void nghttp3_ksl_it_prev(nghttp3_ksl_it *it) { + assert(!nghttp3_ksl_it_begin(it)); + + if (it->i == 0) { + it->blk = it->blk->prev; + it->i = it->blk->n - 1; + } else { + --it->i; + } +} + +int nghttp3_ksl_it_begin(const nghttp3_ksl_it *it) { + return it->i == 0 && it->blk->prev == NULL; +} + +int nghttp3_ksl_range_compar(const nghttp3_ksl_key *lhs, + const nghttp3_ksl_key *rhs) { + const nghttp3_range *a = lhs, *b = rhs; + return a->begin < b->begin; +} + +int nghttp3_ksl_range_exclusive_compar(const nghttp3_ksl_key *lhs, + const nghttp3_ksl_key *rhs) { + const nghttp3_range *a = lhs, *b = rhs; + return a->begin < b->begin && + !(nghttp3_max(a->begin, b->begin) < nghttp3_min(a->end, b->end)); +} diff --git a/lib/nghttp3_ksl.h b/lib/nghttp3_ksl.h new file mode 100644 index 0000000..0bc10e8 --- /dev/null +++ b/lib/nghttp3_ksl.h @@ -0,0 +1,348 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_KSL_H +#define NGHTTP3_KSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdlib.h> + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_objalloc.h" + +/* + * Skip List using single key instead of range. + */ + +#define NGHTTP3_KSL_DEGR 16 +/* NGHTTP3_KSL_MAX_NBLK is the maximum number of nodes which a single + block can contain. */ +#define NGHTTP3_KSL_MAX_NBLK (2 * NGHTTP3_KSL_DEGR - 1) +/* NGHTTP3_KSL_MIN_NBLK is the minimum number of nodes which a single + block other than root must contains. */ +#define NGHTTP3_KSL_MIN_NBLK (NGHTTP3_KSL_DEGR - 1) + +/* + * nghttp3_ksl_key represents key in nghttp3_ksl. + */ +typedef void nghttp3_ksl_key; + +typedef struct nghttp3_ksl_node nghttp3_ksl_node; + +typedef struct nghttp3_ksl_blk nghttp3_ksl_blk; + +/* + * nghttp3_ksl_node is a node which contains either nghttp3_ksl_blk or + * opaque data. If a node is an internal node, it contains + * nghttp3_ksl_blk. Otherwise, it has data. The key is stored at the + * location starting at key. + */ +struct nghttp3_ksl_node { + union { + nghttp3_ksl_blk *blk; + void *data; + }; + union { + uint64_t align; + /* key is a buffer to include key associated to this node. + Because the length of key is unknown until nghttp3_ksl_init is + called, the actual buffer will be allocated after this + field. */ + uint8_t key[1]; + }; +}; + +/* + * nghttp3_ksl_blk contains nghttp3_ksl_node objects. + */ +struct nghttp3_ksl_blk { + union { + struct { + /* next points to the next block if leaf field is nonzero. */ + nghttp3_ksl_blk *next; + /* prev points to the previous block if leaf field is + nonzero. */ + nghttp3_ksl_blk *prev; + /* n is the number of nodes this object contains in nodes. */ + uint32_t n; + /* leaf is nonzero if this block contains leaf nodes. */ + uint32_t leaf; + union { + uint64_t align; + /* nodes is a buffer to contain NGHTTP3_KSL_MAX_NBLK + nghttp3_ksl_node objects. Because nghttp3_ksl_node object + is allocated along with the additional variable length key + storage, the size of buffer is unknown until + nghttp3_ksl_init is called. */ + uint8_t nodes[1]; + }; + }; + + nghttp3_opl_entry oplent; + }; +}; + +nghttp3_objalloc_def(ksl_blk, nghttp3_ksl_blk, oplent); + +/* + * nghttp3_ksl_compar is a function type which returns nonzero if key + * |lhs| should be placed before |rhs|. It returns 0 otherwise. + */ +typedef int (*nghttp3_ksl_compar)(const nghttp3_ksl_key *lhs, + const nghttp3_ksl_key *rhs); + +typedef struct nghttp3_ksl nghttp3_ksl; + +typedef struct nghttp3_ksl_it nghttp3_ksl_it; + +/* + * nghttp3_ksl_it is a forward iterator to iterate nodes. + */ +struct nghttp3_ksl_it { + const nghttp3_ksl *ksl; + nghttp3_ksl_blk *blk; + size_t i; +}; + +/* + * nghttp3_ksl is a deterministic paged skip list. + */ +struct nghttp3_ksl { + nghttp3_objalloc blkalloc; + /* head points to the root block. */ + nghttp3_ksl_blk *head; + /* front points to the first leaf block. */ + nghttp3_ksl_blk *front; + /* back points to the last leaf block. */ + nghttp3_ksl_blk *back; + nghttp3_ksl_compar compar; + size_t n; + /* keylen is the size of key */ + size_t keylen; + /* nodelen is the actual size of nghttp3_ksl_node including key + storage. */ + size_t nodelen; +}; + +/* + * nghttp3_ksl_init initializes |ksl|. |compar| specifies compare + * function. |keylen| is the length of key. + */ +void nghttp3_ksl_init(nghttp3_ksl *ksl, nghttp3_ksl_compar compar, + size_t keylen, const nghttp3_mem *mem); + +/* + * nghttp3_ksl_free frees resources allocated for |ksl|. If |ksl| is + * NULL, this function does nothing. It does not free the memory + * region pointed by |ksl| itself. + */ +void nghttp3_ksl_free(nghttp3_ksl *ksl); + +/* + * nghttp3_ksl_insert inserts |key| with its associated |data|. On + * successful insertion, the iterator points to the inserted node is + * stored in |*it|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + * NGHTTP3_ERR_INVALID_ARGUMENT + * |key| already exists. + */ +int nghttp3_ksl_insert(nghttp3_ksl *ksl, nghttp3_ksl_it *it, + const nghttp3_ksl_key *key, void *data); + +/* + * nghttp3_ksl_remove removes the |key| from |ksl|. + * + * This function assigns the iterator to |*it|, which points to the + * node which is located at the right next of the removed node if |it| + * is not NULL. If |key| is not found, no deletion takes place and + * the return value of nghttp3_ksl_end(ksl) is assigned to |*it|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_INVALID_ARGUMENT + * |key| does not exist. + */ +int nghttp3_ksl_remove(nghttp3_ksl *ksl, nghttp3_ksl_it *it, + const nghttp3_ksl_key *key); + +/* + * nghttp3_ksl_remove_hint removes the |key| from |ksl|. |hint| must + * point to the same node denoted by |key|. |hint| is used to remove + * a node efficiently in some cases. Other than that, it behaves + * exactly like nghttp3_ksl_remove. |it| and |hint| can point to the + * same object. + */ +int nghttp3_ksl_remove_hint(nghttp3_ksl *ksl, nghttp3_ksl_it *it, + const nghttp3_ksl_it *hint, + const nghttp3_ksl_key *key); + +/* + * nghttp3_ksl_lower_bound returns the iterator which points to the + * first node which has the key which is equal to |key| or the last + * node which satisfies !compar(&node->key, key). If there is no such + * node, it returns the iterator which satisfies nghttp3_ksl_it_end(it) + * != 0. + */ +nghttp3_ksl_it nghttp3_ksl_lower_bound(nghttp3_ksl *ksl, + const nghttp3_ksl_key *key); + +/* + * nghttp3_ksl_lower_bound_compar works like nghttp3_ksl_lower_bound, + * but it takes custom function |compar| to do lower bound search. + */ +nghttp3_ksl_it nghttp3_ksl_lower_bound_compar(nghttp3_ksl *ksl, + const nghttp3_ksl_key *key, + nghttp3_ksl_compar compar); + +/* + * nghttp3_ksl_update_key replaces the key of nodes which has |old_key| + * with |new_key|. |new_key| must be strictly greater than the + * previous node and strictly smaller than the next node. + */ +void nghttp3_ksl_update_key(nghttp3_ksl *ksl, const nghttp3_ksl_key *old_key, + const nghttp3_ksl_key *new_key); + +/* + * nghttp3_ksl_begin returns the iterator which points to the first + * node. If there is no node in |ksl|, it returns the iterator which + * satisfies nghttp3_ksl_it_end(it) != 0. + */ +nghttp3_ksl_it nghttp3_ksl_begin(const nghttp3_ksl *ksl); + +/* + * nghttp3_ksl_end returns the iterator which points to the node + * following the last node. The returned object satisfies + * nghttp3_ksl_it_end(). If there is no node in |ksl|, it returns the + * iterator which satisfies nghttp3_ksl_it_begin(it) != 0. + */ +nghttp3_ksl_it nghttp3_ksl_end(const nghttp3_ksl *ksl); + +/* + * nghttp3_ksl_len returns the number of elements stored in |ksl|. + */ +size_t nghttp3_ksl_len(nghttp3_ksl *ksl); + +/* + * nghttp3_ksl_clear removes all elements stored in |ksl|. + */ +void nghttp3_ksl_clear(nghttp3_ksl *ksl); + +/* + * nghttp3_ksl_nth_node returns the |n|th node under |blk|. + */ +#define nghttp3_ksl_nth_node(KSL, BLK, N) \ + ((nghttp3_ksl_node *)(void *)((BLK)->nodes + (KSL)->nodelen * (N))) + +/* + * nghttp3_ksl_print prints its internal state in stderr. It assumes + * that the key is of type int64_t. This function should be used for + * the debugging purpose only. + */ +void nghttp3_ksl_print(nghttp3_ksl *ksl); + +/* + * nghttp3_ksl_it_init initializes |it|. + */ +void nghttp3_ksl_it_init(nghttp3_ksl_it *it, const nghttp3_ksl *ksl, + nghttp3_ksl_blk *blk, size_t i); + +/* + * nghttp3_ksl_it_get returns the data associated to the node which + * |it| points to. It is undefined to call this function when + * nghttp3_ksl_it_end(it) returns nonzero. + */ +#define nghttp3_ksl_it_get(IT) \ + nghttp3_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->data + +/* + * nghttp3_ksl_it_next advances the iterator by one. It is undefined + * if this function is called when nghttp3_ksl_it_end(it) returns + * nonzero. + */ +#define nghttp3_ksl_it_next(IT) \ + (++(IT)->i == (IT)->blk->n && (IT)->blk->next \ + ? ((IT)->blk = (IT)->blk->next, (IT)->i = 0) \ + : 0) + +/* + * nghttp3_ksl_it_prev moves backward the iterator by one. It is + * undefined if this function is called when nghttp3_ksl_it_begin(it) + * returns nonzero. + */ +void nghttp3_ksl_it_prev(nghttp3_ksl_it *it); + +/* + * nghttp3_ksl_it_end returns nonzero if |it| points to the beyond the + * last node. + */ +#define nghttp3_ksl_it_end(IT) \ + ((IT)->blk->n == (IT)->i && (IT)->blk->next == NULL) + +/* + * nghttp3_ksl_it_begin returns nonzero if |it| points to the first + * node. |it| might satisfy both nghttp3_ksl_it_begin(&it) and + * nghttp3_ksl_it_end(&it) if the skip list has no node. + */ +int nghttp3_ksl_it_begin(const nghttp3_ksl_it *it); + +/* + * nghttp3_ksl_key returns the key of the node which |it| points to. + * It is undefined to call this function when nghttp3_ksl_it_end(it) + * returns nonzero. + */ +#define nghttp3_ksl_it_key(IT) \ + ((nghttp3_ksl_key *)nghttp3_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->key) + +/* + * nghttp3_ksl_range_compar is an implementation of + * nghttp3_ksl_compar. lhs->ptr and rhs->ptr must point to + * nghttp3_range object and the function returns nonzero if (const + * nghttp3_range *)(lhs->ptr)->begin < (const nghttp3_range + * *)(rhs->ptr)->begin. + */ +int nghttp3_ksl_range_compar(const nghttp3_ksl_key *lhs, + const nghttp3_ksl_key *rhs); + +/* + * nghttp3_ksl_range_exclusive_compar is an implementation of + * nghttp3_ksl_compar. lhs->ptr and rhs->ptr must point to + * nghttp3_range object and the function returns nonzero if (const + * nghttp3_range *)(lhs->ptr)->begin < (const nghttp3_range + * *)(rhs->ptr)->begin and the 2 ranges do not intersect. + */ +int nghttp3_ksl_range_exclusive_compar(const nghttp3_ksl_key *lhs, + const nghttp3_ksl_key *rhs); + +#endif /* NGHTTP3_KSL_H */ diff --git a/lib/nghttp3_macro.h b/lib/nghttp3_macro.h new file mode 100644 index 0000000..a44e907 --- /dev/null +++ b/lib/nghttp3_macro.h @@ -0,0 +1,51 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_MACRO_H +#define NGHTTP3_MACRO_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stddef.h> + +#include <nghttp3/nghttp3.h> + +#define nghttp3_min(A, B) ((A) < (B) ? (A) : (B)) +#define nghttp3_max(A, B) ((A) > (B) ? (A) : (B)) + +#define nghttp3_struct_of(ptr, type, member) \ + ((type *)(void *)((char *)(ptr)-offsetof(type, member))) + +#define nghttp3_arraylen(A) (sizeof(A) / sizeof(*(A))) + +#define lstreq(A, B, N) ((sizeof((A)) - 1) == (N) && memcmp((A), (B), (N)) == 0) + +/* NGHTTP3_MAX_VARINT` is the maximum value which can be encoded in + variable-length integer encoding. */ +#define NGHTTP3_MAX_VARINT ((1ULL << 62) - 1) + +#endif /* NGHTTP3_MACRO_H */ diff --git a/lib/nghttp3_map.c b/lib/nghttp3_map.c new file mode 100644 index 0000000..fcfc31a --- /dev/null +++ b/lib/nghttp3_map.c @@ -0,0 +1,337 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * 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 "nghttp3_map.h" + +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "nghttp3_conv.h" + +#define NGHTTP3_INITIAL_TABLE_LENBITS 4 + +void nghttp3_map_init(nghttp3_map *map, const nghttp3_mem *mem) { + map->mem = mem; + map->tablelen = 0; + map->tablelenbits = 0; + map->table = NULL; + map->size = 0; +} + +void nghttp3_map_free(nghttp3_map *map) { + if (!map) { + return; + } + + nghttp3_mem_free(map->mem, map->table); +} + +void nghttp3_map_each_free(nghttp3_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + uint32_t i; + nghttp3_map_bucket *bkt; + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + continue; + } + + func(bkt->data, ptr); + } +} + +int nghttp3_map_each(nghttp3_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + int rv; + uint32_t i; + nghttp3_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(nghttp3_map_key_type key) { + return (uint32_t)((key * 11400714819323198485llu) >> 32); +} + +static size_t h2idx(uint32_t hash, uint32_t bits) { + return hash >> (32 - bits); +} + +static size_t distance(uint32_t tablelen, uint32_t tablelenbits, + nghttp3_map_bucket *bkt, size_t idx) { + return (idx - h2idx(bkt->hash, tablelenbits)) & (tablelen - 1); +} + +static void map_bucket_swap(nghttp3_map_bucket *bkt, uint32_t *phash, + nghttp3_map_key_type *pkey, void **pdata) { + uint32_t h = bkt->hash; + nghttp3_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(nghttp3_map_bucket *bkt, uint32_t hash, + nghttp3_map_key_type key, void *data) { + bkt->hash = hash; + bkt->key = key; + bkt->data = data; +} + +void nghttp3_map_print_distance(nghttp3_map *map) { + uint32_t i; + size_t idx; + nghttp3_map_bucket *bkt; + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + fprintf(stderr, "@%u <EMPTY>\n", i); + continue; + } + + idx = h2idx(bkt->hash, map->tablelenbits); + fprintf(stderr, "@%u hash=%08x key=%" PRIu64 " base=%zu distance=%zu\n", i, + bkt->hash, bkt->key, idx, + distance(map->tablelen, map->tablelenbits, bkt, idx)); + } +} + +static int insert(nghttp3_map_bucket *table, uint32_t tablelen, + uint32_t tablelenbits, uint32_t hash, + nghttp3_map_key_type key, void *data) { + size_t idx = h2idx(hash, tablelenbits); + size_t d = 0, dd; + nghttp3_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 NGHTTP3_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(nghttp3_map *map, uint32_t new_tablelen, + uint32_t new_tablelenbits) { + uint32_t i; + nghttp3_map_bucket *new_table; + nghttp3_map_bucket *bkt; + int rv; + (void)rv; + + new_table = + nghttp3_mem_calloc(map->mem, new_tablelen, sizeof(nghttp3_map_bucket)); + if (new_table == NULL) { + return NGHTTP3_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); + } + + nghttp3_mem_free(map->mem, map->table); + map->tablelen = new_tablelen; + map->tablelenbits = new_tablelenbits; + map->table = new_table; + + return 0; +} + +int nghttp3_map_insert(nghttp3_map *map, nghttp3_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 << NGHTTP3_INITIAL_TABLE_LENBITS, + NGHTTP3_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 *nghttp3_map_find(nghttp3_map *map, nghttp3_map_key_type key) { + uint32_t h; + size_t idx; + nghttp3_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 nghttp3_map_remove(nghttp3_map *map, nghttp3_map_key_type key) { + uint32_t h; + size_t idx, didx; + nghttp3_map_bucket *bkt; + size_t d = 0; + + if (map->size == 0) { + return NGHTTP3_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 NGHTTP3_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 nghttp3_map_clear(nghttp3_map *map) { + if (map->tablelen == 0) { + return; + } + + memset(map->table, 0, sizeof(*map->table) * map->tablelen); + map->size = 0; +} + +size_t nghttp3_map_size(nghttp3_map *map) { return map->size; } diff --git a/lib/nghttp3_map.h b/lib/nghttp3_map.h new file mode 100644 index 0000000..79dff02 --- /dev/null +++ b/lib/nghttp3_map.h @@ -0,0 +1,137 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * 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 NGHTTP3_MAP_H +#define NGHTTP3_MAP_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_mem.h" + +/* Implementation of unordered map */ + +typedef uint64_t nghttp3_map_key_type; + +typedef struct nghttp3_map_bucket { + uint32_t hash; + nghttp3_map_key_type key; + void *data; +} nghttp3_map_bucket; + +typedef struct nghttp3_map { + nghttp3_map_bucket *table; + const nghttp3_mem *mem; + size_t size; + uint32_t tablelen; + uint32_t tablelenbits; +} nghttp3_map; + +/* + * Initializes the map |map|. + */ +void nghttp3_map_init(nghttp3_map *map, const nghttp3_mem *mem); + +/* + * Deallocates any resources allocated for |map|. The stored entries + * are not freed by this function. Use nghttp3_map_each_free() to free + * each entries. + */ +void nghttp3_map_free(nghttp3_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 nghttp3_map_each_free(nghttp3_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: + * + * NGHTTP3_ERR_INVALID_ARGUMENT + * The item associated by |key| already exists. + * NGHTTP3_ERR_NOMEM + * Out of memory + */ +int nghttp3_map_insert(nghttp3_map *map, nghttp3_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 *nghttp3_map_find(nghttp3_map *map, nghttp3_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: + * + * NGHTTP3_ERR_INVALID_ARGUMENT + * The data associated by |key| does not exist. + */ +int nghttp3_map_remove(nghttp3_map *map, nghttp3_map_key_type key); + +/* + * Removes all entries from |map|. + */ +void nghttp3_map_clear(nghttp3_map *map); + +/* + * Returns the number of items stored in the map |map|. + */ +size_t nghttp3_map_size(nghttp3_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 + * nghttp3_map_each_free() instead. + */ +int nghttp3_map_each(nghttp3_map *map, int (*func)(void *data, void *ptr), + void *ptr); + +void nghttp3_map_print_distance(nghttp3_map *map); + +#endif /* NGHTTP3_MAP_H */ diff --git a/lib/nghttp3_mem.c b/lib/nghttp3_mem.c new file mode 100644 index 0000000..0379e99 --- /dev/null +++ b/lib/nghttp3_mem.c @@ -0,0 +1,124 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2014 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_mem.h" + +#include <stdio.h> + +static void *default_malloc(size_t size, void *user_data) { + (void)user_data; + + return malloc(size); +} + +static void default_free(void *ptr, void *user_data) { + (void)user_data; + + free(ptr); +} + +static void *default_calloc(size_t nmemb, size_t size, void *user_data) { + (void)user_data; + + return calloc(nmemb, size); +} + +static void *default_realloc(void *ptr, size_t size, void *user_data) { + (void)user_data; + + return realloc(ptr, size); +} + +static nghttp3_mem mem_default = {NULL, default_malloc, default_free, + default_calloc, default_realloc}; + +const nghttp3_mem *nghttp3_mem_default(void) { return &mem_default; } + +#ifndef MEMDEBUG +void *nghttp3_mem_malloc(const nghttp3_mem *mem, size_t size) { + return mem->malloc(size, mem->user_data); +} + +void nghttp3_mem_free(const nghttp3_mem *mem, void *ptr) { + mem->free(ptr, mem->user_data); +} + +void *nghttp3_mem_calloc(const nghttp3_mem *mem, size_t nmemb, size_t size) { + return mem->calloc(nmemb, size, mem->user_data); +} + +void *nghttp3_mem_realloc(const nghttp3_mem *mem, void *ptr, size_t size) { + return mem->realloc(ptr, size, mem->user_data); +} +#else /* MEMDEBUG */ +void *nghttp3_mem_malloc_debug(const nghttp3_mem *mem, size_t size, + const char *func, const char *file, + size_t line) { + void *nptr = mem->malloc(size, mem->user_data); + + fprintf(stderr, "malloc %p size=%zu in %s at %s:%zu\n", nptr, size, func, + file, line); + + return nptr; +} + +void nghttp3_mem_free_debug(const nghttp3_mem *mem, void *ptr, const char *func, + const char *file, size_t line) { + fprintf(stderr, "free ptr=%p in %s at %s:%zu\n", ptr, func, file, line); + + mem->free(ptr, mem->user_data); +} + +void nghttp3_mem_free2_debug(const nghttp3_free free_func, void *ptr, + void *user_data, const char *func, + const char *file, size_t line) { + fprintf(stderr, "free ptr=%p in %s at %s:%zu\n", ptr, func, file, line); + + free_func(ptr, user_data); +} + +void *nghttp3_mem_calloc_debug(const nghttp3_mem *mem, size_t nmemb, + size_t size, const char *func, const char *file, + size_t line) { + void *nptr = mem->calloc(nmemb, size, mem->user_data); + + fprintf(stderr, "calloc %p nmemb=%zu size=%zu in %s at %s:%zu\n", nptr, nmemb, + size, func, file, line); + + return nptr; +} + +void *nghttp3_mem_realloc_debug(const nghttp3_mem *mem, void *ptr, size_t size, + const char *func, const char *file, + size_t line) { + void *nptr = mem->realloc(ptr, size, mem->user_data); + + fprintf(stderr, "realloc %p ptr=%p size=%zu in %s at %s:%zu\n", nptr, ptr, + size, func, file, line); + + return nptr; +} +#endif /* MEMDEBUG */ diff --git a/lib/nghttp3_mem.h b/lib/nghttp3_mem.h new file mode 100644 index 0000000..d6c3ada --- /dev/null +++ b/lib/nghttp3_mem.h @@ -0,0 +1,80 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2014 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_MEM_H +#define NGHTTP3_MEM_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +/* Convenient wrapper functions to call allocator function in + |mem|. */ +#ifndef MEMDEBUG +void *nghttp3_mem_malloc(const nghttp3_mem *mem, size_t size); +void nghttp3_mem_free(const nghttp3_mem *mem, void *ptr); +void *nghttp3_mem_calloc(const nghttp3_mem *mem, size_t nmemb, size_t size); +void *nghttp3_mem_realloc(const nghttp3_mem *mem, void *ptr, size_t size); +#else /* MEMDEBUG */ +void *nghttp3_mem_malloc_debug(const nghttp3_mem *mem, size_t size, + const char *func, const char *file, size_t line); + +# define nghttp3_mem_malloc(MEM, SIZE) \ + nghttp3_mem_malloc_debug((MEM), (SIZE), __func__, __FILE__, __LINE__) + +void nghttp3_mem_free_debug(const nghttp3_mem *mem, void *ptr, const char *func, + const char *file, size_t line); + +# define nghttp3_mem_free(MEM, PTR) \ + nghttp3_mem_free_debug((MEM), (PTR), __func__, __FILE__, __LINE__) + +void nghttp3_mem_free2_debug(nghttp3_free free_func, void *ptr, void *user_data, + const char *func, const char *file, size_t line); + +# define nghttp3_mem_free2(FREE_FUNC, PTR, USER_DATA) \ + nghttp3_mem_free2_debug((FREE_FUNC), (PTR), (USER_DATA), __func__, \ + __FILE__, __LINE__) + +void *nghttp3_mem_calloc_debug(const nghttp3_mem *mem, size_t nmemb, + size_t size, const char *func, const char *file, + size_t line); + +# define nghttp3_mem_calloc(MEM, NMEMB, SIZE) \ + nghttp3_mem_calloc_debug((MEM), (NMEMB), (SIZE), __func__, __FILE__, \ + __LINE__) + +void *nghttp3_mem_realloc_debug(const nghttp3_mem *mem, void *ptr, size_t size, + const char *func, const char *file, + size_t line); + +# define nghttp3_mem_realloc(MEM, PTR, SIZE) \ + nghttp3_mem_realloc_debug((MEM), (PTR), (SIZE), __func__, __FILE__, \ + __LINE__) +#endif /* MEMDEBUG */ + +#endif /* NGHTTP3_MEM_H */ diff --git a/lib/nghttp3_objalloc.c b/lib/nghttp3_objalloc.c new file mode 100644 index 0000000..0c97860 --- /dev/null +++ b/lib/nghttp3_objalloc.c @@ -0,0 +1,41 @@ +/* + * nghttp3 + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_objalloc.h" + +void nghttp3_objalloc_init(nghttp3_objalloc *objalloc, size_t blklen, + const nghttp3_mem *mem) { + nghttp3_balloc_init(&objalloc->balloc, blklen, mem); + nghttp3_opl_init(&objalloc->opl); +} + +void nghttp3_objalloc_free(nghttp3_objalloc *objalloc) { + nghttp3_balloc_free(&objalloc->balloc); +} + +void nghttp3_objalloc_clear(nghttp3_objalloc *objalloc) { + nghttp3_opl_clear(&objalloc->opl); + nghttp3_balloc_clear(&objalloc->balloc); +} diff --git a/lib/nghttp3_objalloc.h b/lib/nghttp3_objalloc.h new file mode 100644 index 0000000..da39447 --- /dev/null +++ b/lib/nghttp3_objalloc.h @@ -0,0 +1,141 @@ +/* + * nghttp3 + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_OBJALLOC_H +#define NGHTTP3_OBJALLOC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_balloc.h" +#include "nghttp3_opl.h" +#include "nghttp3_macro.h" +#include "nghttp3_mem.h" + +/* + * nghttp3_objalloc combines nghttp3_balloc and nghttp3_opl, and + * provides an object pool with the custom allocator to reduce the + * allocation and deallocation overheads for small objects. + */ +typedef struct nghttp3_objalloc { + nghttp3_balloc balloc; + nghttp3_opl opl; +} nghttp3_objalloc; + +/* + * nghttp3_objalloc_init initializes |objalloc|. |blklen| is directly + * passed to nghttp3_balloc_init. + */ +void nghttp3_objalloc_init(nghttp3_objalloc *objalloc, size_t blklen, + const nghttp3_mem *mem); + +/* + * nghttp3_objalloc_free releases all allocated resources. + */ +void nghttp3_objalloc_free(nghttp3_objalloc *objalloc); + +/* + * nghttp3_objalloc_clear releases all allocated resources and + * initializes its state. + */ +void nghttp3_objalloc_clear(nghttp3_objalloc *objalloc); + +#ifndef NOMEMPOOL +# define nghttp3_objalloc_def(NAME, TYPE, OPLENTFIELD) \ + inline static void nghttp3_objalloc_##NAME##_init( \ + nghttp3_objalloc *objalloc, size_t nmemb, const nghttp3_mem *mem) { \ + nghttp3_objalloc_init( \ + objalloc, ((sizeof(TYPE) + 0xfu) & ~(uintptr_t)0xfu) * nmemb, mem); \ + } \ + \ + inline static TYPE *nghttp3_objalloc_##NAME##_get( \ + nghttp3_objalloc *objalloc) { \ + nghttp3_opl_entry *oplent = nghttp3_opl_pop(&objalloc->opl); \ + TYPE *obj; \ + int rv; \ + \ + if (!oplent) { \ + rv = nghttp3_balloc_get(&objalloc->balloc, (void **)&obj, \ + sizeof(TYPE)); \ + if (rv != 0) { \ + return NULL; \ + } \ + \ + return obj; \ + } \ + \ + return nghttp3_struct_of(oplent, TYPE, OPLENTFIELD); \ + } \ + \ + inline static TYPE *nghttp3_objalloc_##NAME##_len_get( \ + nghttp3_objalloc *objalloc, size_t len) { \ + nghttp3_opl_entry *oplent = nghttp3_opl_pop(&objalloc->opl); \ + TYPE *obj; \ + int rv; \ + \ + if (!oplent) { \ + rv = nghttp3_balloc_get(&objalloc->balloc, (void **)&obj, len); \ + if (rv != 0) { \ + return NULL; \ + } \ + \ + return obj; \ + } \ + \ + return nghttp3_struct_of(oplent, TYPE, OPLENTFIELD); \ + } \ + \ + inline static void nghttp3_objalloc_##NAME##_release( \ + nghttp3_objalloc *objalloc, TYPE *obj) { \ + nghttp3_opl_push(&objalloc->opl, &obj->OPLENTFIELD); \ + } +#else /* NOMEMPOOL */ +# define nghttp3_objalloc_def(NAME, TYPE, OPLENTFIELD) \ + inline static void nghttp3_objalloc_##NAME##_init( \ + nghttp3_objalloc *objalloc, size_t nmemb, const nghttp3_mem *mem) { \ + nghttp3_objalloc_init( \ + objalloc, ((sizeof(TYPE) + 0xfu) & ~(uintptr_t)0xfu) * nmemb, mem); \ + } \ + \ + inline static TYPE *nghttp3_objalloc_##NAME##_get( \ + nghttp3_objalloc *objalloc) { \ + return nghttp3_mem_malloc(objalloc->balloc.mem, sizeof(TYPE)); \ + } \ + \ + inline static TYPE *nghttp3_objalloc_##NAME##_len_get( \ + nghttp3_objalloc *objalloc, size_t len) { \ + return nghttp3_mem_malloc(objalloc->balloc.mem, len); \ + } \ + \ + inline static void nghttp3_objalloc_##NAME##_release( \ + nghttp3_objalloc *objalloc, TYPE *obj) { \ + nghttp3_mem_free(objalloc->balloc.mem, obj); \ + } +#endif /* NOMEMPOOL */ + +#endif /* NGHTTP3_OBJALLOC_H */ diff --git a/lib/nghttp3_opl.c b/lib/nghttp3_opl.c new file mode 100644 index 0000000..eb8ebdd --- /dev/null +++ b/lib/nghttp3_opl.c @@ -0,0 +1,47 @@ +/* + * nghttp3 + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_opl.h" + +void nghttp3_opl_init(nghttp3_opl *opl) { opl->head = NULL; } + +void nghttp3_opl_push(nghttp3_opl *opl, nghttp3_opl_entry *ent) { + ent->next = opl->head; + opl->head = ent; +} + +nghttp3_opl_entry *nghttp3_opl_pop(nghttp3_opl *opl) { + nghttp3_opl_entry *ent = opl->head; + + if (!ent) { + return NULL; + } + + opl->head = ent->next; + + return ent; +} + +void nghttp3_opl_clear(nghttp3_opl *opl) { opl->head = NULL; } diff --git a/lib/nghttp3_opl.h b/lib/nghttp3_opl.h new file mode 100644 index 0000000..8c8a4f2 --- /dev/null +++ b/lib/nghttp3_opl.h @@ -0,0 +1,66 @@ +/* + * nghttp3 + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_OPL_H +#define NGHTTP3_OPL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +typedef struct nghttp3_opl_entry nghttp3_opl_entry; + +struct nghttp3_opl_entry { + nghttp3_opl_entry *next; +}; + +/* + * nghttp3_opl is an object memory pool. + */ +typedef struct nghttp3_opl { + nghttp3_opl_entry *head; +} nghttp3_opl; + +/* + * nghttp3_opl_init initializes |opl|. + */ +void nghttp3_opl_init(nghttp3_opl *opl); + +/* + * nghttp3_opl_push inserts |ent| to |opl| head. + */ +void nghttp3_opl_push(nghttp3_opl *opl, nghttp3_opl_entry *ent); + +/* + * nghttp3_opl_pop removes the first nghttp3_opl_entry from |opl| and + * returns it. If |opl| does not have any entry, it returns NULL. + */ +nghttp3_opl_entry *nghttp3_opl_pop(nghttp3_opl *opl); + +void nghttp3_opl_clear(nghttp3_opl *opl); + +#endif /* NGHTTP3_OPL_H */ diff --git a/lib/nghttp3_pq.c b/lib/nghttp3_pq.c new file mode 100644 index 0000000..5d09050 --- /dev/null +++ b/lib/nghttp3_pq.c @@ -0,0 +1,168 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * 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 "nghttp3_pq.h" + +#include <assert.h> + +#include "nghttp3_macro.h" + +void nghttp3_pq_init(nghttp3_pq *pq, nghttp3_less less, + const nghttp3_mem *mem) { + pq->mem = mem; + pq->capacity = 0; + pq->q = NULL; + pq->length = 0; + pq->less = less; +} + +void nghttp3_pq_free(nghttp3_pq *pq) { + nghttp3_mem_free(pq->mem, pq->q); + pq->q = NULL; +} + +static void swap(nghttp3_pq *pq, size_t i, size_t j) { + nghttp3_pq_entry *a = pq->q[i]; + nghttp3_pq_entry *b = pq->q[j]; + + pq->q[i] = b; + b->index = i; + pq->q[j] = a; + a->index = j; +} + +static void bubble_up(nghttp3_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 nghttp3_pq_push(nghttp3_pq *pq, nghttp3_pq_entry *item) { + if (pq->capacity <= pq->length) { + void *nq; + size_t ncapacity; + + ncapacity = nghttp3_max(4, (pq->capacity * 2)); + + nq = nghttp3_mem_realloc(pq->mem, pq->q, + ncapacity * sizeof(nghttp3_pq_entry *)); + if (nq == NULL) { + return NGHTTP3_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; +} + +nghttp3_pq_entry *nghttp3_pq_top(const nghttp3_pq *pq) { + assert(pq->length); + return pq->q[0]; +} + +static void bubble_down(nghttp3_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 nghttp3_pq_pop(nghttp3_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 nghttp3_pq_remove(nghttp3_pq *pq, nghttp3_pq_entry *item) { + assert(pq->q[item->index] == item); + + if (item->index == 0) { + nghttp3_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 nghttp3_pq_empty(const nghttp3_pq *pq) { return pq->length == 0; } + +size_t nghttp3_pq_size(const nghttp3_pq *pq) { return pq->length; } + +int nghttp3_pq_each(const nghttp3_pq *pq, nghttp3_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; +} + +void nghttp3_pq_clear(nghttp3_pq *pq) { pq->length = 0; } diff --git a/lib/nghttp3_pq.h b/lib/nghttp3_pq.h new file mode 100644 index 0000000..c1a54f5 --- /dev/null +++ b/lib/nghttp3_pq.h @@ -0,0 +1,129 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * 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 NGHTTP3_PQ_H +#define NGHTTP3_PQ_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_mem.h" + +/* Implementation of priority queue */ + +/* NGHTTP3_PQ_BAD_INDEX is the priority queue index which indicates + that an entry is not queued. Assigning this value to + nghttp3_pq_entry.index can check that the entry is queued or not. */ +#define NGHTTP3_PQ_BAD_INDEX SIZE_MAX + +typedef struct nghttp3_pq_entry { + size_t index; +} nghttp3_pq_entry; + +/* "less" function, return nonzero if |lhs| is less than |rhs|. */ +typedef int (*nghttp3_less)(const nghttp3_pq_entry *lhs, + const nghttp3_pq_entry *rhs); + +typedef struct nghttp3_pq { + /* The pointer to the pointer to the item stored */ + nghttp3_pq_entry **q; + /* Memory allocator */ + const nghttp3_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 */ + nghttp3_less less; +} nghttp3_pq; + +/* + * Initializes priority queue |pq| with compare function |cmp|. + */ +void nghttp3_pq_init(nghttp3_pq *pq, nghttp3_less less, const nghttp3_mem *mem); + +/* + * Deallocates any resources allocated for |pq|. The stored items are + * not freed by this function. + */ +void nghttp3_pq_free(nghttp3_pq *pq); + +/* + * Adds |item| to the priority queue |pq|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_pq_push(nghttp3_pq *pq, nghttp3_pq_entry *item); + +/* + * Returns item at the top of the queue |pq|. It is undefined if the + * queue is empty. + */ +nghttp3_pq_entry *nghttp3_pq_top(const nghttp3_pq *pq); + +/* + * Pops item at the top of the queue |pq|. The popped item is not + * freed by this function. + */ +void nghttp3_pq_pop(nghttp3_pq *pq); + +/* + * Returns nonzero if the queue |pq| is empty. + */ +int nghttp3_pq_empty(const nghttp3_pq *pq); + +/* + * Returns the number of items in the queue |pq|. + */ +size_t nghttp3_pq_size(const nghttp3_pq *pq); + +typedef int (*nghttp3_pq_item_cb)(nghttp3_pq_entry *item, 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 nghttp3_pq_each(const nghttp3_pq *pq, nghttp3_pq_item_cb fun, void *arg); + +/* + * Removes |item| from priority queue. + */ +void nghttp3_pq_remove(nghttp3_pq *pq, nghttp3_pq_entry *item); + +void nghttp3_pq_clear(nghttp3_pq *pq); + +#endif /* NGHTTP3_PQ_H */ diff --git a/lib/nghttp3_qpack.c b/lib/nghttp3_qpack.c new file mode 100644 index 0000000..fece8f1 --- /dev/null +++ b/lib/nghttp3_qpack.c @@ -0,0 +1,4093 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2013 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_qpack.h" + +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "nghttp3_str.h" +#include "nghttp3_macro.h" +#include "nghttp3_debug.h" +#include "nghttp3_unreachable.h" + +/* NGHTTP3_QPACK_MAX_QPACK_STREAMS is the maximum number of concurrent + nghttp3_qpack_stream object to handle a client which never cancel + or acknowledge header block. After this limit, encoder stops using + dynamic table. */ +#define NGHTTP3_QPACK_MAX_QPACK_STREAMS 2000 + +/* Make scalar initialization form of nghttp3_qpack_static_entry */ +#define MAKE_STATIC_ENT(I, T, H) \ + { I, T, H } + +/* Generated by mkstatichdtbl.py */ +static nghttp3_qpack_static_entry token_stable[] = { + MAKE_STATIC_ENT(0, NGHTTP3_QPACK_TOKEN__AUTHORITY, 3153725150u), + MAKE_STATIC_ENT(15, NGHTTP3_QPACK_TOKEN__METHOD, 695666056u), + MAKE_STATIC_ENT(16, NGHTTP3_QPACK_TOKEN__METHOD, 695666056u), + MAKE_STATIC_ENT(17, NGHTTP3_QPACK_TOKEN__METHOD, 695666056u), + MAKE_STATIC_ENT(18, NGHTTP3_QPACK_TOKEN__METHOD, 695666056u), + MAKE_STATIC_ENT(19, NGHTTP3_QPACK_TOKEN__METHOD, 695666056u), + MAKE_STATIC_ENT(20, NGHTTP3_QPACK_TOKEN__METHOD, 695666056u), + MAKE_STATIC_ENT(21, NGHTTP3_QPACK_TOKEN__METHOD, 695666056u), + MAKE_STATIC_ENT(1, NGHTTP3_QPACK_TOKEN__PATH, 3292848686u), + MAKE_STATIC_ENT(22, NGHTTP3_QPACK_TOKEN__SCHEME, 2510477674u), + MAKE_STATIC_ENT(23, NGHTTP3_QPACK_TOKEN__SCHEME, 2510477674u), + MAKE_STATIC_ENT(24, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(25, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(26, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(27, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(28, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(63, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(64, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(65, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(66, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(67, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(68, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(69, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(70, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(71, NGHTTP3_QPACK_TOKEN__STATUS, 4000288983u), + MAKE_STATIC_ENT(29, NGHTTP3_QPACK_TOKEN_ACCEPT, 136609321u), + MAKE_STATIC_ENT(30, NGHTTP3_QPACK_TOKEN_ACCEPT, 136609321u), + MAKE_STATIC_ENT(31, NGHTTP3_QPACK_TOKEN_ACCEPT_ENCODING, 3379649177u), + MAKE_STATIC_ENT(72, NGHTTP3_QPACK_TOKEN_ACCEPT_LANGUAGE, 1979086614u), + MAKE_STATIC_ENT(32, NGHTTP3_QPACK_TOKEN_ACCEPT_RANGES, 1713753958u), + MAKE_STATIC_ENT(73, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_CREDENTIALS, + 901040780u), + MAKE_STATIC_ENT(74, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_CREDENTIALS, + 901040780u), + MAKE_STATIC_ENT(33, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS, + 1524311232u), + MAKE_STATIC_ENT(34, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS, + 1524311232u), + MAKE_STATIC_ENT(75, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS, + 1524311232u), + MAKE_STATIC_ENT(76, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS, + 2175229868u), + MAKE_STATIC_ENT(77, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS, + 2175229868u), + MAKE_STATIC_ENT(78, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS, + 2175229868u), + MAKE_STATIC_ENT(35, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN, + 2710797292u), + MAKE_STATIC_ENT(79, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_EXPOSE_HEADERS, + 2449824425u), + MAKE_STATIC_ENT(80, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_HEADERS, + 3599549072u), + MAKE_STATIC_ENT(81, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_METHOD, + 2417078055u), + MAKE_STATIC_ENT(82, NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_METHOD, + 2417078055u), + MAKE_STATIC_ENT(2, NGHTTP3_QPACK_TOKEN_AGE, 742476188u), + MAKE_STATIC_ENT(83, NGHTTP3_QPACK_TOKEN_ALT_SVC, 2148877059u), + MAKE_STATIC_ENT(84, NGHTTP3_QPACK_TOKEN_AUTHORIZATION, 2436257726u), + MAKE_STATIC_ENT(36, NGHTTP3_QPACK_TOKEN_CACHE_CONTROL, 1355326669u), + MAKE_STATIC_ENT(37, NGHTTP3_QPACK_TOKEN_CACHE_CONTROL, 1355326669u), + MAKE_STATIC_ENT(38, NGHTTP3_QPACK_TOKEN_CACHE_CONTROL, 1355326669u), + MAKE_STATIC_ENT(39, NGHTTP3_QPACK_TOKEN_CACHE_CONTROL, 1355326669u), + MAKE_STATIC_ENT(40, NGHTTP3_QPACK_TOKEN_CACHE_CONTROL, 1355326669u), + MAKE_STATIC_ENT(41, NGHTTP3_QPACK_TOKEN_CACHE_CONTROL, 1355326669u), + MAKE_STATIC_ENT(3, NGHTTP3_QPACK_TOKEN_CONTENT_DISPOSITION, 3889184348u), + MAKE_STATIC_ENT(42, NGHTTP3_QPACK_TOKEN_CONTENT_ENCODING, 65203592u), + MAKE_STATIC_ENT(43, NGHTTP3_QPACK_TOKEN_CONTENT_ENCODING, 65203592u), + MAKE_STATIC_ENT(4, NGHTTP3_QPACK_TOKEN_CONTENT_LENGTH, 1308181789u), + MAKE_STATIC_ENT(85, NGHTTP3_QPACK_TOKEN_CONTENT_SECURITY_POLICY, + 1569039836u), + MAKE_STATIC_ENT(44, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(45, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(46, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(47, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(48, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(49, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(50, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(51, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(52, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(53, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(54, NGHTTP3_QPACK_TOKEN_CONTENT_TYPE, 4244048277u), + MAKE_STATIC_ENT(5, NGHTTP3_QPACK_TOKEN_COOKIE, 2007449791u), + MAKE_STATIC_ENT(6, NGHTTP3_QPACK_TOKEN_DATE, 3564297305u), + MAKE_STATIC_ENT(86, NGHTTP3_QPACK_TOKEN_EARLY_DATA, 4080895051u), + MAKE_STATIC_ENT(7, NGHTTP3_QPACK_TOKEN_ETAG, 113792960u), + MAKE_STATIC_ENT(87, NGHTTP3_QPACK_TOKEN_EXPECT_CT, 1183214960u), + MAKE_STATIC_ENT(88, NGHTTP3_QPACK_TOKEN_FORWARDED, 1485178027u), + MAKE_STATIC_ENT(8, NGHTTP3_QPACK_TOKEN_IF_MODIFIED_SINCE, 2213050793u), + MAKE_STATIC_ENT(9, NGHTTP3_QPACK_TOKEN_IF_NONE_MATCH, 2536202615u), + MAKE_STATIC_ENT(89, NGHTTP3_QPACK_TOKEN_IF_RANGE, 2340978238u), + MAKE_STATIC_ENT(10, NGHTTP3_QPACK_TOKEN_LAST_MODIFIED, 3226950251u), + MAKE_STATIC_ENT(11, NGHTTP3_QPACK_TOKEN_LINK, 232457833u), + MAKE_STATIC_ENT(12, NGHTTP3_QPACK_TOKEN_LOCATION, 200649126u), + MAKE_STATIC_ENT(90, NGHTTP3_QPACK_TOKEN_ORIGIN, 3649018447u), + MAKE_STATIC_ENT(91, NGHTTP3_QPACK_TOKEN_PURPOSE, 4212263681u), + MAKE_STATIC_ENT(55, NGHTTP3_QPACK_TOKEN_RANGE, 4208725202u), + MAKE_STATIC_ENT(13, NGHTTP3_QPACK_TOKEN_REFERER, 3969579366u), + MAKE_STATIC_ENT(92, NGHTTP3_QPACK_TOKEN_SERVER, 1085029842u), + MAKE_STATIC_ENT(14, NGHTTP3_QPACK_TOKEN_SET_COOKIE, 1848371000u), + MAKE_STATIC_ENT(56, NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY, + 4138147361u), + MAKE_STATIC_ENT(57, NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY, + 4138147361u), + MAKE_STATIC_ENT(58, NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY, + 4138147361u), + MAKE_STATIC_ENT(93, NGHTTP3_QPACK_TOKEN_TIMING_ALLOW_ORIGIN, 2432297564u), + MAKE_STATIC_ENT(94, NGHTTP3_QPACK_TOKEN_UPGRADE_INSECURE_REQUESTS, + 2479169413u), + MAKE_STATIC_ENT(95, NGHTTP3_QPACK_TOKEN_USER_AGENT, 606444526u), + MAKE_STATIC_ENT(59, NGHTTP3_QPACK_TOKEN_VARY, 1085005381u), + MAKE_STATIC_ENT(60, NGHTTP3_QPACK_TOKEN_VARY, 1085005381u), + MAKE_STATIC_ENT(61, NGHTTP3_QPACK_TOKEN_X_CONTENT_TYPE_OPTIONS, + 3644557769u), + MAKE_STATIC_ENT(96, NGHTTP3_QPACK_TOKEN_X_FORWARDED_FOR, 2914187656u), + MAKE_STATIC_ENT(97, NGHTTP3_QPACK_TOKEN_X_FRAME_OPTIONS, 3993834824u), + MAKE_STATIC_ENT(98, NGHTTP3_QPACK_TOKEN_X_FRAME_OPTIONS, 3993834824u), + MAKE_STATIC_ENT(62, NGHTTP3_QPACK_TOKEN_X_XSS_PROTECTION, 2501058888u), +}; + +/* Make scalar initialization form of nghttp3_qpack_static_entry */ +#define MAKE_STATIC_HD(N, V, T) \ + { \ + {NULL, (uint8_t *)(N), sizeof((N)) - 1, -1}, \ + {NULL, (uint8_t *)(V), sizeof((V)) - 1, -1}, T \ + } + +static nghttp3_qpack_static_header stable[] = { + MAKE_STATIC_HD(":authority", "", NGHTTP3_QPACK_TOKEN__AUTHORITY), + MAKE_STATIC_HD(":path", "/", NGHTTP3_QPACK_TOKEN__PATH), + MAKE_STATIC_HD("age", "0", NGHTTP3_QPACK_TOKEN_AGE), + MAKE_STATIC_HD("content-disposition", "", + NGHTTP3_QPACK_TOKEN_CONTENT_DISPOSITION), + MAKE_STATIC_HD("content-length", "0", NGHTTP3_QPACK_TOKEN_CONTENT_LENGTH), + MAKE_STATIC_HD("cookie", "", NGHTTP3_QPACK_TOKEN_COOKIE), + MAKE_STATIC_HD("date", "", NGHTTP3_QPACK_TOKEN_DATE), + MAKE_STATIC_HD("etag", "", NGHTTP3_QPACK_TOKEN_ETAG), + MAKE_STATIC_HD("if-modified-since", "", + NGHTTP3_QPACK_TOKEN_IF_MODIFIED_SINCE), + MAKE_STATIC_HD("if-none-match", "", NGHTTP3_QPACK_TOKEN_IF_NONE_MATCH), + MAKE_STATIC_HD("last-modified", "", NGHTTP3_QPACK_TOKEN_LAST_MODIFIED), + MAKE_STATIC_HD("link", "", NGHTTP3_QPACK_TOKEN_LINK), + MAKE_STATIC_HD("location", "", NGHTTP3_QPACK_TOKEN_LOCATION), + MAKE_STATIC_HD("referer", "", NGHTTP3_QPACK_TOKEN_REFERER), + MAKE_STATIC_HD("set-cookie", "", NGHTTP3_QPACK_TOKEN_SET_COOKIE), + MAKE_STATIC_HD(":method", "CONNECT", NGHTTP3_QPACK_TOKEN__METHOD), + MAKE_STATIC_HD(":method", "DELETE", NGHTTP3_QPACK_TOKEN__METHOD), + MAKE_STATIC_HD(":method", "GET", NGHTTP3_QPACK_TOKEN__METHOD), + MAKE_STATIC_HD(":method", "HEAD", NGHTTP3_QPACK_TOKEN__METHOD), + MAKE_STATIC_HD(":method", "OPTIONS", NGHTTP3_QPACK_TOKEN__METHOD), + MAKE_STATIC_HD(":method", "POST", NGHTTP3_QPACK_TOKEN__METHOD), + MAKE_STATIC_HD(":method", "PUT", NGHTTP3_QPACK_TOKEN__METHOD), + MAKE_STATIC_HD(":scheme", "http", NGHTTP3_QPACK_TOKEN__SCHEME), + MAKE_STATIC_HD(":scheme", "https", NGHTTP3_QPACK_TOKEN__SCHEME), + MAKE_STATIC_HD(":status", "103", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "200", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "304", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "404", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "503", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD("accept", "*/*", NGHTTP3_QPACK_TOKEN_ACCEPT), + MAKE_STATIC_HD("accept", "application/dns-message", + NGHTTP3_QPACK_TOKEN_ACCEPT), + MAKE_STATIC_HD("accept-encoding", "gzip, deflate, br", + NGHTTP3_QPACK_TOKEN_ACCEPT_ENCODING), + MAKE_STATIC_HD("accept-ranges", "bytes", NGHTTP3_QPACK_TOKEN_ACCEPT_RANGES), + MAKE_STATIC_HD("access-control-allow-headers", "cache-control", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS), + MAKE_STATIC_HD("access-control-allow-headers", "content-type", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS), + MAKE_STATIC_HD("access-control-allow-origin", "*", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN), + MAKE_STATIC_HD("cache-control", "max-age=0", + NGHTTP3_QPACK_TOKEN_CACHE_CONTROL), + MAKE_STATIC_HD("cache-control", "max-age=2592000", + NGHTTP3_QPACK_TOKEN_CACHE_CONTROL), + MAKE_STATIC_HD("cache-control", "max-age=604800", + NGHTTP3_QPACK_TOKEN_CACHE_CONTROL), + MAKE_STATIC_HD("cache-control", "no-cache", + NGHTTP3_QPACK_TOKEN_CACHE_CONTROL), + MAKE_STATIC_HD("cache-control", "no-store", + NGHTTP3_QPACK_TOKEN_CACHE_CONTROL), + MAKE_STATIC_HD("cache-control", "public, max-age=31536000", + NGHTTP3_QPACK_TOKEN_CACHE_CONTROL), + MAKE_STATIC_HD("content-encoding", "br", + NGHTTP3_QPACK_TOKEN_CONTENT_ENCODING), + MAKE_STATIC_HD("content-encoding", "gzip", + NGHTTP3_QPACK_TOKEN_CONTENT_ENCODING), + MAKE_STATIC_HD("content-type", "application/dns-message", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "application/javascript", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "application/json", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "application/x-www-form-urlencoded", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "image/gif", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "image/jpeg", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "image/png", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "text/css", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "text/html; charset=utf-8", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "text/plain", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("content-type", "text/plain;charset=utf-8", + NGHTTP3_QPACK_TOKEN_CONTENT_TYPE), + MAKE_STATIC_HD("range", "bytes=0-", NGHTTP3_QPACK_TOKEN_RANGE), + MAKE_STATIC_HD("strict-transport-security", "max-age=31536000", + NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY), + MAKE_STATIC_HD("strict-transport-security", + "max-age=31536000; includesubdomains", + NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY), + MAKE_STATIC_HD("strict-transport-security", + "max-age=31536000; includesubdomains; preload", + NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY), + MAKE_STATIC_HD("vary", "accept-encoding", NGHTTP3_QPACK_TOKEN_VARY), + MAKE_STATIC_HD("vary", "origin", NGHTTP3_QPACK_TOKEN_VARY), + MAKE_STATIC_HD("x-content-type-options", "nosniff", + NGHTTP3_QPACK_TOKEN_X_CONTENT_TYPE_OPTIONS), + MAKE_STATIC_HD("x-xss-protection", "1; mode=block", + NGHTTP3_QPACK_TOKEN_X_XSS_PROTECTION), + MAKE_STATIC_HD(":status", "100", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "204", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "206", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "302", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "400", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "403", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "421", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "425", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD(":status", "500", NGHTTP3_QPACK_TOKEN__STATUS), + MAKE_STATIC_HD("accept-language", "", NGHTTP3_QPACK_TOKEN_ACCEPT_LANGUAGE), + MAKE_STATIC_HD("access-control-allow-credentials", "FALSE", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_CREDENTIALS), + MAKE_STATIC_HD("access-control-allow-credentials", "TRUE", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_CREDENTIALS), + MAKE_STATIC_HD("access-control-allow-headers", "*", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS), + MAKE_STATIC_HD("access-control-allow-methods", "get", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS), + MAKE_STATIC_HD("access-control-allow-methods", "get, post, options", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS), + MAKE_STATIC_HD("access-control-allow-methods", "options", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS), + MAKE_STATIC_HD("access-control-expose-headers", "content-length", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_EXPOSE_HEADERS), + MAKE_STATIC_HD("access-control-request-headers", "content-type", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_HEADERS), + MAKE_STATIC_HD("access-control-request-method", "get", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_METHOD), + MAKE_STATIC_HD("access-control-request-method", "post", + NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_METHOD), + MAKE_STATIC_HD("alt-svc", "clear", NGHTTP3_QPACK_TOKEN_ALT_SVC), + MAKE_STATIC_HD("authorization", "", NGHTTP3_QPACK_TOKEN_AUTHORIZATION), + MAKE_STATIC_HD("content-security-policy", + "script-src 'none'; object-src 'none'; base-uri 'none'", + NGHTTP3_QPACK_TOKEN_CONTENT_SECURITY_POLICY), + MAKE_STATIC_HD("early-data", "1", NGHTTP3_QPACK_TOKEN_EARLY_DATA), + MAKE_STATIC_HD("expect-ct", "", NGHTTP3_QPACK_TOKEN_EXPECT_CT), + MAKE_STATIC_HD("forwarded", "", NGHTTP3_QPACK_TOKEN_FORWARDED), + MAKE_STATIC_HD("if-range", "", NGHTTP3_QPACK_TOKEN_IF_RANGE), + MAKE_STATIC_HD("origin", "", NGHTTP3_QPACK_TOKEN_ORIGIN), + MAKE_STATIC_HD("purpose", "prefetch", NGHTTP3_QPACK_TOKEN_PURPOSE), + MAKE_STATIC_HD("server", "", NGHTTP3_QPACK_TOKEN_SERVER), + MAKE_STATIC_HD("timing-allow-origin", "*", + NGHTTP3_QPACK_TOKEN_TIMING_ALLOW_ORIGIN), + MAKE_STATIC_HD("upgrade-insecure-requests", "1", + NGHTTP3_QPACK_TOKEN_UPGRADE_INSECURE_REQUESTS), + MAKE_STATIC_HD("user-agent", "", NGHTTP3_QPACK_TOKEN_USER_AGENT), + MAKE_STATIC_HD("x-forwarded-for", "", NGHTTP3_QPACK_TOKEN_X_FORWARDED_FOR), + MAKE_STATIC_HD("x-frame-options", "deny", + NGHTTP3_QPACK_TOKEN_X_FRAME_OPTIONS), + MAKE_STATIC_HD("x-frame-options", "sameorigin", + NGHTTP3_QPACK_TOKEN_X_FRAME_OPTIONS), +}; + +static int memeq(const void *s1, const void *s2, size_t n) { + return n == 0 || memcmp(s1, s2, n) == 0; +} + +/* Generated by genlibtokenlookup.py */ +static int32_t qpack_lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) { + case 2: + switch (name[1]) { + case 'e': + if (memeq("t", name, 1)) { + return NGHTTP3_QPACK_TOKEN_TE; + } + break; + } + break; + case 3: + switch (name[2]) { + case 'e': + if (memeq("ag", name, 2)) { + return NGHTTP3_QPACK_TOKEN_AGE; + } + break; + } + break; + case 4: + switch (name[3]) { + case 'e': + if (memeq("dat", name, 3)) { + return NGHTTP3_QPACK_TOKEN_DATE; + } + break; + case 'g': + if (memeq("eta", name, 3)) { + return NGHTTP3_QPACK_TOKEN_ETAG; + } + break; + case 'k': + if (memeq("lin", name, 3)) { + return NGHTTP3_QPACK_TOKEN_LINK; + } + break; + case 't': + if (memeq("hos", name, 3)) { + return NGHTTP3_QPACK_TOKEN_HOST; + } + break; + case 'y': + if (memeq("var", name, 3)) { + return NGHTTP3_QPACK_TOKEN_VARY; + } + break; + } + break; + case 5: + switch (name[4]) { + case 'e': + if (memeq("rang", name, 4)) { + return NGHTTP3_QPACK_TOKEN_RANGE; + } + break; + case 'h': + if (memeq(":pat", name, 4)) { + return NGHTTP3_QPACK_TOKEN__PATH; + } + break; + } + break; + case 6: + switch (name[5]) { + case 'e': + if (memeq("cooki", name, 5)) { + return NGHTTP3_QPACK_TOKEN_COOKIE; + } + break; + case 'n': + if (memeq("origi", name, 5)) { + return NGHTTP3_QPACK_TOKEN_ORIGIN; + } + break; + case 'r': + if (memeq("serve", name, 5)) { + return NGHTTP3_QPACK_TOKEN_SERVER; + } + break; + case 't': + if (memeq("accep", name, 5)) { + return NGHTTP3_QPACK_TOKEN_ACCEPT; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'c': + if (memeq("alt-sv", name, 6)) { + return NGHTTP3_QPACK_TOKEN_ALT_SVC; + } + break; + case 'd': + if (memeq(":metho", name, 6)) { + return NGHTTP3_QPACK_TOKEN__METHOD; + } + break; + case 'e': + if (memeq(":schem", name, 6)) { + return NGHTTP3_QPACK_TOKEN__SCHEME; + } + if (memeq("purpos", name, 6)) { + return NGHTTP3_QPACK_TOKEN_PURPOSE; + } + if (memeq("upgrad", name, 6)) { + return NGHTTP3_QPACK_TOKEN_UPGRADE; + } + break; + case 'r': + if (memeq("refere", name, 6)) { + return NGHTTP3_QPACK_TOKEN_REFERER; + } + break; + case 's': + if (memeq(":statu", name, 6)) { + return NGHTTP3_QPACK_TOKEN__STATUS; + } + break; + } + break; + case 8: + switch (name[7]) { + case 'e': + if (memeq("if-rang", name, 7)) { + return NGHTTP3_QPACK_TOKEN_IF_RANGE; + } + break; + case 'n': + if (memeq("locatio", name, 7)) { + return NGHTTP3_QPACK_TOKEN_LOCATION; + } + break; + case 'y': + if (memeq("priorit", name, 7)) { + return NGHTTP3_QPACK_TOKEN_PRIORITY; + } + break; + } + break; + case 9: + switch (name[8]) { + case 'd': + if (memeq("forwarde", name, 8)) { + return NGHTTP3_QPACK_TOKEN_FORWARDED; + } + break; + case 'l': + if (memeq(":protoco", name, 8)) { + return NGHTTP3_QPACK_TOKEN__PROTOCOL; + } + break; + case 't': + if (memeq("expect-c", name, 8)) { + return NGHTTP3_QPACK_TOKEN_EXPECT_CT; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'a': + if (memeq("early-dat", name, 9)) { + return NGHTTP3_QPACK_TOKEN_EARLY_DATA; + } + break; + case 'e': + if (memeq("keep-aliv", name, 9)) { + return NGHTTP3_QPACK_TOKEN_KEEP_ALIVE; + } + if (memeq("set-cooki", name, 9)) { + return NGHTTP3_QPACK_TOKEN_SET_COOKIE; + } + break; + case 'n': + if (memeq("connectio", name, 9)) { + return NGHTTP3_QPACK_TOKEN_CONNECTION; + } + break; + case 't': + if (memeq("user-agen", name, 9)) { + return NGHTTP3_QPACK_TOKEN_USER_AGENT; + } + break; + case 'y': + if (memeq(":authorit", name, 9)) { + return NGHTTP3_QPACK_TOKEN__AUTHORITY; + } + break; + } + break; + case 12: + switch (name[11]) { + case 'e': + if (memeq("content-typ", name, 11)) { + return NGHTTP3_QPACK_TOKEN_CONTENT_TYPE; + } + break; + } + break; + case 13: + switch (name[12]) { + case 'd': + if (memeq("last-modifie", name, 12)) { + return NGHTTP3_QPACK_TOKEN_LAST_MODIFIED; + } + break; + case 'h': + if (memeq("if-none-matc", name, 12)) { + return NGHTTP3_QPACK_TOKEN_IF_NONE_MATCH; + } + break; + case 'l': + if (memeq("cache-contro", name, 12)) { + return NGHTTP3_QPACK_TOKEN_CACHE_CONTROL; + } + break; + case 'n': + if (memeq("authorizatio", name, 12)) { + return NGHTTP3_QPACK_TOKEN_AUTHORIZATION; + } + break; + case 's': + if (memeq("accept-range", name, 12)) { + return NGHTTP3_QPACK_TOKEN_ACCEPT_RANGES; + } + break; + } + break; + case 14: + switch (name[13]) { + case 'h': + if (memeq("content-lengt", name, 13)) { + return NGHTTP3_QPACK_TOKEN_CONTENT_LENGTH; + } + break; + } + break; + case 15: + switch (name[14]) { + case 'e': + if (memeq("accept-languag", name, 14)) { + return NGHTTP3_QPACK_TOKEN_ACCEPT_LANGUAGE; + } + break; + case 'g': + if (memeq("accept-encodin", name, 14)) { + return NGHTTP3_QPACK_TOKEN_ACCEPT_ENCODING; + } + break; + case 'r': + if (memeq("x-forwarded-fo", name, 14)) { + return NGHTTP3_QPACK_TOKEN_X_FORWARDED_FOR; + } + break; + case 's': + if (memeq("x-frame-option", name, 14)) { + return NGHTTP3_QPACK_TOKEN_X_FRAME_OPTIONS; + } + break; + } + break; + case 16: + switch (name[15]) { + case 'g': + if (memeq("content-encodin", name, 15)) { + return NGHTTP3_QPACK_TOKEN_CONTENT_ENCODING; + } + break; + case 'n': + if (memeq("proxy-connectio", name, 15)) { + return NGHTTP3_QPACK_TOKEN_PROXY_CONNECTION; + } + if (memeq("x-xss-protectio", name, 15)) { + return NGHTTP3_QPACK_TOKEN_X_XSS_PROTECTION; + } + break; + } + break; + case 17: + switch (name[16]) { + case 'e': + if (memeq("if-modified-sinc", name, 16)) { + return NGHTTP3_QPACK_TOKEN_IF_MODIFIED_SINCE; + } + break; + case 'g': + if (memeq("transfer-encodin", name, 16)) { + return NGHTTP3_QPACK_TOKEN_TRANSFER_ENCODING; + } + break; + } + break; + case 19: + switch (name[18]) { + case 'n': + if (memeq("content-dispositio", name, 18)) { + return NGHTTP3_QPACK_TOKEN_CONTENT_DISPOSITION; + } + if (memeq("timing-allow-origi", name, 18)) { + return NGHTTP3_QPACK_TOKEN_TIMING_ALLOW_ORIGIN; + } + break; + } + break; + case 22: + switch (name[21]) { + case 's': + if (memeq("x-content-type-option", name, 21)) { + return NGHTTP3_QPACK_TOKEN_X_CONTENT_TYPE_OPTIONS; + } + break; + } + break; + case 23: + switch (name[22]) { + case 'y': + if (memeq("content-security-polic", name, 22)) { + return NGHTTP3_QPACK_TOKEN_CONTENT_SECURITY_POLICY; + } + break; + } + break; + case 25: + switch (name[24]) { + case 's': + if (memeq("upgrade-insecure-request", name, 24)) { + return NGHTTP3_QPACK_TOKEN_UPGRADE_INSECURE_REQUESTS; + } + break; + case 'y': + if (memeq("strict-transport-securit", name, 24)) { + return NGHTTP3_QPACK_TOKEN_STRICT_TRANSPORT_SECURITY; + } + break; + } + break; + case 27: + switch (name[26]) { + case 'n': + if (memeq("access-control-allow-origi", name, 26)) { + return NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN; + } + break; + } + break; + case 28: + switch (name[27]) { + case 's': + if (memeq("access-control-allow-header", name, 27)) { + return NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_HEADERS; + } + if (memeq("access-control-allow-method", name, 27)) { + return NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_METHODS; + } + break; + } + break; + case 29: + switch (name[28]) { + case 'd': + if (memeq("access-control-request-metho", name, 28)) { + return NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_METHOD; + } + break; + case 's': + if (memeq("access-control-expose-header", name, 28)) { + return NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_EXPOSE_HEADERS; + } + break; + } + break; + case 30: + switch (name[29]) { + case 's': + if (memeq("access-control-request-header", name, 29)) { + return NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_REQUEST_HEADERS; + } + break; + } + break; + case 32: + switch (name[31]) { + case 's': + if (memeq("access-control-allow-credential", name, 31)) { + return NGHTTP3_QPACK_TOKEN_ACCESS_CONTROL_ALLOW_CREDENTIALS; + } + break; + } + break; + } + return -1; +} + +static size_t table_space(size_t namelen, size_t valuelen) { + return NGHTTP3_QPACK_ENTRY_OVERHEAD + namelen + valuelen; +} + +static int qpack_nv_name_eq(const nghttp3_qpack_nv *a, const nghttp3_nv *b) { + return a->name->len == b->namelen && + memeq(a->name->base, b->name, b->namelen); +} + +static int qpack_nv_value_eq(const nghttp3_qpack_nv *a, const nghttp3_nv *b) { + return a->value->len == b->valuelen && + memeq(a->value->base, b->value, b->valuelen); +} + +static void qpack_map_init(nghttp3_qpack_map *map) { + memset(map, 0, sizeof(nghttp3_qpack_map)); +} + +static void qpack_map_insert(nghttp3_qpack_map *map, nghttp3_qpack_entry *ent) { + nghttp3_qpack_entry **bucket; + + bucket = &map->table[ent->hash & (NGHTTP3_QPACK_MAP_SIZE - 1)]; + + if (*bucket == NULL) { + *bucket = ent; + return; + } + + /* larger absidx is linked near the root */ + ent->map_next = *bucket; + *bucket = ent; +} + +static void qpack_map_remove(nghttp3_qpack_map *map, nghttp3_qpack_entry *ent) { + nghttp3_qpack_entry **dst; + + dst = &map->table[ent->hash & (NGHTTP3_QPACK_MAP_SIZE - 1)]; + + for (; *dst; dst = &(*dst)->map_next) { + if (*dst != ent) { + continue; + } + + *dst = ent->map_next; + ent->map_next = NULL; + return; + } +} + +/* + * qpack_context_can_reference returns nonzero if dynamic table entry + * at |absidx| can be referenced. In other words, it is within + * ctx->max_dtable_capacity. + */ +static int qpack_context_can_reference(nghttp3_qpack_context *ctx, + uint64_t absidx) { + nghttp3_qpack_entry *ent = nghttp3_qpack_context_dtable_get(ctx, absidx); + return ctx->dtable_sum - ent->sum <= ctx->max_dtable_capacity; +} + +/* |*ppb_match| (post-base match), if it is not NULL, is always exact + match. */ +static void encoder_qpack_map_find(nghttp3_qpack_encoder *encoder, + int *exact_match, + nghttp3_qpack_entry **pmatch, + nghttp3_qpack_entry **ppb_match, + const nghttp3_nv *nv, int32_t token, + uint32_t hash, uint64_t krcnt, + int allow_blocking, int name_only) { + nghttp3_qpack_entry *p; + + *exact_match = 0; + *pmatch = NULL; + *ppb_match = NULL; + + for (p = encoder->dtable_map.table[hash & (NGHTTP3_QPACK_MAP_SIZE - 1)]; p; + p = p->map_next) { + if (token != p->nv.token || + (token == -1 && (hash != p->hash || !qpack_nv_name_eq(&p->nv, nv))) || + !qpack_context_can_reference(&encoder->ctx, p->absidx)) { + continue; + } + if (allow_blocking || p->absidx + 1 <= krcnt) { + if (!*pmatch) { + *pmatch = p; + if (name_only) { + return; + } + } + if (qpack_nv_value_eq(&p->nv, nv)) { + *pmatch = p; + *exact_match = 1; + return; + } + } else if (!*ppb_match && qpack_nv_value_eq(&p->nv, nv)) { + *ppb_match = p; + } + } +} + +/* + * qpack_context_init initializes |ctx|. |hard_max_dtable_capacity| + * is the upper bound of the dynamic table capacity. |mem| is a + * memory allocator. + * + * The maximum dynamic table size is governed by + * ctx->max_dtable_capacity and it is initialized to 0. + * |hard_max_dtable_capacity| is the upper bound of + * ctx->max_dtable_capacity. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +static int qpack_context_init(nghttp3_qpack_context *ctx, + size_t hard_max_dtable_capacity, + size_t max_blocked_streams, + const nghttp3_mem *mem) { + int rv; + size_t len = 4096 / NGHTTP3_QPACK_ENTRY_OVERHEAD; + size_t len2; + + for (len2 = 1; len2 < len; len2 <<= 1) + ; + + rv = nghttp3_ringbuf_init(&ctx->dtable, len2, sizeof(nghttp3_qpack_entry *), + mem); + if (rv != 0) { + return rv; + } + + ctx->mem = mem; + ctx->dtable_size = 0; + ctx->dtable_sum = 0; + ctx->hard_max_dtable_capacity = hard_max_dtable_capacity; + ctx->max_dtable_capacity = 0; + ctx->max_blocked_streams = max_blocked_streams; + ctx->next_absidx = 0; + ctx->bad = 0; + + return 0; +} + +static void qpack_context_free(nghttp3_qpack_context *ctx) { + nghttp3_qpack_entry *ent; + size_t i, len = nghttp3_ringbuf_len(&ctx->dtable); + + for (i = 0; i < len; ++i) { + ent = *(nghttp3_qpack_entry **)nghttp3_ringbuf_get(&ctx->dtable, i); + nghttp3_qpack_entry_free(ent); + nghttp3_mem_free(ctx->mem, ent); + } + nghttp3_ringbuf_free(&ctx->dtable); +} + +static int ref_min_cnt_less(const nghttp3_pq_entry *lhsx, + const nghttp3_pq_entry *rhsx) { + nghttp3_qpack_header_block_ref *lhs = + nghttp3_struct_of(lhsx, nghttp3_qpack_header_block_ref, min_cnts_pe); + nghttp3_qpack_header_block_ref *rhs = + nghttp3_struct_of(rhsx, nghttp3_qpack_header_block_ref, min_cnts_pe); + + return lhs->min_cnt < rhs->min_cnt; +} + +typedef struct nghttp3_blocked_streams_key { + uint64_t max_cnt; + uint64_t id; +} nghttp3_blocked_streams_key; + +static int max_cnt_greater(const nghttp3_ksl_key *lhs, + const nghttp3_ksl_key *rhs) { + const nghttp3_blocked_streams_key *a = lhs; + const nghttp3_blocked_streams_key *b = rhs; + return a->max_cnt > b->max_cnt || (a->max_cnt == b->max_cnt && a->id < b->id); +} + +int nghttp3_qpack_encoder_init(nghttp3_qpack_encoder *encoder, + size_t hard_max_dtable_capacity, + const nghttp3_mem *mem) { + int rv; + + rv = qpack_context_init(&encoder->ctx, hard_max_dtable_capacity, 0, mem); + if (rv != 0) { + return rv; + } + + nghttp3_map_init(&encoder->streams, mem); + + nghttp3_ksl_init(&encoder->blocked_streams, max_cnt_greater, + sizeof(nghttp3_blocked_streams_key), mem); + + qpack_map_init(&encoder->dtable_map); + nghttp3_pq_init(&encoder->min_cnts, ref_min_cnt_less, mem); + + encoder->krcnt = 0; + encoder->state = NGHTTP3_QPACK_DS_STATE_OPCODE; + encoder->opcode = 0; + encoder->min_dtable_update = SIZE_MAX; + encoder->last_max_dtable_update = 0; + encoder->flags = NGHTTP3_QPACK_ENCODER_FLAG_NONE; + + nghttp3_qpack_read_state_reset(&encoder->rstate); + + return 0; +} + +static int map_stream_free(void *data, void *ptr) { + const nghttp3_mem *mem = ptr; + nghttp3_qpack_stream *stream = data; + nghttp3_qpack_stream_del(stream, mem); + return 0; +} + +void nghttp3_qpack_encoder_free(nghttp3_qpack_encoder *encoder) { + nghttp3_pq_free(&encoder->min_cnts); + nghttp3_ksl_free(&encoder->blocked_streams); + nghttp3_map_each_free(&encoder->streams, map_stream_free, + (void *)encoder->ctx.mem); + nghttp3_map_free(&encoder->streams); + qpack_context_free(&encoder->ctx); +} + +void nghttp3_qpack_encoder_set_max_dtable_capacity( + nghttp3_qpack_encoder *encoder, size_t max_dtable_capacity) { + max_dtable_capacity = + nghttp3_min(max_dtable_capacity, encoder->ctx.hard_max_dtable_capacity); + + if (encoder->ctx.max_dtable_capacity == max_dtable_capacity) { + return; + } + + encoder->flags |= NGHTTP3_QPACK_ENCODER_FLAG_PENDING_SET_DTABLE_CAP; + + if (encoder->min_dtable_update > max_dtable_capacity) { + encoder->min_dtable_update = max_dtable_capacity; + encoder->ctx.max_dtable_capacity = max_dtable_capacity; + } + encoder->last_max_dtable_update = max_dtable_capacity; +} + +void nghttp3_qpack_encoder_set_max_blocked_streams( + nghttp3_qpack_encoder *encoder, size_t max_blocked_streams) { + encoder->ctx.max_blocked_streams = max_blocked_streams; +} + +uint64_t nghttp3_qpack_encoder_get_min_cnt(nghttp3_qpack_encoder *encoder) { + assert(!nghttp3_pq_empty(&encoder->min_cnts)); + + return nghttp3_struct_of(nghttp3_pq_top(&encoder->min_cnts), + nghttp3_qpack_header_block_ref, min_cnts_pe) + ->min_cnt; +} + +void nghttp3_qpack_encoder_shrink_dtable(nghttp3_qpack_encoder *encoder) { + nghttp3_ringbuf *dtable = &encoder->ctx.dtable; + const nghttp3_mem *mem = encoder->ctx.mem; + uint64_t min_cnt = UINT64_MAX; + size_t len; + nghttp3_qpack_entry *ent; + + if (encoder->ctx.dtable_size <= encoder->ctx.max_dtable_capacity) { + return; + } + + if (!nghttp3_pq_empty(&encoder->min_cnts)) { + min_cnt = nghttp3_qpack_encoder_get_min_cnt(encoder); + } + + for (; encoder->ctx.dtable_size > encoder->ctx.max_dtable_capacity;) { + len = nghttp3_ringbuf_len(dtable); + ent = *(nghttp3_qpack_entry **)nghttp3_ringbuf_get(dtable, len - 1); + if (ent->absidx + 1 == min_cnt) { + return; + } + + encoder->ctx.dtable_size -= + table_space(ent->nv.name->len, ent->nv.value->len); + + nghttp3_ringbuf_pop_back(dtable); + qpack_map_remove(&encoder->dtable_map, ent); + + nghttp3_qpack_entry_free(ent); + nghttp3_mem_free(mem, ent); + } +} + +/* + * qpack_encoder_add_stream_ref adds another dynamic table reference + * to a stream denoted by |stream_id|. |stream| must be NULL if no + * stream object is not found for the given stream ID. |max_cnt| and + * |min_cnt| is the maximum and minimum insert count it references + * respectively. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +static int qpack_encoder_add_stream_ref(nghttp3_qpack_encoder *encoder, + int64_t stream_id, + nghttp3_qpack_stream *stream, + uint64_t max_cnt, uint64_t min_cnt) { + nghttp3_qpack_header_block_ref *ref; + const nghttp3_mem *mem = encoder->ctx.mem; + uint64_t prev_max_cnt = 0; + int rv; + + if (stream == NULL) { + rv = nghttp3_qpack_stream_new(&stream, stream_id, mem); + if (rv != 0) { + assert(rv == NGHTTP3_ERR_NOMEM); + return rv; + } + rv = nghttp3_map_insert(&encoder->streams, + (nghttp3_map_key_type)stream->stream_id, stream); + if (rv != 0) { + assert(rv == NGHTTP3_ERR_NOMEM); + nghttp3_qpack_stream_del(stream, mem); + return rv; + } + } else { + prev_max_cnt = nghttp3_qpack_stream_get_max_cnt(stream); + if (nghttp3_qpack_encoder_stream_is_blocked(encoder, stream) && + max_cnt > prev_max_cnt) { + nghttp3_qpack_encoder_unblock_stream(encoder, stream); + } + } + + rv = nghttp3_qpack_header_block_ref_new(&ref, max_cnt, min_cnt, mem); + if (rv != 0) { + return rv; + } + + rv = nghttp3_qpack_stream_add_ref(stream, ref); + if (rv != 0) { + nghttp3_qpack_header_block_ref_del(ref, mem); + return rv; + } + + if (max_cnt > prev_max_cnt && + nghttp3_qpack_encoder_stream_is_blocked(encoder, stream)) { + rv = nghttp3_qpack_encoder_block_stream(encoder, stream); + if (rv != 0) { + return rv; + } + } + + return nghttp3_pq_push(&encoder->min_cnts, &ref->min_cnts_pe); +} + +static void qpack_encoder_remove_stream(nghttp3_qpack_encoder *encoder, + nghttp3_qpack_stream *stream) { + size_t i, len; + nghttp3_qpack_header_block_ref *ref; + + nghttp3_map_remove(&encoder->streams, + (nghttp3_map_key_type)stream->stream_id); + + len = nghttp3_ringbuf_len(&stream->refs); + for (i = 0; i < len; ++i) { + ref = *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, + i); + + assert(ref->min_cnts_pe.index != NGHTTP3_PQ_BAD_INDEX); + + nghttp3_pq_remove(&encoder->min_cnts, &ref->min_cnts_pe); + } +} + +/* + * reserve_buf_internal ensures that |buf| contains at least + * |extra_size| of free space. In other words, if this function + * succeeds, nghttp2_buf_left(buf) >= extra_size holds. |min_size| is + * the minimum size of buffer. The allocated buffer has at least + * |min_size| bytes. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +static int reserve_buf_internal(nghttp3_buf *buf, size_t extra_size, + size_t min_size, const nghttp3_mem *mem) { + size_t left = nghttp3_buf_left(buf); + size_t n = min_size, need; + + if (left >= extra_size) { + return 0; + } + + need = nghttp3_buf_cap(buf) + extra_size - left; + + for (; n < need; n *= 2) + ; + + return nghttp3_buf_reserve(buf, n, mem); +} + +static int reserve_buf_small(nghttp3_buf *buf, size_t extra_size, + const nghttp3_mem *mem) { + return reserve_buf_internal(buf, extra_size, 32, mem); +} + +static int reserve_buf(nghttp3_buf *buf, size_t extra_size, + const nghttp3_mem *mem) { + return reserve_buf_internal(buf, extra_size, 32, mem); +} + +int nghttp3_qpack_encoder_encode(nghttp3_qpack_encoder *encoder, + nghttp3_buf *pbuf, nghttp3_buf *rbuf, + nghttp3_buf *ebuf, int64_t stream_id, + const nghttp3_nv *nva, size_t nvlen) { + size_t i; + uint64_t max_cnt = 0, min_cnt = UINT64_MAX; + uint64_t base; + int rv = 0; + int allow_blocking; + int blocked_stream; + nghttp3_qpack_stream *stream; + + if (encoder->ctx.bad) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + rv = nghttp3_qpack_encoder_process_dtable_update(encoder, ebuf); + if (rv != 0) { + goto fail; + } + + base = encoder->ctx.next_absidx; + + stream = nghttp3_qpack_encoder_find_stream(encoder, stream_id); + blocked_stream = + stream && nghttp3_qpack_encoder_stream_is_blocked(encoder, stream); + allow_blocking = + blocked_stream || encoder->ctx.max_blocked_streams > + nghttp3_ksl_len(&encoder->blocked_streams); + + DEBUGF("qpack::encode: stream %ld blocked=%d allow_blocking=%d\n", stream_id, + blocked_stream, allow_blocking); + + for (i = 0; i < nvlen; ++i) { + rv = nghttp3_qpack_encoder_encode_nv(encoder, &max_cnt, &min_cnt, rbuf, + ebuf, &nva[i], base, allow_blocking); + if (rv != 0) { + goto fail; + } + } + + nghttp3_qpack_encoder_write_field_section_prefix(encoder, pbuf, max_cnt, + base); + + /* TODO If max_cnt == 0, no reference is made to dtable. */ + if (!max_cnt) { + return 0; + } + + rv = qpack_encoder_add_stream_ref(encoder, stream_id, stream, max_cnt, + min_cnt); + if (rv != 0) { + goto fail; + } + + return 0; + +fail: + encoder->ctx.bad = 1; + return rv; +} + +/* + * qpack_write_number writes variable integer to |rbuf|. |num| is an + * integer to write. |prefix| is a prefix of variable integer + * encoding. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +static int qpack_write_number(nghttp3_buf *rbuf, uint8_t fb, uint64_t num, + size_t prefix, const nghttp3_mem *mem) { + int rv; + size_t len = nghttp3_qpack_put_varint_len(num, prefix); + uint8_t *p; + + rv = reserve_buf(rbuf, len, mem); + if (rv != 0) { + return rv; + } + + p = rbuf->last; + + *p = fb; + p = nghttp3_qpack_put_varint(p, num, prefix); + + assert((size_t)(p - rbuf->last) == len); + + rbuf->last = p; + + return 0; +} + +int nghttp3_qpack_encoder_process_dtable_update(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf) { + int rv; + + nghttp3_qpack_encoder_shrink_dtable(encoder); + + if (encoder->ctx.max_dtable_capacity < encoder->ctx.dtable_size || + !(encoder->flags & NGHTTP3_QPACK_ENCODER_FLAG_PENDING_SET_DTABLE_CAP)) { + return 0; + } + + if (encoder->min_dtable_update < encoder->last_max_dtable_update) { + rv = nghttp3_qpack_encoder_write_set_dtable_cap(encoder, ebuf, + encoder->min_dtable_update); + if (rv != 0) { + return rv; + } + } + + rv = nghttp3_qpack_encoder_write_set_dtable_cap( + encoder, ebuf, encoder->last_max_dtable_update); + if (rv != 0) { + return rv; + } + + encoder->flags &= (uint8_t)~NGHTTP3_QPACK_ENCODER_FLAG_PENDING_SET_DTABLE_CAP; + encoder->min_dtable_update = SIZE_MAX; + encoder->ctx.max_dtable_capacity = encoder->last_max_dtable_update; + + return 0; +} + +int nghttp3_qpack_encoder_write_set_dtable_cap(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, size_t cap) { + DEBUGF("qpack::encode: Set Dynamic Table Capacity capacity=%zu\n", cap); + return qpack_write_number(ebuf, 0x20, cap, 5, encoder->ctx.mem); +} + +nghttp3_qpack_stream * +nghttp3_qpack_encoder_find_stream(nghttp3_qpack_encoder *encoder, + int64_t stream_id) { + return nghttp3_map_find(&encoder->streams, (nghttp3_map_key_type)stream_id); +} + +int nghttp3_qpack_encoder_stream_is_blocked(nghttp3_qpack_encoder *encoder, + nghttp3_qpack_stream *stream) { + return stream && encoder->krcnt < nghttp3_qpack_stream_get_max_cnt(stream); +} + +/* + * qpack_encoder_decide_indexing_mode determines and returns indexing + * mode for header field |nv|. |token| is a token of header field + * name. + */ +static nghttp3_qpack_indexing_mode +qpack_encoder_decide_indexing_mode(nghttp3_qpack_encoder *encoder, + const nghttp3_nv *nv, int32_t token) { + if (nv->flags & NGHTTP3_NV_FLAG_NEVER_INDEX) { + return NGHTTP3_QPACK_INDEXING_MODE_NEVER; + } + + switch (token) { + case NGHTTP3_QPACK_TOKEN_AUTHORIZATION: + return NGHTTP3_QPACK_INDEXING_MODE_NEVER; + case NGHTTP3_QPACK_TOKEN_COOKIE: + if (nv->valuelen < 20) { + return NGHTTP3_QPACK_INDEXING_MODE_NEVER; + } + break; + case -1: + case NGHTTP3_QPACK_TOKEN__PATH: + case NGHTTP3_QPACK_TOKEN_AGE: + case NGHTTP3_QPACK_TOKEN_CONTENT_LENGTH: + case NGHTTP3_QPACK_TOKEN_ETAG: + case NGHTTP3_QPACK_TOKEN_IF_MODIFIED_SINCE: + case NGHTTP3_QPACK_TOKEN_IF_NONE_MATCH: + case NGHTTP3_QPACK_TOKEN_LOCATION: + case NGHTTP3_QPACK_TOKEN_SET_COOKIE: + return NGHTTP3_QPACK_INDEXING_MODE_LITERAL; + case NGHTTP3_QPACK_TOKEN_HOST: + case NGHTTP3_QPACK_TOKEN_TE: + case NGHTTP3_QPACK_TOKEN__PROTOCOL: + case NGHTTP3_QPACK_TOKEN_PRIORITY: + break; + default: + if (token >= 1000) { + return NGHTTP3_QPACK_INDEXING_MODE_LITERAL; + } + } + + if (table_space(nv->namelen, nv->valuelen) > + encoder->ctx.max_dtable_capacity * 3 / 4) { + return NGHTTP3_QPACK_INDEXING_MODE_LITERAL; + } + + return NGHTTP3_QPACK_INDEXING_MODE_STORE; +} + +/* + * qpack_encoder_can_index returns nonzero if an entry which occupies + * |need| bytes can be inserted into dynamic table. |min_cnt| is the + * minimum insert count which blocked stream requires. + */ +static int qpack_encoder_can_index(nghttp3_qpack_encoder *encoder, size_t need, + uint64_t min_cnt) { + size_t avail = 0; + size_t len; + uint64_t gmin_cnt; + nghttp3_qpack_entry *min_ent, *last_ent; + nghttp3_ringbuf *dtable = &encoder->ctx.dtable; + + if (encoder->ctx.max_dtable_capacity > encoder->ctx.dtable_size) { + avail = encoder->ctx.max_dtable_capacity - encoder->ctx.dtable_size; + if (need <= avail) { + return 1; + } + } + + if (!nghttp3_pq_empty(&encoder->min_cnts)) { + gmin_cnt = nghttp3_qpack_encoder_get_min_cnt(encoder); + min_cnt = nghttp3_min(min_cnt, gmin_cnt); + } + + if (min_cnt == UINT64_MAX) { + return encoder->ctx.max_dtable_capacity >= need; + } + + min_ent = nghttp3_qpack_context_dtable_get(&encoder->ctx, min_cnt - 1); + + len = nghttp3_ringbuf_len(&encoder->ctx.dtable); + assert(len); + last_ent = *(nghttp3_qpack_entry **)nghttp3_ringbuf_get(dtable, len - 1); + + if (min_ent == last_ent) { + return 0; + } + + return avail + min_ent->sum - last_ent->sum >= need; +} + +/* + * qpack_encoder_can_index_nv returns nonzero if header field |nv| can + * be inserted into dynamic table. |min_cnt| is the minimum insert + * count which blocked stream requires. + */ +static int qpack_encoder_can_index_nv(nghttp3_qpack_encoder *encoder, + const nghttp3_nv *nv, uint64_t min_cnt) { + return qpack_encoder_can_index( + encoder, table_space(nv->namelen, nv->valuelen), min_cnt); +} + +/* + * qpack_encoder_can_index_duplicate returns nonzero if an entry at + * |absidx| in dynamic table can be inserted to dynamic table as + * duplicate. |min_cnt| is the minimum insert count which blocked + * stream requires. + */ +static int qpack_encoder_can_index_duplicate(nghttp3_qpack_encoder *encoder, + uint64_t absidx, + uint64_t min_cnt) { + nghttp3_qpack_entry *ent = + nghttp3_qpack_context_dtable_get(&encoder->ctx, absidx); + + return qpack_encoder_can_index( + encoder, table_space(ent->nv.name->len, ent->nv.value->len), min_cnt); +} + +/* + * qpack_context_check_draining returns nonzero if an entry at + * |absidx| in dynamic table is one of draining entries. + */ +static int qpack_context_check_draining(nghttp3_qpack_context *ctx, + uint64_t absidx) { + const size_t safe = ctx->max_dtable_capacity - + nghttp3_min(512, ctx->max_dtable_capacity * 1 / 8); + nghttp3_qpack_entry *ent = nghttp3_qpack_context_dtable_get(ctx, absidx); + + return ctx->dtable_sum - ent->sum > safe; +} + +int nghttp3_qpack_encoder_encode_nv(nghttp3_qpack_encoder *encoder, + uint64_t *pmax_cnt, uint64_t *pmin_cnt, + nghttp3_buf *rbuf, nghttp3_buf *ebuf, + const nghttp3_nv *nv, uint64_t base, + int allow_blocking) { + uint32_t hash = 0; + int32_t token; + nghttp3_qpack_indexing_mode indexing_mode; + nghttp3_qpack_lookup_result sres = {-1, 0, -1}, dres = {-1, 0, -1}; + nghttp3_qpack_entry *new_ent = NULL; + int static_entry; + int just_index = 0; + int rv; + + token = qpack_lookup_token(nv->name, nv->namelen); + static_entry = token != -1 && (size_t)token < nghttp3_arraylen(token_stable); + if (static_entry) { + hash = token_stable[token].hash; + } else { + switch (token) { + case NGHTTP3_QPACK_TOKEN_HOST: + hash = 2952701295u; + break; + case NGHTTP3_QPACK_TOKEN_TE: + hash = 1011170994u; + break; + case NGHTTP3_QPACK_TOKEN__PROTOCOL: + hash = 1128642621u; + break; + case NGHTTP3_QPACK_TOKEN_PRIORITY: + hash = 2498028297u; + break; + } + } + + indexing_mode = qpack_encoder_decide_indexing_mode(encoder, nv, token); + + if (static_entry) { + sres = nghttp3_qpack_lookup_stable(nv, token, indexing_mode); + if (sres.index != -1 && sres.name_value_match) { + return nghttp3_qpack_encoder_write_static_indexed(encoder, rbuf, + (size_t)sres.index); + } + } + + if (hash && + nghttp3_map_size(&encoder->streams) < NGHTTP3_QPACK_MAX_QPACK_STREAMS) { + dres = nghttp3_qpack_encoder_lookup_dtable(encoder, nv, token, hash, + indexing_mode, encoder->krcnt, + allow_blocking); + just_index = indexing_mode == NGHTTP3_QPACK_INDEXING_MODE_STORE && + dres.pb_index == -1; + } + + if (dres.index != -1 && dres.name_value_match) { + if (allow_blocking && + qpack_context_check_draining(&encoder->ctx, (size_t)dres.index) && + qpack_encoder_can_index_duplicate(encoder, (size_t)dres.index, + *pmin_cnt)) { + rv = nghttp3_qpack_encoder_write_duplicate_insert(encoder, ebuf, + (size_t)dres.index); + if (rv != 0) { + return rv; + } + rv = nghttp3_qpack_encoder_dtable_duplicate_add(encoder, + (size_t)dres.index); + if (rv != 0) { + return rv; + } + + new_ent = nghttp3_qpack_context_dtable_top(&encoder->ctx); + dres.index = (nghttp3_ssize)new_ent->absidx; + } + *pmax_cnt = nghttp3_max(*pmax_cnt, (size_t)(dres.index + 1)); + *pmin_cnt = nghttp3_min(*pmin_cnt, (size_t)(dres.index + 1)); + + return nghttp3_qpack_encoder_write_dynamic_indexed( + encoder, rbuf, (size_t)dres.index, base); + } + + if (sres.index != -1) { + if (just_index && qpack_encoder_can_index_nv(encoder, nv, *pmin_cnt)) { + rv = nghttp3_qpack_encoder_write_static_insert(encoder, ebuf, + (size_t)sres.index, nv); + if (rv != 0) { + return rv; + } + rv = nghttp3_qpack_encoder_dtable_static_add(encoder, (size_t)sres.index, + nv, hash); + if (rv != 0) { + return rv; + } + if (allow_blocking) { + new_ent = nghttp3_qpack_context_dtable_top(&encoder->ctx); + *pmax_cnt = nghttp3_max(*pmax_cnt, new_ent->absidx + 1); + *pmin_cnt = nghttp3_min(*pmin_cnt, new_ent->absidx + 1); + + return nghttp3_qpack_encoder_write_dynamic_indexed( + encoder, rbuf, new_ent->absidx, base); + } + } + + return nghttp3_qpack_encoder_write_static_indexed_name( + encoder, rbuf, (size_t)sres.index, nv); + } + + if (dres.index != -1) { + if (just_index && + qpack_encoder_can_index_nv( + encoder, nv, + allow_blocking ? *pmin_cnt + : nghttp3_min((size_t)dres.index + 1, *pmin_cnt))) { + rv = nghttp3_qpack_encoder_write_dynamic_insert(encoder, ebuf, + (size_t)dres.index, nv); + if (rv != 0) { + return rv; + } + + if (!allow_blocking) { + *pmin_cnt = nghttp3_min(*pmin_cnt, (size_t)dres.index + 1); + } + + rv = nghttp3_qpack_encoder_dtable_dynamic_add(encoder, (size_t)dres.index, + nv, hash); + if (rv != 0) { + return rv; + } + + if (allow_blocking) { + new_ent = nghttp3_qpack_context_dtable_top(&encoder->ctx); + *pmax_cnt = nghttp3_max(*pmax_cnt, new_ent->absidx + 1); + *pmin_cnt = nghttp3_min(*pmin_cnt, new_ent->absidx + 1); + + return nghttp3_qpack_encoder_write_dynamic_indexed( + encoder, rbuf, new_ent->absidx, base); + } + } + + *pmax_cnt = nghttp3_max(*pmax_cnt, (size_t)(dres.index + 1)); + *pmin_cnt = nghttp3_min(*pmin_cnt, (size_t)(dres.index + 1)); + + return nghttp3_qpack_encoder_write_dynamic_indexed_name( + encoder, rbuf, (size_t)dres.index, base, nv); + } + + if (just_index && qpack_encoder_can_index_nv(encoder, nv, *pmin_cnt)) { + rv = nghttp3_qpack_encoder_dtable_literal_add(encoder, nv, token, hash); + if (rv != 0) { + return rv; + } + rv = nghttp3_qpack_encoder_write_literal_insert(encoder, ebuf, nv); + if (rv != 0) { + return rv; + } + if (allow_blocking) { + new_ent = nghttp3_qpack_context_dtable_top(&encoder->ctx); + *pmax_cnt = nghttp3_max(*pmax_cnt, new_ent->absidx + 1); + *pmin_cnt = nghttp3_min(*pmin_cnt, new_ent->absidx + 1); + + return nghttp3_qpack_encoder_write_dynamic_indexed(encoder, rbuf, + new_ent->absidx, base); + } + } + + return nghttp3_qpack_encoder_write_literal(encoder, rbuf, nv); +} + +nghttp3_qpack_lookup_result +nghttp3_qpack_lookup_stable(const nghttp3_nv *nv, int32_t token, + nghttp3_qpack_indexing_mode indexing_mode) { + nghttp3_qpack_lookup_result res = {(nghttp3_ssize)token_stable[token].absidx, + 0, -1}; + nghttp3_qpack_static_entry *ent; + nghttp3_qpack_static_header *hdr; + size_t i; + + assert(token >= 0); + + if (indexing_mode == NGHTTP3_QPACK_INDEXING_MODE_NEVER) { + return res; + } + + for (i = (size_t)token; + i < nghttp3_arraylen(token_stable) && token_stable[i].token == token; + ++i) { + ent = &token_stable[i]; + hdr = &stable[ent->absidx]; + if (hdr->value.len == nv->valuelen && + memeq(hdr->value.base, nv->value, nv->valuelen)) { + res.index = (nghttp3_ssize)ent->absidx; + res.name_value_match = 1; + return res; + } + } + return res; +} + +nghttp3_qpack_lookup_result nghttp3_qpack_encoder_lookup_dtable( + nghttp3_qpack_encoder *encoder, const nghttp3_nv *nv, int32_t token, + uint32_t hash, nghttp3_qpack_indexing_mode indexing_mode, uint64_t krcnt, + int allow_blocking) { + nghttp3_qpack_lookup_result res = {-1, 0, -1}; + int exact_match = 0; + nghttp3_qpack_entry *match, *pb_match; + + encoder_qpack_map_find(encoder, &exact_match, &match, &pb_match, nv, token, + hash, krcnt, allow_blocking, + indexing_mode == NGHTTP3_QPACK_INDEXING_MODE_NEVER); + if (match) { + res.index = (nghttp3_ssize)match->absidx; + res.name_value_match = exact_match; + } + if (pb_match) { + res.pb_index = (nghttp3_ssize)pb_match->absidx; + } + + return res; +} + +int nghttp3_qpack_header_block_ref_new(nghttp3_qpack_header_block_ref **pref, + uint64_t max_cnt, uint64_t min_cnt, + const nghttp3_mem *mem) { + nghttp3_qpack_header_block_ref *ref = + nghttp3_mem_malloc(mem, sizeof(nghttp3_qpack_header_block_ref)); + + if (ref == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + ref->max_cnts_pe.index = NGHTTP3_PQ_BAD_INDEX; + ref->min_cnts_pe.index = NGHTTP3_PQ_BAD_INDEX; + ref->max_cnt = max_cnt; + ref->min_cnt = min_cnt; + + *pref = ref; + + return 0; +} + +void nghttp3_qpack_header_block_ref_del(nghttp3_qpack_header_block_ref *ref, + const nghttp3_mem *mem) { + nghttp3_mem_free(mem, ref); +} + +static int ref_max_cnt_greater(const nghttp3_pq_entry *lhsx, + const nghttp3_pq_entry *rhsx) { + const nghttp3_qpack_header_block_ref *lhs = + nghttp3_struct_of(lhsx, nghttp3_qpack_header_block_ref, max_cnts_pe); + const nghttp3_qpack_header_block_ref *rhs = + nghttp3_struct_of(rhsx, nghttp3_qpack_header_block_ref, max_cnts_pe); + + return lhs->max_cnt > rhs->max_cnt; +} + +int nghttp3_qpack_stream_new(nghttp3_qpack_stream **pstream, int64_t stream_id, + const nghttp3_mem *mem) { + int rv; + nghttp3_qpack_stream *stream; + + stream = nghttp3_mem_malloc(mem, sizeof(nghttp3_qpack_stream)); + if (stream == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + rv = nghttp3_ringbuf_init(&stream->refs, 4, + sizeof(nghttp3_qpack_header_block_ref *), mem); + if (rv != 0) { + nghttp3_mem_free(mem, stream); + return rv; + } + + nghttp3_pq_init(&stream->max_cnts, ref_max_cnt_greater, mem); + + stream->stream_id = stream_id; + + *pstream = stream; + + return 0; +} + +void nghttp3_qpack_stream_del(nghttp3_qpack_stream *stream, + const nghttp3_mem *mem) { + nghttp3_qpack_header_block_ref *ref; + size_t i, len; + + if (stream == NULL) { + return; + } + + nghttp3_pq_free(&stream->max_cnts); + + len = nghttp3_ringbuf_len(&stream->refs); + for (i = 0; i < len; ++i) { + ref = *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, + i); + nghttp3_qpack_header_block_ref_del(ref, mem); + } + + nghttp3_ringbuf_free(&stream->refs); + + nghttp3_mem_free(mem, stream); +} + +uint64_t nghttp3_qpack_stream_get_max_cnt(const nghttp3_qpack_stream *stream) { + nghttp3_qpack_header_block_ref *ref; + + if (nghttp3_pq_empty(&stream->max_cnts)) { + return 0; + } + + ref = nghttp3_struct_of(nghttp3_pq_top(&stream->max_cnts), + nghttp3_qpack_header_block_ref, max_cnts_pe); + return ref->max_cnt; +} + +int nghttp3_qpack_stream_add_ref(nghttp3_qpack_stream *stream, + nghttp3_qpack_header_block_ref *ref) { + nghttp3_qpack_header_block_ref **dest; + int rv; + + if (nghttp3_ringbuf_full(&stream->refs)) { + rv = nghttp3_ringbuf_reserve(&stream->refs, + nghttp3_ringbuf_len(&stream->refs) * 2); + if (rv != 0) { + return rv; + } + } + + dest = nghttp3_ringbuf_push_back(&stream->refs); + *dest = ref; + + return nghttp3_pq_push(&stream->max_cnts, &ref->max_cnts_pe); +} + +void nghttp3_qpack_stream_pop_ref(nghttp3_qpack_stream *stream) { + nghttp3_qpack_header_block_ref *ref; + + assert(nghttp3_ringbuf_len(&stream->refs)); + + ref = + *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 0); + + assert(ref->max_cnts_pe.index != NGHTTP3_PQ_BAD_INDEX); + + nghttp3_pq_remove(&stream->max_cnts, &ref->max_cnts_pe); + + nghttp3_ringbuf_pop_front(&stream->refs); +} + +int nghttp3_qpack_encoder_write_static_indexed(nghttp3_qpack_encoder *encoder, + nghttp3_buf *rbuf, + uint64_t absidx) { + DEBUGF("qpack::encode: Indexed Field Line (static) absidx=%" PRIu64 "\n", + absidx); + return qpack_write_number(rbuf, 0xc0, absidx, 6, encoder->ctx.mem); +} + +int nghttp3_qpack_encoder_write_dynamic_indexed(nghttp3_qpack_encoder *encoder, + nghttp3_buf *rbuf, + uint64_t absidx, + uint64_t base) { + DEBUGF("qpack::encode: Indexed Field Line (dynamic) absidx=%" PRIu64 + " base=%" PRIu64 "\n", + absidx, base); + + if (absidx < base) { + return qpack_write_number(rbuf, 0x80, base - absidx - 1, 6, + encoder->ctx.mem); + } + + return qpack_write_number(rbuf, 0x10, absidx - base, 4, encoder->ctx.mem); +} + +/* + * qpack_encoder_write_indexed_name writes generic indexed name. |fb| + * is the first byte. |nameidx| is an index of referenced name. + * |prefix| is a prefix of variable integer encoding. |nv| is a + * header field to encode. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +static int qpack_encoder_write_indexed_name(nghttp3_qpack_encoder *encoder, + nghttp3_buf *buf, uint8_t fb, + uint64_t nameidx, size_t prefix, + const nghttp3_nv *nv) { + int rv; + size_t len = nghttp3_qpack_put_varint_len(nameidx, prefix); + uint8_t *p; + size_t hlen; + int h = 0; + + hlen = nghttp3_qpack_huffman_encode_count(nv->value, nv->valuelen); + if (hlen < nv->valuelen) { + h = 1; + len += nghttp3_qpack_put_varint_len(hlen, 7) + hlen; + } else { + len += nghttp3_qpack_put_varint_len(nv->valuelen, 7) + nv->valuelen; + } + + rv = reserve_buf(buf, len, encoder->ctx.mem); + if (rv != 0) { + return rv; + } + + p = buf->last; + + *p = fb; + p = nghttp3_qpack_put_varint(p, nameidx, prefix); + + if (h) { + *p = 0x80; + p = nghttp3_qpack_put_varint(p, hlen, 7); + p = nghttp3_qpack_huffman_encode(p, nv->value, nv->valuelen); + } else { + *p = 0; + p = nghttp3_qpack_put_varint(p, nv->valuelen, 7); + if (nv->valuelen) { + p = nghttp3_cpymem(p, nv->value, nv->valuelen); + } + } + + assert((size_t)(p - buf->last) == len); + + buf->last = p; + + return 0; +} + +int nghttp3_qpack_encoder_write_static_indexed_name( + nghttp3_qpack_encoder *encoder, nghttp3_buf *rbuf, uint64_t absidx, + const nghttp3_nv *nv) { + uint8_t fb = + (uint8_t)(0x50 | ((nv->flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? 0x20 : 0)); + + DEBUGF("qpack::encode: Literal Field Line With Name Reference (static) " + "absidx=%" PRIu64 " never=%d\n", + absidx, (nv->flags & NGHTTP3_NV_FLAG_NEVER_INDEX) != 0); + return qpack_encoder_write_indexed_name(encoder, rbuf, fb, absidx, 4, nv); +} + +int nghttp3_qpack_encoder_write_dynamic_indexed_name( + nghttp3_qpack_encoder *encoder, nghttp3_buf *rbuf, uint64_t absidx, + uint64_t base, const nghttp3_nv *nv) { + uint8_t fb; + + DEBUGF("qpack::encode: Literal Field Line With Name Reference (dynamic) " + "absidx=%" PRIu64 " base=%" PRIu64 " never=%d\n", + absidx, base, (nv->flags & NGHTTP3_NV_FLAG_NEVER_INDEX) != 0); + + if (absidx < base) { + fb = (uint8_t)(0x40 | + ((nv->flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? 0x20 : 0)); + return qpack_encoder_write_indexed_name(encoder, rbuf, fb, + base - absidx - 1, 4, nv); + } + + fb = (nv->flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? 0x08 : 0; + return qpack_encoder_write_indexed_name(encoder, rbuf, fb, absidx - base, 3, + nv); +} + +/* + * qpack_encoder_write_literal writes generic literal header field + * representation. |fb| is a first byte. |prefix| is a prefix of + * variable integer encoding for name length. |nv| is a header field + * to encode. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +static int qpack_encoder_write_literal(nghttp3_qpack_encoder *encoder, + nghttp3_buf *buf, uint8_t fb, + size_t prefix, const nghttp3_nv *nv) { + int rv; + size_t len; + uint8_t *p; + size_t nhlen, vhlen; + int nh = 0, vh = 0; + + nhlen = nghttp3_qpack_huffman_encode_count(nv->name, nv->namelen); + if (nhlen < nv->namelen) { + nh = 1; + len = nghttp3_qpack_put_varint_len(nhlen, prefix) + nhlen; + } else { + len = nghttp3_qpack_put_varint_len(nv->namelen, prefix) + nv->namelen; + } + + vhlen = nghttp3_qpack_huffman_encode_count(nv->value, nv->valuelen); + if (vhlen < nv->valuelen) { + vh = 1; + len += nghttp3_qpack_put_varint_len(vhlen, 7) + vhlen; + } else { + len += nghttp3_qpack_put_varint_len(nv->valuelen, 7) + nv->valuelen; + } + + rv = reserve_buf(buf, len, encoder->ctx.mem); + if (rv != 0) { + return rv; + } + + p = buf->last; + + *p = fb; + if (nh) { + *p |= (uint8_t)(1 << prefix); + p = nghttp3_qpack_put_varint(p, nhlen, prefix); + p = nghttp3_qpack_huffman_encode(p, nv->name, nv->namelen); + } else { + p = nghttp3_qpack_put_varint(p, nv->namelen, prefix); + if (nv->namelen) { + p = nghttp3_cpymem(p, nv->name, nv->namelen); + } + } + + *p = 0; + + if (vh) { + *p |= 0x80; + p = nghttp3_qpack_put_varint(p, vhlen, 7); + p = nghttp3_qpack_huffman_encode(p, nv->value, nv->valuelen); + } else { + p = nghttp3_qpack_put_varint(p, nv->valuelen, 7); + if (nv->valuelen) { + p = nghttp3_cpymem(p, nv->value, nv->valuelen); + } + } + + assert((size_t)(p - buf->last) == len); + + buf->last = p; + + return 0; +} + +int nghttp3_qpack_encoder_write_literal(nghttp3_qpack_encoder *encoder, + nghttp3_buf *rbuf, + const nghttp3_nv *nv) { + uint8_t fb = + (uint8_t)(0x20 | ((nv->flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? 0x10 : 0)); + + DEBUGF("qpack::encode: Literal Field Line With Literal Name\n"); + return qpack_encoder_write_literal(encoder, rbuf, fb, 3, nv); +} + +int nghttp3_qpack_encoder_write_static_insert(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, + uint64_t absidx, + const nghttp3_nv *nv) { + DEBUGF("qpack::encode: Insert With Name Reference (static) absidx=%" PRIu64 + "\n", + absidx); + return qpack_encoder_write_indexed_name(encoder, ebuf, 0xc0, absidx, 6, nv); +} + +int nghttp3_qpack_encoder_write_dynamic_insert(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, + uint64_t absidx, + const nghttp3_nv *nv) { + DEBUGF("qpack::encode: Insert With Name Reference (dynamic) absidx=%" PRIu64 + "\n", + absidx); + return qpack_encoder_write_indexed_name( + encoder, ebuf, 0x80, encoder->ctx.next_absidx - absidx - 1, 6, nv); +} + +int nghttp3_qpack_encoder_write_duplicate_insert(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, + uint64_t absidx) { + uint64_t idx = encoder->ctx.next_absidx - absidx - 1; + size_t len = nghttp3_qpack_put_varint_len(idx, 5); + uint8_t *p; + int rv; + + DEBUGF("qpack::encode: Insert duplicate absidx=%" PRIu64 "\n", absidx); + + rv = reserve_buf(ebuf, len, encoder->ctx.mem); + if (rv != 0) { + return rv; + } + + p = ebuf->last; + + *p = 0; + p = nghttp3_qpack_put_varint(p, idx, 5); + + assert((size_t)(p - ebuf->last) == len); + + ebuf->last = p; + + return 0; +} + +int nghttp3_qpack_encoder_write_literal_insert(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, + const nghttp3_nv *nv) { + DEBUGF("qpack::encode: Insert With Literal Name\n"); + return qpack_encoder_write_literal(encoder, ebuf, 0x40, 5, nv); +} + +int nghttp3_qpack_context_dtable_add(nghttp3_qpack_context *ctx, + nghttp3_qpack_nv *qnv, + nghttp3_qpack_map *dtable_map, + uint32_t hash) { + nghttp3_qpack_entry *new_ent, **p, *ent; + const nghttp3_mem *mem = ctx->mem; + size_t space; + size_t i; + int rv; + + space = table_space(qnv->name->len, qnv->value->len); + + assert(space <= ctx->max_dtable_capacity); + + while (ctx->dtable_size + space > ctx->max_dtable_capacity) { + i = nghttp3_ringbuf_len(&ctx->dtable); + assert(i); + ent = *(nghttp3_qpack_entry **)nghttp3_ringbuf_get(&ctx->dtable, i - 1); + + ctx->dtable_size -= table_space(ent->nv.name->len, ent->nv.value->len); + + nghttp3_ringbuf_pop_back(&ctx->dtable); + if (dtable_map) { + qpack_map_remove(dtable_map, ent); + } + + nghttp3_qpack_entry_free(ent); + nghttp3_mem_free(mem, ent); + } + + new_ent = nghttp3_mem_malloc(mem, sizeof(nghttp3_qpack_entry)); + if (new_ent == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + nghttp3_qpack_entry_init(new_ent, qnv, ctx->dtable_sum, ctx->next_absidx++, + hash); + + if (nghttp3_ringbuf_full(&ctx->dtable)) { + rv = nghttp3_ringbuf_reserve(&ctx->dtable, + nghttp3_ringbuf_len(&ctx->dtable) * 2); + if (rv != 0) { + goto fail; + } + } + + p = nghttp3_ringbuf_push_front(&ctx->dtable); + *p = new_ent; + + if (dtable_map) { + qpack_map_insert(dtable_map, new_ent); + } + + ctx->dtable_size += space; + ctx->dtable_sum += space; + + return 0; + +fail: + nghttp3_qpack_entry_free(new_ent); + nghttp3_mem_free(mem, new_ent); + + return rv; +} + +int nghttp3_qpack_encoder_dtable_static_add(nghttp3_qpack_encoder *encoder, + uint64_t absidx, + const nghttp3_nv *nv, + uint32_t hash) { + const nghttp3_qpack_static_header *shd; + nghttp3_qpack_nv qnv; + const nghttp3_mem *mem = encoder->ctx.mem; + int rv; + + rv = nghttp3_rcbuf_new2(&qnv.value, nv->value, nv->valuelen, mem); + if (rv != 0) { + return rv; + } + + assert(nghttp3_arraylen(stable) > absidx); + + shd = &stable[absidx]; + + qnv.name = (nghttp3_rcbuf *)&shd->name; + qnv.token = shd->token; + qnv.flags = NGHTTP3_NV_FLAG_NONE; + + rv = nghttp3_qpack_context_dtable_add(&encoder->ctx, &qnv, + &encoder->dtable_map, hash); + + nghttp3_rcbuf_decref(qnv.value); + + return rv; +} + +int nghttp3_qpack_encoder_dtable_dynamic_add(nghttp3_qpack_encoder *encoder, + uint64_t absidx, + const nghttp3_nv *nv, + uint32_t hash) { + nghttp3_qpack_nv qnv; + nghttp3_qpack_entry *ent; + const nghttp3_mem *mem = encoder->ctx.mem; + int rv; + + rv = nghttp3_rcbuf_new2(&qnv.value, nv->value, nv->valuelen, mem); + if (rv != 0) { + return rv; + } + + ent = nghttp3_qpack_context_dtable_get(&encoder->ctx, absidx); + + qnv.name = ent->nv.name; + qnv.token = ent->nv.token; + qnv.flags = NGHTTP3_NV_FLAG_NONE; + + nghttp3_rcbuf_incref(qnv.name); + + rv = nghttp3_qpack_context_dtable_add(&encoder->ctx, &qnv, + &encoder->dtable_map, hash); + + nghttp3_rcbuf_decref(qnv.value); + nghttp3_rcbuf_decref(qnv.name); + + return rv; +} + +int nghttp3_qpack_encoder_dtable_duplicate_add(nghttp3_qpack_encoder *encoder, + uint64_t absidx) { + nghttp3_qpack_nv qnv; + nghttp3_qpack_entry *ent; + int rv; + + ent = nghttp3_qpack_context_dtable_get(&encoder->ctx, absidx); + + qnv = ent->nv; + nghttp3_rcbuf_incref(qnv.name); + nghttp3_rcbuf_incref(qnv.value); + + rv = nghttp3_qpack_context_dtable_add(&encoder->ctx, &qnv, + &encoder->dtable_map, ent->hash); + + nghttp3_rcbuf_decref(qnv.name); + nghttp3_rcbuf_decref(qnv.value); + + return rv; +} + +int nghttp3_qpack_encoder_dtable_literal_add(nghttp3_qpack_encoder *encoder, + const nghttp3_nv *nv, + int32_t token, uint32_t hash) { + nghttp3_qpack_nv qnv; + const nghttp3_mem *mem = encoder->ctx.mem; + int rv; + + rv = nghttp3_rcbuf_new2(&qnv.name, nv->name, nv->namelen, mem); + if (rv != 0) { + return rv; + } + + rv = nghttp3_rcbuf_new2(&qnv.value, nv->value, nv->valuelen, mem); + if (rv != 0) { + nghttp3_rcbuf_decref(qnv.name); + return rv; + } + + qnv.token = token; + qnv.flags = NGHTTP3_NV_FLAG_NONE; + + rv = nghttp3_qpack_context_dtable_add(&encoder->ctx, &qnv, + &encoder->dtable_map, hash); + + nghttp3_rcbuf_decref(qnv.value); + nghttp3_rcbuf_decref(qnv.name); + + return rv; +} + +nghttp3_qpack_entry * +nghttp3_qpack_context_dtable_get(nghttp3_qpack_context *ctx, uint64_t absidx) { + size_t relidx; + + assert(ctx->next_absidx > absidx); + assert(ctx->next_absidx - absidx - 1 < nghttp3_ringbuf_len(&ctx->dtable)); + + relidx = (size_t)(ctx->next_absidx - absidx - 1); + + return *(nghttp3_qpack_entry **)nghttp3_ringbuf_get(&ctx->dtable, relidx); +} + +nghttp3_qpack_entry * +nghttp3_qpack_context_dtable_top(nghttp3_qpack_context *ctx) { + assert(nghttp3_ringbuf_len(&ctx->dtable)); + return *(nghttp3_qpack_entry **)nghttp3_ringbuf_get(&ctx->dtable, 0); +} + +void nghttp3_qpack_entry_init(nghttp3_qpack_entry *ent, nghttp3_qpack_nv *qnv, + size_t sum, uint64_t absidx, uint32_t hash) { + ent->nv = *qnv; + ent->map_next = NULL; + ent->sum = sum; + ent->absidx = absidx; + ent->hash = hash; + + nghttp3_rcbuf_incref(ent->nv.name); + nghttp3_rcbuf_incref(ent->nv.value); +} + +void nghttp3_qpack_entry_free(nghttp3_qpack_entry *ent) { + nghttp3_rcbuf_decref(ent->nv.value); + nghttp3_rcbuf_decref(ent->nv.name); +} + +int nghttp3_qpack_encoder_block_stream(nghttp3_qpack_encoder *encoder, + nghttp3_qpack_stream *stream) { + nghttp3_blocked_streams_key bsk = { + nghttp3_struct_of(nghttp3_pq_top(&stream->max_cnts), + nghttp3_qpack_header_block_ref, max_cnts_pe) + ->max_cnt, + (uint64_t)stream->stream_id}; + + return nghttp3_ksl_insert(&encoder->blocked_streams, NULL, &bsk, stream); +} + +void nghttp3_qpack_encoder_unblock_stream(nghttp3_qpack_encoder *encoder, + nghttp3_qpack_stream *stream) { + nghttp3_blocked_streams_key bsk = { + nghttp3_struct_of(nghttp3_pq_top(&stream->max_cnts), + nghttp3_qpack_header_block_ref, max_cnts_pe) + ->max_cnt, + (uint64_t)stream->stream_id}; + nghttp3_ksl_it it; + + /* This is purely debugging purpose only */ + it = nghttp3_ksl_lower_bound(&encoder->blocked_streams, &bsk); + + assert(!nghttp3_ksl_it_end(&it)); + assert(nghttp3_ksl_it_get(&it) == stream); + + nghttp3_ksl_remove_hint(&encoder->blocked_streams, NULL, &it, &bsk); +} + +void nghttp3_qpack_encoder_unblock(nghttp3_qpack_encoder *encoder, + uint64_t max_cnt) { + nghttp3_blocked_streams_key bsk = {max_cnt, 0}; + nghttp3_ksl_it it; + + it = nghttp3_ksl_lower_bound(&encoder->blocked_streams, &bsk); + + for (; !nghttp3_ksl_it_end(&it);) { + bsk = *(nghttp3_blocked_streams_key *)nghttp3_ksl_it_key(&it); + nghttp3_ksl_remove_hint(&encoder->blocked_streams, &it, &it, &bsk); + } +} + +int nghttp3_qpack_encoder_ack_header(nghttp3_qpack_encoder *encoder, + int64_t stream_id) { + nghttp3_qpack_stream *stream = + nghttp3_qpack_encoder_find_stream(encoder, stream_id); + const nghttp3_mem *mem = encoder->ctx.mem; + nghttp3_qpack_header_block_ref *ref; + + if (stream == NULL) { + return NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR; + } + + assert(nghttp3_ringbuf_len(&stream->refs)); + + ref = + *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 0); + + DEBUGF("qpack::encoder: Header acknowledgement stream=%ld ricnt=%" PRIu64 + " krcnt=%" PRIu64 "\n", + stream_id, ref->max_cnt, encoder->krcnt); + + if (encoder->krcnt < ref->max_cnt) { + encoder->krcnt = ref->max_cnt; + + nghttp3_qpack_encoder_unblock(encoder, ref->max_cnt); + } + + nghttp3_qpack_stream_pop_ref(stream); + + assert(ref->min_cnts_pe.index != NGHTTP3_PQ_BAD_INDEX); + + nghttp3_pq_remove(&encoder->min_cnts, &ref->min_cnts_pe); + + nghttp3_qpack_header_block_ref_del(ref, mem); + + if (nghttp3_ringbuf_len(&stream->refs)) { + return 0; + } + + qpack_encoder_remove_stream(encoder, stream); + + nghttp3_qpack_stream_del(stream, mem); + + return 0; +} + +int nghttp3_qpack_encoder_add_icnt(nghttp3_qpack_encoder *encoder, uint64_t n) { + if (n == 0 || encoder->ctx.next_absidx - encoder->krcnt < n) { + return NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR; + } + encoder->krcnt += n; + + nghttp3_qpack_encoder_unblock(encoder, encoder->krcnt); + + return 0; +} + +void nghttp3_qpack_encoder_ack_everything(nghttp3_qpack_encoder *encoder) { + encoder->krcnt = encoder->ctx.next_absidx; + + nghttp3_ksl_clear(&encoder->blocked_streams); + nghttp3_pq_clear(&encoder->min_cnts); + nghttp3_map_each_free(&encoder->streams, map_stream_free, + (void *)encoder->ctx.mem); + nghttp3_map_clear(&encoder->streams); +} + +void nghttp3_qpack_encoder_cancel_stream(nghttp3_qpack_encoder *encoder, + int64_t stream_id) { + nghttp3_qpack_stream *stream = + nghttp3_qpack_encoder_find_stream(encoder, stream_id); + const nghttp3_mem *mem = encoder->ctx.mem; + + if (stream == NULL) { + return; + } + + if (nghttp3_qpack_encoder_stream_is_blocked(encoder, stream)) { + nghttp3_qpack_encoder_unblock_stream(encoder, stream); + } + + qpack_encoder_remove_stream(encoder, stream); + + nghttp3_qpack_stream_del(stream, mem); +} + +size_t +nghttp3_qpack_encoder_get_num_blocked_streams(nghttp3_qpack_encoder *encoder) { + return nghttp3_ksl_len(&encoder->blocked_streams); +} + +int nghttp3_qpack_encoder_write_field_section_prefix( + nghttp3_qpack_encoder *encoder, nghttp3_buf *pbuf, uint64_t ricnt, + uint64_t base) { + size_t max_ents = + encoder->ctx.hard_max_dtable_capacity / NGHTTP3_QPACK_ENTRY_OVERHEAD; + uint64_t encricnt = ricnt == 0 ? 0 : (ricnt % (2 * max_ents)) + 1; + int sign = base < ricnt; + uint64_t delta_base = sign ? ricnt - base - 1 : base - ricnt; + size_t len = nghttp3_qpack_put_varint_len(encricnt, 8) + + nghttp3_qpack_put_varint_len(delta_base, 7); + uint8_t *p; + int rv; + + DEBUGF("qpack::encode: ricnt=%" PRIu64 " base=%" PRIu64 " icnt=%" PRIu64 "\n", + ricnt, base, encoder->ctx.next_absidx); + + rv = reserve_buf(pbuf, len, encoder->ctx.mem); + if (rv != 0) { + return rv; + } + + p = pbuf->last; + + p = nghttp3_qpack_put_varint(p, encricnt, 8); + if (sign) { + *p = 0x80; + } else { + *p = 0; + } + p = nghttp3_qpack_put_varint(p, delta_base, 7); + + assert((size_t)(p - pbuf->last) == len); + + pbuf->last = p; + + return 0; +} + +/* + * qpack_read_varint reads |rstate->prefix| prefixed integer stored + * from |begin|. The |end| represents the 1 beyond the last of the + * valid contiguous memory region from |begin|. The decoded integer + * must be less than or equal to NGHTTP3_QPACK_INT_MAX. + * + * If the |rstate->left| is nonzero, it is used as an initial value, + * and this function assumes the |begin| starts with intermediate + * data. |rstate->shift| is used as initial integer shift. + * + * If an entire integer is decoded successfully, the |*fin| is set to + * nonzero. + * + * This function stores the decoded integer in |rstate->left| if it + * succeeds, including partial decoding (in this case, number of shift + * to make in the next call will be stored in |rstate->shift|) and + * returns number of bytes processed, or returns negative error code + * NGHTTP3_ERR_QPACK_FATAL, indicating decoding error. + */ +static nghttp3_ssize qpack_read_varint(int *fin, + nghttp3_qpack_read_state *rstate, + const uint8_t *begin, + const uint8_t *end) { + uint64_t k = (uint8_t)((1 << rstate->prefix) - 1); + uint64_t n = rstate->left; + uint64_t add; + const uint8_t *p = begin; + size_t shift = rstate->shift; + + rstate->shift = 0; + *fin = 0; + + if (n == 0) { + if (((*p) & k) != k) { + rstate->left = (*p) & k; + *fin = 1; + return 1; + } + + n = k; + + if (++p == end) { + rstate->left = n; + return (nghttp3_ssize)(p - begin); + } + } + + for (; p != end; ++p, shift += 7) { + add = (*p) & 0x7f; + + if (shift > 62) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + if ((NGHTTP3_QPACK_INT_MAX >> shift) < add) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + add <<= shift; + + if (NGHTTP3_QPACK_INT_MAX - add < n) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + n += add; + + if (((*p) & (1 << 7)) == 0) { + break; + } + } + + rstate->shift = shift; + + if (p == end) { + rstate->left = n; + return (nghttp3_ssize)(p - begin); + } + + rstate->left = n; + *fin = 1; + return (nghttp3_ssize)(p + 1 - begin); +} + +nghttp3_ssize nghttp3_qpack_encoder_read_decoder(nghttp3_qpack_encoder *encoder, + const uint8_t *src, + size_t srclen) { + const uint8_t *p = src, *end; + int rv; + nghttp3_ssize nread; + int rfin; + + if (encoder->ctx.bad) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + if (srclen == 0) { + return 0; + } + + end = src + srclen; + + for (; p != end;) { + switch (encoder->state) { + case NGHTTP3_QPACK_DS_STATE_OPCODE: + if ((*p) & 0x80) { + DEBUGF("qpack::encode: OPCODE_SECTION_ACK\n"); + encoder->opcode = NGHTTP3_QPACK_DS_OPCODE_SECTION_ACK; + encoder->rstate.prefix = 7; + } else if ((*p) & 0x40) { + DEBUGF("qpack::encode: OPCODE_STREAM_CANCEL\n"); + encoder->opcode = NGHTTP3_QPACK_DS_OPCODE_STREAM_CANCEL; + encoder->rstate.prefix = 6; + } else { + DEBUGF("qpack::encode: OPCODE_ICNT_INCREMENT\n"); + encoder->opcode = NGHTTP3_QPACK_DS_OPCODE_ICNT_INCREMENT; + encoder->rstate.prefix = 6; + } + encoder->state = NGHTTP3_QPACK_DS_STATE_READ_NUMBER; + /* fall through */ + case NGHTTP3_QPACK_DS_STATE_READ_NUMBER: + nread = qpack_read_varint(&rfin, &encoder->rstate, p, end); + if (nread < 0) { + assert(nread == NGHTTP3_ERR_QPACK_FATAL); + rv = NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR; + goto fail; + } + + p += nread; + + if (!rfin) { + return p - src; + } + + switch (encoder->opcode) { + case NGHTTP3_QPACK_DS_OPCODE_ICNT_INCREMENT: + rv = nghttp3_qpack_encoder_add_icnt(encoder, encoder->rstate.left); + if (rv != 0) { + goto fail; + } + break; + case NGHTTP3_QPACK_DS_OPCODE_SECTION_ACK: + rv = nghttp3_qpack_encoder_ack_header(encoder, + (int64_t)encoder->rstate.left); + if (rv != 0) { + goto fail; + } + break; + case NGHTTP3_QPACK_DS_OPCODE_STREAM_CANCEL: + nghttp3_qpack_encoder_cancel_stream(encoder, + (int64_t)encoder->rstate.left); + break; + default: + nghttp3_unreachable(); + } + + encoder->state = NGHTTP3_QPACK_DS_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&encoder->rstate); + break; + default: + nghttp3_unreachable(); + } + } + + return p - src; + +fail: + encoder->ctx.bad = 1; + return rv; +} + +size_t nghttp3_qpack_put_varint_len(uint64_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; +} + +uint8_t *nghttp3_qpack_put_varint(uint8_t *buf, uint64_t n, size_t prefix) { + size_t k = (size_t)((1 << prefix) - 1); + + *buf = (uint8_t)(*buf & ~k); + + if (n < k) { + *buf = (uint8_t)(*buf | n); + return buf + 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 buf; +} + +void nghttp3_qpack_read_state_free(nghttp3_qpack_read_state *rstate) { + nghttp3_rcbuf_decref(rstate->value); + nghttp3_rcbuf_decref(rstate->name); +} + +void nghttp3_qpack_read_state_reset(nghttp3_qpack_read_state *rstate) { + rstate->name = NULL; + rstate->value = NULL; + nghttp3_buf_init(&rstate->namebuf); + nghttp3_buf_init(&rstate->valuebuf); + rstate->left = 0; + rstate->prefix = 0; + rstate->shift = 0; + rstate->absidx = 0; + rstate->never = 0; + rstate->dynamic = 0; + rstate->huffman_encoded = 0; +} + +int nghttp3_qpack_decoder_init(nghttp3_qpack_decoder *decoder, + size_t hard_max_dtable_capacity, + size_t max_blocked_streams, + const nghttp3_mem *mem) { + int rv; + + rv = qpack_context_init(&decoder->ctx, hard_max_dtable_capacity, + max_blocked_streams, mem); + if (rv != 0) { + return rv; + } + + decoder->state = NGHTTP3_QPACK_ES_STATE_OPCODE; + decoder->opcode = 0; + decoder->written_icnt = 0; + decoder->max_concurrent_streams = 0; + + nghttp3_qpack_read_state_reset(&decoder->rstate); + nghttp3_buf_init(&decoder->dbuf); + + return 0; +} + +void nghttp3_qpack_decoder_free(nghttp3_qpack_decoder *decoder) { + nghttp3_buf_free(&decoder->dbuf, decoder->ctx.mem); + nghttp3_qpack_read_state_free(&decoder->rstate); + qpack_context_free(&decoder->ctx); +} + +/* + * qpack_read_huffman_string decodes huffman string in buffer [begin, + * end) and writes the decoded string to |dest|. This function + * assumes the buffer pointed by |dest| has enough space. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_QPACK_FATAL + * Could not decode huffman string. + */ +static nghttp3_ssize qpack_read_huffman_string(nghttp3_qpack_read_state *rstate, + nghttp3_buf *dest, + const uint8_t *begin, + const uint8_t *end) { + nghttp3_ssize nwrite; + size_t len = (size_t)(end - begin); + int fin = 0; + + if (len >= rstate->left) { + len = (size_t)rstate->left; + fin = 1; + } + + nwrite = nghttp3_qpack_huffman_decode(&rstate->huffman_ctx, dest->last, begin, + len, fin); + if (nwrite < 0) { + return nwrite; + } + + if (nghttp3_qpack_huffman_decode_failure_state(&rstate->huffman_ctx)) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + dest->last += nwrite; + rstate->left -= len; + return (nghttp3_ssize)len; +} + +static nghttp3_ssize qpack_read_string(nghttp3_qpack_read_state *rstate, + nghttp3_buf *dest, const uint8_t *begin, + const uint8_t *end) { + size_t len = (size_t)(end - begin); + size_t n = (size_t)nghttp3_min((uint64_t)len, rstate->left); + + dest->last = nghttp3_cpymem(dest->last, begin, n); + + rstate->left -= n; + return (nghttp3_ssize)n; +} + +/* + * qpack_decoder_validate_index checks rstate->absidx is acceptable. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGHTTP3_ERR_QPACK_FATAL + * rstate->absidx is invalid. + */ +static int qpack_decoder_validate_index(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_read_state *rstate) { + if (rstate->dynamic) { + return rstate->absidx < decoder->ctx.next_absidx && + decoder->ctx.next_absidx - rstate->absidx - 1 < + nghttp3_ringbuf_len(&decoder->ctx.dtable) + ? 0 + : NGHTTP3_ERR_QPACK_FATAL; + } + return rstate->absidx < nghttp3_arraylen(stable) ? 0 + : NGHTTP3_ERR_QPACK_FATAL; +} + +static void qpack_read_state_check_huffman(nghttp3_qpack_read_state *rstate, + const uint8_t b) { + rstate->huffman_encoded = (b & (1 << rstate->prefix)) != 0; +} + +static void qpack_read_state_terminate_name(nghttp3_qpack_read_state *rstate) { + *rstate->namebuf.last = '\0'; + rstate->name->len = nghttp3_buf_len(&rstate->namebuf); +} + +static void qpack_read_state_terminate_value(nghttp3_qpack_read_state *rstate) { + *rstate->valuebuf.last = '\0'; + rstate->value->len = nghttp3_buf_len(&rstate->valuebuf); +} + +nghttp3_ssize nghttp3_qpack_decoder_read_encoder(nghttp3_qpack_decoder *decoder, + const uint8_t *src, + size_t srclen) { + const uint8_t *p = src, *end; + int rv; + int busy = 0; + const nghttp3_mem *mem = decoder->ctx.mem; + nghttp3_ssize nread; + int rfin; + + if (decoder->ctx.bad) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + if (srclen == 0) { + return 0; + } + + end = src + srclen; + + for (; p != end || busy;) { + busy = 0; + switch (decoder->state) { + case NGHTTP3_QPACK_ES_STATE_OPCODE: + if ((*p) & 0x80) { + DEBUGF("qpack::decode: OPCODE_INSERT_INDEXED\n"); + decoder->opcode = NGHTTP3_QPACK_ES_OPCODE_INSERT_INDEXED; + decoder->rstate.dynamic = !((*p) & 0x40); + decoder->rstate.prefix = 6; + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_INDEX; + } else if ((*p) & 0x40) { + DEBUGF("qpack::decode: OPCODE_INSERT\n"); + decoder->opcode = NGHTTP3_QPACK_ES_OPCODE_INSERT; + decoder->rstate.dynamic = 0; + decoder->rstate.prefix = 5; + decoder->state = NGHTTP3_QPACK_ES_STATE_CHECK_NAME_HUFFMAN; + } else if ((*p) & 0x20) { + DEBUGF("qpack::decode: OPCODE_SET_DTABLE_TABLE_CAP\n"); + decoder->opcode = NGHTTP3_QPACK_ES_OPCODE_SET_DTABLE_CAP; + decoder->rstate.prefix = 5; + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_INDEX; + } else if (!((*p) & 0x20)) { + DEBUGF("qpack::decode: OPCODE_DUPLICATE\n"); + decoder->opcode = NGHTTP3_QPACK_ES_OPCODE_DUPLICATE; + decoder->rstate.dynamic = 1; + decoder->rstate.prefix = 5; + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_INDEX; + } else { + DEBUGF("qpack::decode: unknown opcode %02x\n", *p); + rv = NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + goto fail; + } + break; + case NGHTTP3_QPACK_ES_STATE_READ_INDEX: + nread = qpack_read_varint(&rfin, &decoder->rstate, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + goto fail; + } + + p += nread; + + if (!rfin) { + return p - src; + } + + if (decoder->opcode == NGHTTP3_QPACK_ES_OPCODE_SET_DTABLE_CAP) { + DEBUGF("qpack::decode: Set dtable capacity to %" PRIu64 "\n", + decoder->rstate.left); + rv = nghttp3_qpack_decoder_set_max_dtable_capacity( + decoder, (size_t)decoder->rstate.left); + if (rv != 0) { + rv = NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + goto fail; + } + + decoder->state = NGHTTP3_QPACK_ES_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&decoder->rstate); + break; + } + + rv = nghttp3_qpack_decoder_rel2abs(decoder, &decoder->rstate); + if (rv < 0) { + goto fail; + } + + switch (decoder->opcode) { + case NGHTTP3_QPACK_ES_OPCODE_DUPLICATE: + rv = nghttp3_qpack_decoder_dtable_duplicate_add(decoder); + if (rv != 0) { + goto fail; + } + + decoder->state = NGHTTP3_QPACK_ES_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&decoder->rstate); + + break; + case NGHTTP3_QPACK_ES_OPCODE_INSERT_INDEXED: + decoder->rstate.prefix = 7; + decoder->state = NGHTTP3_QPACK_ES_STATE_CHECK_VALUE_HUFFMAN; + + break; + default: + nghttp3_unreachable(); + } + + break; + case NGHTTP3_QPACK_ES_STATE_CHECK_NAME_HUFFMAN: + qpack_read_state_check_huffman(&decoder->rstate, *p); + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_NAMELEN; + decoder->rstate.left = 0; + decoder->rstate.shift = 0; + /* Fall through */ + case NGHTTP3_QPACK_ES_STATE_READ_NAMELEN: + nread = qpack_read_varint(&rfin, &decoder->rstate, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + goto fail; + } + + p += nread; + + if (!rfin) { + return p - src; + } + + if (decoder->rstate.left > NGHTTP3_QPACK_MAX_NAMELEN) { + rv = NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE; + goto fail; + } + + if (decoder->rstate.huffman_encoded) { + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_NAME_HUFFMAN; + nghttp3_qpack_huffman_decode_context_init(&decoder->rstate.huffman_ctx); + rv = nghttp3_rcbuf_new(&decoder->rstate.name, + (size_t)decoder->rstate.left * 2 + 1, mem); + } else { + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_NAME; + rv = nghttp3_rcbuf_new(&decoder->rstate.name, + (size_t)decoder->rstate.left + 1, mem); + } + if (rv != 0) { + goto fail; + } + + nghttp3_buf_wrap_init(&decoder->rstate.namebuf, + decoder->rstate.name->base, + decoder->rstate.name->len); + break; + case NGHTTP3_QPACK_ES_STATE_READ_NAME_HUFFMAN: + nread = qpack_read_huffman_string(&decoder->rstate, + &decoder->rstate.namebuf, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + goto fail; + } + + p += nread; + + if (decoder->rstate.left) { + return p - src; + } + + qpack_read_state_terminate_name(&decoder->rstate); + + decoder->state = NGHTTP3_QPACK_ES_STATE_CHECK_VALUE_HUFFMAN; + decoder->rstate.prefix = 7; + break; + case NGHTTP3_QPACK_ES_STATE_READ_NAME: + nread = + qpack_read_string(&decoder->rstate, &decoder->rstate.namebuf, p, end); + if (nread < 0) { + rv = (int)nread; + goto fail; + } + + p += nread; + + if (decoder->rstate.left) { + return p - src; + } + + qpack_read_state_terminate_name(&decoder->rstate); + + decoder->state = NGHTTP3_QPACK_ES_STATE_CHECK_VALUE_HUFFMAN; + decoder->rstate.prefix = 7; + break; + case NGHTTP3_QPACK_ES_STATE_CHECK_VALUE_HUFFMAN: + qpack_read_state_check_huffman(&decoder->rstate, *p); + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_VALUELEN; + decoder->rstate.left = 0; + decoder->rstate.shift = 0; + /* Fall through */ + case NGHTTP3_QPACK_ES_STATE_READ_VALUELEN: + nread = qpack_read_varint(&rfin, &decoder->rstate, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + goto fail; + } + + p += nread; + + if (!rfin) { + return p - src; + } + + if (decoder->rstate.left > NGHTTP3_QPACK_MAX_VALUELEN) { + rv = NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE; + goto fail; + } + + if (decoder->rstate.huffman_encoded) { + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_VALUE_HUFFMAN; + nghttp3_qpack_huffman_decode_context_init(&decoder->rstate.huffman_ctx); + rv = nghttp3_rcbuf_new(&decoder->rstate.value, + (size_t)decoder->rstate.left * 2 + 1, mem); + } else { + decoder->state = NGHTTP3_QPACK_ES_STATE_READ_VALUE; + rv = nghttp3_rcbuf_new(&decoder->rstate.value, + (size_t)decoder->rstate.left + 1, mem); + } + if (rv != 0) { + goto fail; + } + + nghttp3_buf_wrap_init(&decoder->rstate.valuebuf, + decoder->rstate.value->base, + decoder->rstate.value->len); + + /* value might be 0 length */ + busy = 1; + break; + case NGHTTP3_QPACK_ES_STATE_READ_VALUE_HUFFMAN: + nread = qpack_read_huffman_string(&decoder->rstate, + &decoder->rstate.valuebuf, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + goto fail; + } + + p += nread; + + if (decoder->rstate.left) { + return p - src; + } + + qpack_read_state_terminate_value(&decoder->rstate); + + switch (decoder->opcode) { + case NGHTTP3_QPACK_ES_OPCODE_INSERT_INDEXED: + rv = nghttp3_qpack_decoder_dtable_indexed_add(decoder); + break; + case NGHTTP3_QPACK_ES_OPCODE_INSERT: + rv = nghttp3_qpack_decoder_dtable_literal_add(decoder); + break; + default: + nghttp3_unreachable(); + } + if (rv != 0) { + goto fail; + } + + decoder->state = NGHTTP3_QPACK_ES_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&decoder->rstate); + break; + case NGHTTP3_QPACK_ES_STATE_READ_VALUE: + nread = qpack_read_string(&decoder->rstate, &decoder->rstate.valuebuf, p, + end); + if (nread < 0) { + rv = (int)nread; + goto fail; + } + + p += nread; + + if (decoder->rstate.left) { + return p - src; + } + + qpack_read_state_terminate_value(&decoder->rstate); + + switch (decoder->opcode) { + case NGHTTP3_QPACK_ES_OPCODE_INSERT_INDEXED: + rv = nghttp3_qpack_decoder_dtable_indexed_add(decoder); + break; + case NGHTTP3_QPACK_ES_OPCODE_INSERT: + rv = nghttp3_qpack_decoder_dtable_literal_add(decoder); + break; + default: + nghttp3_unreachable(); + } + if (rv != 0) { + goto fail; + } + + decoder->state = NGHTTP3_QPACK_ES_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&decoder->rstate); + break; + } + } + + return p - src; + +fail: + decoder->ctx.bad = 1; + return rv; +} + +int nghttp3_qpack_decoder_set_max_dtable_capacity( + nghttp3_qpack_decoder *decoder, size_t max_dtable_capacity) { + nghttp3_qpack_entry *ent; + size_t i; + nghttp3_qpack_context *ctx = &decoder->ctx; + const nghttp3_mem *mem = ctx->mem; + + if (max_dtable_capacity > decoder->ctx.hard_max_dtable_capacity) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + ctx->max_dtable_capacity = max_dtable_capacity; + + while (ctx->dtable_size > max_dtable_capacity) { + i = nghttp3_ringbuf_len(&ctx->dtable); + assert(i); + ent = *(nghttp3_qpack_entry **)nghttp3_ringbuf_get(&ctx->dtable, i - 1); + + ctx->dtable_size -= table_space(ent->nv.name->len, ent->nv.value->len); + + nghttp3_ringbuf_pop_back(&ctx->dtable); + nghttp3_qpack_entry_free(ent); + nghttp3_mem_free(mem, ent); + } + + return 0; +} + +int nghttp3_qpack_decoder_dtable_indexed_add(nghttp3_qpack_decoder *decoder) { + DEBUGF("qpack::decode: Insert With Name Reference (%s) absidx=%" PRIu64 ": " + "value=%*s\n", + decoder->rstate.dynamic ? "dynamic" : "static", decoder->rstate.absidx, + (int)decoder->rstate.value->len, decoder->rstate.value->base); + + if (decoder->rstate.dynamic) { + return nghttp3_qpack_decoder_dtable_dynamic_add(decoder); + } + + return nghttp3_qpack_decoder_dtable_static_add(decoder); +} + +int nghttp3_qpack_decoder_dtable_static_add(nghttp3_qpack_decoder *decoder) { + nghttp3_qpack_nv qnv; + int rv; + const nghttp3_qpack_static_header *shd; + + shd = &stable[decoder->rstate.absidx]; + + if (table_space(shd->name.len, decoder->rstate.value->len) > + decoder->ctx.max_dtable_capacity) { + return NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + } + + qnv.name = (nghttp3_rcbuf *)&shd->name; + qnv.value = decoder->rstate.value; + qnv.token = shd->token; + qnv.flags = NGHTTP3_NV_FLAG_NONE; + + rv = nghttp3_qpack_context_dtable_add(&decoder->ctx, &qnv, NULL, 0); + + nghttp3_rcbuf_decref(qnv.value); + + return rv; +} + +int nghttp3_qpack_decoder_dtable_dynamic_add(nghttp3_qpack_decoder *decoder) { + nghttp3_qpack_nv qnv; + int rv; + nghttp3_qpack_entry *ent; + + ent = nghttp3_qpack_context_dtable_get(&decoder->ctx, decoder->rstate.absidx); + + if (table_space(ent->nv.name->len, decoder->rstate.value->len) > + decoder->ctx.max_dtable_capacity) { + return NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + } + + qnv.name = ent->nv.name; + qnv.value = decoder->rstate.value; + qnv.token = ent->nv.token; + qnv.flags = NGHTTP3_NV_FLAG_NONE; + + nghttp3_rcbuf_incref(qnv.name); + + rv = nghttp3_qpack_context_dtable_add(&decoder->ctx, &qnv, NULL, 0); + + nghttp3_rcbuf_decref(qnv.value); + nghttp3_rcbuf_decref(qnv.name); + + return rv; +} + +int nghttp3_qpack_decoder_dtable_duplicate_add(nghttp3_qpack_decoder *decoder) { + int rv; + nghttp3_qpack_entry *ent; + nghttp3_qpack_nv qnv; + + DEBUGF("qpack::decode: Insert duplicate absidx=%" PRIu64 "\n", + decoder->rstate.absidx); + + ent = nghttp3_qpack_context_dtable_get(&decoder->ctx, decoder->rstate.absidx); + + if (table_space(ent->nv.name->len, ent->nv.value->len) > + decoder->ctx.max_dtable_capacity) { + return NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + } + + qnv = ent->nv; + nghttp3_rcbuf_incref(qnv.name); + nghttp3_rcbuf_incref(qnv.value); + + rv = nghttp3_qpack_context_dtable_add(&decoder->ctx, &qnv, NULL, 0); + + nghttp3_rcbuf_decref(qnv.value); + nghttp3_rcbuf_decref(qnv.name); + + return rv; +} + +int nghttp3_qpack_decoder_dtable_literal_add(nghttp3_qpack_decoder *decoder) { + nghttp3_qpack_nv qnv; + int rv; + + DEBUGF("qpack::decode: Insert With Literal Name: name=%*s value=%*s\n", + (int)decoder->rstate.name->len, decoder->rstate.name->base, + (int)decoder->rstate.value->len, decoder->rstate.value->base); + + if (table_space(decoder->rstate.name->len, decoder->rstate.value->len) > + decoder->ctx.max_dtable_capacity) { + return NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + } + + qnv.name = decoder->rstate.name; + qnv.value = decoder->rstate.value; + qnv.token = qpack_lookup_token(qnv.name->base, qnv.name->len); + qnv.flags = NGHTTP3_NV_FLAG_NONE; + + rv = nghttp3_qpack_context_dtable_add(&decoder->ctx, &qnv, NULL, 0); + + nghttp3_rcbuf_decref(qnv.value); + nghttp3_rcbuf_decref(qnv.name); + + return rv; +} + +void nghttp3_qpack_decoder_set_max_concurrent_streams( + nghttp3_qpack_decoder *decoder, size_t max_concurrent_streams) { + decoder->max_concurrent_streams = + nghttp3_max(decoder->max_concurrent_streams, max_concurrent_streams); +} + +void nghttp3_qpack_stream_context_init(nghttp3_qpack_stream_context *sctx, + int64_t stream_id, + const nghttp3_mem *mem) { + nghttp3_qpack_read_state_reset(&sctx->rstate); + + sctx->mem = mem; + sctx->rstate.prefix = 8; + sctx->state = NGHTTP3_QPACK_RS_STATE_RICNT; + sctx->opcode = 0; + sctx->stream_id = stream_id; + sctx->ricnt = 0; + sctx->dbase_sign = 0; + sctx->base = 0; +} + +void nghttp3_qpack_stream_context_free(nghttp3_qpack_stream_context *sctx) { + nghttp3_qpack_read_state_free(&sctx->rstate); +} + +void nghttp3_qpack_stream_context_reset(nghttp3_qpack_stream_context *sctx) { + nghttp3_qpack_stream_context_init(sctx, sctx->stream_id, sctx->mem); +} + +uint64_t +nghttp3_qpack_stream_context_get_ricnt(nghttp3_qpack_stream_context *sctx) { + return sctx->ricnt; +} + +nghttp3_ssize +nghttp3_qpack_decoder_read_request(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv, uint8_t *pflags, + const uint8_t *src, size_t srclen, int fin) { + const uint8_t *p = src, *end = src ? src + srclen : src; + int rv; + int busy = 0; + nghttp3_ssize nread; + int rfin; + const nghttp3_mem *mem = decoder->ctx.mem; + + if (decoder->ctx.bad) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + *pflags = NGHTTP3_QPACK_DECODE_FLAG_NONE; + + for (; p != end || busy;) { + busy = 0; + switch (sctx->state) { + case NGHTTP3_QPACK_RS_STATE_RICNT: + nread = qpack_read_varint(&rfin, &sctx->rstate, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + + p += nread; + + if (!rfin) { + goto almost_ok; + } + + rv = nghttp3_qpack_decoder_reconstruct_ricnt(decoder, &sctx->ricnt, + sctx->rstate.left); + if (rv != 0) { + goto fail; + } + + sctx->state = NGHTTP3_QPACK_RS_STATE_DBASE_SIGN; + break; + case NGHTTP3_QPACK_RS_STATE_DBASE_SIGN: + if ((*p) & 0x80) { + sctx->dbase_sign = 1; + } + sctx->state = NGHTTP3_QPACK_RS_STATE_DBASE; + sctx->rstate.left = 0; + sctx->rstate.prefix = 7; + sctx->rstate.shift = 0; + /* Fall through */ + case NGHTTP3_QPACK_RS_STATE_DBASE: + nread = qpack_read_varint(&rfin, &sctx->rstate, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + + p += nread; + + if (!rfin) { + goto almost_ok; + } + + if (sctx->dbase_sign) { + if (sctx->ricnt <= sctx->rstate.left) { + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + sctx->base = sctx->ricnt - sctx->rstate.left - 1; + } else { + sctx->base = sctx->ricnt + sctx->rstate.left; + } + + DEBUGF("qpack::decode: ricnt=%" PRIu64 " base=%" PRIu64 " icnt=%" PRIu64 + "\n", + sctx->ricnt, sctx->base, decoder->ctx.next_absidx); + + if (sctx->ricnt > decoder->ctx.next_absidx) { + DEBUGF("qpack::decode: stream blocked\n"); + sctx->state = NGHTTP3_QPACK_RS_STATE_BLOCKED; + *pflags |= NGHTTP3_QPACK_DECODE_FLAG_BLOCKED; + return p - src; + } + + sctx->state = NGHTTP3_QPACK_RS_STATE_OPCODE; + sctx->rstate.left = 0; + sctx->rstate.shift = 0; + break; + case NGHTTP3_QPACK_RS_STATE_OPCODE: + assert(sctx->rstate.left == 0); + assert(sctx->rstate.shift == 0); + if ((*p) & 0x80) { + DEBUGF("qpack::decode: OPCODE_INDEXED\n"); + sctx->opcode = NGHTTP3_QPACK_RS_OPCODE_INDEXED; + sctx->rstate.dynamic = !((*p) & 0x40); + sctx->rstate.prefix = 6; + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_INDEX; + } else if ((*p) & 0x40) { + DEBUGF("qpack::decode: OPCODE_INDEXED_NAME\n"); + sctx->opcode = NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME; + sctx->rstate.never = (*p) & 0x20; + sctx->rstate.dynamic = !((*p) & 0x10); + sctx->rstate.prefix = 4; + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_INDEX; + } else if ((*p) & 0x20) { + DEBUGF("qpack::decode: OPCODE_LITERAL\n"); + sctx->opcode = NGHTTP3_QPACK_RS_OPCODE_LITERAL; + sctx->rstate.never = (*p) & 0x10; + sctx->rstate.dynamic = 0; + sctx->rstate.prefix = 3; + sctx->state = NGHTTP3_QPACK_RS_STATE_CHECK_NAME_HUFFMAN; + } else if ((*p) & 0x10) { + DEBUGF("qpack::decode: OPCODE_INDEXED_PB\n"); + sctx->opcode = NGHTTP3_QPACK_RS_OPCODE_INDEXED_PB; + sctx->rstate.dynamic = 1; + sctx->rstate.prefix = 4; + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_INDEX; + } else { + DEBUGF("qpack::decode: OPCODE_INDEXED_NAME_PB\n"); + sctx->opcode = NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME_PB; + sctx->rstate.never = (*p) & 0x08; + sctx->rstate.dynamic = 1; + sctx->rstate.prefix = 3; + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_INDEX; + } + break; + case NGHTTP3_QPACK_RS_STATE_READ_INDEX: + nread = qpack_read_varint(&rfin, &sctx->rstate, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + + p += nread; + + if (!rfin) { + goto almost_ok; + } + + switch (sctx->opcode) { + case NGHTTP3_QPACK_RS_OPCODE_INDEXED: + rv = nghttp3_qpack_decoder_brel2abs(decoder, sctx); + if (rv != 0) { + goto fail; + } + nghttp3_qpack_decoder_emit_indexed(decoder, sctx, nv); + *pflags |= NGHTTP3_QPACK_DECODE_FLAG_EMIT; + + sctx->state = NGHTTP3_QPACK_RS_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&sctx->rstate); + + return p - src; + case NGHTTP3_QPACK_RS_OPCODE_INDEXED_PB: + rv = nghttp3_qpack_decoder_pbrel2abs(decoder, sctx); + if (rv != 0) { + goto fail; + } + nghttp3_qpack_decoder_emit_indexed(decoder, sctx, nv); + *pflags |= NGHTTP3_QPACK_DECODE_FLAG_EMIT; + + sctx->state = NGHTTP3_QPACK_RS_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&sctx->rstate); + + return p - src; + case NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME: + rv = nghttp3_qpack_decoder_brel2abs(decoder, sctx); + if (rv != 0) { + goto fail; + } + sctx->rstate.prefix = 7; + sctx->state = NGHTTP3_QPACK_RS_STATE_CHECK_VALUE_HUFFMAN; + break; + case NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME_PB: + rv = nghttp3_qpack_decoder_pbrel2abs(decoder, sctx); + if (rv != 0) { + goto fail; + } + sctx->rstate.prefix = 7; + sctx->state = NGHTTP3_QPACK_RS_STATE_CHECK_VALUE_HUFFMAN; + break; + default: + nghttp3_unreachable(); + } + break; + case NGHTTP3_QPACK_RS_STATE_CHECK_NAME_HUFFMAN: + qpack_read_state_check_huffman(&sctx->rstate, *p); + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_NAMELEN; + sctx->rstate.left = 0; + sctx->rstate.shift = 0; + /* Fall through */ + case NGHTTP3_QPACK_RS_STATE_READ_NAMELEN: + nread = qpack_read_varint(&rfin, &sctx->rstate, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + + p += nread; + + if (!rfin) { + goto almost_ok; + } + + if (sctx->rstate.left > NGHTTP3_QPACK_MAX_NAMELEN) { + rv = NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE; + goto fail; + } + + if (sctx->rstate.huffman_encoded) { + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_NAME_HUFFMAN; + nghttp3_qpack_huffman_decode_context_init(&sctx->rstate.huffman_ctx); + rv = nghttp3_rcbuf_new(&sctx->rstate.name, + (size_t)sctx->rstate.left * 2 + 1, mem); + } else { + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_NAME; + rv = nghttp3_rcbuf_new(&sctx->rstate.name, + (size_t)sctx->rstate.left + 1, mem); + } + if (rv != 0) { + goto fail; + } + + nghttp3_buf_wrap_init(&sctx->rstate.namebuf, sctx->rstate.name->base, + sctx->rstate.name->len); + break; + case NGHTTP3_QPACK_RS_STATE_READ_NAME_HUFFMAN: + nread = qpack_read_huffman_string(&sctx->rstate, &sctx->rstate.namebuf, p, + end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + + p += nread; + + if (sctx->rstate.left) { + goto almost_ok; + } + + qpack_read_state_terminate_name(&sctx->rstate); + + sctx->state = NGHTTP3_QPACK_RS_STATE_CHECK_VALUE_HUFFMAN; + sctx->rstate.prefix = 7; + break; + case NGHTTP3_QPACK_RS_STATE_READ_NAME: + nread = qpack_read_string(&sctx->rstate, &sctx->rstate.namebuf, p, end); + if (nread < 0) { + rv = (int)nread; + goto fail; + } + + p += nread; + + if (sctx->rstate.left) { + goto almost_ok; + } + + qpack_read_state_terminate_name(&sctx->rstate); + + sctx->state = NGHTTP3_QPACK_RS_STATE_CHECK_VALUE_HUFFMAN; + sctx->rstate.prefix = 7; + break; + case NGHTTP3_QPACK_RS_STATE_CHECK_VALUE_HUFFMAN: + qpack_read_state_check_huffman(&sctx->rstate, *p); + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_VALUELEN; + sctx->rstate.left = 0; + sctx->rstate.shift = 0; + /* Fall through */ + case NGHTTP3_QPACK_RS_STATE_READ_VALUELEN: + nread = qpack_read_varint(&rfin, &sctx->rstate, p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + + p += nread; + + if (!rfin) { + goto almost_ok; + } + + if (sctx->rstate.left > NGHTTP3_QPACK_MAX_VALUELEN) { + rv = NGHTTP3_ERR_QPACK_HEADER_TOO_LARGE; + goto fail; + } + + if (sctx->rstate.huffman_encoded) { + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_VALUE_HUFFMAN; + nghttp3_qpack_huffman_decode_context_init(&sctx->rstate.huffman_ctx); + rv = nghttp3_rcbuf_new(&sctx->rstate.value, + (size_t)sctx->rstate.left * 2 + 1, mem); + } else { + sctx->state = NGHTTP3_QPACK_RS_STATE_READ_VALUE; + rv = nghttp3_rcbuf_new(&sctx->rstate.value, + (size_t)sctx->rstate.left + 1, mem); + } + if (rv != 0) { + goto fail; + } + + nghttp3_buf_wrap_init(&sctx->rstate.valuebuf, sctx->rstate.value->base, + sctx->rstate.value->len); + + /* value might be 0 length */ + busy = 1; + break; + case NGHTTP3_QPACK_RS_STATE_READ_VALUE_HUFFMAN: + nread = qpack_read_huffman_string(&sctx->rstate, &sctx->rstate.valuebuf, + p, end); + if (nread < 0) { + assert(NGHTTP3_ERR_QPACK_FATAL == nread); + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + + p += nread; + + if (sctx->rstate.left) { + goto almost_ok; + } + + qpack_read_state_terminate_value(&sctx->rstate); + + switch (sctx->opcode) { + case NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME: + case NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME_PB: + rv = nghttp3_qpack_decoder_emit_indexed_name(decoder, sctx, nv); + if (rv != 0) { + goto fail; + } + + break; + case NGHTTP3_QPACK_RS_OPCODE_LITERAL: + nghttp3_qpack_decoder_emit_literal(decoder, sctx, nv); + break; + default: + nghttp3_unreachable(); + } + + *pflags |= NGHTTP3_QPACK_DECODE_FLAG_EMIT; + + sctx->state = NGHTTP3_QPACK_RS_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&sctx->rstate); + + return p - src; + case NGHTTP3_QPACK_RS_STATE_READ_VALUE: + nread = qpack_read_string(&sctx->rstate, &sctx->rstate.valuebuf, p, end); + if (nread < 0) { + rv = (int)nread; + goto fail; + } + + p += nread; + + if (sctx->rstate.left) { + goto almost_ok; + } + + qpack_read_state_terminate_value(&sctx->rstate); + + switch (sctx->opcode) { + case NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME: + case NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME_PB: + rv = nghttp3_qpack_decoder_emit_indexed_name(decoder, sctx, nv); + if (rv != 0) { + goto fail; + } + + break; + case NGHTTP3_QPACK_RS_OPCODE_LITERAL: + nghttp3_qpack_decoder_emit_literal(decoder, sctx, nv); + break; + default: + nghttp3_unreachable(); + } + + *pflags |= NGHTTP3_QPACK_DECODE_FLAG_EMIT; + + sctx->state = NGHTTP3_QPACK_RS_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&sctx->rstate); + + return p - src; + case NGHTTP3_QPACK_RS_STATE_BLOCKED: + if (sctx->ricnt > decoder->ctx.next_absidx) { + DEBUGF("qpack::decode: stream still blocked\n"); + *pflags |= NGHTTP3_QPACK_DECODE_FLAG_BLOCKED; + return p - src; + } + sctx->state = NGHTTP3_QPACK_RS_STATE_OPCODE; + nghttp3_qpack_read_state_reset(&sctx->rstate); + break; + } + } + +almost_ok: + if (fin) { + if (sctx->state != NGHTTP3_QPACK_RS_STATE_OPCODE) { + rv = NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + goto fail; + } + + *pflags |= NGHTTP3_QPACK_DECODE_FLAG_FINAL; + + if (sctx->ricnt) { + rv = nghttp3_qpack_decoder_write_section_ack(decoder, sctx); + if (rv != 0) { + goto fail; + } + } + } + + return p - src; + +fail: + decoder->ctx.bad = 1; + return rv; +} + +static int qpack_decoder_dbuf_overflow(nghttp3_qpack_decoder *decoder) { + size_t limit = nghttp3_max(decoder->max_concurrent_streams, 100); + /* 10 = nghttp3_qpack_put_varint_len((1ULL << 62) - 1, 2)) */ + return nghttp3_buf_len(&decoder->dbuf) > limit * 2 * 10; +} + +int nghttp3_qpack_decoder_write_section_ack( + nghttp3_qpack_decoder *decoder, const nghttp3_qpack_stream_context *sctx) { + nghttp3_buf *dbuf = &decoder->dbuf; + uint8_t *p; + int rv; + + if (qpack_decoder_dbuf_overflow(decoder)) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + rv = reserve_buf_small( + dbuf, nghttp3_qpack_put_varint_len((uint64_t)sctx->stream_id, 7), + decoder->ctx.mem); + if (rv != 0) { + return rv; + } + + p = dbuf->last; + *p = 0x80; + dbuf->last = nghttp3_qpack_put_varint(p, (uint64_t)sctx->stream_id, 7); + + if (decoder->written_icnt < sctx->ricnt) { + decoder->written_icnt = sctx->ricnt; + } + + return 0; +} + +size_t +nghttp3_qpack_decoder_get_decoder_streamlen(nghttp3_qpack_decoder *decoder) { + uint64_t n; + size_t len = 0; + + if (decoder->written_icnt < decoder->ctx.next_absidx) { + n = decoder->ctx.next_absidx - decoder->written_icnt; + len = nghttp3_qpack_put_varint_len(n, 6); + } + + return nghttp3_buf_len(&decoder->dbuf) + len; +} + +void nghttp3_qpack_decoder_write_decoder(nghttp3_qpack_decoder *decoder, + nghttp3_buf *dbuf) { + uint8_t *p; + uint64_t n = 0; + size_t len = 0; + (void)len; + + if (decoder->written_icnt < decoder->ctx.next_absidx) { + n = decoder->ctx.next_absidx - decoder->written_icnt; + len = nghttp3_qpack_put_varint_len(n, 6); + } + + assert(nghttp3_buf_left(dbuf) >= nghttp3_buf_len(&decoder->dbuf) + len); + + if (nghttp3_buf_len(&decoder->dbuf)) { + dbuf->last = nghttp3_cpymem(dbuf->last, decoder->dbuf.pos, + nghttp3_buf_len(&decoder->dbuf)); + } + + if (n) { + p = dbuf->last; + *p = 0; + dbuf->last = nghttp3_qpack_put_varint(p, n, 6); + + decoder->written_icnt = decoder->ctx.next_absidx; + } + + nghttp3_buf_reset(&decoder->dbuf); +} + +int nghttp3_qpack_decoder_cancel_stream(nghttp3_qpack_decoder *decoder, + int64_t stream_id) { + uint8_t *p; + int rv; + + if (qpack_decoder_dbuf_overflow(decoder)) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + rv = reserve_buf(&decoder->dbuf, + nghttp3_qpack_put_varint_len((uint64_t)stream_id, 6), + decoder->ctx.mem); + if (rv != 0) { + return rv; + } + + p = decoder->dbuf.last; + *p = 0x40; + decoder->dbuf.last = nghttp3_qpack_put_varint(p, (uint64_t)stream_id, 6); + + return 0; +} + +int nghttp3_qpack_decoder_reconstruct_ricnt(nghttp3_qpack_decoder *decoder, + uint64_t *dest, uint64_t encricnt) { + uint64_t max_ents, full, max, max_wrapped, ricnt; + + if (encricnt == 0) { + *dest = 0; + return 0; + } + + max_ents = + decoder->ctx.hard_max_dtable_capacity / NGHTTP3_QPACK_ENTRY_OVERHEAD; + full = 2 * max_ents; + + if (encricnt > full) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + + max = decoder->ctx.next_absidx + max_ents; + max_wrapped = max / full * full; + ricnt = max_wrapped + encricnt - 1; + + if (ricnt > max) { + if (ricnt <= full) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + ricnt -= full; + } + + if (ricnt == 0) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + + *dest = ricnt; + + return 0; +} + +int nghttp3_qpack_decoder_rel2abs(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_read_state *rstate) { + DEBUGF("qpack::decode: dynamic=%d relidx=%" PRIu64 " icnt=%" PRIu64 "\n", + rstate->dynamic, rstate->left, decoder->ctx.next_absidx); + + if (rstate->dynamic) { + if (decoder->ctx.next_absidx < rstate->left + 1) { + return NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + } + rstate->absidx = decoder->ctx.next_absidx - rstate->left - 1; + } else { + rstate->absidx = rstate->left; + } + if (qpack_decoder_validate_index(decoder, rstate) != 0) { + return NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR; + } + return 0; +} + +int nghttp3_qpack_decoder_brel2abs(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx) { + nghttp3_qpack_read_state *rstate = &sctx->rstate; + + DEBUGF("qpack::decode: dynamic=%d relidx=%" PRIu64 " base=%" PRIu64 + " icnt=%" PRIu64 "\n", + rstate->dynamic, rstate->left, sctx->base, decoder->ctx.next_absidx); + + if (rstate->dynamic) { + if (sctx->base < rstate->left + 1) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + rstate->absidx = sctx->base - rstate->left - 1; + + if (rstate->absidx >= sctx->ricnt) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + } else { + rstate->absidx = rstate->left; + } + + if (qpack_decoder_validate_index(decoder, rstate) != 0) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + return 0; +} + +int nghttp3_qpack_decoder_pbrel2abs(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx) { + nghttp3_qpack_read_state *rstate = &sctx->rstate; + + DEBUGF("qpack::decode: pbidx=%" PRIu64 " base=%" PRIu64 " icnt=%" PRIu64 "\n", + rstate->left, sctx->base, decoder->ctx.next_absidx); + + assert(rstate->dynamic); + + rstate->absidx = rstate->left + sctx->base; + + if (rstate->absidx >= sctx->ricnt) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + + if (qpack_decoder_validate_index(decoder, rstate) != 0) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + return 0; +} + +static void +qpack_decoder_emit_static_indexed(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv) { + const nghttp3_qpack_static_header *shd = &stable[sctx->rstate.absidx]; + (void)decoder; + + nv->name = (nghttp3_rcbuf *)&shd->name; + nv->value = (nghttp3_rcbuf *)&shd->value; + nv->token = shd->token; + nv->flags = NGHTTP3_NV_FLAG_NONE; +} + +static void +qpack_decoder_emit_dynamic_indexed(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv) { + nghttp3_qpack_entry *ent = + nghttp3_qpack_context_dtable_get(&decoder->ctx, sctx->rstate.absidx); + + *nv = ent->nv; + + nghttp3_rcbuf_incref(nv->name); + nghttp3_rcbuf_incref(nv->value); +} + +void nghttp3_qpack_decoder_emit_indexed(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv) { + DEBUGF("qpack::decode: Indexed (%s) absidx=%" PRIu64 "\n", + sctx->rstate.dynamic ? "dynamic" : "static", sctx->rstate.absidx); + + if (sctx->rstate.dynamic) { + qpack_decoder_emit_dynamic_indexed(decoder, sctx, nv); + } else { + qpack_decoder_emit_static_indexed(decoder, sctx, nv); + } +} + +static void +qpack_decoder_emit_static_indexed_name(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv) { + const nghttp3_qpack_static_header *shd = &stable[sctx->rstate.absidx]; + (void)decoder; + + nv->name = (nghttp3_rcbuf *)&shd->name; + nv->value = sctx->rstate.value; + nv->token = shd->token; + nv->flags = + sctx->rstate.never ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE; + + sctx->rstate.value = NULL; +} + +static int +qpack_decoder_emit_dynamic_indexed_name(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv) { + nghttp3_qpack_entry *ent; + + /* A broken encoder might change dtable capacity while processing + request stream instruction. Check the absidx again. */ + if (qpack_decoder_validate_index(decoder, &sctx->rstate) != 0) { + return NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED; + } + + ent = nghttp3_qpack_context_dtable_get(&decoder->ctx, sctx->rstate.absidx); + + nv->name = ent->nv.name; + nv->value = sctx->rstate.value; + nv->token = ent->nv.token; + nv->flags = + sctx->rstate.never ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE; + + nghttp3_rcbuf_incref(nv->name); + + sctx->rstate.value = NULL; + + return 0; +} + +int nghttp3_qpack_decoder_emit_indexed_name(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv) { + (void)decoder; + + DEBUGF("qpack::decode: Indexed name (%s) absidx=%" PRIu64 " value=%*s\n", + sctx->rstate.dynamic ? "dynamic" : "static", sctx->rstate.absidx, + (int)sctx->rstate.value->len, sctx->rstate.value->base); + + if (sctx->rstate.dynamic) { + return qpack_decoder_emit_dynamic_indexed_name(decoder, sctx, nv); + } + + qpack_decoder_emit_static_indexed_name(decoder, sctx, nv); + + return 0; +} + +void nghttp3_qpack_decoder_emit_literal(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv) { + (void)decoder; + + DEBUGF("qpack::decode: Emit literal name=%*s value=%*s\n", + (int)sctx->rstate.name->len, sctx->rstate.name->base, + (int)sctx->rstate.value->len, sctx->rstate.value->base); + + nv->name = sctx->rstate.name; + nv->value = sctx->rstate.value; + nv->token = qpack_lookup_token(nv->name->base, nv->name->len); + nv->flags = + sctx->rstate.never ? NGHTTP3_NV_FLAG_NEVER_INDEX : NGHTTP3_NV_FLAG_NONE; + + sctx->rstate.name = NULL; + sctx->rstate.value = NULL; +} + +int nghttp3_qpack_encoder_new(nghttp3_qpack_encoder **pencoder, + size_t hard_max_dtable_capacity, + const nghttp3_mem *mem) { + int rv; + nghttp3_qpack_encoder *p; + + p = nghttp3_mem_malloc(mem, sizeof(nghttp3_qpack_encoder)); + if (p == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + rv = nghttp3_qpack_encoder_init(p, hard_max_dtable_capacity, mem); + if (rv != 0) { + return rv; + } + + *pencoder = p; + + return 0; +} + +void nghttp3_qpack_encoder_del(nghttp3_qpack_encoder *encoder) { + const nghttp3_mem *mem; + + if (encoder == NULL) { + return; + } + + mem = encoder->ctx.mem; + + nghttp3_qpack_encoder_free(encoder); + nghttp3_mem_free(mem, encoder); +} + +int nghttp3_qpack_stream_context_new(nghttp3_qpack_stream_context **psctx, + int64_t stream_id, + const nghttp3_mem *mem) { + nghttp3_qpack_stream_context *p; + + p = nghttp3_mem_malloc(mem, sizeof(nghttp3_qpack_stream_context)); + if (p == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + nghttp3_qpack_stream_context_init(p, stream_id, mem); + + *psctx = p; + + return 0; +} + +void nghttp3_qpack_stream_context_del(nghttp3_qpack_stream_context *sctx) { + const nghttp3_mem *mem; + + if (sctx == NULL) { + return; + } + + mem = sctx->mem; + + nghttp3_qpack_stream_context_free(sctx); + nghttp3_mem_free(mem, sctx); +} + +int nghttp3_qpack_decoder_new(nghttp3_qpack_decoder **pdecoder, + size_t hard_max_dtable_capacity, + size_t max_blocked_streams, + const nghttp3_mem *mem) { + int rv; + nghttp3_qpack_decoder *p; + + p = nghttp3_mem_malloc(mem, sizeof(nghttp3_qpack_decoder)); + if (p == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + rv = nghttp3_qpack_decoder_init(p, hard_max_dtable_capacity, + max_blocked_streams, mem); + if (rv != 0) { + return rv; + } + + *pdecoder = p; + + return 0; +} + +void nghttp3_qpack_decoder_del(nghttp3_qpack_decoder *decoder) { + const nghttp3_mem *mem; + + if (decoder == NULL) { + return; + } + + mem = decoder->ctx.mem; + + nghttp3_qpack_decoder_free(decoder); + nghttp3_mem_free(mem, decoder); +} + +uint64_t nghttp3_qpack_decoder_get_icnt(const nghttp3_qpack_decoder *decoder) { + return decoder->ctx.next_absidx; +} diff --git a/lib/nghttp3_qpack.h b/lib/nghttp3_qpack.h new file mode 100644 index 0000000..804969e --- /dev/null +++ b/lib/nghttp3_qpack.h @@ -0,0 +1,996 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2013 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_QPACK_H +#define NGHTTP3_QPACK_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_rcbuf.h" +#include "nghttp3_map.h" +#include "nghttp3_pq.h" +#include "nghttp3_ringbuf.h" +#include "nghttp3_buf.h" +#include "nghttp3_ksl.h" +#include "nghttp3_qpack_huffman.h" + +#define NGHTTP3_QPACK_INT_MAX ((1ull << 62) - 1) + +/* NGHTTP3_QPACK_MAX_NAMELEN is the maximum (compressed) length of + header name this library can decode. */ +#define NGHTTP3_QPACK_MAX_NAMELEN 256 +/* NGHTTP3_QPACK_MAX_VALUELEN is the maximum (compressed) length of + header value this library can decode. */ +#define NGHTTP3_QPACK_MAX_VALUELEN 65536 + +/* nghttp3_qpack_indexing_mode is a indexing strategy. */ +typedef enum nghttp3_qpack_indexing_mode { + /* NGHTTP3_QPACK_INDEXING_MODE_LITERAL means that header field + should not be inserted into dynamic table. */ + NGHTTP3_QPACK_INDEXING_MODE_LITERAL, + /* NGHTTP3_QPACK_INDEXING_MODE_STORE means that header field can be + inserted into dynamic table. */ + NGHTTP3_QPACK_INDEXING_MODE_STORE, + /* NGHTTP3_QPACK_INDEXING_MODE_NEVER means that header field should + not be inserted into dynamic table and this must be true for all + forwarding paths. */ + NGHTTP3_QPACK_INDEXING_MODE_NEVER, +} nghttp3_qpack_indexing_mode; + +typedef struct nghttp3_qpack_entry nghttp3_qpack_entry; + +struct nghttp3_qpack_entry { + /* The header field name/value pair */ + nghttp3_qpack_nv nv; + /* map_next points to the entry which shares same bucket in hash + table. */ + nghttp3_qpack_entry *map_next; + /* sum is the sum of all entries inserted up to this entry. This + value does not contain the space required for this entry. */ + size_t sum; + /* absidx is the absolute index of this entry. */ + uint64_t absidx; + /* The hash value for header name (nv.name). */ + uint32_t hash; +}; + +/* The entry used for static table. */ +typedef struct nghttp3_qpack_static_entry { + uint64_t absidx; + int32_t token; + uint32_t hash; +} nghttp3_qpack_static_entry; + +typedef struct nghttp3_qpack_static_header { + nghttp3_rcbuf name; + nghttp3_rcbuf value; + int32_t token; +} nghttp3_qpack_static_header; + +/* + * nghttp3_qpack_header_block_ref is created per encoded header block + * and includes the required insert count and the minimum insert count + * of dynamic table entry it refers to. + */ +typedef struct nghttp3_qpack_header_block_ref { + nghttp3_pq_entry max_cnts_pe; + nghttp3_pq_entry min_cnts_pe; + /* max_cnt is the required insert count. */ + uint64_t max_cnt; + /* min_cnt is the minimum insert count of dynamic table entry it + refers to. In other words, this is the minimum absolute index of + dynamic header table entry this encoded block refers to plus + 1. */ + uint64_t min_cnt; +} nghttp3_qpack_header_block_ref; + +int nghttp3_qpack_header_block_ref_new(nghttp3_qpack_header_block_ref **pref, + uint64_t max_cnt, uint64_t min_cnt, + const nghttp3_mem *mem); + +void nghttp3_qpack_header_block_ref_del(nghttp3_qpack_header_block_ref *ref, + const nghttp3_mem *mem); + +typedef struct nghttp3_qpack_stream { + int64_t stream_id; + /* refs is an array of pointer to nghttp3_qpack_header_block_ref in + the order of the time they are encoded. HTTP/3 allows multiple + header blocks (e.g., non-final response headers, final response + headers, trailers, and push promises) per stream. */ + nghttp3_ringbuf refs; + /* max_cnts is a priority queue sorted by descending order of + max_cnt of nghttp3_qpack_header_block_ref. */ + nghttp3_pq max_cnts; +} nghttp3_qpack_stream; + +int nghttp3_qpack_stream_new(nghttp3_qpack_stream **pstream, int64_t stream_id, + const nghttp3_mem *mem); + +void nghttp3_qpack_stream_del(nghttp3_qpack_stream *stream, + const nghttp3_mem *mem); + +uint64_t nghttp3_qpack_stream_get_max_cnt(const nghttp3_qpack_stream *stream); + +int nghttp3_qpack_stream_add_ref(nghttp3_qpack_stream *stream, + nghttp3_qpack_header_block_ref *ref); + +void nghttp3_qpack_stream_pop_ref(nghttp3_qpack_stream *stream); + +#define NGHTTP3_QPACK_ENTRY_OVERHEAD 32 + +typedef struct nghttp3_qpack_context { + /* dtable is a dynamic table */ + nghttp3_ringbuf dtable; + /* mem is memory allocator */ + const nghttp3_mem *mem; + /* dtable_size is abstracted buffer size of dtable as described in + the spec. This is the sum of length of name/value in dtable + + NGHTTP3_QPACK_ENTRY_OVERHEAD bytes overhead per each entry. */ + size_t dtable_size; + size_t dtable_sum; + /* hard_max_dtable_capacity is the upper bound of + max_dtable_capacity. */ + size_t hard_max_dtable_capacity; + /* max_dtable_capacity is the maximum capacity of the dynamic + table. */ + size_t max_dtable_capacity; + /* max_blocked_streams is the maximum number of stream which can be + blocked. */ + size_t max_blocked_streams; + /* next_absidx is the next absolute index for nghttp3_qpack_entry. + It is equivalent to insert count. */ + uint64_t next_absidx; + /* If inflate/deflate error occurred, this value is set to 1 and + further invocation of inflate/deflate will fail with + NGHTTP3_ERR_QPACK_FATAL. */ + uint8_t bad; +} nghttp3_qpack_context; + +typedef struct nghttp3_qpack_read_state { + nghttp3_qpack_huffman_decode_context huffman_ctx; + nghttp3_buf namebuf; + nghttp3_buf valuebuf; + nghttp3_rcbuf *name; + nghttp3_rcbuf *value; + uint64_t left; + size_t prefix; + size_t shift; + uint64_t absidx; + int never; + int dynamic; + int huffman_encoded; +} nghttp3_qpack_read_state; + +void nghttp3_qpack_read_state_free(nghttp3_qpack_read_state *rstate); + +void nghttp3_qpack_read_state_reset(nghttp3_qpack_read_state *rstate); + +#define NGHTTP3_QPACK_MAP_SIZE 64 + +typedef struct nghttp3_qpack_map { + nghttp3_qpack_entry *table[NGHTTP3_QPACK_MAP_SIZE]; +} nghttp3_qpack_map; + +/* nghttp3_qpack_decoder_stream_state is a set of states when decoding + decoder stream. */ +typedef enum nghttp3_qpack_decoder_stream_state { + NGHTTP3_QPACK_DS_STATE_OPCODE, + NGHTTP3_QPACK_DS_STATE_READ_NUMBER, +} nghttp3_qpack_decoder_stream_state; + +/* nghttp3_qpack_decoder_stream_opcode is opcode used in decoder + stream. */ +typedef enum nghttp3_qpack_decoder_stream_opcode { + NGHTTP3_QPACK_DS_OPCODE_ICNT_INCREMENT, + NGHTTP3_QPACK_DS_OPCODE_SECTION_ACK, + NGHTTP3_QPACK_DS_OPCODE_STREAM_CANCEL, +} nghttp3_qpack_decoder_stream_opcode; + +/* QPACK encoder flags */ + +/* NGHTTP3_QPACK_ENCODER_FLAG_NONE indicates that no flag is set. */ +#define NGHTTP3_QPACK_ENCODER_FLAG_NONE 0x00u +/* NGHTTP3_QPACK_ENCODER_FLAG_PENDING_SET_DTABLE_CAP indicates that + Set Dynamic Table Capacity is required. */ +#define NGHTTP3_QPACK_ENCODER_FLAG_PENDING_SET_DTABLE_CAP 0x01u + +struct nghttp3_qpack_encoder { + nghttp3_qpack_context ctx; + /* dtable_map is a map of hash to nghttp3_qpack_entry to provide + fast access to an entry in dynamic table. */ + nghttp3_qpack_map dtable_map; + /* streams is a map of stream ID to nghttp3_qpack_stream to keep + track of unacknowledged streams. */ + nghttp3_map streams; + /* blocked_streams is an ordered list of nghttp3_qpack_stream, in + descending order of max_cnt, to search the unblocked streams by + received known count. */ + nghttp3_ksl blocked_streams; + /* min_cnts is a priority queue of nghttp3_qpack_header_block_ref + sorted by ascending order of min_cnt to know that an entry can be + evicted from dynamic table. */ + nghttp3_pq min_cnts; + /* krcnt is Known Received Count. */ + uint64_t krcnt; + /* state is a current state of reading decoder stream. */ + nghttp3_qpack_decoder_stream_state state; + /* opcode is a decoder stream opcode being processed. */ + nghttp3_qpack_decoder_stream_opcode opcode; + /* rstate is a set of intermediate state which are used to process + decoder stream. */ + nghttp3_qpack_read_state rstate; + /* min_dtable_update is the minimum dynamic table size required. */ + size_t min_dtable_update; + /* last_max_dtable_update is the dynamic table size last + requested. */ + size_t last_max_dtable_update; + /* flags is bitwise OR of zero or more of + NGHTTP3_QPACK_ENCODER_FLAG_*. */ + uint8_t flags; +}; + +/* + * nghttp3_qpack_encoder_init initializes |encoder|. + * |hard_max_dtable_capacity| is the upper bound of the dynamic table + * capacity. |mem| is a memory allocator. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_init(nghttp3_qpack_encoder *encoder, + size_t hard_max_dtable_capacity, + const nghttp3_mem *mem); + +/* + * nghttp3_qpack_encoder_free frees memory allocated for |encoder|. + * This function does not free memory pointed by |encoder|. + */ +void nghttp3_qpack_encoder_free(nghttp3_qpack_encoder *encoder); + +/* + * nghttp3_qpack_encoder_encode_nv encodes |nv|. It writes request + * stream into |rbuf| and writes encoder stream into |ebuf|. |nv| is + * a header field to encode. |base| is base. |allow_blocking| is + * nonzero if this stream can be blocked (or it has been blocked + * already). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_encode_nv(nghttp3_qpack_encoder *encoder, + uint64_t *pmax_cnt, uint64_t *pmin_cnt, + nghttp3_buf *rbuf, nghttp3_buf *ebuf, + const nghttp3_nv *nv, uint64_t base, + int allow_blocking); + +/* nghttp3_qpack_lookup_result stores a result of table lookup. */ +typedef struct nghttp3_qpack_lookup_result { + /* index is an index of matched entry. -1 if no match is made. */ + nghttp3_ssize index; + /* name_value_match is nonzero if both name and value are + matched. */ + int name_value_match; + /* pb_index is the absolute index of matched post-based dynamic + table entry. -1 if no such entry exists. */ + nghttp3_ssize pb_index; +} nghttp3_qpack_lookup_result; + +/* + * nghttp3_qpack_lookup_stable searches |nv| in static table. |token| + * is a token of nv->name and it is -1 if there is no corresponding + * token defined. |indexing_mode| provides indexing strategy. + */ +nghttp3_qpack_lookup_result +nghttp3_qpack_lookup_stable(const nghttp3_nv *nv, int32_t token, + nghttp3_qpack_indexing_mode indexing_mode); + +/* + * nghttp3_qpack_encoder_lookup_dtable searches |nv| in dynamic table. + * |token| is a token of nv->name and it is -1 if there is no + * corresponding token defined. |hash| is a hash of nv->name. + * |indexing_mode| provides indexing strategy. |krcnt| is Known + * Received Count. |allow_blocking| is nonzero if this stream can be + * blocked (or it has been blocked already). + */ +nghttp3_qpack_lookup_result nghttp3_qpack_encoder_lookup_dtable( + nghttp3_qpack_encoder *encoder, const nghttp3_nv *nv, int32_t token, + uint32_t hash, nghttp3_qpack_indexing_mode indexing_mode, uint64_t krcnt, + int allow_blocking); + +/* + * nghttp3_qpack_encoder_write_field_section_prefix writes Encoded + * Field Section Prefix into |pbuf|. |ricnt| is Required Insert + * Count. |base| is Base. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_field_section_prefix( + nghttp3_qpack_encoder *encoder, nghttp3_buf *pbuf, uint64_t ricnt, + uint64_t base); + +/* + * nghttp3_qpack_encoder_write_static_indexed writes Indexed Header + * Field to |rbuf|. |absidx| is an absolute index into static table. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_static_indexed(nghttp3_qpack_encoder *encoder, + nghttp3_buf *rbuf, + uint64_t absidx); + +/* + * nghttp3_qpack_encoder_write_dynamic_indexed writes Indexed Header + * Field to |rbuf|. |absidx| is an absolute index into dynamic table. + * |base| is base. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_dynamic_indexed(nghttp3_qpack_encoder *encoder, + nghttp3_buf *rbuf, + uint64_t absidx, uint64_t base); + +/* + * nghttp3_qpack_encoder_write_static_indexed writes Literal Header + * Field With Name Reference to |rbuf|. |absidx| is an absolute index + * into static table to reference a name. |nv| is a header field to + * encode. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_static_indexed_name( + nghttp3_qpack_encoder *encoder, nghttp3_buf *rbuf, uint64_t absidx, + const nghttp3_nv *nv); + +/* + * nghttp3_qpack_encoder_write_dynamic_indexed writes Literal Header + * Field With Name Reference to |rbuf|. |absidx| is an absolute index + * into dynamic table to reference a name. |base| is a base. |nv| is + * a header field to encode. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_dynamic_indexed_name( + nghttp3_qpack_encoder *encoder, nghttp3_buf *rbuf, uint64_t absidx, + uint64_t base, const nghttp3_nv *nv); + +/* + * nghttp3_qpack_encoder_write_literal writes Literal Header Field + * With Literal Name to |rbuf|. |nv| is a header field to encode. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_literal(nghttp3_qpack_encoder *encoder, + nghttp3_buf *rbuf, + const nghttp3_nv *nv); + +/* + * nghttp3_qpack_encoder_write_static_insert writes Insert With Name + * Reference to |ebuf|. |absidx| is an absolute index into static + * table to reference a name. |nv| is a header field to insert. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_static_insert(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, + uint64_t absidx, + const nghttp3_nv *nv); + +/* + * nghttp3_qpack_encoder_write_dynamic_insert writes Insert With Name + * Reference to |ebuf|. |absidx| is an absolute index into dynamic + * table to reference a name. |nv| is a header field to insert. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_dynamic_insert(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, + uint64_t absidx, + const nghttp3_nv *nv); + +/* + * nghttp3_qpack_encoder_write_duplicate_insert writes Duplicate to + * |ebuf|. |absidx| is an absolute index into dynamic table to + * reference an entry. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_duplicate_insert(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, + uint64_t absidx); + +/* + * nghttp3_qpack_encoder_write_literal_insert writes Insert With + * Literal Name to |ebuf|. |nv| is a header field to insert. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_literal_insert(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, + const nghttp3_nv *nv); + +int nghttp3_qpack_encoder_stream_is_blocked(nghttp3_qpack_encoder *encoder, + nghttp3_qpack_stream *stream); + +/* + * nghttp3_qpack_encoder_block_stream blocks |stream|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_block_stream(nghttp3_qpack_encoder *encoder, + nghttp3_qpack_stream *stream); + +/* + * nghttp3_qpack_encoder_unblock_stream unblocks |stream|. + */ +void nghttp3_qpack_encoder_unblock_stream(nghttp3_qpack_encoder *encoder, + nghttp3_qpack_stream *stream); + +/* + * nghttp3_qpack_encoder_unblock unblocks stream whose max_cnt is less + * than or equal to |max_cnt|. + */ +void nghttp3_qpack_encoder_unblock(nghttp3_qpack_encoder *encoder, + uint64_t max_cnt); + +/* + * nghttp3_qpack_encoder_find_stream returns stream whose stream ID is + * |stream_id|. This function returns NULL if there is no such + * stream. + */ +nghttp3_qpack_stream * +nghttp3_qpack_encoder_find_stream(nghttp3_qpack_encoder *encoder, + int64_t stream_id); + +uint64_t nghttp3_qpack_encoder_get_min_cnt(nghttp3_qpack_encoder *encoder); + +/* + * nghttp3_qpack_encoder_shrink_dtable shrinks dynamic table so that + * the dynamic table size is less than or equal to maximum size. + */ +void nghttp3_qpack_encoder_shrink_dtable(nghttp3_qpack_encoder *encoder); + +/* + * nghttp3_qpack_encoder_process_dtable_update processes pending + * dynamic table size update. It might write encoder stream into + * |ebuf|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_process_dtable_update(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf); + +/* + * nghttp3_qpack_encoder_write_set_dtable_cap writes Set Dynamic Table + * Capacity. to |ebuf|. |cap| is the capacity of dynamic table. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_write_set_dtable_cap(nghttp3_qpack_encoder *encoder, + nghttp3_buf *ebuf, size_t cap); + +/* + * nghttp3_qpack_context_dtable_add adds |qnv| to dynamic table. If + * |ctx| is a part of encoder, |dtable_map| is not NULL. |hash| is a + * hash value of name. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_context_dtable_add(nghttp3_qpack_context *ctx, + nghttp3_qpack_nv *qnv, + nghttp3_qpack_map *dtable_map, + uint32_t hash); + +/* + * nghttp3_qpack_encoder_dtable_static_add adds |nv| to dynamic table + * by referencing static table entry at an absolute index |absidx|. + * The hash of name is given as |hash|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_dtable_static_add(nghttp3_qpack_encoder *encoder, + uint64_t absidx, + const nghttp3_nv *nv, + uint32_t hash); + +/* + * nghttp3_qpack_encoder_dtable_dynamic_add adds |nv| to dynamic table + * by referencing dynamic table entry at an absolute index |absidx|. + * The hash of name is given as |hash|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_dtable_dynamic_add(nghttp3_qpack_encoder *encoder, + uint64_t absidx, + const nghttp3_nv *nv, + uint32_t hash); + +/* + * nghttp3_qpack_encoder_dtable_duplicate_add duplicates dynamic table + * entry at an absolute index |absidx|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_encoder_dtable_duplicate_add(nghttp3_qpack_encoder *encoder, + uint64_t absidx); + +/* + * nghttp3_qpack_encoder_dtable_literal_add adds |nv| to dynamic + * table. |token| is a token of name and is -1 if it has no token + * value defined. |hash| is a hash of name. + * + * NGHTTP3_ERR_NOMEM Out of memory. + */ +int nghttp3_qpack_encoder_dtable_literal_add(nghttp3_qpack_encoder *encoder, + const nghttp3_nv *nv, + int32_t token, uint32_t hash); + +/* + * `nghttp3_qpack_encoder_ack_header` tells |encoder| that header + * block for a stream denoted by |stream_id| was acknowledged by + * decoder. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR` + * Section Acknowledgement for a stream denoted by |stream_id| is + * unexpected. + */ +int nghttp3_qpack_encoder_ack_header(nghttp3_qpack_encoder *encoder, + int64_t stream_id); + +/* + * `nghttp3_qpack_encoder_add_icnt` increments known received count of + * |encoder| by |n|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGHTTP3_ERR_NOMEM` + * Out of memory. + * :macro:`NGHTTP3_ERR_QPACK_DECODER_STREAM_ERROR` + * |n| is too large. + */ +int nghttp3_qpack_encoder_add_icnt(nghttp3_qpack_encoder *encoder, uint64_t n); + +/* + * `nghttp3_qpack_encoder_cancel_stream` tells |encoder| that stream + * denoted by |stream_id| is cancelled. This function is provided for + * debugging purpose only. In HTTP/3, |encoder| knows this by reading + * decoder stream with `nghttp3_qpack_encoder_read_decoder()`. + */ +void nghttp3_qpack_encoder_cancel_stream(nghttp3_qpack_encoder *encoder, + int64_t stream_id); + +/* + * nghttp3_qpack_context_dtable_get returns dynamic table entry whose + * absolute index is |absidx|. This function assumes that such entry + * exists. + */ +nghttp3_qpack_entry * +nghttp3_qpack_context_dtable_get(nghttp3_qpack_context *ctx, uint64_t absidx); + +/* + * nghttp3_qpack_context_dtable_top returns latest dynamic table + * entry. This function assumes dynamic table is not empty. + */ +nghttp3_qpack_entry * +nghttp3_qpack_context_dtable_top(nghttp3_qpack_context *ctx); + +/* + * nghttp3_qpack_entry_init initializes |ent|. |qnv| is a header + * field. |sum| is the sum of table space occupied by all entries + * inserted so far. It does not include this entry. |absidx| is an + * absolute index of this entry. |hash| is a hash of header field + * name. This function increases reference count of qnv->nv.name and + * qnv->nv.value. + */ +void nghttp3_qpack_entry_init(nghttp3_qpack_entry *ent, nghttp3_qpack_nv *qnv, + size_t sum, uint64_t absidx, uint32_t hash); + +/* + * nghttp3_qpack_entry_free frees memory allocated for |ent|. + */ +void nghttp3_qpack_entry_free(nghttp3_qpack_entry *ent); + +/* + * nghttp3_qpack_put_varint_len returns the required number of bytes + * to encode |n| with |prefix| bits. + */ +size_t nghttp3_qpack_put_varint_len(uint64_t n, size_t prefix); + +/* + * nghttp3_qpack_put_varint encodes |n| using variable integer + * encoding with |prefix| bits into |buf|. This function assumes the + * buffer pointed by |buf| has enough space. This function returns + * the one byte beyond the last write (buf + + * nghttp3_qpack_put_varint_len(n, prefix)). + */ +uint8_t *nghttp3_qpack_put_varint(uint8_t *buf, uint64_t n, size_t prefix); + +/* nghttp3_qpack_encoder_stream_state is a set of states for encoder + stream decoding. */ +typedef enum nghttp3_qpack_encoder_stream_state { + NGHTTP3_QPACK_ES_STATE_OPCODE, + NGHTTP3_QPACK_ES_STATE_READ_INDEX, + NGHTTP3_QPACK_ES_STATE_CHECK_NAME_HUFFMAN, + NGHTTP3_QPACK_ES_STATE_READ_NAMELEN, + NGHTTP3_QPACK_ES_STATE_READ_NAME_HUFFMAN, + NGHTTP3_QPACK_ES_STATE_READ_NAME, + NGHTTP3_QPACK_ES_STATE_CHECK_VALUE_HUFFMAN, + NGHTTP3_QPACK_ES_STATE_READ_VALUELEN, + NGHTTP3_QPACK_ES_STATE_READ_VALUE_HUFFMAN, + NGHTTP3_QPACK_ES_STATE_READ_VALUE, +} nghttp3_qpack_encoder_stream_state; + +/* nghttp3_qpack_encoder_stream_opcode is a set of opcodes used in + encoder stream. */ +typedef enum nghttp3_qpack_encoder_stream_opcode { + NGHTTP3_QPACK_ES_OPCODE_INSERT_INDEXED, + NGHTTP3_QPACK_ES_OPCODE_INSERT, + NGHTTP3_QPACK_ES_OPCODE_DUPLICATE, + NGHTTP3_QPACK_ES_OPCODE_SET_DTABLE_CAP, +} nghttp3_qpack_encoder_stream_opcode; + +/* nghttp3_qpack_request_stream_state is a set of states for request + stream decoding. */ +typedef enum nghttp3_qpack_request_stream_state { + NGHTTP3_QPACK_RS_STATE_RICNT, + NGHTTP3_QPACK_RS_STATE_DBASE_SIGN, + NGHTTP3_QPACK_RS_STATE_DBASE, + NGHTTP3_QPACK_RS_STATE_OPCODE, + NGHTTP3_QPACK_RS_STATE_READ_INDEX, + NGHTTP3_QPACK_RS_STATE_CHECK_NAME_HUFFMAN, + NGHTTP3_QPACK_RS_STATE_READ_NAMELEN, + NGHTTP3_QPACK_RS_STATE_READ_NAME_HUFFMAN, + NGHTTP3_QPACK_RS_STATE_READ_NAME, + NGHTTP3_QPACK_RS_STATE_CHECK_VALUE_HUFFMAN, + NGHTTP3_QPACK_RS_STATE_READ_VALUELEN, + NGHTTP3_QPACK_RS_STATE_READ_VALUE_HUFFMAN, + NGHTTP3_QPACK_RS_STATE_READ_VALUE, + NGHTTP3_QPACK_RS_STATE_BLOCKED, +} nghttp3_qpack_request_stream_state; + +/* nghttp3_qpack_request_stream_opcode is a set of opcodes used in + request stream. */ +typedef enum nghttp3_qpack_request_stream_opcode { + NGHTTP3_QPACK_RS_OPCODE_INDEXED, + NGHTTP3_QPACK_RS_OPCODE_INDEXED_PB, + NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME, + NGHTTP3_QPACK_RS_OPCODE_INDEXED_NAME_PB, + NGHTTP3_QPACK_RS_OPCODE_LITERAL, +} nghttp3_qpack_request_stream_opcode; + +struct nghttp3_qpack_decoder { + nghttp3_qpack_context ctx; + /* state is a current state of reading encoder stream. */ + nghttp3_qpack_encoder_stream_state state; + /* opcode is an encoder stream opcode being processed. */ + nghttp3_qpack_encoder_stream_opcode opcode; + /* rstate is a set of intermediate state which are used to process + encoder stream. */ + nghttp3_qpack_read_state rstate; + /* dbuf is decoder stream. */ + nghttp3_buf dbuf; + /* written_icnt is Insert Count written to decoder stream so far. */ + uint64_t written_icnt; + /* max_concurrent_streams is the number of concurrent streams that a + remote endpoint can open, including both bidirectional and + unidirectional streams which potentially receives QPACK encoded + HEADER frame. */ + size_t max_concurrent_streams; +}; + +/* + * nghttp3_qpack_decoder_init initializes |decoder|. + * |hard_max_dtable_capacity| is the upper bound of the dynamic table + * capacity. |max_blocked_streams| is the maximum number of stream + * which can be blocked. |mem| is a memory allocator. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_qpack_decoder_init(nghttp3_qpack_decoder *decoder, + size_t hard_max_dtable_capacity, + size_t max_blocked_streams, + const nghttp3_mem *mem); + +/* + * nghttp3_qpack_decoder_free frees memory allocated for |decoder|. + * This function does not free memory pointed by |decoder|. + */ +void nghttp3_qpack_decoder_free(nghttp3_qpack_decoder *decoder); + +/* + * nghttp3_qpack_decoder_dtable_indexed_add adds entry received in + * Insert With Name Reference to dynamic table. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + * NGHTTP3_ERR_QPACK_ENCODER_STREAM + * Space required for a decoded entry exceeds max dynamic table + * size. + */ +int nghttp3_qpack_decoder_dtable_indexed_add(nghttp3_qpack_decoder *decoder); + +/* + * nghttp3_qpack_decoder_dtable_static_add adds entry received in + * Insert With Name Reference (static) to dynamic table. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + * NGHTTP3_ERR_QPACK_ENCODER_STREAM + * Space required for a decoded entry exceeds max dynamic table + * size. + */ +int nghttp3_qpack_decoder_dtable_static_add(nghttp3_qpack_decoder *decoder); + +/* + * nghttp3_qpack_decoder_dtable_dynamic_add adds entry received in + * Insert With Name Reference (dynamic) to dynamic table. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + * NGHTTP3_ERR_QPACK_ENCODER_STREAM + * Space required for a decoded entry exceeds max dynamic table + * size. + */ +int nghttp3_qpack_decoder_dtable_dynamic_add(nghttp3_qpack_decoder *decoder); + +/* + * nghttp3_qpack_decoder_dtable_duplicate_add adds entry received in + * Duplicate to dynamic table. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + * NGHTTP3_ERR_QPACK_ENCODER_STREAM + * Space required for a decoded entry exceeds max dynamic table + * size. + */ +int nghttp3_qpack_decoder_dtable_duplicate_add(nghttp3_qpack_decoder *decoder); + +/* + * nghttp3_qpack_decoder_dtable_literal_add adds entry received in + * Insert With Literal Name to dynamic table. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + * NGHTTP3_ERR_QPACK_ENCODER_STREAM + * Space required for a decoded entry exceeds max dynamic table + * size. + */ +int nghttp3_qpack_decoder_dtable_literal_add(nghttp3_qpack_decoder *decoder); + +struct nghttp3_qpack_stream_context { + /* state is a current state of reading request stream. */ + nghttp3_qpack_request_stream_state state; + /* rstate is a set of intermediate state which are used to process + request stream. */ + nghttp3_qpack_read_state rstate; + const nghttp3_mem *mem; + /* opcode is a request stream opcode being processed. */ + nghttp3_qpack_request_stream_opcode opcode; + int64_t stream_id; + /* ricnt is Required Insert Count to decode this header block. */ + uint64_t ricnt; + /* base is Base in Header Block Prefix. */ + uint64_t base; + /* dbase_sign is the delta base sign in Header Block Prefix. */ + int dbase_sign; +}; + +/* + * nghttp3_qpack_stream_context_init initializes |sctx|. + */ +void nghttp3_qpack_stream_context_init(nghttp3_qpack_stream_context *sctx, + int64_t stream_id, + const nghttp3_mem *mem); + +/* + * nghttp3_qpack_stream_context_free frees memory allocated for + * |sctx|. This function does not free memory pointed by |sctx|. + */ +void nghttp3_qpack_stream_context_free(nghttp3_qpack_stream_context *sctx); + +/* + * nghttp3_qpack_decoder_reconstruct_ricnt reconstructs Required + * Insert Count from the encoded form |encricnt| and stores Required + * Insert Count in |*dest|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED + * Unable to reconstruct Required Insert Count. + */ +int nghttp3_qpack_decoder_reconstruct_ricnt(nghttp3_qpack_decoder *decoder, + uint64_t *dest, uint64_t encricnt); + +/* + * nghttp3_qpack_decoder_rel2abs converts relative index rstate->left + * received in encoder stream to absolute index and stores it in + * rstate->absidx. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_QPACK_ENCODER_STREAM + * Relative index is invalid. + */ +int nghttp3_qpack_decoder_rel2abs(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_read_state *rstate); + +/* + * nghttp3_qpack_decoder_brel2abs converts Base relative index + * rstate->left received in request stream to absolute index and + * stores it in rstate->absidx. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED + * Base relative index is invalid. + */ +int nghttp3_qpack_decoder_brel2abs(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx); + +/* + * nghttp3_qpack_decoder_pbrel2abs converts Post-Base relative index + * rstate->left received in request stream to absolute index and + * stores it in rstate->absidx. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED + * Post-Base relative index is invalid. + */ +int nghttp3_qpack_decoder_pbrel2abs(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx); + +void nghttp3_qpack_decoder_emit_indexed(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv); + +int nghttp3_qpack_decoder_emit_indexed_name(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv); + +void nghttp3_qpack_decoder_emit_literal(nghttp3_qpack_decoder *decoder, + nghttp3_qpack_stream_context *sctx, + nghttp3_qpack_nv *nv); + +/* + * nghttp3_qpack_decoder_write_section_ack writes Section + * Acknowledgement to decoder stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + * NGHTTP3_ERR_QPACK_FATAL + * Decoder stream overflow. + */ +int nghttp3_qpack_decoder_write_section_ack( + nghttp3_qpack_decoder *decoder, const nghttp3_qpack_stream_context *sctx); + +#endif /* NGHTTP3_QPACK_H */ diff --git a/lib/nghttp3_qpack_huffman.c b/lib/nghttp3_qpack_huffman.c new file mode 100644 index 0000000..c36a68e --- /dev/null +++ b/lib/nghttp3_qpack_huffman.c @@ -0,0 +1,122 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2013 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_qpack_huffman.h" + +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "nghttp3_conv.h" + +size_t nghttp3_qpack_huffman_encode_count(const uint8_t *src, size_t len) { + size_t i; + size_t nbits = 0; + + for (i = 0; i < len; ++i) { + nbits += huffman_sym_table[src[i]].nbits; + } + /* pad the prefix of EOS (256) */ + return (nbits + 7) / 8; +} + +uint8_t *nghttp3_qpack_huffman_encode(uint8_t *dest, const uint8_t *src, + size_t srclen) { + const nghttp3_qpack_huffman_sym *sym; + const uint8_t *end = src + srclen; + uint64_t code = 0; + size_t nbits = 0; + uint32_t x; + + for (; src != end;) { + sym = &huffman_sym_table[*src++]; + code |= (uint64_t)sym->code << (32 - nbits); + nbits += sym->nbits; + if (nbits < 32) { + continue; + } + x = htonl((uint32_t)(code >> 32)); + memcpy(dest, &x, 4); + dest += 4; + code <<= 32; + nbits -= 32; + } + + for (; nbits >= 8;) { + *dest++ = (uint8_t)(code >> 56); + code <<= 8; + nbits -= 8; + } + + if (nbits) { + *dest++ = (uint8_t)((uint8_t)(code >> 56) | ((1 << (8 - nbits)) - 1)); + } + + return dest; +} + +void nghttp3_qpack_huffman_decode_context_init( + nghttp3_qpack_huffman_decode_context *ctx) { + ctx->fstate = NGHTTP3_QPACK_HUFFMAN_ACCEPTED; +} + +nghttp3_ssize +nghttp3_qpack_huffman_decode(nghttp3_qpack_huffman_decode_context *ctx, + uint8_t *dest, const uint8_t *src, size_t srclen, + int fin) { + uint8_t *p = dest; + const uint8_t *end = src + srclen; + nghttp3_qpack_huffman_decode_node node = {ctx->fstate, 0}; + const nghttp3_qpack_huffman_decode_node *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 = &qpack_huffman_decode_table[t->fstate & 0x1ff][c >> 4]; + if (t->fstate & NGHTTP3_QPACK_HUFFMAN_SYM) { + *p++ = t->sym; + } + + t = &qpack_huffman_decode_table[t->fstate & 0x1ff][c & 0xf]; + if (t->fstate & NGHTTP3_QPACK_HUFFMAN_SYM) { + *p++ = t->sym; + } + } + + ctx->fstate = t->fstate; + + if (fin && !(ctx->fstate & NGHTTP3_QPACK_HUFFMAN_ACCEPTED)) { + return NGHTTP3_ERR_QPACK_FATAL; + } + + return p - dest; +} + +int nghttp3_qpack_huffman_decode_failure_state( + nghttp3_qpack_huffman_decode_context *ctx) { + return ctx->fstate == 0x100; +} diff --git a/lib/nghttp3_qpack_huffman.h b/lib/nghttp3_qpack_huffman.h new file mode 100644 index 0000000..fc3bc7b --- /dev/null +++ b/lib/nghttp3_qpack_huffman.h @@ -0,0 +1,108 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2013 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_QPACK_HUFFMAN_H +#define NGHTTP3_QPACK_HUFFMAN_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +typedef struct nghttp3_qpack_huffman_sym { + /* The number of bits in this code */ + uint32_t nbits; + /* Huffman code aligned to LSB */ + uint32_t code; +} nghttp3_qpack_huffman_sym; + +extern const nghttp3_qpack_huffman_sym huffman_sym_table[]; + +size_t nghttp3_qpack_huffman_encode_count(const uint8_t *src, size_t len); + +uint8_t *nghttp3_qpack_huffman_encode(uint8_t *dest, const uint8_t *src, + size_t srclen); + +typedef enum nghttp3_qpack_huffman_decode_flag { + /* FSA accepts this state as the end of huffman encoding + sequence. */ + NGHTTP3_QPACK_HUFFMAN_ACCEPTED = 1 << 14, + /* This state emits symbol */ + NGHTTP3_QPACK_HUFFMAN_SYM = 1 << 15, +} nghttp3_qpack_huffman_decode_flag; + +typedef struct nghttp3_qpack_huffman_decode_node { + /* fstate is the current huffman decoding state, which is actually + the node ID of internal huffman tree with + nghttp3_qpack_huffman_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..256], inclusive. The node ID + 256 is a special node and it is a terminal state that means + decoding failed. */ + uint16_t fstate; + /* symbol if NGHTTP3_QPACK_HUFFMAN_SYM flag set */ + uint8_t sym; +} nghttp3_qpack_huffman_decode_node; + +typedef struct nghttp3_qpack_huffman_decode_context { + /* fstate is the current huffman decoding state. */ + uint16_t fstate; +} nghttp3_qpack_huffman_decode_context; + +extern const nghttp3_qpack_huffman_decode_node qpack_huffman_decode_table[][16]; + +void nghttp3_qpack_huffman_decode_context_init( + nghttp3_qpack_huffman_decode_context *ctx); + +/* + * nghttp3_qpack_huffman_decode decodes huffman encoded byte string + * stored in |src| of length |srclen|. |ctx| is a decoding context. + * |ctx| remembers the decoding state, and caller can call this + * function multiple times to feed each chunk of huffman encoded + * substring. |fin| must be nonzero if |src| contains the last chunk + * of huffman string. The decoded string is written to the buffer + * pointed by |dest|. This function assumes that the buffer pointed + * by |dest| contains enough memory to store decoded byte string. + * + * This function returns the number of bytes written to |dest|, or one + * of the following negative error codes: + * + * NGHTTP3_ERR_QPACK_FATAL + * Could not decode huffman string. + */ +nghttp3_ssize +nghttp3_qpack_huffman_decode(nghttp3_qpack_huffman_decode_context *ctx, + uint8_t *dest, const uint8_t *src, size_t srclen, + int fin); + +/* + * nghttp3_qpack_huffman_decode_failure_state returns nonzero if |ctx| + * indicates that huffman decoding context is in failure state. + */ +int nghttp3_qpack_huffman_decode_failure_state( + nghttp3_qpack_huffman_decode_context *ctx); + +#endif /* NGHTTP3_QPACK_HUFFMAN_H */ diff --git a/lib/nghttp3_qpack_huffman_data.c b/lib/nghttp3_qpack_huffman_data.c new file mode 100644 index 0000000..0c104db --- /dev/null +++ b/lib/nghttp3_qpack_huffman_data.c @@ -0,0 +1,4981 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2013 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_qpack_huffman.h" + +/* Generated by mkhufftbl.py */ + +const nghttp3_qpack_huffman_sym huffman_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 nghttp3_qpack_huffman_decode_node qpack_huffman_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/nghttp3_range.c b/lib/nghttp3_range.c new file mode 100644 index 0000000..0ce7148 --- /dev/null +++ b/lib/nghttp3_range.c @@ -0,0 +1,62 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_range.h" +#include "nghttp3_macro.h" + +void nghttp3_range_init(nghttp3_range *r, uint64_t begin, uint64_t end) { + r->begin = begin; + r->end = end; +} + +nghttp3_range nghttp3_range_intersect(const nghttp3_range *a, + const nghttp3_range *b) { + nghttp3_range r = {0, 0}; + uint64_t begin = nghttp3_max(a->begin, b->begin); + uint64_t end = nghttp3_min(a->end, b->end); + if (begin < end) { + nghttp3_range_init(&r, begin, end); + } + return r; +} + +uint64_t nghttp3_range_len(const nghttp3_range *r) { return r->end - r->begin; } + +int nghttp3_range_eq(const nghttp3_range *a, const nghttp3_range *b) { + return a->begin == b->begin && a->end == b->end; +} + +void nghttp3_range_cut(nghttp3_range *left, nghttp3_range *right, + const nghttp3_range *a, const nghttp3_range *b) { + /* Assume that b is included in a */ + left->begin = a->begin; + left->end = b->begin; + right->begin = b->end; + right->end = a->end; +} + +int nghttp3_range_not_after(const nghttp3_range *a, const nghttp3_range *b) { + return a->end <= b->end; +} diff --git a/lib/nghttp3_range.h b/lib/nghttp3_range.h new file mode 100644 index 0000000..20dab69 --- /dev/null +++ b/lib/nghttp3_range.h @@ -0,0 +1,81 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_RANGE_H +#define NGHTTP3_RANGE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +/* + * nghttp3_range represents half-closed range [begin, end). + */ +typedef struct nghttp3_range { + uint64_t begin; + uint64_t end; +} nghttp3_range; + +/* + * nghttp3_range_init initializes |r| with the range [|begin|, |end|). + */ +void nghttp3_range_init(nghttp3_range *r, uint64_t begin, uint64_t end); + +/* + * nghttp3_range_intersect returns the intersection of |a| and |b|. + * If they do not overlap, it returns empty range. + */ +nghttp3_range nghttp3_range_intersect(const nghttp3_range *a, + const nghttp3_range *b); + +/* + * nghttp3_range_len returns the length of |r|. + */ +uint64_t nghttp3_range_len(const nghttp3_range *r); + +/* + * nghttp3_range_eq returns nonzero if |a| equals |b|, such that + * a->begin == b->begin, and a->end == b->end hold. + */ +int nghttp3_range_eq(const nghttp3_range *a, const nghttp3_range *b); + +/* + * nghttp3_range_cut returns the left and right range after removing + * |b| from |a|. This function assumes that |a| completely includes + * |b|. In other words, a->begin <= b->begin and b->end <= a->end + * hold. + */ +void nghttp3_range_cut(nghttp3_range *left, nghttp3_range *right, + const nghttp3_range *a, const nghttp3_range *b); + +/* + * nghttp3_range_not_after returns nonzero if the right edge of |a| + * does not go beyond of the right edge of |b|. + */ +int nghttp3_range_not_after(const nghttp3_range *a, const nghttp3_range *b); + +#endif /* NGHTTP3_RANGE_H */ diff --git a/lib/nghttp3_rcbuf.c b/lib/nghttp3_rcbuf.c new file mode 100644 index 0000000..9e9dab5 --- /dev/null +++ b/lib/nghttp3_rcbuf.c @@ -0,0 +1,108 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2016 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_rcbuf.h" + +#include <assert.h> + +#include "nghttp3_mem.h" +#include "nghttp3_str.h" + +int nghttp3_rcbuf_new(nghttp3_rcbuf **rcbuf_ptr, size_t size, + const nghttp3_mem *mem) { + uint8_t *p; + + p = nghttp3_mem_malloc(mem, sizeof(nghttp3_rcbuf) + size); + if (p == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + *rcbuf_ptr = (void *)p; + + (*rcbuf_ptr)->mem = mem; + (*rcbuf_ptr)->base = p + sizeof(nghttp3_rcbuf); + (*rcbuf_ptr)->len = size; + (*rcbuf_ptr)->ref = 1; + + return 0; +} + +int nghttp3_rcbuf_new2(nghttp3_rcbuf **rcbuf_ptr, const uint8_t *src, + size_t srclen, const nghttp3_mem *mem) { + int rv; + uint8_t *p; + + rv = nghttp3_rcbuf_new(rcbuf_ptr, srclen + 1, mem); + if (rv != 0) { + return rv; + } + + (*rcbuf_ptr)->len = srclen; + p = (*rcbuf_ptr)->base; + + if (srclen) { + p = nghttp3_cpymem(p, src, srclen); + } + + *p = '\0'; + + return 0; +} + +/* + * Frees |rcbuf| itself, regardless of its reference cout. + */ +void nghttp3_rcbuf_del(nghttp3_rcbuf *rcbuf) { + nghttp3_mem_free(rcbuf->mem, rcbuf); +} + +void nghttp3_rcbuf_incref(nghttp3_rcbuf *rcbuf) { + if (rcbuf->ref == -1) { + return; + } + + ++rcbuf->ref; +} + +void nghttp3_rcbuf_decref(nghttp3_rcbuf *rcbuf) { + if (rcbuf == NULL || rcbuf->ref == -1) { + return; + } + + assert(rcbuf->ref > 0); + + if (--rcbuf->ref == 0) { + nghttp3_rcbuf_del(rcbuf); + } +} + +nghttp3_vec nghttp3_rcbuf_get_buf(const nghttp3_rcbuf *rcbuf) { + nghttp3_vec res = {rcbuf->base, rcbuf->len}; + return res; +} + +int nghttp3_rcbuf_is_static(const nghttp3_rcbuf *rcbuf) { + return rcbuf->ref == -1; +} diff --git a/lib/nghttp3_rcbuf.h b/lib/nghttp3_rcbuf.h new file mode 100644 index 0000000..f589c37 --- /dev/null +++ b/lib/nghttp3_rcbuf.h @@ -0,0 +1,81 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2016 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_RCBUF_H +#define NGHTTP3_RCBUF_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +struct nghttp3_rcbuf { + /* mem is the memory allocator that allocates memory for this + object. */ + const nghttp3_mem *mem; + /* The pointer to the underlying buffer */ + uint8_t *base; + /* Size of buffer pointed by |base|. */ + size_t len; + /* Reference count */ + int32_t ref; +}; + +/* + * Allocates nghttp3_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: + * + * NGHTTP3_ERR_NOMEM: + * Out of memory. + */ +int nghttp3_rcbuf_new(nghttp3_rcbuf **rcbuf_ptr, size_t size, + const nghttp3_mem *mem); + +/* + * Like nghttp3_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: + * + * NGHTTP3_ERR_NOMEM: + * Out of memory. + */ +int nghttp3_rcbuf_new2(nghttp3_rcbuf **rcbuf_ptr, const uint8_t *src, + size_t srclen, const nghttp3_mem *mem); + +/* + * Frees |rcbuf| itself, regardless of its reference cout. + */ +void nghttp3_rcbuf_del(nghttp3_rcbuf *rcbuf); + +#endif /* NGHTTP3_RCBUF_H */ diff --git a/lib/nghttp3_ringbuf.c b/lib/nghttp3_ringbuf.c new file mode 100644 index 0000000..75ef7ad --- /dev/null +++ b/lib/nghttp3_ringbuf.c @@ -0,0 +1,159 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_ringbuf.h" + +#include <assert.h> +#include <string.h> +#ifdef WIN32 +# include <intrin.h> +#endif + +#include "nghttp3_macro.h" + +#if defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) +unsigned int __popcnt(unsigned int x) { + unsigned int c = 0; + for (; x; ++c) { + x &= x - 1; + } + return c; +} +#endif + +int nghttp3_ringbuf_init(nghttp3_ringbuf *rb, size_t nmemb, size_t size, + const nghttp3_mem *mem) { + if (nmemb) { +#ifdef WIN32 + assert(1 == __popcnt((unsigned int)nmemb)); +#else + assert(1 == __builtin_popcount((unsigned int)nmemb)); +#endif + + rb->buf = nghttp3_mem_malloc(mem, nmemb * size); + if (rb->buf == NULL) { + return NGHTTP3_ERR_NOMEM; + } + } else { + rb->buf = NULL; + } + + rb->mem = mem; + rb->nmemb = nmemb; + rb->size = size; + rb->first = 0; + rb->len = 0; + + return 0; +} + +void nghttp3_ringbuf_free(nghttp3_ringbuf *rb) { + if (rb == NULL) { + return; + } + + nghttp3_mem_free(rb->mem, rb->buf); +} + +void *nghttp3_ringbuf_push_front(nghttp3_ringbuf *rb) { + rb->first = (rb->first - 1) & (rb->nmemb - 1); + rb->len = nghttp3_min(rb->nmemb, rb->len + 1); + + return (void *)&rb->buf[rb->first * rb->size]; +} + +void *nghttp3_ringbuf_push_back(nghttp3_ringbuf *rb) { + size_t offset = (rb->first + rb->len) & (rb->nmemb - 1); + + if (rb->len == rb->nmemb) { + rb->first = (rb->first + 1) & (rb->nmemb - 1); + } else { + ++rb->len; + } + + return (void *)&rb->buf[offset * rb->size]; +} + +void nghttp3_ringbuf_pop_front(nghttp3_ringbuf *rb) { + rb->first = (rb->first + 1) & (rb->nmemb - 1); + --rb->len; +} + +void nghttp3_ringbuf_pop_back(nghttp3_ringbuf *rb) { + assert(rb->len); + --rb->len; +} + +void nghttp3_ringbuf_resize(nghttp3_ringbuf *rb, size_t len) { + assert(len <= rb->nmemb); + rb->len = len; +} + +void *nghttp3_ringbuf_get(nghttp3_ringbuf *rb, size_t offset) { + assert(offset < rb->len); + offset = (rb->first + offset) & (rb->nmemb - 1); + return &rb->buf[offset * rb->size]; +} + +int nghttp3_ringbuf_full(nghttp3_ringbuf *rb) { return rb->len == rb->nmemb; } + +int nghttp3_ringbuf_reserve(nghttp3_ringbuf *rb, size_t nmemb) { + uint8_t *buf; + + if (rb->nmemb >= nmemb) { + return 0; + } + +#ifdef WIN32 + assert(1 == __popcnt((unsigned int)nmemb)); +#else + assert(1 == __builtin_popcount((unsigned int)nmemb)); +#endif + + buf = nghttp3_mem_malloc(rb->mem, nmemb * rb->size); + if (buf == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + if (rb->buf != NULL) { + if (rb->first + rb->len <= rb->nmemb) { + memcpy(buf, rb->buf + rb->first * rb->size, rb->len * rb->size); + rb->first = 0; + } else { + memcpy(buf, rb->buf + rb->first * rb->size, + (rb->nmemb - rb->first) * rb->size); + memcpy(buf + (rb->nmemb - rb->first) * rb->size, rb->buf, + (rb->len - (rb->nmemb - rb->first)) * rb->size); + rb->first = 0; + } + + nghttp3_mem_free(rb->mem, rb->buf); + } + + rb->buf = buf; + rb->nmemb = nmemb; + + return 0; +} diff --git a/lib/nghttp3_ringbuf.h b/lib/nghttp3_ringbuf.h new file mode 100644 index 0000000..8e05ec5 --- /dev/null +++ b/lib/nghttp3_ringbuf.h @@ -0,0 +1,113 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_RINGBUF_H +#define NGHTTP3_RINGBUF_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_mem.h" + +typedef struct nghttp3_ringbuf { + /* buf points to the underlying buffer. */ + uint8_t *buf; + const nghttp3_mem *mem; + /* nmemb is the number of elements that can be stored in this ring + buffer. */ + size_t nmemb; + /* size is the size of each element. */ + size_t size; + /* first is the offset to the first element. */ + size_t first; + /* len is the number of elements actually stored. */ + size_t len; +} nghttp3_ringbuf; + +/* + * nghttp3_ringbuf_init initializes |rb|. |nmemb| is the number of + * elements that can be stored in this buffer. |size| is the size of + * each element. |size| must be power of 2. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP3_ERR_NOMEM + * Out of memory. + */ +int nghttp3_ringbuf_init(nghttp3_ringbuf *rb, size_t nmemb, size_t size, + const nghttp3_mem *mem); + +/* + * nghttp3_ringbuf_free frees resources allocated for |rb|. This + * function does not free the memory pointed by |rb|. + */ +void nghttp3_ringbuf_free(nghttp3_ringbuf *rb); + +/* nghttp3_ringbuf_push_front moves the offset to the first element in + the buffer backward, and returns the pointer to the element. + Caller can store data to the buffer pointed by the returned + pointer. If this action exceeds the capacity of the ring buffer, + the last element is silently overwritten, and rb->len remains + unchanged. */ +void *nghttp3_ringbuf_push_front(nghttp3_ringbuf *rb); + +/* nghttp3_ringbuf_push_back moves the offset to the last element in + the buffer forward, and returns the pointer to the element. Caller + can store data to the buffer pointed by the returned pointer. If + this action exceeds the capacity of the ring buffer, the first + element is silently overwritten, and rb->len remains unchanged. */ +void *nghttp3_ringbuf_push_back(nghttp3_ringbuf *rb); + +/* + * nghttp3_ringbuf_pop_front removes first element in |rb|. + */ +void nghttp3_ringbuf_pop_front(nghttp3_ringbuf *rb); + +/* + * nghttp3_ringbuf_pop_back removes the last element in |rb|. + */ +void nghttp3_ringbuf_pop_back(nghttp3_ringbuf *rb); + +/* nghttp3_ringbuf_resize changes the number of elements stored. This + does not change the capacity of the underlying buffer. */ +void nghttp3_ringbuf_resize(nghttp3_ringbuf *rb, size_t len); + +/* nghttp3_ringbuf_get returns the pointer to the element at + |offset|. */ +void *nghttp3_ringbuf_get(nghttp3_ringbuf *rb, size_t offset); + +/* nghttp3_ringbuf_len returns the number of elements stored. */ +#define nghttp3_ringbuf_len(RB) ((RB)->len) + +/* nghttp3_ringbuf_full returns nonzero if |rb| is full. */ +int nghttp3_ringbuf_full(nghttp3_ringbuf *rb); + +int nghttp3_ringbuf_reserve(nghttp3_ringbuf *rb, size_t nmemb); + +#endif /* NGHTTP3_RINGBUF_H */ diff --git a/lib/nghttp3_str.c b/lib/nghttp3_str.c new file mode 100644 index 0000000..3782aa7 --- /dev/null +++ b/lib/nghttp3_str.c @@ -0,0 +1,110 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * 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 "nghttp3_str.h" + +#include <string.h> +#include <assert.h> + +uint8_t *nghttp3_cpymem(uint8_t *dest, const uint8_t *src, size_t n) { + memcpy(dest, src, n); + return dest + 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 nghttp3_downcase(uint8_t *s, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + s[i] = DOWNCASE_TBL[s[i]]; + } +} diff --git a/lib/nghttp3_str.h b/lib/nghttp3_str.h new file mode 100644 index 0000000..19c1d2c --- /dev/null +++ b/lib/nghttp3_str.h @@ -0,0 +1,40 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * 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 NGHTTP3_STR_H +#define NGHTTP3_STR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +uint8_t *nghttp3_cpymem(uint8_t *dest, const uint8_t *src, size_t n); + +void nghttp3_downcase(uint8_t *s, size_t len); + +#endif /* NGHTTP3_STR_H */ diff --git a/lib/nghttp3_stream.c b/lib/nghttp3_stream.c new file mode 100644 index 0000000..457f5e4 --- /dev/null +++ b/lib/nghttp3_stream.c @@ -0,0 +1,1248 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "nghttp3_stream.h" + +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "nghttp3_conv.h" +#include "nghttp3_macro.h" +#include "nghttp3_frame.h" +#include "nghttp3_conn.h" +#include "nghttp3_str.h" +#include "nghttp3_http.h" +#include "nghttp3_vec.h" +#include "nghttp3_unreachable.h" + +/* NGHTTP3_STREAM_MAX_COPY_THRES is the maximum size of buffer which + makes a copy to outq. */ +#define NGHTTP3_STREAM_MAX_COPY_THRES 128 + +/* NGHTTP3_MIN_RBLEN is the minimum length of nghttp3_ringbuf */ +#define NGHTTP3_MIN_RBLEN 4 + +int nghttp3_stream_new(nghttp3_stream **pstream, int64_t stream_id, + const nghttp3_stream_callbacks *callbacks, + nghttp3_objalloc *out_chunk_objalloc, + nghttp3_objalloc *stream_objalloc, + const nghttp3_mem *mem) { + nghttp3_stream *stream = nghttp3_objalloc_stream_get(stream_objalloc); + + if (stream == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + memset(stream, 0, sizeof(*stream)); + + stream->out_chunk_objalloc = out_chunk_objalloc; + stream->stream_objalloc = stream_objalloc; + + nghttp3_tnode_init(&stream->node, stream_id, NGHTTP3_DEFAULT_URGENCY); + + nghttp3_ringbuf_init(&stream->frq, 0, sizeof(nghttp3_frame_entry), mem); + nghttp3_ringbuf_init(&stream->chunks, 0, sizeof(nghttp3_buf), mem); + nghttp3_ringbuf_init(&stream->outq, 0, sizeof(nghttp3_typed_buf), mem); + nghttp3_ringbuf_init(&stream->inq, 0, sizeof(nghttp3_buf), mem); + + nghttp3_qpack_stream_context_init(&stream->qpack_sctx, stream_id, mem); + + stream->qpack_blocked_pe.index = NGHTTP3_PQ_BAD_INDEX; + stream->mem = mem; + stream->tx.offset = 0; + stream->rx.http.status_code = -1; + stream->rx.http.content_length = -1; + stream->rx.http.pri = NGHTTP3_DEFAULT_URGENCY; + stream->error_code = NGHTTP3_H3_NO_ERROR; + + if (callbacks) { + stream->callbacks = *callbacks; + } + + *pstream = stream; + + return 0; +} + +static void delete_outq(nghttp3_ringbuf *outq, const nghttp3_mem *mem) { + nghttp3_typed_buf *tbuf; + size_t i, len = nghttp3_ringbuf_len(outq); + + for (i = 0; i < len; ++i) { + tbuf = nghttp3_ringbuf_get(outq, i); + if (tbuf->type == NGHTTP3_BUF_TYPE_PRIVATE) { + nghttp3_buf_free(&tbuf->buf, mem); + } + } + + nghttp3_ringbuf_free(outq); +} + +static void delete_chunks(nghttp3_ringbuf *chunks, const nghttp3_mem *mem) { + nghttp3_buf *buf; + size_t i, len = nghttp3_ringbuf_len(chunks); + + for (i = 0; i < len; ++i) { + buf = nghttp3_ringbuf_get(chunks, i); + nghttp3_buf_free(buf, mem); + } + + nghttp3_ringbuf_free(chunks); +} + +static void delete_out_chunks(nghttp3_ringbuf *chunks, + nghttp3_objalloc *out_chunk_objalloc, + const nghttp3_mem *mem) { + nghttp3_buf *buf; + size_t i, len = nghttp3_ringbuf_len(chunks); + + for (i = 0; i < len; ++i) { + buf = nghttp3_ringbuf_get(chunks, i); + + if (nghttp3_buf_cap(buf) == NGHTTP3_STREAM_MIN_CHUNK_SIZE) { + nghttp3_objalloc_chunk_release(out_chunk_objalloc, + (nghttp3_chunk *)(void *)buf->begin); + continue; + } + + nghttp3_buf_free(buf, mem); + } + + nghttp3_ringbuf_free(chunks); +} + +static void delete_frq(nghttp3_ringbuf *frq, const nghttp3_mem *mem) { + nghttp3_frame_entry *frent; + size_t i, len = nghttp3_ringbuf_len(frq); + + for (i = 0; i < len; ++i) { + frent = nghttp3_ringbuf_get(frq, i); + switch (frent->fr.hd.type) { + case NGHTTP3_FRAME_HEADERS: + nghttp3_frame_headers_free(&frent->fr.headers, mem); + break; + default: + break; + } + } + + nghttp3_ringbuf_free(frq); +} + +void nghttp3_stream_del(nghttp3_stream *stream) { + if (stream == NULL) { + return; + } + + nghttp3_qpack_stream_context_free(&stream->qpack_sctx); + delete_chunks(&stream->inq, stream->mem); + delete_outq(&stream->outq, stream->mem); + delete_out_chunks(&stream->chunks, stream->out_chunk_objalloc, stream->mem); + delete_frq(&stream->frq, stream->mem); + nghttp3_tnode_free(&stream->node); + + nghttp3_objalloc_stream_release(stream->stream_objalloc, stream); +} + +void nghttp3_varint_read_state_reset(nghttp3_varint_read_state *rvint) { + memset(rvint, 0, sizeof(*rvint)); +} + +void nghttp3_stream_read_state_reset(nghttp3_stream_read_state *rstate) { + memset(rstate, 0, sizeof(*rstate)); +} + +nghttp3_ssize nghttp3_read_varint(nghttp3_varint_read_state *rvint, + const uint8_t *src, size_t srclen, int fin) { + size_t nread = 0; + size_t n; + size_t i; + + assert(srclen > 0); + + if (rvint->left == 0) { + assert(rvint->acc == 0); + + rvint->left = nghttp3_get_varintlen(src); + if (rvint->left <= srclen) { + rvint->acc = nghttp3_get_varint(&nread, src); + rvint->left = 0; + return (nghttp3_ssize)nread; + } + + if (fin) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + rvint->acc = nghttp3_get_varint_fb(src); + nread = 1; + ++src; + --srclen; + --rvint->left; + } + + n = nghttp3_min(rvint->left, srclen); + + for (i = 0; i < n; ++i) { + rvint->acc = (rvint->acc << 8) + src[i]; + } + + rvint->left -= n; + nread += n; + + if (fin && rvint->left) { + return NGHTTP3_ERR_INVALID_ARGUMENT; + } + + return (nghttp3_ssize)nread; +} + +int nghttp3_stream_frq_add(nghttp3_stream *stream, + const nghttp3_frame_entry *frent) { + nghttp3_ringbuf *frq = &stream->frq; + nghttp3_frame_entry *dest; + int rv; + + if (nghttp3_ringbuf_full(frq)) { + size_t nlen = nghttp3_max(NGHTTP3_MIN_RBLEN, nghttp3_ringbuf_len(frq) * 2); + rv = nghttp3_ringbuf_reserve(frq, nlen); + if (rv != 0) { + return rv; + } + } + + dest = nghttp3_ringbuf_push_back(frq); + *dest = *frent; + + return 0; +} + +int nghttp3_stream_fill_outq(nghttp3_stream *stream) { + nghttp3_ringbuf *frq = &stream->frq; + nghttp3_frame_entry *frent; + int data_eof; + int rv; + + for (; nghttp3_ringbuf_len(frq) && !nghttp3_stream_outq_is_full(stream) && + stream->unsent_bytes < NGHTTP3_MIN_UNSENT_BYTES;) { + frent = nghttp3_ringbuf_get(frq, 0); + + switch (frent->fr.hd.type) { + case NGHTTP3_FRAME_SETTINGS: + rv = nghttp3_stream_write_settings(stream, frent); + if (rv != 0) { + return rv; + } + break; + case NGHTTP3_FRAME_HEADERS: + rv = nghttp3_stream_write_headers(stream, frent); + if (rv != 0) { + return rv; + } + nghttp3_frame_headers_free(&frent->fr.headers, stream->mem); + break; + case NGHTTP3_FRAME_DATA: + rv = nghttp3_stream_write_data(stream, &data_eof, frent); + if (rv != 0) { + return rv; + } + if (stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED) { + return 0; + } + if (!data_eof) { + return 0; + } + break; + case NGHTTP3_FRAME_GOAWAY: + rv = nghttp3_stream_write_goaway(stream, frent); + if (rv != 0) { + return rv; + } + break; + case NGHTTP3_FRAME_PRIORITY_UPDATE: + rv = nghttp3_stream_write_priority_update(stream, frent); + if (rv != 0) { + return rv; + } + break; + default: + /* TODO Not implemented */ + break; + } + + nghttp3_ringbuf_pop_front(frq); + } + + return 0; +} + +static void typed_buf_shared_init(nghttp3_typed_buf *tbuf, + const nghttp3_buf *chunk) { + nghttp3_typed_buf_init(tbuf, chunk, NGHTTP3_BUF_TYPE_SHARED); + tbuf->buf.pos = tbuf->buf.last; +} + +int nghttp3_stream_write_stream_type(nghttp3_stream *stream) { + size_t len = nghttp3_put_varintlen((int64_t)stream->type); + nghttp3_buf *chunk; + nghttp3_typed_buf tbuf; + int rv; + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_put_varint(chunk->last, (int64_t)stream->type); + tbuf.buf.last = chunk->last; + + return nghttp3_stream_outq_add(stream, &tbuf); +} + +int nghttp3_stream_write_settings(nghttp3_stream *stream, + nghttp3_frame_entry *frent) { + size_t len; + int rv; + nghttp3_buf *chunk; + nghttp3_typed_buf tbuf; + struct { + nghttp3_frame_settings settings; + nghttp3_settings_entry iv[15]; + } fr; + nghttp3_settings_entry *iv; + nghttp3_settings *local_settings = frent->aux.settings.local_settings; + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 3; + iv = &fr.settings.iv[0]; + + iv[0].id = NGHTTP3_SETTINGS_ID_MAX_FIELD_SECTION_SIZE; + iv[0].value = local_settings->max_field_section_size; + iv[1].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[1].value = local_settings->qpack_max_dtable_capacity; + iv[2].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[2].value = local_settings->qpack_blocked_streams; + + if (local_settings->enable_connect_protocol) { + ++fr.settings.niv; + iv[3].id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL; + iv[3].value = 1; + } + + len = nghttp3_frame_write_settings_len(&fr.settings.hd.length, &fr.settings); + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_frame_write_settings(chunk->last, &fr.settings); + + tbuf.buf.last = chunk->last; + + return nghttp3_stream_outq_add(stream, &tbuf); +} + +int nghttp3_stream_write_goaway(nghttp3_stream *stream, + nghttp3_frame_entry *frent) { + nghttp3_frame_goaway *fr = &frent->fr.goaway; + size_t len; + int rv; + nghttp3_buf *chunk; + nghttp3_typed_buf tbuf; + + len = nghttp3_frame_write_goaway_len(&fr->hd.length, fr); + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_frame_write_goaway(chunk->last, fr); + + tbuf.buf.last = chunk->last; + + return nghttp3_stream_outq_add(stream, &tbuf); +} + +int nghttp3_stream_write_priority_update(nghttp3_stream *stream, + nghttp3_frame_entry *frent) { + nghttp3_frame_priority_update *fr = &frent->fr.priority_update; + size_t len; + int rv; + nghttp3_buf *chunk; + nghttp3_typed_buf tbuf; + + len = nghttp3_frame_write_priority_update_len(&fr->hd.length, fr); + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_frame_write_priority_update(chunk->last, fr); + + tbuf.buf.last = chunk->last; + + return nghttp3_stream_outq_add(stream, &tbuf); +} + +int nghttp3_stream_write_headers(nghttp3_stream *stream, + nghttp3_frame_entry *frent) { + nghttp3_frame_headers *fr = &frent->fr.headers; + nghttp3_conn *conn = stream->conn; + + assert(conn); + + return nghttp3_stream_write_header_block( + stream, &conn->qenc, conn->tx.qenc, &conn->tx.qpack.rbuf, + &conn->tx.qpack.ebuf, NGHTTP3_FRAME_HEADERS, fr->nva, fr->nvlen); +} + +int nghttp3_stream_write_header_block(nghttp3_stream *stream, + nghttp3_qpack_encoder *qenc, + nghttp3_stream *qenc_stream, + nghttp3_buf *rbuf, nghttp3_buf *ebuf, + int64_t frame_type, const nghttp3_nv *nva, + size_t nvlen) { + nghttp3_buf pbuf; + int rv; + size_t len; + nghttp3_buf *chunk; + nghttp3_typed_buf tbuf; + nghttp3_frame_hd hd; + uint8_t raw_pbuf[16]; + size_t pbuflen, rbuflen, ebuflen; + + nghttp3_buf_wrap_init(&pbuf, raw_pbuf, sizeof(raw_pbuf)); + + rv = nghttp3_qpack_encoder_encode(qenc, &pbuf, rbuf, ebuf, stream->node.id, + nva, nvlen); + if (rv != 0) { + goto fail; + } + + pbuflen = nghttp3_buf_len(&pbuf); + rbuflen = nghttp3_buf_len(rbuf); + ebuflen = nghttp3_buf_len(ebuf); + + hd.type = frame_type; + hd.length = (int64_t)(pbuflen + rbuflen); + + len = nghttp3_frame_write_hd_len(&hd) + pbuflen; + + if (rbuflen <= NGHTTP3_STREAM_MAX_COPY_THRES) { + len += rbuflen; + } + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + goto fail; + } + + chunk = nghttp3_stream_get_chunk(stream); + typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_frame_write_hd(chunk->last, &hd); + + chunk->last = nghttp3_cpymem(chunk->last, pbuf.pos, pbuflen); + nghttp3_buf_init(&pbuf); + + if (rbuflen > NGHTTP3_STREAM_MAX_COPY_THRES) { + tbuf.buf.last = chunk->last; + + rv = nghttp3_stream_outq_add(stream, &tbuf); + if (rv != 0) { + goto fail; + } + + nghttp3_typed_buf_init(&tbuf, rbuf, NGHTTP3_BUF_TYPE_PRIVATE); + rv = nghttp3_stream_outq_add(stream, &tbuf); + if (rv != 0) { + goto fail; + } + nghttp3_buf_init(rbuf); + } else if (rbuflen) { + chunk->last = nghttp3_cpymem(chunk->last, rbuf->pos, rbuflen); + tbuf.buf.last = chunk->last; + + rv = nghttp3_stream_outq_add(stream, &tbuf); + if (rv != 0) { + goto fail; + } + nghttp3_buf_reset(rbuf); + } + + if (ebuflen > NGHTTP3_STREAM_MAX_COPY_THRES) { + assert(qenc_stream); + + nghttp3_typed_buf_init(&tbuf, ebuf, NGHTTP3_BUF_TYPE_PRIVATE); + rv = nghttp3_stream_outq_add(qenc_stream, &tbuf); + if (rv != 0) { + return rv; + } + nghttp3_buf_init(ebuf); + } else if (ebuflen) { + assert(qenc_stream); + + rv = nghttp3_stream_ensure_chunk(qenc_stream, ebuflen); + if (rv != 0) { + goto fail; + } + + chunk = nghttp3_stream_get_chunk(qenc_stream); + typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_cpymem(chunk->last, ebuf->pos, ebuflen); + tbuf.buf.last = chunk->last; + + rv = nghttp3_stream_outq_add(qenc_stream, &tbuf); + if (rv != 0) { + goto fail; + } + nghttp3_buf_reset(ebuf); + } + + assert(0 == nghttp3_buf_len(&pbuf)); + assert(0 == nghttp3_buf_len(rbuf)); + assert(0 == nghttp3_buf_len(ebuf)); + + return 0; + +fail: + + return rv; +} + +int nghttp3_stream_write_data(nghttp3_stream *stream, int *peof, + nghttp3_frame_entry *frent) { + int rv; + size_t len; + nghttp3_typed_buf tbuf; + nghttp3_buf buf; + nghttp3_buf *chunk; + nghttp3_read_data_callback read_data = frent->aux.data.dr.read_data; + nghttp3_conn *conn = stream->conn; + int64_t datalen; + uint32_t flags = 0; + nghttp3_frame_hd hd; + nghttp3_vec vec[8]; + nghttp3_vec *v; + nghttp3_ssize sveccnt; + size_t i; + + assert(!(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED)); + assert(read_data); + assert(conn); + + *peof = 0; + + sveccnt = read_data(conn, stream->node.id, vec, nghttp3_arraylen(vec), &flags, + conn->user_data, stream->user_data); + if (sveccnt < 0) { + if (sveccnt == NGHTTP3_ERR_WOULDBLOCK) { + stream->flags |= NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED; + return 0; + } + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + + datalen = nghttp3_vec_len_varint(vec, (size_t)sveccnt); + if (datalen == -1) { + return NGHTTP3_ERR_STREAM_DATA_OVERFLOW; + } + + assert(datalen || flags & NGHTTP3_DATA_FLAG_EOF); + + if (flags & NGHTTP3_DATA_FLAG_EOF) { + *peof = 1; + if (!(flags & NGHTTP3_DATA_FLAG_NO_END_STREAM)) { + stream->flags |= NGHTTP3_STREAM_FLAG_WRITE_END_STREAM; + if (datalen == 0) { + if (nghttp3_stream_outq_write_done(stream)) { + /* If this is the last data and its is 0 length, we don't + need send DATA frame. We rely on the non-emptiness of + outq to schedule stream, so add empty tbuf to outq to + just send fin. */ + nghttp3_buf_init(&buf); + nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_PRIVATE); + return nghttp3_stream_outq_add(stream, &tbuf); + } + return 0; + } + } + + if (datalen == 0) { + /* We are going to send more frames, but no DATA frame this + time. */ + return 0; + } + } + + hd.type = NGHTTP3_FRAME_DATA; + hd.length = datalen; + + len = nghttp3_frame_write_hd_len(&hd); + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + typed_buf_shared_init(&tbuf, chunk); + + chunk->last = nghttp3_frame_write_hd(chunk->last, &hd); + + tbuf.buf.last = chunk->last; + + rv = nghttp3_stream_outq_add(stream, &tbuf); + if (rv != 0) { + return rv; + } + + if (datalen) { + for (i = 0; i < (size_t)sveccnt; ++i) { + v = &vec[i]; + if (v->len == 0) { + continue; + } + nghttp3_buf_wrap_init(&buf, v->base, v->len); + buf.last = buf.end; + nghttp3_typed_buf_init(&tbuf, &buf, NGHTTP3_BUF_TYPE_ALIEN); + rv = nghttp3_stream_outq_add(stream, &tbuf); + if (rv != 0) { + return rv; + } + } + } + + return 0; +} + +int nghttp3_stream_write_qpack_decoder_stream(nghttp3_stream *stream) { + nghttp3_qpack_decoder *qdec; + nghttp3_buf *chunk; + int rv; + nghttp3_typed_buf tbuf; + size_t len; + + assert(stream->conn); + assert(stream->conn->tx.qdec == stream); + + qdec = &stream->conn->qdec; + + assert(qdec); + + len = nghttp3_qpack_decoder_get_decoder_streamlen(qdec); + if (len == 0) { + return 0; + } + + rv = nghttp3_stream_ensure_chunk(stream, len); + if (rv != 0) { + return rv; + } + + chunk = nghttp3_stream_get_chunk(stream); + typed_buf_shared_init(&tbuf, chunk); + + nghttp3_qpack_decoder_write_decoder(qdec, chunk); + + tbuf.buf.last = chunk->last; + + return nghttp3_stream_outq_add(stream, &tbuf); +} + +int nghttp3_stream_outq_is_full(nghttp3_stream *stream) { + /* TODO Verify that the limit is reasonable. */ + return nghttp3_ringbuf_len(&stream->outq) >= 1024; +} + +int nghttp3_stream_outq_add(nghttp3_stream *stream, + const nghttp3_typed_buf *tbuf) { + nghttp3_ringbuf *outq = &stream->outq; + int rv; + nghttp3_typed_buf *dest; + size_t len = nghttp3_ringbuf_len(outq); + size_t buflen = nghttp3_buf_len(&tbuf->buf); + + if (buflen > NGHTTP3_MAX_VARINT - stream->tx.offset) { + return NGHTTP3_ERR_STREAM_DATA_OVERFLOW; + } + + stream->tx.offset += buflen; + stream->unsent_bytes += buflen; + + if (len) { + dest = nghttp3_ringbuf_get(outq, len - 1); + if (dest->type == tbuf->type && dest->type == NGHTTP3_BUF_TYPE_SHARED && + dest->buf.begin == tbuf->buf.begin && dest->buf.last == tbuf->buf.pos) { + /* If we have already written last entry, adjust outq_idx and + offset so that this entry is eligible to send. */ + if (len == stream->outq_idx) { + --stream->outq_idx; + stream->outq_offset = nghttp3_buf_len(&dest->buf); + } + + dest->buf.last = tbuf->buf.last; + /* TODO Is this required? */ + dest->buf.end = tbuf->buf.end; + + return 0; + } + } + + if (nghttp3_ringbuf_full(outq)) { + size_t nlen = nghttp3_max(NGHTTP3_MIN_RBLEN, len * 2); + rv = nghttp3_ringbuf_reserve(outq, nlen); + if (rv != 0) { + return rv; + } + } + + dest = nghttp3_ringbuf_push_back(outq); + *dest = *tbuf; + + return 0; +} + +int nghttp3_stream_ensure_chunk(nghttp3_stream *stream, size_t need) { + nghttp3_ringbuf *chunks = &stream->chunks; + nghttp3_buf *chunk; + size_t len = nghttp3_ringbuf_len(chunks); + uint8_t *p; + int rv; + size_t n = NGHTTP3_STREAM_MIN_CHUNK_SIZE; + + if (len) { + chunk = nghttp3_ringbuf_get(chunks, len - 1); + if (nghttp3_buf_left(chunk) >= need) { + return 0; + } + } + + for (; n < need; n *= 2) + ; + + if (n == NGHTTP3_STREAM_MIN_CHUNK_SIZE) { + p = (uint8_t *)nghttp3_objalloc_chunk_len_get(stream->out_chunk_objalloc, + n); + } else { + p = nghttp3_mem_malloc(stream->mem, n); + } + if (p == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + if (nghttp3_ringbuf_full(chunks)) { + size_t nlen = nghttp3_max(NGHTTP3_MIN_RBLEN, len * 2); + rv = nghttp3_ringbuf_reserve(chunks, nlen); + if (rv != 0) { + return rv; + } + } + + chunk = nghttp3_ringbuf_push_back(chunks); + nghttp3_buf_wrap_init(chunk, p, n); + + return 0; +} + +nghttp3_buf *nghttp3_stream_get_chunk(nghttp3_stream *stream) { + nghttp3_ringbuf *chunks = &stream->chunks; + size_t len = nghttp3_ringbuf_len(chunks); + + assert(len); + + return nghttp3_ringbuf_get(chunks, len - 1); +} + +int nghttp3_stream_is_blocked(nghttp3_stream *stream) { + return (stream->flags & NGHTTP3_STREAM_FLAG_FC_BLOCKED) || + (stream->flags & NGHTTP3_STREAM_FLAG_SHUT_WR) || + (stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED); +} + +int nghttp3_stream_require_schedule(nghttp3_stream *stream) { + return (!nghttp3_stream_outq_write_done(stream) && + !(stream->flags & NGHTTP3_STREAM_FLAG_FC_BLOCKED) && + !(stream->flags & NGHTTP3_STREAM_FLAG_SHUT_WR)) || + (nghttp3_ringbuf_len(&stream->frq) && + !(stream->flags & NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED)); +} + +nghttp3_ssize nghttp3_stream_writev(nghttp3_stream *stream, int *pfin, + nghttp3_vec *vec, size_t veccnt) { + nghttp3_ringbuf *outq = &stream->outq; + size_t len = nghttp3_ringbuf_len(outq); + size_t i = stream->outq_idx; + uint64_t offset = stream->outq_offset; + size_t buflen; + nghttp3_vec *vbegin = vec, *vend = vec + veccnt; + nghttp3_typed_buf *tbuf; + + assert(veccnt > 0); + + if (i < len) { + tbuf = nghttp3_ringbuf_get(outq, i); + buflen = nghttp3_buf_len(&tbuf->buf); + + if (offset < buflen) { + vec->base = tbuf->buf.pos + offset; + vec->len = (size_t)(buflen - offset); + ++vec; + } else { + /* This is the only case that satisfies offset >= buflen */ + assert(0 == offset); + assert(0 == buflen); + } + + ++i; + + for (; i < len && vec != vend; ++i, ++vec) { + tbuf = nghttp3_ringbuf_get(outq, i); + vec->base = tbuf->buf.pos; + vec->len = nghttp3_buf_len(&tbuf->buf); + } + } + + /* TODO Rework this if we have finished implementing HTTP + messaging */ + *pfin = nghttp3_ringbuf_len(&stream->frq) == 0 && i == len && + (stream->flags & NGHTTP3_STREAM_FLAG_WRITE_END_STREAM); + + return vec - vbegin; +} + +int nghttp3_stream_add_outq_offset(nghttp3_stream *stream, size_t n) { + nghttp3_ringbuf *outq = &stream->outq; + size_t i; + size_t len = nghttp3_ringbuf_len(outq); + uint64_t offset = stream->outq_offset + n; + size_t buflen; + nghttp3_typed_buf *tbuf; + + for (i = stream->outq_idx; i < len; ++i) { + tbuf = nghttp3_ringbuf_get(outq, i); + buflen = nghttp3_buf_len(&tbuf->buf); + if (offset >= buflen) { + offset -= buflen; + continue; + } + + break; + } + + assert(i < len || offset == 0); + + stream->unsent_bytes -= n; + stream->outq_idx = i; + stream->outq_offset = offset; + + return 0; +} + +int nghttp3_stream_outq_write_done(nghttp3_stream *stream) { + nghttp3_ringbuf *outq = &stream->outq; + size_t len = nghttp3_ringbuf_len(outq); + + return len == 0 || stream->outq_idx >= len; +} + +static int stream_pop_outq_entry(nghttp3_stream *stream, + nghttp3_typed_buf *tbuf) { + nghttp3_ringbuf *chunks = &stream->chunks; + nghttp3_buf *chunk; + + switch (tbuf->type) { + case NGHTTP3_BUF_TYPE_PRIVATE: + nghttp3_buf_free(&tbuf->buf, stream->mem); + break; + case NGHTTP3_BUF_TYPE_ALIEN: + break; + case NGHTTP3_BUF_TYPE_SHARED: + assert(nghttp3_ringbuf_len(chunks)); + + chunk = nghttp3_ringbuf_get(chunks, 0); + + assert(chunk->begin == tbuf->buf.begin); + assert(chunk->end == tbuf->buf.end); + + if (chunk->last == tbuf->buf.last) { + if (nghttp3_buf_cap(chunk) == NGHTTP3_STREAM_MIN_CHUNK_SIZE) { + nghttp3_objalloc_chunk_release(stream->out_chunk_objalloc, + (nghttp3_chunk *)(void *)chunk->begin); + } else { + nghttp3_buf_free(chunk, stream->mem); + } + nghttp3_ringbuf_pop_front(chunks); + } + break; + default: + nghttp3_unreachable(); + }; + + nghttp3_ringbuf_pop_front(&stream->outq); + + return 0; +} + +int nghttp3_stream_add_ack_offset(nghttp3_stream *stream, uint64_t n) { + nghttp3_ringbuf *outq = &stream->outq; + uint64_t offset = stream->ack_offset + n; + size_t buflen; + size_t npopped = 0; + uint64_t nack; + nghttp3_typed_buf *tbuf; + int rv; + + for (; nghttp3_ringbuf_len(outq);) { + tbuf = nghttp3_ringbuf_get(outq, 0); + buflen = nghttp3_buf_len(&tbuf->buf); + + if (tbuf->type == NGHTTP3_BUF_TYPE_ALIEN) { + nack = nghttp3_min(offset, (uint64_t)buflen) - stream->ack_done; + if (stream->callbacks.acked_data) { + rv = stream->callbacks.acked_data(stream, stream->node.id, nack, + stream->user_data); + if (rv != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + stream->ack_done += nack; + } + + if (offset >= buflen) { + rv = stream_pop_outq_entry(stream, tbuf); + if (rv != 0) { + return rv; + } + + offset -= buflen; + ++npopped; + stream->ack_done = 0; + + if (stream->outq_idx + 1 == npopped) { + stream->outq_offset = 0; + break; + } + + continue; + } + + break; + } + + assert(stream->outq_idx + 1 >= npopped); + if (stream->outq_idx >= npopped) { + stream->outq_idx -= npopped; + } else { + stream->outq_idx = 0; + } + + stream->ack_offset = offset; + + return 0; +} + +int nghttp3_stream_buffer_data(nghttp3_stream *stream, const uint8_t *data, + size_t datalen) { + nghttp3_ringbuf *inq = &stream->inq; + size_t len = nghttp3_ringbuf_len(inq); + nghttp3_buf *buf; + size_t nwrite; + uint8_t *rawbuf; + size_t bufleft; + int rv; + + if (len) { + buf = nghttp3_ringbuf_get(inq, len - 1); + bufleft = nghttp3_buf_left(buf); + nwrite = nghttp3_min(datalen, bufleft); + buf->last = nghttp3_cpymem(buf->last, data, nwrite); + data += nwrite; + datalen -= nwrite; + } + + for (; datalen;) { + if (nghttp3_ringbuf_full(inq)) { + size_t nlen = + nghttp3_max(NGHTTP3_MIN_RBLEN, nghttp3_ringbuf_len(inq) * 2); + rv = nghttp3_ringbuf_reserve(inq, nlen); + if (rv != 0) { + return rv; + } + } + + rawbuf = nghttp3_mem_malloc(stream->mem, 16384); + if (rawbuf == NULL) { + return NGHTTP3_ERR_NOMEM; + } + + buf = nghttp3_ringbuf_push_back(inq); + nghttp3_buf_wrap_init(buf, rawbuf, 16384); + bufleft = nghttp3_buf_left(buf); + nwrite = nghttp3_min(datalen, bufleft); + buf->last = nghttp3_cpymem(buf->last, data, nwrite); + data += nwrite; + datalen -= nwrite; + } + + return 0; +} + +size_t nghttp3_stream_get_buffered_datalen(nghttp3_stream *stream) { + nghttp3_ringbuf *inq = &stream->inq; + size_t len = nghttp3_ringbuf_len(inq); + size_t i, n = 0; + nghttp3_buf *buf; + + for (i = 0; i < len; ++i) { + buf = nghttp3_ringbuf_get(inq, i); + n += nghttp3_buf_len(buf); + } + + return n; +} + +int nghttp3_stream_transit_rx_http_state(nghttp3_stream *stream, + nghttp3_stream_http_event event) { + int rv; + + switch (stream->rx.hstate) { + case NGHTTP3_HTTP_STATE_NONE: + return NGHTTP3_ERR_H3_INTERNAL_ERROR; + case NGHTTP3_HTTP_STATE_REQ_INITIAL: + switch (event) { + case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN: + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN; + return 0; + default: + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + case NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN: + if (event != NGHTTP3_HTTP_EVENT_HEADERS_END) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_HEADERS_END; + return 0; + case NGHTTP3_HTTP_STATE_REQ_HEADERS_END: + switch (event) { + case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN: + /* TODO Better to check status code */ + if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN; + return 0; + case NGHTTP3_HTTP_EVENT_DATA_BEGIN: + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN; + return 0; + case NGHTTP3_HTTP_EVENT_MSG_END: + rv = nghttp3_http_on_remote_end_stream(stream); + if (rv != 0) { + return rv; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END; + return 0; + default: + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + case NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN: + if (event != NGHTTP3_HTTP_EVENT_DATA_END) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_END; + return 0; + case NGHTTP3_HTTP_STATE_REQ_DATA_END: + switch (event) { + case NGHTTP3_HTTP_EVENT_DATA_BEGIN: + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN; + return 0; + case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN: + /* TODO Better to check status code */ + if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN; + return 0; + case NGHTTP3_HTTP_EVENT_MSG_END: + rv = nghttp3_http_on_remote_end_stream(stream); + if (rv != 0) { + return rv; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END; + return 0; + default: + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN: + if (event != NGHTTP3_HTTP_EVENT_HEADERS_END) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_TRAILERS_END; + return 0; + case NGHTTP3_HTTP_STATE_REQ_TRAILERS_END: + if (event != NGHTTP3_HTTP_EVENT_MSG_END) { + /* TODO Should ignore unexpected frame in this state as per + spec. */ + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + rv = nghttp3_http_on_remote_end_stream(stream); + if (rv != 0) { + return rv; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_REQ_END; + return 0; + case NGHTTP3_HTTP_STATE_REQ_END: + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + case NGHTTP3_HTTP_STATE_RESP_INITIAL: + if (event != NGHTTP3_HTTP_EVENT_HEADERS_BEGIN) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN; + return 0; + case NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN: + if (event != NGHTTP3_HTTP_EVENT_HEADERS_END) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_END; + return 0; + case NGHTTP3_HTTP_STATE_RESP_HEADERS_END: + switch (event) { + case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN: + if (stream->rx.http.status_code == -1) { + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN; + return 0; + } + if ((stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) && + stream->rx.http.status_code / 100 == 2) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN; + return 0; + case NGHTTP3_HTTP_EVENT_DATA_BEGIN: + if (stream->rx.http.flags & NGHTTP3_HTTP_FLAG_EXPECT_FINAL_RESPONSE) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN; + return 0; + case NGHTTP3_HTTP_EVENT_MSG_END: + rv = nghttp3_http_on_remote_end_stream(stream); + if (rv != 0) { + return rv; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END; + return 0; + default: + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + case NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN: + if (event != NGHTTP3_HTTP_EVENT_DATA_END) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_END; + return 0; + case NGHTTP3_HTTP_STATE_RESP_DATA_END: + switch (event) { + case NGHTTP3_HTTP_EVENT_DATA_BEGIN: + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN; + return 0; + case NGHTTP3_HTTP_EVENT_HEADERS_BEGIN: + if ((stream->rx.http.flags & NGHTTP3_HTTP_FLAG_METH_CONNECT) && + stream->rx.http.status_code / 100 == 2) { + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN; + return 0; + case NGHTTP3_HTTP_EVENT_MSG_END: + rv = nghttp3_http_on_remote_end_stream(stream); + if (rv != 0) { + return rv; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END; + return 0; + default: + return NGHTTP3_ERR_H3_FRAME_UNEXPECTED; + } + case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN: + if (event != NGHTTP3_HTTP_EVENT_HEADERS_END) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_TRAILERS_END; + return 0; + case NGHTTP3_HTTP_STATE_RESP_TRAILERS_END: + if (event != NGHTTP3_HTTP_EVENT_MSG_END) { + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } + rv = nghttp3_http_on_remote_end_stream(stream); + if (rv != 0) { + return rv; + } + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_END; + return 0; + case NGHTTP3_HTTP_STATE_RESP_END: + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + default: + nghttp3_unreachable(); + } +} + +int nghttp3_stream_empty_headers_allowed(nghttp3_stream *stream) { + switch (stream->rx.hstate) { + case NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN: + case NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN: + return 0; + default: + return NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING; + } +} + +int nghttp3_stream_uni(int64_t stream_id) { return (stream_id & 0x2) != 0; } + +int nghttp3_client_stream_bidi(int64_t stream_id) { + return (stream_id & 0x3) == 0; +} + +int nghttp3_client_stream_uni(int64_t stream_id) { + return (stream_id & 0x3) == 0x2; +} + +int nghttp3_server_stream_uni(int64_t stream_id) { + return (stream_id & 0x3) == 0x3; +} diff --git a/lib/nghttp3_stream.h b/lib/nghttp3_stream.h new file mode 100644 index 0000000..be8881f --- /dev/null +++ b/lib/nghttp3_stream.h @@ -0,0 +1,396 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 NGHTTP3_STREAM_H +#define NGHTTP3_STREAM_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_map.h" +#include "nghttp3_tnode.h" +#include "nghttp3_ringbuf.h" +#include "nghttp3_buf.h" +#include "nghttp3_frame.h" +#include "nghttp3_qpack.h" +#include "nghttp3_objalloc.h" + +#define NGHTTP3_STREAM_MIN_CHUNK_SIZE 256 + +/* NGHTTP3_MIN_UNSENT_BYTES is the minimum unsent bytes which is large + enough to fill outgoing single QUIC packet. */ +#define NGHTTP3_MIN_UNSENT_BYTES 4096 + +/* NGHTTP3_STREAM_MIN_WRITELEN is the minimum length of write to cause + the stream to reschedule. */ +#define NGHTTP3_STREAM_MIN_WRITELEN 800 + +/* nghttp3_stream_type is unidirectional stream type. */ +typedef enum nghttp3_stream_type { + NGHTTP3_STREAM_TYPE_CONTROL = 0x00, + NGHTTP3_STREAM_TYPE_PUSH = 0x01, + NGHTTP3_STREAM_TYPE_QPACK_ENCODER = 0x02, + NGHTTP3_STREAM_TYPE_QPACK_DECODER = 0x03, + NGHTTP3_STREAM_TYPE_UNKNOWN = UINT64_MAX, +} nghttp3_stream_type; + +typedef enum nghttp3_ctrl_stream_state { + NGHTTP3_CTRL_STREAM_STATE_FRAME_TYPE, + NGHTTP3_CTRL_STREAM_STATE_FRAME_LENGTH, + NGHTTP3_CTRL_STREAM_STATE_SETTINGS, + NGHTTP3_CTRL_STREAM_STATE_GOAWAY, + NGHTTP3_CTRL_STREAM_STATE_MAX_PUSH_ID, + NGHTTP3_CTRL_STREAM_STATE_IGN_FRAME, + NGHTTP3_CTRL_STREAM_STATE_SETTINGS_ID, + NGHTTP3_CTRL_STREAM_STATE_SETTINGS_VALUE, + NGHTTP3_CTRL_STREAM_STATE_PRIORITY_UPDATE_PRI_ELEM_ID, + NGHTTP3_CTRL_STREAM_STATE_PRIORITY_UPDATE, +} nghttp3_ctrl_stream_state; + +typedef enum nghttp3_req_stream_state { + NGHTTP3_REQ_STREAM_STATE_FRAME_TYPE, + NGHTTP3_REQ_STREAM_STATE_FRAME_LENGTH, + NGHTTP3_REQ_STREAM_STATE_DATA, + NGHTTP3_REQ_STREAM_STATE_HEADERS, + NGHTTP3_REQ_STREAM_STATE_IGN_FRAME, + NGHTTP3_REQ_STREAM_STATE_IGN_REST, +} nghttp3_req_stream_state; + +typedef struct nghttp3_varint_read_state { + int64_t acc; + size_t left; +} nghttp3_varint_read_state; + +typedef struct nghttp3_stream_read_state { + nghttp3_varint_read_state rvint; + nghttp3_frame fr; + int state; + int64_t left; +} nghttp3_stream_read_state; + +/* NGHTTP3_STREAM_FLAG_NONE indicates that no flag is set. */ +#define NGHTTP3_STREAM_FLAG_NONE 0x0000u +/* NGHTTP3_STREAM_FLAG_TYPE_IDENTIFIED is set when a unidirectional + stream type is identified. */ +#define NGHTTP3_STREAM_FLAG_TYPE_IDENTIFIED 0x0001u +/* NGHTTP3_STREAM_FLAG_FC_BLOCKED indicates that stream is blocked by + QUIC flow control. */ +#define NGHTTP3_STREAM_FLAG_FC_BLOCKED 0x0002u +/* NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED indicates that application is + temporarily unable to provide data. */ +#define NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED 0x0004u +/* NGHTTP3_STREAM_FLAG_WRITE_END_STREAM indicates that application + finished to feed outgoing data. */ +#define NGHTTP3_STREAM_FLAG_WRITE_END_STREAM 0x0008u +/* NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED indicates that stream is + blocked due to QPACK decoding. */ +#define NGHTTP3_STREAM_FLAG_QPACK_DECODE_BLOCKED 0x0010u +/* NGHTTP3_STREAM_FLAG_READ_EOF indicates that remote endpoint sent + fin. */ +#define NGHTTP3_STREAM_FLAG_READ_EOF 0x0020u +/* NGHTTP3_STREAM_FLAG_CLOSED indicates that QUIC stream was closed. + nghttp3_stream object can still alive because it might be blocked + by QPACK decoder. */ +#define NGHTTP3_STREAM_FLAG_CLOSED 0x0040u +/* NGHTTP3_STREAM_FLAG_SHUT_WR indicates that any further write + operation to a stream is prohibited. */ +#define NGHTTP3_STREAM_FLAG_SHUT_WR 0x0100u +/* NGHTTP3_STREAM_FLAG_SHUT_RD indicates that a read-side stream is + closed abruptly and any incoming and pending stream data is just + discarded for a stream. */ +#define NGHTTP3_STREAM_FLAG_SHUT_RD 0x0200u +/* NGHTTP3_STREAM_FLAG_SERVER_PRIORITY_SET indicates that server + overrides stream priority. */ +#define NGHTTP3_STREAM_FLAG_SERVER_PRIORITY_SET 0x0400u +/* NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED indicates that server + received PRIORITY_UPDATE frame for this stream. */ +#define NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED 0x0800u +/* NGHTTP3_STREAM_FLAG_HTTP_ERROR indicates that + NGHTTP3_ERR_MALFORMED_HTTP_HEADER error is encountered while + processing incoming HTTP fields. */ +#define NGHTTP3_STREAM_FLAG_HTTP_ERROR 0x1000u + +typedef enum nghttp3_stream_http_state { + NGHTTP3_HTTP_STATE_NONE, + NGHTTP3_HTTP_STATE_REQ_INITIAL, + NGHTTP3_HTTP_STATE_REQ_BEGIN, + NGHTTP3_HTTP_STATE_REQ_HEADERS_BEGIN, + NGHTTP3_HTTP_STATE_REQ_HEADERS_END, + NGHTTP3_HTTP_STATE_REQ_DATA_BEGIN, + NGHTTP3_HTTP_STATE_REQ_DATA_END, + NGHTTP3_HTTP_STATE_REQ_TRAILERS_BEGIN, + NGHTTP3_HTTP_STATE_REQ_TRAILERS_END, + NGHTTP3_HTTP_STATE_REQ_END, + NGHTTP3_HTTP_STATE_RESP_INITIAL, + NGHTTP3_HTTP_STATE_RESP_BEGIN, + NGHTTP3_HTTP_STATE_RESP_HEADERS_BEGIN, + NGHTTP3_HTTP_STATE_RESP_HEADERS_END, + NGHTTP3_HTTP_STATE_RESP_DATA_BEGIN, + NGHTTP3_HTTP_STATE_RESP_DATA_END, + NGHTTP3_HTTP_STATE_RESP_TRAILERS_BEGIN, + NGHTTP3_HTTP_STATE_RESP_TRAILERS_END, + NGHTTP3_HTTP_STATE_RESP_END, +} nghttp3_stream_http_state; + +typedef enum nghttp3_stream_http_event { + NGHTTP3_HTTP_EVENT_DATA_BEGIN, + NGHTTP3_HTTP_EVENT_DATA_END, + NGHTTP3_HTTP_EVENT_HEADERS_BEGIN, + NGHTTP3_HTTP_EVENT_HEADERS_END, + NGHTTP3_HTTP_EVENT_MSG_END, +} nghttp3_stream_http_event; + +typedef struct nghttp3_stream nghttp3_stream; + +/* + * nghttp3_stream_acked_data is a callback function which is invoked + * when data sent on stream denoted by |stream_id| supplied from + * application is acknowledged by remote endpoint. The number of + * bytes acknowledged is given in |datalen|. + * + * The implementation of this callback must return 0 if it succeeds. + * Returning NGHTTP3_ERR_CALLBACK_FAILURE will return to the caller + * immediately. Any values other than 0 is treated as + * NGHTTP3_ERR_CALLBACK_FAILURE. + */ +typedef int (*nghttp3_stream_acked_data)(nghttp3_stream *stream, + int64_t stream_id, uint64_t datalen, + void *user_data); + +typedef struct nghttp3_stream_callbacks { + nghttp3_stream_acked_data acked_data; +} nghttp3_stream_callbacks; + +typedef struct nghttp3_http_state { + /* status_code is HTTP status code received. This field is used + if connection is initialized as client. */ + int32_t status_code; + /* content_length is the value of received content-length header + field. */ + int64_t content_length; + /* recv_content_length is the number of body bytes received so + far. */ + int64_t recv_content_length; + uint32_t flags; + /* pri is a stream priority produced by nghttp3_pri_to_uint8. */ + uint8_t pri; +} nghttp3_http_state; + +struct nghttp3_stream { + union { + struct { + const nghttp3_mem *mem; + nghttp3_objalloc *out_chunk_objalloc; + nghttp3_objalloc *stream_objalloc; + nghttp3_tnode node; + nghttp3_pq_entry qpack_blocked_pe; + nghttp3_stream_callbacks callbacks; + nghttp3_ringbuf frq; + nghttp3_ringbuf chunks; + nghttp3_ringbuf outq; + /* inq stores the stream raw data which cannot be read because + stream is blocked by QPACK decoder. */ + nghttp3_ringbuf inq; + nghttp3_qpack_stream_context qpack_sctx; + /* conn is a reference to underlying connection. It could be NULL + if stream is not a request stream. */ + nghttp3_conn *conn; + void *user_data; + /* unsent_bytes is the number of bytes in outq not written yet */ + uint64_t unsent_bytes; + /* outq_idx is an index into outq where next write is made. */ + size_t outq_idx; + /* outq_offset is write offset relative to the element at outq_idx + in outq. */ + uint64_t outq_offset; + /* ack_offset is offset acknowledged by peer relative to the first + element in outq. */ + uint64_t ack_offset; + /* ack_done is the number of bytes notified to an application that + they are acknowledged inside the first outq element if it is of + type NGHTTP3_BUF_TYPE_ALIEN. */ + uint64_t ack_done; + uint64_t unscheduled_nwrite; + nghttp3_stream_type type; + nghttp3_stream_read_state rstate; + /* error_code indicates the reason of closure of this stream. */ + uint64_t error_code; + + struct { + uint64_t offset; + nghttp3_stream_http_state hstate; + } tx; + + struct { + nghttp3_stream_http_state hstate; + nghttp3_http_state http; + } rx; + + uint16_t flags; + }; + + nghttp3_opl_entry oplent; + }; +}; + +nghttp3_objalloc_def(stream, nghttp3_stream, oplent); + +typedef struct nghttp3_frame_entry { + nghttp3_frame fr; + union { + struct { + nghttp3_settings *local_settings; + } settings; + struct { + nghttp3_data_reader dr; + } data; + } aux; +} nghttp3_frame_entry; + +int nghttp3_stream_new(nghttp3_stream **pstream, int64_t stream_id, + const nghttp3_stream_callbacks *callbacks, + nghttp3_objalloc *out_chunk_objalloc, + nghttp3_objalloc *stream_objalloc, + const nghttp3_mem *mem); + +void nghttp3_stream_del(nghttp3_stream *stream); + +void nghttp3_varint_read_state_reset(nghttp3_varint_read_state *rvint); + +void nghttp3_stream_read_state_reset(nghttp3_stream_read_state *rstate); + +nghttp3_ssize nghttp3_read_varint(nghttp3_varint_read_state *rvint, + const uint8_t *src, size_t srclen, int fin); + +int nghttp3_stream_frq_add(nghttp3_stream *stream, + const nghttp3_frame_entry *frent); + +int nghttp3_stream_fill_outq(nghttp3_stream *stream); + +int nghttp3_stream_write_stream_type(nghttp3_stream *stream); + +nghttp3_ssize nghttp3_stream_writev(nghttp3_stream *stream, int *pfin, + nghttp3_vec *vec, size_t veccnt); + +int nghttp3_stream_write_qpack_decoder_stream(nghttp3_stream *stream); + +int nghttp3_stream_outq_is_full(nghttp3_stream *stream); + +int nghttp3_stream_outq_add(nghttp3_stream *stream, + const nghttp3_typed_buf *tbuf); + +int nghttp3_stream_write_headers(nghttp3_stream *stream, + nghttp3_frame_entry *frent); + +int nghttp3_stream_write_header_block(nghttp3_stream *stream, + nghttp3_qpack_encoder *qenc, + nghttp3_stream *qenc_stream, + nghttp3_buf *rbuf, nghttp3_buf *ebuf, + int64_t frame_type, const nghttp3_nv *nva, + size_t nvlen); + +int nghttp3_stream_write_data(nghttp3_stream *stream, int *peof, + nghttp3_frame_entry *frent); + +int nghttp3_stream_write_settings(nghttp3_stream *stream, + nghttp3_frame_entry *frent); + +int nghttp3_stream_write_goaway(nghttp3_stream *stream, + nghttp3_frame_entry *frent); + +int nghttp3_stream_write_priority_update(nghttp3_stream *stream, + nghttp3_frame_entry *frent); + +int nghttp3_stream_ensure_chunk(nghttp3_stream *stream, size_t need); + +nghttp3_buf *nghttp3_stream_get_chunk(nghttp3_stream *stream); + +int nghttp3_stream_is_blocked(nghttp3_stream *stream); + +int nghttp3_stream_add_outq_offset(nghttp3_stream *stream, size_t n); + +/* + * nghttp3_stream_outq_write_done returns nonzero if all contents in + * outq have been written. + */ +int nghttp3_stream_outq_write_done(nghttp3_stream *stream); + +int nghttp3_stream_add_ack_offset(nghttp3_stream *stream, uint64_t n); + +/* + * nghttp3_stream_is_active returns nonzero if |stream| is active. In + * other words, it has something to send. This function does not take + * into account its descendants. + */ +int nghttp3_stream_is_active(nghttp3_stream *stream); + +/* + * nghttp3_stream_require_schedule returns nonzero if |stream| should + * be scheduled. In other words, |stream| or its descendants have + * something to send. + */ +int nghttp3_stream_require_schedule(nghttp3_stream *stream); + +int nghttp3_stream_buffer_data(nghttp3_stream *stream, const uint8_t *src, + size_t srclen); + +size_t nghttp3_stream_get_buffered_datalen(nghttp3_stream *stream); + +int nghttp3_stream_ensure_qpack_stream_context(nghttp3_stream *stream); + +void nghttp3_stream_delete_qpack_stream_context(nghttp3_stream *stream); + +int nghttp3_stream_transit_rx_http_state(nghttp3_stream *stream, + nghttp3_stream_http_event event); + +int nghttp3_stream_empty_headers_allowed(nghttp3_stream *stream); + +/* + * nghttp3_stream_uni returns nonzero if stream identified by + * |stream_id| is unidirectional. + */ +int nghttp3_stream_uni(int64_t stream_id); + +/* + * nghttp3_client_stream_bidi returns nonzero if stream identified by + * |stream_id| is client initiated bidirectional stream. + */ +int nghttp3_client_stream_bidi(int64_t stream_id); + +/* + * nghttp3_client_stream_uni returns nonzero if stream identified by + * |stream_id| is client initiated unidirectional stream. + */ +int nghttp3_client_stream_uni(int64_t stream_id); + +/* + * nghttp3_server_stream_uni returns nonzero if stream identified by + * |stream_id| is server initiated unidirectional stream. + */ +int nghttp3_server_stream_uni(int64_t stream_id); + +#endif /* NGHTTP3_STREAM_H */ diff --git a/lib/nghttp3_tnode.c b/lib/nghttp3_tnode.c new file mode 100644 index 0000000..2fe7f3f --- /dev/null +++ b/lib/nghttp3_tnode.c @@ -0,0 +1,97 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "nghttp3_tnode.h" + +#include <assert.h> + +#include "nghttp3_macro.h" +#include "nghttp3_stream.h" +#include "nghttp3_conn.h" +#include "nghttp3_conv.h" + +void nghttp3_tnode_init(nghttp3_tnode *tnode, int64_t id, uint8_t pri) { + assert(nghttp3_pri_uint8_urgency(pri) < NGHTTP3_URGENCY_LEVELS); + + tnode->pe.index = NGHTTP3_PQ_BAD_INDEX; + tnode->id = id; + tnode->cycle = 0; + tnode->pri = pri; +} + +void nghttp3_tnode_free(nghttp3_tnode *tnode) { (void)tnode; } + +static void tnode_unschedule(nghttp3_tnode *tnode, nghttp3_pq *pq) { + assert(tnode->pe.index != NGHTTP3_PQ_BAD_INDEX); + + nghttp3_pq_remove(pq, &tnode->pe); + tnode->pe.index = NGHTTP3_PQ_BAD_INDEX; +} + +void nghttp3_tnode_unschedule(nghttp3_tnode *tnode, nghttp3_pq *pq) { + if (tnode->pe.index == NGHTTP3_PQ_BAD_INDEX) { + return; + } + + tnode_unschedule(tnode, pq); +} + +static uint64_t pq_get_first_cycle(nghttp3_pq *pq) { + nghttp3_tnode *top; + + if (nghttp3_pq_empty(pq)) { + return 0; + } + + top = nghttp3_struct_of(nghttp3_pq_top(pq), nghttp3_tnode, pe); + return top->cycle; +} + +int nghttp3_tnode_schedule(nghttp3_tnode *tnode, nghttp3_pq *pq, + uint64_t nwrite) { + uint64_t penalty = nwrite / NGHTTP3_STREAM_MIN_WRITELEN; + + if (tnode->pe.index == NGHTTP3_PQ_BAD_INDEX) { + tnode->cycle = pq_get_first_cycle(pq) + + ((nwrite == 0 || !nghttp3_pri_uint8_inc(tnode->pri)) + ? 0 + : nghttp3_max(1, penalty)); + } else if (nwrite > 0) { + if (!nghttp3_pri_uint8_inc(tnode->pri) || nghttp3_pq_size(pq) == 1) { + return 0; + } + + nghttp3_pq_remove(pq, &tnode->pe); + tnode->pe.index = NGHTTP3_PQ_BAD_INDEX; + tnode->cycle += nghttp3_max(1, penalty); + } else { + return 0; + } + + return nghttp3_pq_push(pq, &tnode->pe); +} + +int nghttp3_tnode_is_scheduled(nghttp3_tnode *tnode) { + return tnode->pe.index != NGHTTP3_PQ_BAD_INDEX; +} diff --git a/lib/nghttp3_tnode.h b/lib/nghttp3_tnode.h new file mode 100644 index 0000000..f97120a --- /dev/null +++ b/lib/nghttp3_tnode.h @@ -0,0 +1,66 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 NGHTTP3_TNODE_H +#define NGHTTP3_TNODE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include "nghttp3_pq.h" + +#define NGHTTP3_TNODE_MAX_CYCLE_GAP (1llu << 24) + +typedef struct nghttp3_tnode { + nghttp3_pq_entry pe; + size_t num_children; + int64_t id; + uint64_t cycle; + /* pri is a stream priority produced by nghttp3_pri_to_uint8. */ + uint8_t pri; +} nghttp3_tnode; + +void nghttp3_tnode_init(nghttp3_tnode *tnode, int64_t id, uint8_t pri); + +void nghttp3_tnode_free(nghttp3_tnode *tnode); + +void nghttp3_tnode_unschedule(nghttp3_tnode *tnode, nghttp3_pq *pq); + +/* + * nghttp3_tnode_schedule schedules |tnode| using |nwrite| as penalty. + * If |tnode| has already been scheduled, it is rescheduled by the + * amount of |nwrite|. + */ +int nghttp3_tnode_schedule(nghttp3_tnode *tnode, nghttp3_pq *pq, + uint64_t nwrite); + +/* + * nghttp3_tnode_is_scheduled returns nonzero if |tnode| is scheduled. + */ +int nghttp3_tnode_is_scheduled(nghttp3_tnode *tnode); + +#endif /* NGHTTP3_TNODE_H */ diff --git a/lib/nghttp3_unreachable.c b/lib/nghttp3_unreachable.c new file mode 100644 index 0000000..6fea89b --- /dev/null +++ b/lib/nghttp3_unreachable.c @@ -0,0 +1,72 @@ +/* + * nghttp3 + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_unreachable.h" + +#include <stdio.h> +#include <errno.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ +#include <stdlib.h> +#ifdef WIN32 +# include <io.h> +#endif /* WIN32 */ + +void nghttp3_unreachable_fail(const char *file, int line, const char *func) { + char *buf; + size_t buflen; + int rv; + +#define NGHTTP3_UNREACHABLE_TEMPLATE "%s:%d %s: Unreachable.\n" + + rv = snprintf(NULL, 0, NGHTTP3_UNREACHABLE_TEMPLATE, file, line, func); + if (rv < 0) { + abort(); + } + + /* here we explicitly use system malloc */ + buflen = (size_t)rv + 1; + buf = malloc(buflen); + if (buf == NULL) { + abort(); + } + + rv = snprintf(buf, buflen, NGHTTP3_UNREACHABLE_TEMPLATE, file, line, func); + if (rv < 0) { + abort(); + } + +#ifndef WIN32 + while (write(STDERR_FILENO, buf, (size_t)rv) == -1 && errno == EINTR) + ; +#else /* WIN32 */ + _write(_fileno(stderr), buf, (unsigned int)rv); +#endif /* WIN32 */ + + free(buf); + + abort(); +} diff --git a/lib/nghttp3_unreachable.h b/lib/nghttp3_unreachable.h new file mode 100644 index 0000000..c3520a4 --- /dev/null +++ b/lib/nghttp3_unreachable.h @@ -0,0 +1,47 @@ +/* + * nghttp3 + * + * Copyright (c) 2022 nghttp3 contributors + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_UNREACHABLE_H +#define NGHTTP3_UNREACHABLE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#define nghttp3_unreachable() \ + nghttp3_unreachable_fail(__FILE__, __LINE__, __func__) + +#ifdef _MSC_VER +__declspec(noreturn) +#endif /* _MSC_VER */ + void nghttp3_unreachable_fail(const char *file, int line, const char *func) +#ifndef _MSC_VER + __attribute__((noreturn)) +#endif /* !_MSC_VER */ + ; + +#endif /* NGHTTP3_UNREACHABLE_H */ diff --git a/lib/nghttp3_vec.c b/lib/nghttp3_vec.c new file mode 100644 index 0000000..ab58ff5 --- /dev/null +++ b/lib/nghttp3_vec.c @@ -0,0 +1,55 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp3_vec.h" +#include "nghttp3_macro.h" + +uint64_t nghttp3_vec_len(const nghttp3_vec *vec, size_t n) { + size_t i; + uint64_t res = 0; + + for (i = 0; i < n; ++i) { + res += vec[i].len; + } + + return res; +} + +int64_t nghttp3_vec_len_varint(const nghttp3_vec *vec, size_t n) { + uint64_t res = 0; + size_t len; + size_t i; + + for (i = 0; i < n; ++i) { + len = vec[i].len; + if (len > NGHTTP3_MAX_VARINT - res) { + return -1; + } + + res += len; + } + + return (int64_t)res; +} diff --git a/lib/nghttp3_vec.h b/lib/nghttp3_vec.h new file mode 100644 index 0000000..473d146 --- /dev/null +++ b/lib/nghttp3_vec.h @@ -0,0 +1,41 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP3_VEC_H +#define NGHTTP3_VEC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +/* + * nghttp3_vec_len_varint is similar to nghttp3_vec_len, but it + * returns -1 if the sum of the length exceeds NGHTTP3_MAX_VARINT. + */ +int64_t nghttp3_vec_len_varint(const nghttp3_vec *vec, size_t n); + +#endif /* NGHTTP3_VEC_H */ diff --git a/lib/nghttp3_version.c b/lib/nghttp3_version.c new file mode 100644 index 0000000..c460cc7 --- /dev/null +++ b/lib/nghttp3_version.c @@ -0,0 +1,39 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +static nghttp3_info version = {NGHTTP3_VERSION_AGE, NGHTTP3_VERSION_NUM, + NGHTTP3_VERSION}; + +const nghttp3_info *nghttp3_version(int least_version) { + if (least_version > NGHTTP3_VERSION_NUM) { + return NULL; + } + return &version; +} diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 0000000..ca36397 --- /dev/null +++ b/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 <guidod@gmx.de> +# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com> +# +# 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 <http://www.gnu.org/licenses/>. +# +# 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/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 0000000..9e9eaed --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,948 @@ +# =========================================================================== +# 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' (for the C++11 standard) +# or '14' (for the C++14 standard). +# +# 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 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 <bkoz@redhat.com> +# Copyright (c) 2012 Zack Weinberg <zackw@panix.com> +# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu> +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com> +# Copyright (c) 2015 Paul Norman <penorman@mac.com> +# Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu> +# Copyright (c) 2016, 2018 Krzesimir Nowak <qdlacz@gmail.com> +# +# 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 10 + +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"], + [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], [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" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + 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 + 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 +) + +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 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" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual void f() {} + }; + + struct Derived : public Base + { + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check<void> single_type; + typedef check<check<void>> double_type; + typedef check<check<check<void>>> triple_type; + typedef check<check<check<check<void>>>> 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<T, T> + { + 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<int, decltype(0)>::value == true, ""); + static_assert(is_same<int, decltype(c)>::value == false, ""); + static_assert(is_same<int, decltype(v)>::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same<int, decltype(ac)>::value == true, ""); + static_assert(is_same<int, decltype(av)>::value == true, ""); + static_assert(is_same<int, decltype(sumi)>::value == true, ""); + static_assert(is_same<int, decltype(sumf)>::value == false, ""); + static_assert(is_same<int, decltype(add(c, v))>::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 <int...> + struct sum; + + template <int N0, int... N1toN> + struct sum<N0, N1toN...> + { + static constexpr auto value = N0 + sum<N1toN...>::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<typename T> + using member = typename T::member_type; + + template<typename T> + void func(...) {} + + template<typename T> + void func(member<T>*) {} + + void test(); + + void test() { func<foo>(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 + +#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<T, T> + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same<int, decltype(f(x))>::value, ""); + static_assert(is_same<int&, decltype(g(x))>::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 + +#error "This is not a C++17 compiler" + +#else + +#include <initializer_list> +#include <utility> +#include <type_traits> + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template<typename... Args> + int multiply(Args... args) + { + return (args * ... * 1); + } + + template<typename... Args> + 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<std::initializer_list<int>, decltype(foo)>::value); + static_assert(std::is_same<int, decltype(bar)>::value); + } + + namespace test_typename_in_template_template_parameter + { + + template<template<typename> 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 <bool cond> + 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 <typename T1, typename T2> + 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 <auto n> + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair<int, int> pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair<int, int>& + { + 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<typename T> + Bad + f(T*, T*); + + template<typename T1, typename T2> + Good + f(T1*, T2*); + + static_assert (std::is_same_v<Good, decltype(f(g1, g2))>); + + } + + namespace test_inline_variables + { + + template<class T> void f(T) + {} + + template<class T> inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L + +]]) diff --git a/mkhufftbl.py b/mkhufftbl.py new file mode 100755 index 0000000..3c30188 --- /dev/null +++ b/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/nghttp3_qpack_huffman.h and lib/nghttp3_qpack_huffman_data.c +# +# [1] http://http2.github.io/http2-spec/compression.html + +import re +import sys +import io + +# 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) + +NGHTTP3_QPACK_HUFFMAN_ACCEPTED = 1 << 14 +NGHTTP3_QPACK_HUFFMAN_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 |= NGHTTP3_QPACK_HUFFMAN_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 |= NGHTTP3_QPACK_HUFFMAN_ACCEPTED + elif nd.accept: + flags |= NGHTTP3_QPACK_HUFFMAN_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 io.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; +} nghttp3_qpack_huffman_sym; +''') + + print('''\ +const nghttp3_qpack_huffman_sym huffman_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 {{ + NGHTTP3_QPACK_HUFFMAN_ACCEPTED = {}, + NGHTTP3_QPACK_HUFFMAN_SYM = {}, +}} nghttp3_qpack_huffman_decode_flag; +'''.format(NGHTTP3_QPACK_HUFFMAN_ACCEPTED, NGHTTP3_QPACK_HUFFMAN_SYM)) + + print('''\ +typedef struct { + uint16_t fstate; + uint8_t sym; +} nghttp3_qpack_huffman_decode_node; +''') + + print('''\ +const nghttp3_qpack_huffman_decode_node qpack_huffman_decode_table[][16] = {''') + huffman_tree_print_transition_table(ctx) + print('};') diff --git a/mkstatichdtbl.py b/mkstatichdtbl.py new file mode 100755 index 0000000..feacd2a --- /dev/null +++ b/mkstatichdtbl.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# This scripts reads static table entries [1] and generates +# token_stable and stable. This table is used in lib/nghttp3_qpack.c. +# +# [1] https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#name-static-table-2 + +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 + +class Header: + def __init__(self, idx, name, value): + self.idx = idx + self.name = name + self.value = value + self.token = -1 + +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(Header(int(m.group(1)), m.group(2), val)) + +token_entries = sorted(entries, key=lambda ent: ent.name) + +token = 0 +seq = 0 +name = token_entries[0].name +for i, ent in enumerate(token_entries): + if name != ent.name: + name = ent.name + token = seq + seq += 1 + ent.token = token + +def to_enum_hd(k): + res = 'NGHTTP3_QPACK_TOKEN_' + for c in k.upper(): + if c == ':' or c == '-': + res += '_' + continue + res += c + return res + +def gen_enum(entries): + used = {} + print('typedef enum nghttp3_qpack_token {') + for ent in entries: + if ent.name in used: + continue + used[ent.name] = True + enumname = to_enum_hd(ent.name) + print('''\ + /** + * :enum:`{enumname}` is a token for ``{name}``. + */'''.format(enumname=enumname, name=ent.name)) + if ent.token is None: + print(' {},'.format(enumname)) + else: + print(' {} = {},'.format(enumname, ent.token)) + print('} nghttp3_qpack_token;') + +gen_enum(entries) + +print() + +print('static nghttp3_qpack_static_entry token_stable[] = {') +for i, ent in enumerate(token_entries): + print('MAKE_STATIC_ENT({}, {}, {}u),'\ + .format(ent.idx, to_enum_hd(ent.name), hd_map_hash(ent.name))) +print('};') + +print() + +print('static nghttp3_qpack_static_header stable[] = {') +for ent in entries: + print('MAKE_STATIC_HD("{}", "{}", {}),'\ + .format(ent.name, ent.value, to_enum_hd(ent.name))) +print('};') diff --git a/qifs-check.sh b/qifs-check.sh new file mode 100755 index 0000000..c343a29 --- /dev/null +++ b/qifs-check.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +for f in qifs/encoded/qpack-06/*/*; do + echo $f + + name=`basename "$f"` + IFS='.' read -ra params <<< "$name" + [ "${params[1]}" = "out" ] || continue + prefix=${params[0]} + maxtablesize=${params[2]} + maxblocked=${params[3]} + immediateack=${params[4]} + + opts="-s$maxtablesize -m$maxblocked" + if [ "$immediateack" = "1" ]; then + opts="$opts -a" + fi + + examples/qpack decode "$f" qpack-check.out $opts + qifs/bin/sort-qif.pl --strip-comments qpack-check.out > qpack-check-canonical.out + diff -u qpack-check-canonical.out "qifs/qifs/$prefix.qif" +done @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +destdir=qifs/encoded/qpack-05/nghttp3 +mkdir -p "$destdir" + +for f in qifs/qifs/*.qif; do + prefix=`basename ${f%.qif}` + + for maxtablesize in 0 256 512 4096; do + for maxblocked in 0 100; do + echo $f $maxtablesize $maxblocked 0 + outprefix=$prefix.out.$maxtablesize.$maxblocked + examples/qpack encode "$f" "$destdir/$outprefix.0" -s$maxtablesize -m$maxblocked + echo $f $maxtablesize $maxblocked 1 + examples/qpack encode "$f" "$destdir/$outprefix.1" -s$maxtablesize -m$maxblocked -a + done + done +done diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..3e57a18 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +/main
\ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..003706b --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,57 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2016 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. + +if(HAVE_CUNIT) + include_directories( + "${CMAKE_SOURCE_DIR}/lib" + "${CMAKE_SOURCE_DIR}/lib/includes" + "${CMAKE_BINARY_DIR}/lib/includes" + ${CUNIT_INCLUDE_DIRS} + ) + + set(main_SOURCES + main.c + nghttp3_qpack_test.c + nghttp3_conn_test.c + nghttp3_tnode_test.c + nghttp3_http_test.c + nghttp3_conv_test.c + nghttp3_test_helper.c + ) + + add_executable(main EXCLUDE_FROM_ALL + ${main_SOURCES} + ) + target_include_directories(main PRIVATE ${CUNIT_INCLUDE_DIRS}) + # FIXME enable and fix warnings + #set_target_properties(main PROPERTIES COMPILE_FLAGS "${WARNCFLAGS}") + target_link_libraries(main + nghttp3_static + ${CUNIT_LIBRARIES} + m + ) + add_test(main main) + add_dependencies(check main) +endif() diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..aa3566c --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,65 @@ +# nghttp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2016 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. +EXTRA_DIST = CMakeLists.txt + +if HAVE_CUNIT + +check_PROGRAMS = main + +OBJECTS = \ + main.c \ + nghttp3_qpack_test.c \ + nghttp3_conn_test.c \ + nghttp3_tnode_test.c \ + nghttp3_http_test.c \ + nghttp3_conv_test.c \ + nghttp3_test_helper.c +HFILES = \ + nghttp3_qpack_test.h \ + nghttp3_conn_test.h \ + nghttp3_tnode_test.h \ + nghttp3_http_test.h \ + nghttp3_conv_test.h \ + nghttp3_test_helper.h + +main_SOURCES = $(HFILES) $(OBJECTS) + +# 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 +main_LDADD += @CUNIT_LIBS@ -lm +main_LDFLAGS = -static + +AM_CFLAGS = $(WARNCFLAGS) \ + -I${top_srcdir}/lib \ + -I${top_srcdir}/lib/includes \ + -I${top_builddir}/lib/includes \ + -DBUILDING_NGHTTP3 \ + @CUNIT_CFLAGS@ @DEFS@ +AM_LDFLAGS = -no-install + +TESTS = main + +endif # HAVE_CUNIT diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..201c1bc --- /dev/null +++ b/tests/main.c @@ -0,0 +1,146 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2016 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. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdio.h> +#include <string.h> +#include <CUnit/Basic.h> +/* include test cases' include files here */ +#include "nghttp3_qpack_test.h" +#include "nghttp3_conn_test.h" +#include "nghttp3_tnode_test.h" +#include "nghttp3_http_test.h" +#include "nghttp3_conv_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("libnghttp3_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, "qpack_encoder_encode", + test_nghttp3_qpack_encoder_encode) || + !CU_add_test(pSuite, "qpack_encoder_still_blocked", + test_nghttp3_qpack_encoder_still_blocked) || + !CU_add_test(pSuite, "qpack_encoder_set_dtable_cap", + test_nghttp3_qpack_encoder_set_dtable_cap) || + !CU_add_test(pSuite, "qpack_decoder_feedback", + test_nghttp3_qpack_decoder_feedback) || + !CU_add_test(pSuite, "qpack_decoder_stream_overflow", + test_nghttp3_qpack_decoder_stream_overflow) || + !CU_add_test(pSuite, "qpack_huffman", test_nghttp3_qpack_huffman) || + !CU_add_test(pSuite, "qpack_huffman_decode_failure_state", + test_nghttp3_qpack_huffman_decode_failure_state) || + !CU_add_test(pSuite, "qpack_decoder_reconstruct_ricnt", + test_nghttp3_qpack_decoder_reconstruct_ricnt) || + !CU_add_test(pSuite, "conn_read_control", + test_nghttp3_conn_read_control) || + !CU_add_test(pSuite, "conn_write_control", + test_nghttp3_conn_write_control) || + !CU_add_test(pSuite, "conn_submit_request", + test_nghttp3_conn_submit_request) || + !CU_add_test(pSuite, "conn_http_request", + test_nghttp3_conn_http_request) || + !CU_add_test(pSuite, "conn_http_resp_header", + test_nghttp3_conn_http_resp_header) || + !CU_add_test(pSuite, "conn_http_req_header", + test_nghttp3_conn_http_req_header) || + !CU_add_test(pSuite, "conn_http_content_length", + test_nghttp3_conn_http_content_length) || + !CU_add_test(pSuite, "conn_http_content_length_mismatch", + test_nghttp3_conn_http_content_length_mismatch) || + !CU_add_test(pSuite, "conn_http_non_final_response", + test_nghttp3_conn_http_non_final_response) || + !CU_add_test(pSuite, "conn_http_trailers", + test_nghttp3_conn_http_trailers) || + !CU_add_test(pSuite, "conn_http_ignore_content_length", + test_nghttp3_conn_http_ignore_content_length) || + !CU_add_test(pSuite, "conn_http_record_request_method", + test_nghttp3_conn_http_record_request_method) || + !CU_add_test(pSuite, "conn_http_error", test_nghttp3_conn_http_error) || + !CU_add_test(pSuite, "conn_qpack_blocked_stream", + test_nghttp3_conn_qpack_blocked_stream) || + !CU_add_test(pSuite, "conn_submit_response_read_blocked", + test_nghttp3_conn_submit_response_read_blocked) || + !CU_add_test(pSuite, "conn_just_fin", test_nghttp3_conn_just_fin) || + !CU_add_test(pSuite, "conn_recv_uni", test_nghttp3_conn_recv_uni) || + !CU_add_test(pSuite, "conn_recv_goaway", test_nghttp3_conn_recv_goaway) || + !CU_add_test(pSuite, "conn_shutdown_server", + test_nghttp3_conn_shutdown_server) || + !CU_add_test(pSuite, "conn_shutdown_client", + test_nghttp3_conn_shutdown_client) || + !CU_add_test(pSuite, "conn_priority_update", + test_nghttp3_conn_priority_update) || + !CU_add_test(pSuite, "conn_request_priority", + test_nghttp3_conn_request_priority) || + !CU_add_test(pSuite, "conn_set_stream_priority", + test_nghttp3_conn_set_stream_priority) || + !CU_add_test(pSuite, "conn_shutdown_stream_read", + test_nghttp3_conn_shutdown_stream_read) || + !CU_add_test(pSuite, "conn_stream_data_overflow", + test_nghttp3_conn_stream_data_overflow) || + !CU_add_test(pSuite, "tnode_schedule", test_nghttp3_tnode_schedule) || + !CU_add_test(pSuite, "http_parse_priority", + test_nghttp3_http_parse_priority) || + !CU_add_test(pSuite, "sf_parse_item", test_nghttp3_sf_parse_item) || + !CU_add_test(pSuite, "sf_parse_inner_list", + test_nghttp3_sf_parse_inner_list) || + !CU_add_test(pSuite, "check_header_value", + test_nghttp3_check_header_value) || + !CU_add_test(pSuite, "pri_to_uint8", test_nghttp3_pri_to_uint8)) { + 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/tests/nghttp3_conn_test.c b/tests/nghttp3_conn_test.c new file mode 100644 index 0000000..b60220a --- /dev/null +++ b/tests/nghttp3_conn_test.c @@ -0,0 +1,3448 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "nghttp3_conn_test.h" + +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "nghttp3_conn.h" +#include "nghttp3_macro.h" +#include "nghttp3_conv.h" +#include "nghttp3_frame.h" +#include "nghttp3_vec.h" +#include "nghttp3_test_helper.h" +#include "nghttp3_http.h" + +static uint8_t nulldata[4096]; + +typedef struct { + struct { + size_t nblock; + size_t left; + size_t step; + } data; + struct { + uint64_t acc; + } ack; + struct { + size_t ncalled; + int64_t stream_id; + uint64_t app_error_code; + } stop_sending_cb; + struct { + size_t ncalled; + int64_t stream_id; + uint64_t app_error_code; + } reset_stream_cb; + struct { + size_t ncalled; + int64_t id; + } shutdown_cb; + struct { + uint64_t consumed_total; + } deferred_consume_cb; +} userdata; + +static int acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + + (void)conn; + (void)stream_id; + (void)stream_user_data; + + ud->ack.acc += datalen; + + return 0; +} + +static int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + (void)conn; + (void)stream_id; + (void)stream_user_data; + (void)user_data; + return 0; +} + +static 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) { + (void)conn; + (void)stream_id; + (void)token; + (void)name; + (void)value; + (void)flags; + (void)stream_user_data; + (void)user_data; + return 0; +} + +static int end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, + void *user_data, void *stream_user_data) { + (void)conn; + (void)stream_id; + (void)fin; + (void)stream_user_data; + (void)user_data; + return 0; +} + +static nghttp3_ssize step_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) { + userdata *ud = user_data; + size_t n = nghttp3_min(ud->data.left, ud->data.step); + + (void)conn; + (void)stream_id; + (void)veccnt; + (void)stream_user_data; + + ud->data.left -= n; + if (ud->data.left == 0) { + *pflags = NGHTTP3_DATA_FLAG_EOF; + + if (n == 0) { + return 0; + } + } + + vec[0].base = nulldata; + vec[0].len = n; + + return 1; +} + +static nghttp3_ssize +block_then_step_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) { + userdata *ud = user_data; + + if (ud->data.nblock == 0) { + return step_read_data(conn, stream_id, vec, veccnt, pflags, user_data, + stream_user_data); + } + + --ud->data.nblock; + + return NGHTTP3_ERR_WOULDBLOCK; +} + +static nghttp3_ssize +step_then_block_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) { + nghttp3_ssize rv; + + rv = step_read_data(conn, stream_id, vec, veccnt, pflags, user_data, + stream_user_data); + + assert(rv >= 0); + + if (*pflags & NGHTTP3_DATA_FLAG_EOF) { + *pflags &= (uint32_t)~NGHTTP3_DATA_FLAG_EOF; + + if (nghttp3_vec_len(vec, (size_t)rv) == 0) { + return NGHTTP3_ERR_WOULDBLOCK; + } + } + + return rv; +} + +#if SIZE_MAX > UINT32_MAX +static nghttp3_ssize stream_data_overflow_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) { + (void)conn; + (void)stream_id; + (void)veccnt; + (void)pflags; + (void)user_data; + (void)stream_user_data; + + vec[0].base = nulldata; + vec[0].len = NGHTTP3_MAX_VARINT + 1; + + return 1; +} + +static nghttp3_ssize stream_data_almost_overflow_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) { + (void)conn; + (void)stream_id; + (void)veccnt; + (void)pflags; + (void)user_data; + (void)stream_user_data; + + vec[0].base = nulldata; + vec[0].len = NGHTTP3_MAX_VARINT; + + return 1; +} +#endif /* SIZE_MAX > UINT32_MAX */ + +static int stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + + (void)conn; + (void)stream_user_data; + + ++ud->stop_sending_cb.ncalled; + ud->stop_sending_cb.stream_id = stream_id; + ud->stop_sending_cb.app_error_code = app_error_code; + + return 0; +} + +static int reset_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + + (void)conn; + (void)stream_user_data; + + ++ud->reset_stream_cb.ncalled; + ud->reset_stream_cb.stream_id = stream_id; + ud->reset_stream_cb.app_error_code = app_error_code; + + return 0; +} + +static int conn_shutdown(nghttp3_conn *conn, int64_t id, void *user_data) { + userdata *ud = user_data; + + (void)conn; + + ++ud->shutdown_cb.ncalled; + ud->shutdown_cb.id = id; + + return 0; +} + +static int deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t consumed, void *user_data, + void *stream_user_data) { + userdata *ud = user_data; + + (void)conn; + (void)stream_user_data; + (void)stream_id; + + ud->deferred_consume_cb.consumed_total += consumed; + + return 0; +} + +void test_nghttp3_conn_read_control(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + int rv; + uint8_t rawbuf[2048]; + nghttp3_buf buf; + struct { + nghttp3_frame_settings settings; + nghttp3_settings_entry iv[15]; + } fr; + nghttp3_ssize nconsumed; + nghttp3_settings_entry *iv; + size_t i; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_MAX_FIELD_SECTION_SIZE; + iv[0].value = 65536; + iv[1].id = 1000000009; + iv[1].value = 1000000007; + iv[2].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[2].value = 4096; + iv[3].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[3].value = 99; + fr.settings.niv = 4; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(nconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + CU_ASSERT(65536 == conn->remote.settings.max_field_section_size); + CU_ASSERT(4096 == conn->remote.settings.qpack_max_dtable_capacity); + CU_ASSERT(99 == conn->remote.settings.qpack_blocked_streams); + CU_ASSERT(4096 == conn->qenc.ctx.hard_max_dtable_capacity); + CU_ASSERT(4096 == conn->qenc.ctx.max_dtable_capacity); + CU_ASSERT(99 == conn->qenc.ctx.max_blocked_streams); + + nghttp3_conn_del(conn); + + /* Feed 1 byte at a time to verify that state machine works */ + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + for (i = 0; i < nghttp3_buf_len(&buf); ++i) { + nconsumed = + nghttp3_conn_read_stream(conn, 2, buf.pos + i, 1, /* fin = */ 0); + + CU_ASSERT(1 == nconsumed); + } + + CU_ASSERT(65536 == conn->remote.settings.max_field_section_size); + CU_ASSERT(4096 == conn->remote.settings.qpack_max_dtable_capacity); + CU_ASSERT(99 == conn->remote.settings.qpack_blocked_streams); + + nghttp3_conn_del(conn); + + /* Receiver should enforce its own limits for QPACK parameters. */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[0].value = 4097; + iv[1].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[1].value = 101; + fr.settings.niv = 2; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(nconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + CU_ASSERT(4097 == conn->remote.settings.qpack_max_dtable_capacity); + CU_ASSERT(101 == conn->remote.settings.qpack_blocked_streams); + CU_ASSERT(4096 == conn->qenc.ctx.hard_max_dtable_capacity); + CU_ASSERT(4096 == conn->qenc.ctx.max_dtable_capacity); + CU_ASSERT(100 == conn->qenc.ctx.max_blocked_streams); + + nghttp3_conn_del(conn); + + /* Receiving multiple nonzero SETTINGS_QPACK_MAX_TABLE_CAPACITY is + treated as error. */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[0].value = 4097; + iv[1].id = NGHTTP3_SETTINGS_ID_QPACK_MAX_TABLE_CAPACITY; + iv[1].value = 4097; + fr.settings.niv = 2; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_SETTINGS_ERROR == nconsumed); + + nghttp3_conn_del(conn); + + /* Receiving multiple nonzero SETTINGS_QPACK_BLOCKED_STREAMS is + treated as error. */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[0].value = 1; + iv[1].id = NGHTTP3_SETTINGS_ID_QPACK_BLOCKED_STREAMS; + iv[1].value = 1; + fr.settings.niv = 2; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_SETTINGS_ERROR == nconsumed); + + nghttp3_conn_del(conn); + + /* Receive ENABLE_CONNECT_PROTOCOL = 1 */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL; + iv[0].value = 1; + fr.settings.niv = 1; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(1 == conn->remote.settings.enable_connect_protocol); + + nghttp3_conn_del(conn); + + /* Receiving ENABLE_CONNECT_PROTOCOL = 0 after seeing + ENABLE_CONNECT_PROTOCOL = 1 */ + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.settings.hd.type = NGHTTP3_FRAME_SETTINGS; + iv = fr.settings.iv; + iv[0].id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL; + iv[0].value = 1; + iv[1].id = NGHTTP3_SETTINGS_ID_ENABLE_CONNECT_PROTOCOL; + iv[1].value = 0; + fr.settings.niv = 2; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_SETTINGS_ERROR == nconsumed); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_write_control(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + int rv; + int64_t stream_id; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + rv = nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_bind_control_stream(conn, 3); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->tx.ctrl); + CU_ASSERT(NGHTTP3_STREAM_TYPE_CONTROL == conn->tx.ctrl->type); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(3 == stream_id); + CU_ASSERT(1 == sveccnt); + CU_ASSERT(vec[0].len > 1); + CU_ASSERT(NGHTTP3_STREAM_TYPE_CONTROL == vec[0].base[0]); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_submit_request(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + int rv; + int64_t stream_id; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + uint64_t len; + size_t i; + nghttp3_stream *stream; + userdata ud; + nghttp3_data_reader dr; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + memset(&ud, 0, sizeof(ud)); + nghttp3_settings_default(&settings); + + callbacks.acked_stream_data = acked_stream_data; + + ud.data.left = 2000; + ud.data.step = 1200; + + rv = nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->tx.qenc); + CU_ASSERT(NGHTTP3_STREAM_TYPE_QPACK_ENCODER == conn->tx.qenc->type); + CU_ASSERT(NULL != conn->tx.qdec); + CU_ASSERT(NGHTTP3_STREAM_TYPE_QPACK_DECODER == conn->tx.qdec->type); + + dr.read_data = step_read_data; + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + /* This will write QPACK decoder stream; just stream type */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(10 == stream_id); + CU_ASSERT(1 == sveccnt); + CU_ASSERT(1 == nghttp3_ringbuf_len(&conn->tx.qdec->outq)); + CU_ASSERT(0 == conn->tx.qdec->outq_idx); + CU_ASSERT(0 == conn->tx.qdec->outq_offset); + + /* Calling twice will return the same result */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(10 == stream_id); + CU_ASSERT(1 == sveccnt); + + rv = nghttp3_conn_add_write_offset(conn, 10, vec[0].len); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp3_ringbuf_len(&conn->tx.qdec->outq)); + CU_ASSERT(1 == conn->tx.qdec->outq_idx); + CU_ASSERT(0 == conn->tx.qdec->outq_offset); + + rv = nghttp3_conn_add_ack_offset(conn, 10, vec[0].len); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == nghttp3_ringbuf_len(&conn->tx.qdec->outq)); + CU_ASSERT(0 == conn->tx.qdec->outq_idx); + CU_ASSERT(0 == conn->tx.qdec->outq_offset); + CU_ASSERT(0 == conn->tx.qdec->ack_offset); + + /* This will write QPACK encoder stream; just stream type */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(6 == stream_id); + CU_ASSERT(1 == sveccnt); + + rv = nghttp3_conn_add_write_offset(conn, 6, vec[0].len); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_add_ack_offset(conn, 6, vec[0].len); + + CU_ASSERT(0 == rv); + + /* This will write request stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(0 == stream_id); + CU_ASSERT(2 == sveccnt); + + len = nghttp3_vec_len(vec, (size_t)sveccnt); + for (i = 0; i < len; ++i) { + rv = nghttp3_conn_add_write_offset(conn, 0, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_add_ack_offset(conn, 0, 1); + + CU_ASSERT(0 == rv); + } + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(0 == stream_id); + CU_ASSERT(2 == sveccnt); + + len = nghttp3_vec_len(vec, (size_t)sveccnt); + + for (i = 0; i < len; ++i) { + rv = nghttp3_conn_add_write_offset(conn, 0, 1); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_add_ack_offset(conn, 0, 1); + + CU_ASSERT(0 == rv); + } + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(0 == nghttp3_ringbuf_len(&stream->outq)); + CU_ASSERT(0 == nghttp3_ringbuf_len(&stream->chunks)); + CU_ASSERT(0 == stream->outq_idx); + CU_ASSERT(0 == stream->outq_offset); + CU_ASSERT(0 == stream->ack_offset); + CU_ASSERT(2000 == ud.ack.acc); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_http_request(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *cl, *sv; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + nghttp3_ssize sconsumed; + int rv; + int64_t stream_id; + const nghttp3_nv reqnva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + const nghttp3_nv respnva[] = { + MAKE_NV(":status", "200"), + MAKE_NV("server", "nghttp3"), + MAKE_NV("content-length", "1999"), + }; + nghttp3_data_reader dr; + int fin; + userdata clud, svud; + size_t i; + size_t nconsumed; + size_t nread; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + memset(&clud, 0, sizeof(clud)); + + callbacks.begin_headers = begin_headers; + callbacks.recv_header = recv_header; + callbacks.end_headers = end_headers; + + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + clud.data.left = 2000; + clud.data.step = 1200; + + svud.data.left = 1999; + svud.data.step = 1000; + + nghttp3_conn_client_new(&cl, &callbacks, &settings, mem, &clud); + nghttp3_conn_server_new(&sv, &callbacks, &settings, mem, &svud); + + nghttp3_conn_bind_control_stream(cl, 2); + nghttp3_conn_bind_control_stream(sv, 3); + + nghttp3_conn_bind_qpack_streams(cl, 6, 10); + nghttp3_conn_bind_qpack_streams(sv, 7, 11); + + dr.read_data = step_read_data; + rv = nghttp3_conn_submit_request(cl, 0, reqnva, nghttp3_arraylen(reqnva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + nread = 0; + nconsumed = 0; + + for (;;) { + sveccnt = nghttp3_conn_writev_stream(cl, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt >= 0); + + if (sveccnt <= 0) { + break; + } + + rv = nghttp3_conn_add_write_offset( + cl, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + for (i = 0; i < (size_t)sveccnt; ++i) { + sconsumed = + nghttp3_conn_read_stream(sv, stream_id, vec[i].base, vec[i].len, + fin && i == (size_t)sveccnt - 1); + CU_ASSERT(sconsumed >= 0); + + nread += vec[i].len; + nconsumed += (size_t)sconsumed; + } + + rv = nghttp3_conn_add_ack_offset(cl, stream_id, + nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + } + + CU_ASSERT(nread == nconsumed + 2000); + + rv = nghttp3_conn_submit_response(sv, 0, respnva, nghttp3_arraylen(respnva), + &dr); + + CU_ASSERT(0 == rv); + + nread = 0; + nconsumed = 0; + + for (;;) { + sveccnt = nghttp3_conn_writev_stream(sv, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt >= 0); + + if (sveccnt <= 0) { + break; + } + + rv = nghttp3_conn_add_write_offset( + sv, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + for (i = 0; i < (size_t)sveccnt; ++i) { + sconsumed = + nghttp3_conn_read_stream(cl, stream_id, vec[i].base, vec[i].len, + fin && i == (size_t)sveccnt - 1); + CU_ASSERT(sconsumed >= 0); + + nread += vec[i].len; + nconsumed += (size_t)sconsumed; + } + + rv = nghttp3_conn_add_ack_offset(sv, stream_id, + nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + } + + CU_ASSERT(nread == nconsumed + 1999); + + nghttp3_conn_del(sv); + nghttp3_conn_del(cl); +} + +static void check_http_header(const nghttp3_nv *nva, size_t nvlen, int request, + int want_lib_error) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + settings.enable_connect_protocol = 1; + + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)nva; + fr.nvlen = nvlen; + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + if (request) { + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + } else { + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + } + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + if (want_lib_error) { + if (want_lib_error == NGHTTP3_ERR_MALFORMED_HTTP_HEADER) { + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + } else { + CU_ASSERT(want_lib_error == sconsumed); + } + } else { + CU_ASSERT(sconsumed > 0); + } + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +static void check_http_resp_header(const nghttp3_nv *nva, size_t nvlen, + int want_lib_error) { + check_http_header(nva, nvlen, /* request = */ 0, want_lib_error); +} + +static void check_http_req_header(const nghttp3_nv *nva, size_t nvlen, + int want_lib_error) { + check_http_header(nva, nvlen, /* request = */ 1, want_lib_error); +} + +void test_nghttp3_conn_http_resp_header(void) { + /* test case for response */ + /* response header lacks :status */ + const nghttp3_nv nostatus_resnv[] = { + MAKE_NV("server", "foo"), + }; + /* response header has 2 :status */ + const nghttp3_nv dupstatus_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV(":status", "200"), + }; + /* response header has bad pseudo header :scheme */ + const nghttp3_nv badpseudo_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV(":scheme", "https"), + }; + /* response header has :status after regular header field */ + const nghttp3_nv latepseudo_resnv[] = { + MAKE_NV("server", "foo"), + MAKE_NV(":status", "200"), + }; + /* response header has bad status code */ + const nghttp3_nv badstatus_resnv[] = { + MAKE_NV(":status", "2000"), + }; + /* response header has bad content-length */ + const nghttp3_nv badcl_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "-1"), + }; + /* response header has multiple content-length */ + const nghttp3_nv dupcl_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0"), + MAKE_NV("content-length", "0"), + }; + /* response header has disallowed header field */ + const nghttp3_nv badhd_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("connection", "close"), + }; + /* response header has content-length with 100 status code */ + const nghttp3_nv cl1xx_resnv[] = { + MAKE_NV(":status", "100"), + MAKE_NV("content-length", "0"), + }; + /* response header has 0 content-length with 204 status code */ + const nghttp3_nv cl204_resnv[] = { + MAKE_NV(":status", "204"), + MAKE_NV("content-length", "0"), + }; + /* response header has nonzero content-length with 204 status + code */ + const nghttp3_nv clnonzero204_resnv[] = { + MAKE_NV(":status", "204"), + MAKE_NV("content-length", "100"), + }; + /* status code 101 should not be used in HTTP/3 because it is used + for HTTP Upgrade which HTTP/3 removes. */ + const nghttp3_nv status101_resnv[] = { + MAKE_NV(":status", "101"), + }; + + check_http_resp_header(nostatus_resnv, nghttp3_arraylen(nostatus_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(dupstatus_resnv, nghttp3_arraylen(dupstatus_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(badpseudo_resnv, nghttp3_arraylen(badpseudo_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(latepseudo_resnv, nghttp3_arraylen(latepseudo_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(badstatus_resnv, nghttp3_arraylen(badstatus_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(badcl_resnv, nghttp3_arraylen(badcl_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(dupcl_resnv, nghttp3_arraylen(dupcl_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(badhd_resnv, nghttp3_arraylen(badhd_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + /* Ignore content-length in 1xx response. */ + check_http_resp_header(cl1xx_resnv, nghttp3_arraylen(cl1xx_resnv), 0); + /* This is allowed to work with widely used services. */ + check_http_resp_header(cl204_resnv, nghttp3_arraylen(cl204_resnv), 0); + check_http_resp_header(clnonzero204_resnv, + nghttp3_arraylen(clnonzero204_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_resp_header(status101_resnv, nghttp3_arraylen(status101_resnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); +} + +void test_nghttp3_conn_http_req_header(void) { + /* test case for request */ + /* request header has no :path */ + const nghttp3_nv nopath_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + }; + /* request header has CONNECT method, but followed by :path */ + const nghttp3_nv earlyconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "localhost"), + }; + /* request header has CONNECT method following :path */ + const nghttp3_nv lateconnect_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), + }; + /* request header has multiple :path */ + const nghttp3_nv duppath_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":path", "/"), + MAKE_NV(":path", "/"), + }; + /* request header has bad content-length */ + const nghttp3_nv badcl_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "POST"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("content-length", "-1"), + }; + /* request header has multiple content-length */ + const nghttp3_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"), + }; + /* request header has disallowed header field */ + const nghttp3_nv badhd_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("connection", "close"), + }; + /* request header has :authority header field containing illegal + characters */ + const nghttp3_nv badauthority_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "\x0d\x0alocalhost"), + MAKE_NV(":path", "/"), + }; + /* request header has regular header field containing illegal + character before all mandatory header fields are seen. */ + const nghttp3_nv badhdbtw_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("foo", "\x0d\x0a"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":path", "/"), + }; + /* request header has "*" in :path header field while method is GET. + :path is received before :method */ + const nghttp3_nv asteriskget1_reqnv[] = { + MAKE_NV(":path", "*"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), + }; + /* request header has "*" in :path header field while method is GET. + :method is received before :path */ + const nghttp3_nv asteriskget2_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), + MAKE_NV(":path", "*"), + }; + /* OPTIONS method can include "*" in :path header field. :path is + received before :method. */ + const nghttp3_nv asteriskoptions1_reqnv[] = { + MAKE_NV(":path", "*"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "OPTIONS"), + }; + /* OPTIONS method can include "*" in :path header field. :method is + received before :path. */ + const nghttp3_nv asteriskoptions2_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "OPTIONS"), + MAKE_NV(":path", "*"), + }; + /* header name contains invalid character */ + const nghttp3_nv invalidname_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("\x0foo", "zzz"), + }; + /* header value contains invalid character */ + const nghttp3_nv invalidvalue_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), MAKE_NV(":path", "/"), + MAKE_NV("foo", "\x0zzz"), + }; + /* :protocol is not allowed unless it is enabled by the local + endpoint. */ + /* :protocol is allowed if SETTINGS_CONNECT_PROTOCOL is enabled by + the local endpoint. */ + const nghttp3_nv connectproto_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket"), + }; + /* :protocol is only allowed with CONNECT method. */ + const nghttp3_nv connectprotoget_reqnv[] = { + MAKE_NV(":scheme", "https"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket"), + }; + /* CONNECT method with :protocol requires :path. */ + const nghttp3_nv connectprotonopath_reqnv[] = { + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":protocol", "websocket"), + }; + /* CONNECT method with :protocol requires :authority. */ + const nghttp3_nv connectprotonoauth_reqnv[] = { + MAKE_NV(":scheme", "http"), MAKE_NV(":path", "/"), + MAKE_NV(":method", "CONNECT"), MAKE_NV("host", "localhost"), + MAKE_NV(":protocol", "websocket"), + }; + /* regular CONNECT method should succeed with + SETTINGS_CONNECT_PROTOCOL */ + const nghttp3_nv regularconnect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), + }; + + /* request header has no :path */ + check_http_req_header(nopath_reqnv, nghttp3_arraylen(nopath_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(earlyconnect_reqnv, + nghttp3_arraylen(earlyconnect_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(lateconnect_reqnv, nghttp3_arraylen(lateconnect_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(duppath_reqnv, nghttp3_arraylen(duppath_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(badcl_reqnv, nghttp3_arraylen(badcl_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(dupcl_reqnv, nghttp3_arraylen(dupcl_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(badhd_reqnv, nghttp3_arraylen(badhd_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(badauthority_reqnv, + nghttp3_arraylen(badauthority_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(badhdbtw_reqnv, nghttp3_arraylen(badhdbtw_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(asteriskget1_reqnv, + nghttp3_arraylen(asteriskget1_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(asteriskget2_reqnv, + nghttp3_arraylen(asteriskget2_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(asteriskoptions1_reqnv, + nghttp3_arraylen(asteriskoptions1_reqnv), 0); + check_http_req_header(asteriskoptions2_reqnv, + nghttp3_arraylen(asteriskoptions2_reqnv), 0); + check_http_req_header(invalidname_reqnv, nghttp3_arraylen(invalidname_reqnv), + 0); + check_http_req_header(invalidvalue_reqnv, + nghttp3_arraylen(invalidvalue_reqnv), 0); + check_http_req_header(connectproto_reqnv, + nghttp3_arraylen(connectproto_reqnv), 0); + check_http_req_header(connectprotoget_reqnv, + nghttp3_arraylen(connectprotoget_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(connectprotonopath_reqnv, + nghttp3_arraylen(connectprotonopath_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(connectprotonoauth_reqnv, + nghttp3_arraylen(connectprotonoauth_reqnv), + NGHTTP3_ERR_MALFORMED_HTTP_HEADER); + check_http_req_header(regularconnect_reqnv, + nghttp3_arraylen(regularconnect_reqnv), 0); +} + +void test_nghttp3_conn_http_content_length(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + nghttp3_stream *stream; + const nghttp3_nv 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"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("te", "trailers"), + MAKE_NV("content-length", "9000000000"), + }; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* client */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(9000000000LL == stream->rx.http.content_length); + CU_ASSERT(200 == stream->rx.http.status_code); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* server */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(9000000000LL == stream->rx.http.content_length); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_content_length_mismatch(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv reqnv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "PUT"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + MAKE_NV("content-length", "20"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "20"), + }; + int rv; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* content-length is 20, but no DATA is present and see fin */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* content-length is 20, but no DATA is present and stream is + reset */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + rv = nghttp3_conn_shutdown_stream_read(conn, 0); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* content-length is 20, but server receives 21 bytes DATA. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 21); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* Check client side as well */ + + /* content-length is 20, but no DATA is present and see fin */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* content-length is 20, but no DATA is present and stream is + reset */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + rv = nghttp3_conn_shutdown_stream_read(conn, 0); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* content-length is 20, but server receives 21 bytes DATA. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 21); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_non_final_response(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv infonv[] = { + MAKE_NV(":status", "103"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "204"), + }; + const nghttp3_nv trnv[] = { + MAKE_NV("my-status", "ok"), + }; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* non-final followed by DATA is illegal. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)infonv; + fr.nvlen = nghttp3_arraylen(infonv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 0); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* 2 non-finals followed by final headers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)infonv; + fr.nvlen = nghttp3_arraylen(infonv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* non-finals followed by trailers; this trailer is treated as + another non-final or final header fields. Since it does not + include mandatory header field, it is treated as error. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)infonv; + fr.nvlen = nghttp3_arraylen(infonv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_trailers(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv reqnv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "PUT"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + }; + const nghttp3_nv connect_reqnv[] = { + MAKE_NV(":method", "CONNECT"), + MAKE_NV(":authority", "localhost"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + }; + const nghttp3_nv trnv[] = { + MAKE_NV("foo", "bar"), + }; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* final response followed by trailers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* trailers contain :status */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* Receiving 2 trailers HEADERS is invalid*/ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_MALFORMED_HTTP_MESSAGING == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* We don't expect response trailers after HEADERS with CONNECT + request */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + nghttp3_http_record_request_method(stream, connect_reqnv, + nghttp3_arraylen(connect_reqnv)); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* We don't expect response trailers after DATA with CONNECT + request */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 99); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + nghttp3_http_record_request_method(stream, connect_reqnv, + nghttp3_arraylen(connect_reqnv)); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* request followed by trailers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* request followed by trailers which contains pseudo headers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* request followed by 2 trailers */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* We don't expect trailers after HEADERS with CONNECT request */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)connect_reqnv; + fr.nvlen = nghttp3_arraylen(connect_reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* We don't expect trailers after DATA with CONNECT request */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)connect_reqnv; + fr.nvlen = nghttp3_arraylen(connect_reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + nghttp3_write_frame_data(&buf, 11); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)trnv; + fr.nvlen = nghttp3_arraylen(trnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_FRAME_UNEXPECTED == sconsumed); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_ignore_content_length(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "CONNECT"), + MAKE_NV("content-length", "999999"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "304"), + MAKE_NV("content-length", "20"), + }; + const nghttp3_nv cl_resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "0"), + }; + int rv; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* If status code is 304, content-length must be ignored. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(0 == stream->rx.http.content_length); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* If method is CONNECT, content-length must be ignored. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)reqnv; + fr.nvlen = nghttp3_arraylen(reqnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(-1 == stream->rx.http.content_length); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* Content-Length in 200 response to CONNECT is ignored */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)cl_resnv; + fr.nvlen = nghttp3_arraylen(cl_resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + stream->rx.http.flags |= NGHTTP3_HTTP_FLAG_METH_CONNECT; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(-1 == stream->rx.http.content_length); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_record_request_method(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + const nghttp3_nv connect_reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "CONNECT"), + }; + const nghttp3_nv head_reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "HEAD"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":path", "/"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-length", "1000000007"), + }; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* content-length is not allowed with 200 status code in response to + CONNECT request. Just ignore it. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + nghttp3_http_record_request_method(stream, connect_reqnv, + nghttp3_arraylen(connect_reqnv)); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(-1 == stream->rx.http.content_length); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* The content-length in response to HEAD request must be + ignored. */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)resnv; + fr.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_create_stream(conn, &stream, 0); + stream->rx.hstate = NGHTTP3_HTTP_STATE_RESP_INITIAL; + nghttp3_http_record_request_method(stream, head_reqnv, + nghttp3_arraylen(head_reqnv)); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(0 == stream->rx.http.content_length); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_http_error(void) { + uint8_t rawbuf[4096]; + nghttp3_buf buf, ebuf; + nghttp3_frame_headers fr; + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_ssize sconsumed; + nghttp3_qpack_encoder qenc; + nghttp3_settings settings; + const nghttp3_nv dupschemenv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":scheme", "https"), + }; + const nghttp3_nv noschemenv[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":method", "GET"), + MAKE_NV(":authority", "localhost"), + }; + userdata ud; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.stop_sending = stop_sending; + callbacks.reset_stream = reset_stream; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + /* duplicated :scheme */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + memset(&ud, 0, sizeof(ud)); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)dupschemenv; + fr.nvlen = nghttp3_arraylen(dupschemenv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(0 == ud.stop_sending_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.stop_sending_cb.app_error_code); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + CU_ASSERT(0 == ud.reset_stream_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.reset_stream_cb.app_error_code); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + /* After the error, everything is just discarded. */ + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* without :scheme */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + memset(&ud, 0, sizeof(ud)); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)noschemenv; + fr.nvlen = nghttp3_arraylen(noschemenv); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(0 == ud.stop_sending_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.stop_sending_cb.app_error_code); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + CU_ASSERT(0 == ud.reset_stream_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.reset_stream_cb.app_error_code); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + + /* After the error, everything is just discarded. */ + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + + /* error on blocked stream */ + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + nghttp3_qpack_encoder_init(&qenc, settings.qpack_max_dtable_capacity, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&qenc, + settings.qpack_blocked_streams); + nghttp3_qpack_encoder_set_max_dtable_capacity( + &qenc, settings.qpack_max_dtable_capacity); + memset(&ud, 0, sizeof(ud)); + + nghttp3_buf_init(&ebuf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.nva = (nghttp3_nv *)noschemenv; + fr.nvlen = nghttp3_arraylen(noschemenv); + + nghttp3_write_frame_qpack_dyn(&buf, &ebuf, &qenc, 0, (nghttp3_frame *)&fr); + + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed > 0); + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) != sconsumed); + CU_ASSERT(0 == ud.stop_sending_cb.ncalled); + CU_ASSERT(0 == ud.reset_stream_cb.ncalled); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(!(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR)); + CU_ASSERT(0 != nghttp3_ringbuf_len(&stream->inq)); + + nghttp3_buf_reset(&buf); + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_QPACK_ENCODER); + + sconsumed = nghttp3_conn_read_stream(conn, 7, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + + sconsumed = nghttp3_conn_read_stream(conn, 7, ebuf.pos, + nghttp3_buf_len(&ebuf), /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&ebuf) == sconsumed); + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_HTTP_ERROR); + CU_ASSERT(0 == nghttp3_ringbuf_len(&stream->inq)); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(0 == ud.stop_sending_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.stop_sending_cb.app_error_code); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + CU_ASSERT(0 == ud.reset_stream_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_MESSAGE_ERROR == ud.reset_stream_cb.app_error_code); + + /* After the error, everything is just discarded. */ + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == sconsumed); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + + nghttp3_buf_free(&ebuf, mem); + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); +} + +void test_nghttp3_conn_qpack_blocked_stream(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_qpack_encoder qenc; + int rv; + nghttp3_buf ebuf; + uint8_t rawbuf[4096]; + nghttp3_buf buf; + const nghttp3_nv reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), + MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("server", "nghttp3"), + }; + nghttp3_frame fr; + nghttp3_ssize sconsumed; + nghttp3_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + /* The deletion of QPACK blocked stream is deferred to the moment + when it is unblocked */ + nghttp3_buf_init(&ebuf); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + nghttp3_qpack_encoder_init(&qenc, settings.qpack_max_dtable_capacity, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&qenc, + settings.qpack_blocked_streams); + nghttp3_qpack_encoder_set_max_dtable_capacity( + &qenc, settings.qpack_max_dtable_capacity); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_qpack_streams(conn, 2, 6); + + rv = nghttp3_conn_submit_request(conn, 0, reqnv, nghttp3_arraylen(reqnv), + NULL, NULL); + + CU_ASSERT(0 == rv); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)resnv; + fr.headers.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack_dyn(&buf, &ebuf, &qenc, 0, &fr); + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT(sconsumed > 0); + CU_ASSERT(sconsumed != (nghttp3_ssize)nghttp3_buf_len(&buf)); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_CLOSED); + + nghttp3_buf_reset(&buf); + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_QPACK_ENCODER); + + sconsumed = nghttp3_conn_read_stream(conn, 7, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 0)); + + sconsumed = nghttp3_conn_read_stream(conn, 7, ebuf.pos, + nghttp3_buf_len(&ebuf), /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + CU_ASSERT(NULL == nghttp3_conn_find_stream(conn, 0)); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + nghttp3_buf_free(&ebuf, mem); + + /* Stream that is blocked receives HEADERS which has empty + representation (that is only include Header Block Prefix) */ + nghttp3_buf_init(&ebuf); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + nghttp3_qpack_encoder_init(&qenc, settings.qpack_max_dtable_capacity, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&qenc, + settings.qpack_blocked_streams); + nghttp3_qpack_encoder_set_max_dtable_capacity( + &qenc, settings.qpack_max_dtable_capacity); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_qpack_streams(conn, 2, 6); + + rv = nghttp3_conn_submit_request(conn, 0, reqnv, nghttp3_arraylen(reqnv), + NULL, NULL); + + CU_ASSERT(0 == rv); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)resnv; + fr.headers.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack_dyn(&buf, &ebuf, &qenc, 0, &fr); + + assert(nghttp3_buf_len(&buf) > 4); + + /* Craft empty HEADERS (just leave Header Block Prefix) */ + buf.pos[1] = 2; + /* Write garbage to continue to read stream */ + buf.pos[4] = 0xff; + + sconsumed = nghttp3_conn_read_stream( + conn, 0, buf.pos, 5 /* Frame header + Header Block Prefix */, + /* fin = */ 1); + + CU_ASSERT(sconsumed > 0); + CU_ASSERT(sconsumed != (nghttp3_ssize)nghttp3_buf_len(&buf)); + + rv = nghttp3_conn_close_stream(conn, 0, NGHTTP3_H3_NO_ERROR); + + CU_ASSERT(0 == rv); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_CLOSED); + + nghttp3_buf_reset(&buf); + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_QPACK_ENCODER); + + sconsumed = nghttp3_conn_read_stream(conn, 7, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 0)); + + sconsumed = nghttp3_conn_read_stream(conn, 7, ebuf.pos, + nghttp3_buf_len(&ebuf), /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_QPACK_DECOMPRESSION_FAILED == sconsumed); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 0)); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + nghttp3_buf_free(&ebuf, mem); +} + +void test_nghttp3_conn_just_fin(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + int rv; + int64_t stream_id; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + nghttp3_data_reader dr; + int fin; + userdata ud; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + memset(&ud, 0, sizeof(ud)); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + /* Write control streams */ + for (;;) { + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt >= 0); + + if (sveccnt == 0) { + break; + } + + rv = nghttp3_conn_add_write_offset( + conn, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + } + + /* No DATA frame header */ + dr.read_data = step_read_data; + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(1 == sveccnt); + CU_ASSERT(0 == stream_id); + CU_ASSERT(1 == fin); + + rv = nghttp3_conn_add_write_offset( + conn, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + /* Just fin */ + ud.data.nblock = 1; + dr.read_data = block_then_step_read_data; + + rv = nghttp3_conn_submit_request(conn, 4, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(1 == sveccnt); + CU_ASSERT(4 == stream_id); + CU_ASSERT(0 == fin); + + rv = nghttp3_conn_add_write_offset( + conn, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + /* Resume stream 4 because it was blocked */ + nghttp3_conn_resume_stream(conn, 4); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(0 == sveccnt); + CU_ASSERT(4 == stream_id); + CU_ASSERT(1 == fin); + + rv = nghttp3_conn_add_write_offset( + conn, stream_id, (size_t)nghttp3_vec_len(vec, (size_t)sveccnt)); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(0 == sveccnt); + CU_ASSERT(-1 == stream_id); + CU_ASSERT(0 == fin); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_submit_response_read_blocked(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_nv nva[] = { + MAKE_NV(":status", "200"), + }; + nghttp3_stream *stream; + int rv; + nghttp3_vec vec[256]; + int fin; + int64_t stream_id; + nghttp3_ssize sveccnt; + nghttp3_data_reader dr = {step_then_block_read_data}; + userdata ud; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* Make sure that flushing serialized data while + NGHTTP3_STREAM_FLAG_READ_DATA_BLOCKED is set does not cause any + error */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + conn->remote.bidi.max_client_streams = 1; + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + + nghttp3_conn_create_stream(conn, &stream, 0); + + ud.data.left = 1000; + ud.data.step = 1000; + rv = nghttp3_conn_submit_response(conn, 0, nva, nghttp3_arraylen(nva), &dr); + + CU_ASSERT(0 == rv); + + for (;;) { + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt >= 0); + + if (sveccnt <= 0) { + break; + } + + rv = nghttp3_conn_add_write_offset(conn, stream_id, 1); + + CU_ASSERT(0 == rv); + } + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_recv_uni(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_ssize nread; + uint8_t buf[256]; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* 0 length unidirectional stream must be ignored */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + + nread = nghttp3_conn_read_stream(conn, 3, NULL, 0, /* fin = */ 1); + + CU_ASSERT(0 == nread); + CU_ASSERT(NULL == nghttp3_conn_find_stream(conn, 3)); + + nghttp3_conn_del(conn); + + /* 0 length unidirectional stream; first get 0 length without fin, + and then get 0 length with fin. */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + + nread = nghttp3_conn_read_stream(conn, 3, NULL, 0, /* fin = */ 0); + + CU_ASSERT(0 == nread); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 3)); + + nread = nghttp3_conn_read_stream(conn, 3, NULL, 0, /* fin = */ 1); + + CU_ASSERT(0 == nread); + CU_ASSERT(NULL == nghttp3_conn_find_stream(conn, 3)); + + nghttp3_conn_del(conn); + + /* Fin while reading stream header is treated as error. */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + + /* 4 bytes integer */ + buf[0] = 0xc0; + nread = nghttp3_conn_read_stream(conn, 3, buf, 1, /* fin = */ 0); + + CU_ASSERT(1 == nread); + CU_ASSERT(NULL != nghttp3_conn_find_stream(conn, 3)); + + nread = nghttp3_conn_read_stream(conn, 3, NULL, 0, /* fin = */ 1); + + CU_ASSERT(NGHTTP3_ERR_H3_GENERAL_PROTOCOL_ERROR == nread); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_recv_goaway(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_frame fr; + uint8_t rawbuf[1024]; + nghttp3_buf buf; + nghttp3_ssize nconsumed; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + int rv; + userdata ud; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.shutdown = conn_shutdown; + memset(&ud, 0, sizeof(ud)); + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Client receives GOAWAY */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, &fr); + + fr.hd.type = NGHTTP3_FRAME_GOAWAY; + fr.goaway.id = 12; + + nghttp3_write_frame(&buf, &fr); + + ud.shutdown_cb.ncalled = 0; + ud.shutdown_cb.id = 0; + nconsumed = nghttp3_conn_read_stream(conn, 3, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_RECVED); + CU_ASSERT(12 == conn->rx.goaway_id); + CU_ASSERT(1 == ud.shutdown_cb.ncalled); + CU_ASSERT(12 == ud.shutdown_cb.id); + + /* Cannot submit request anymore */ + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), NULL, + NULL); + + CU_ASSERT(NGHTTP3_ERR_CONN_CLOSING == rv); + + nghttp3_conn_del(conn); + + nghttp3_buf_reset(&buf); + + /* Receiving GOAWAY with increased ID is treated as error */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, &fr); + + fr.hd.type = NGHTTP3_FRAME_GOAWAY; + fr.goaway.id = 12; + + nghttp3_write_frame(&buf, &fr); + + fr.hd.type = NGHTTP3_FRAME_GOAWAY; + fr.goaway.id = 16; + + nghttp3_write_frame(&buf, &fr); + + ud.shutdown_cb.ncalled = 0; + ud.shutdown_cb.id = 0; + nconsumed = nghttp3_conn_read_stream(conn, 3, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_ID_ERROR == nconsumed); + CU_ASSERT(conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_RECVED); + CU_ASSERT(12 == conn->rx.goaway_id); + CU_ASSERT(1 == ud.shutdown_cb.ncalled); + CU_ASSERT(12 == ud.shutdown_cb.id); + + nghttp3_conn_del(conn); + + nghttp3_buf_reset(&buf); + + /* Server receives GOAWAY */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, &fr); + + fr.hd.type = NGHTTP3_FRAME_GOAWAY; + fr.goaway.id = 101; + + nghttp3_write_frame(&buf, &fr); + + ud.shutdown_cb.ncalled = 0; + ud.shutdown_cb.id = 0; + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_RECVED); + CU_ASSERT(101 == conn->rx.goaway_id); + CU_ASSERT(1 == ud.shutdown_cb.ncalled); + CU_ASSERT(101 == ud.shutdown_cb.id); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_shutdown_server(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_frame fr; + uint8_t rawbuf[1024]; + nghttp3_buf buf; + nghttp3_ssize nconsumed; + nghttp3_stream *stream; + nghttp3_qpack_encoder qenc; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + int rv; + userdata ud; + nghttp3_ssize sveccnt; + nghttp3_vec vec[256]; + int64_t stream_id; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.stop_sending = stop_sending; + callbacks.reset_stream = reset_stream; + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Server sends GOAWAY and rejects stream whose ID is greater than + or equal to the ID in GOAWAY. */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)nva; + fr.headers.nvlen = nghttp3_arraylen(nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 4, &fr); + + nconsumed = nghttp3_conn_read_stream(conn, 4, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(4 == conn->rx.max_stream_id_bidi); + + rv = nghttp3_conn_shutdown(conn); + + CU_ASSERT(0 == rv); + CU_ASSERT(conn->flags & NGHTTP3_CONN_FLAG_GOAWAY_QUEUED); + CU_ASSERT(8 == conn->tx.goaway_id); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt > 0); + CU_ASSERT(3 == stream_id); + + nghttp3_buf_reset(&buf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)nva; + fr.headers.nvlen = nghttp3_arraylen(nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 8, &fr); + + memset(&ud, 0, sizeof(ud)); + nconsumed = nghttp3_conn_read_stream(conn, 8, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(8 == conn->rx.max_stream_id_bidi); + CU_ASSERT(1 == ud.stop_sending_cb.ncalled); + CU_ASSERT(8 == ud.stop_sending_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_REQUEST_REJECTED == ud.stop_sending_cb.app_error_code); + CU_ASSERT(1 == ud.reset_stream_cb.ncalled); + CU_ASSERT(8 == ud.reset_stream_cb.stream_id); + CU_ASSERT(NGHTTP3_H3_REQUEST_REJECTED == ud.reset_stream_cb.app_error_code); + + stream = nghttp3_conn_find_stream(conn, 8); + + CU_ASSERT(NGHTTP3_REQ_STREAM_STATE_IGN_REST == stream->rstate.state); + + nghttp3_qpack_encoder_free(&qenc); + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_shutdown_client(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + uint8_t rawbuf[1024]; + nghttp3_buf buf; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + int rv; + userdata ud; + nghttp3_ssize sveccnt; + nghttp3_vec vec[256]; + int64_t stream_id; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.stop_sending = stop_sending; + callbacks.reset_stream = reset_stream; + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Client sends GOAWAY and rejects PUSH_PROMISE whose ID is greater + than or equal to the ID in GOAWAY. */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), NULL, + NULL); + + CU_ASSERT(0 == rv); + + rv = nghttp3_conn_shutdown(conn); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == conn->tx.goaway_id); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(sveccnt > 0); + CU_ASSERT(2 == stream_id); + + nghttp3_buf_reset(&buf); + + nghttp3_conn_del(conn); + + nghttp3_buf_reset(&buf); +} + +void test_nghttp3_conn_priority_update(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_frame fr; + nghttp3_ssize nconsumed; + uint8_t rawbuf[2048]; + nghttp3_buf buf; + nghttp3_stream *stream; + int rv; + userdata ud; + nghttp3_qpack_encoder qenc; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("priority", "u=5, i"), + }; + + memset(&callbacks, 0, sizeof(callbacks)); + memset(&ud, 0, sizeof(ud)); + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Receive PRIORITY_UPDATE and stream has not been created yet */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE; + fr.priority_update.pri_elem_id = 0; + fr.priority_update.pri.urgency = 2; + fr.priority_update.pri.inc = 1; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(NULL != stream); + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED); + CU_ASSERT(2 == nghttp3_pri_uint8_urgency(stream->node.pri)); + CU_ASSERT(1 == nghttp3_pri_uint8_inc(stream->node.pri)); + + nghttp3_buf_reset(&buf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)nva; + fr.headers.nvlen = nghttp3_arraylen(nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, &fr); + + nconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + + /* priority header field should not override the value set by + PRIORITY_UPDATE frame. */ + CU_ASSERT(2 == nghttp3_pri_uint8_urgency(stream->node.pri)); + CU_ASSERT(1 == nghttp3_pri_uint8_inc(stream->node.pri)); + + nghttp3_qpack_encoder_free(&qenc); + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); + + /* Receive PRIORITY_UPDATE and stream has been created */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + rv = nghttp3_conn_create_stream(conn, &stream, 0); + + CU_ASSERT(0 == rv); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE; + fr.priority_update.pri_elem_id = 0; + fr.priority_update.pri.urgency = 6; + fr.priority_update.pri.inc = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_PRIORITY_UPDATE_RECVED); + CU_ASSERT(6 == nghttp3_pri_uint8_urgency(stream->node.pri)); + CU_ASSERT(0 == nghttp3_pri_uint8_inc(stream->node.pri)); + + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); + + /* Receive PRIORITY_UPDATE against non-existent push_promise */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID; + fr.priority_update.pri_elem_id = 0; + fr.priority_update.pri.urgency = 6; + fr.priority_update.pri.inc = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(NGHTTP3_ERR_H3_ID_ERROR == nconsumed); + + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); + + /* Receive PRIORITY_UPDATE and its Priority Field Value is larger + than buffer */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + fr.hd.type = NGHTTP3_FRAME_PRIORITY_UPDATE; + fr.priority_update.pri_elem_id = 0; + fr.priority_update.pri.urgency = 2; + fr.priority_update.pri.inc = 1; + + nghttp3_frame_write_priority_update_len(&fr.hd.length, &fr.priority_update); + fr.hd.length += 10; + buf.last = nghttp3_frame_write_priority_update(buf.last, &fr.priority_update); + memset(buf.last, ' ', 10); + buf.last += 10; + + /* Make sure boundary check works when frame is fragmented. */ + nconsumed = + nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf) - 10, + /* fin = */ 0); + stream = nghttp3_conn_find_stream(conn, 2); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) - 10 == nconsumed); + CU_ASSERT(NGHTTP3_CTRL_STREAM_STATE_PRIORITY_UPDATE == stream->rstate.state); + + nconsumed = + nghttp3_conn_read_stream(conn, 2, buf.pos + nconsumed, 10, /* fin = */ 0); + + CU_ASSERT(10 == nconsumed); + CU_ASSERT(NGHTTP3_CTRL_STREAM_STATE_FRAME_TYPE == stream->rstate.state); + CU_ASSERT(NULL == nghttp3_conn_find_stream(conn, 0)); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_request_priority(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_frame fr; + nghttp3_ssize nconsumed; + uint8_t rawbuf[2048]; + nghttp3_buf buf; + nghttp3_stream *stream; + userdata ud; + nghttp3_qpack_encoder qenc; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("priority", "u=5, i"), + }; + const nghttp3_nv badpri_nva[] = { + MAKE_NV(":path", "/"), MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), MAKE_NV(":method", "GET"), + MAKE_NV("priority", "u=5, i"), MAKE_NV("priority", "i, u=x"), + }; + + memset(&callbacks, 0, sizeof(callbacks)); + memset(&ud, 0, sizeof(ud)); + nghttp3_settings_default(&settings); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + /* Priority in request */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + + nghttp3_buf_reset(&buf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)nva; + fr.headers.nvlen = nghttp3_arraylen(nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, &fr); + + nconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(NULL != stream); + CU_ASSERT(5 == nghttp3_pri_uint8_urgency(stream->node.pri)); + CU_ASSERT(1 == nghttp3_pri_uint8_inc(stream->node.pri)); + + nghttp3_qpack_encoder_free(&qenc); + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); + + /* Bad priority in request */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + nghttp3_conn_bind_qpack_streams(conn, 7, 11); + nghttp3_conn_set_max_client_streams_bidi(conn, 1); + nghttp3_qpack_encoder_init(&qenc, 0, mem); + + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_CONTROL); + + fr.hd.type = NGHTTP3_FRAME_SETTINGS; + fr.settings.niv = 0; + + nghttp3_write_frame(&buf, (nghttp3_frame *)&fr); + + nconsumed = nghttp3_conn_read_stream(conn, 2, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&buf) == nconsumed); + + nghttp3_buf_reset(&buf); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)badpri_nva; + fr.headers.nvlen = nghttp3_arraylen(badpri_nva); + + nghttp3_write_frame_qpack(&buf, &qenc, 0, &fr); + + nconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(NULL != stream); + CU_ASSERT(NGHTTP3_DEFAULT_URGENCY == stream->node.pri); + + nghttp3_qpack_encoder_free(&qenc); + nghttp3_conn_del(conn); + nghttp3_buf_reset(&buf); +} + +void test_nghttp3_conn_set_stream_priority(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + int rv; + nghttp3_pri pri; + nghttp3_frame_entry *ent; + nghttp3_stream *stream; + size_t i; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* Update stream priority by client */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), NULL, + NULL); + + CU_ASSERT(0 == rv); + + pri.urgency = 2; + pri.inc = 1; + + rv = nghttp3_conn_set_stream_priority(conn, 0, &pri); + + CU_ASSERT(0 == rv); + + stream = nghttp3_conn_find_stream(conn, 2); + + for (i = 0; i < nghttp3_ringbuf_len(&stream->frq); ++i) { + ent = nghttp3_ringbuf_get(&stream->frq, i); + if (ent->fr.hd.type != NGHTTP3_FRAME_PRIORITY_UPDATE) { + continue; + } + + CU_ASSERT(2 == ent->fr.priority_update.pri.urgency); + CU_ASSERT(1 == ent->fr.priority_update.pri.inc); + + break; + } + + CU_ASSERT(i < nghttp3_ringbuf_len(&stream->frq)); + + nghttp3_conn_del(conn); + + /* Updating priority of stream which does not exist is an error */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 2); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + pri.urgency = 2; + pri.inc = 1; + + rv = nghttp3_conn_set_stream_priority(conn, 0, &pri); + + CU_ASSERT(NGHTTP3_ERR_STREAM_NOT_FOUND == rv); + + nghttp3_conn_del(conn); + + /* Update stream priority by server */ + nghttp3_conn_server_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_control_stream(conn, 3); + + rv = nghttp3_conn_create_stream(conn, &stream, 0); + + CU_ASSERT(0 == rv); + + pri.urgency = 4; + pri.inc = 0; + + rv = nghttp3_conn_set_stream_priority(conn, 0, &pri); + + CU_ASSERT(0 == rv); + + stream = nghttp3_conn_find_stream(conn, 0); + + CU_ASSERT(stream->flags & NGHTTP3_STREAM_FLAG_SERVER_PRIORITY_SET); + CU_ASSERT(nghttp3_pri_to_uint8(&pri) == stream->node.pri); + + nghttp3_conn_del(conn); +} + +void test_nghttp3_conn_shutdown_stream_read(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + nghttp3_qpack_encoder qenc; + int rv; + nghttp3_buf ebuf; + uint8_t rawbuf[4096]; + nghttp3_buf buf; + const nghttp3_nv reqnv[] = { + MAKE_NV(":authority", "localhost"), + MAKE_NV(":method", "GET"), + MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "https"), + }; + const nghttp3_nv resnv[] = { + MAKE_NV(":status", "200"), + MAKE_NV("server", "nghttp3"), + }; + nghttp3_frame fr; + nghttp3_ssize sconsumed; + size_t consumed_total; + userdata ud; + size_t indatalen; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.deferred_consume = deferred_consume; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + /* Shutting down read-side stream when a stream is blocked by QPACK + dependency. */ + nghttp3_buf_init(&ebuf); + nghttp3_buf_wrap_init(&buf, rawbuf, sizeof(rawbuf)); + + nghttp3_qpack_encoder_init(&qenc, settings.qpack_max_dtable_capacity, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&qenc, + settings.qpack_blocked_streams); + nghttp3_qpack_encoder_set_max_dtable_capacity( + &qenc, settings.qpack_max_dtable_capacity); + + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, &ud); + nghttp3_conn_bind_qpack_streams(conn, 2, 6); + + rv = nghttp3_conn_submit_request(conn, 0, reqnv, nghttp3_arraylen(reqnv), + NULL, NULL); + + CU_ASSERT(0 == rv); + + fr.hd.type = NGHTTP3_FRAME_HEADERS; + fr.headers.nva = (nghttp3_nv *)resnv; + fr.headers.nvlen = nghttp3_arraylen(resnv); + + nghttp3_write_frame_qpack_dyn(&buf, &ebuf, &qenc, 0, &fr); + + indatalen = nghttp3_buf_len(&buf); + + ud.deferred_consume_cb.consumed_total = 0; + consumed_total = 0; + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed > 0); + CU_ASSERT(sconsumed != (nghttp3_ssize)nghttp3_buf_len(&buf)); + + consumed_total += (size_t)sconsumed; + + rv = nghttp3_conn_shutdown_stream_read(conn, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp3_buf_len(&conn->qdec.dbuf)); + + /* Reading further stream data is discarded. */ + nghttp3_buf_reset(&buf); + *buf.pos = 0; + ++buf.last; + + ++indatalen; + + sconsumed = nghttp3_conn_read_stream(conn, 0, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 1); + + CU_ASSERT(1 == sconsumed); + + consumed_total += (size_t)sconsumed; + + nghttp3_buf_reset(&buf); + buf.last = nghttp3_put_varint(buf.last, NGHTTP3_STREAM_TYPE_QPACK_ENCODER); + + sconsumed = nghttp3_conn_read_stream(conn, 7, buf.pos, nghttp3_buf_len(&buf), + /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&buf)); + + sconsumed = nghttp3_conn_read_stream(conn, 7, ebuf.pos, + nghttp3_buf_len(&ebuf), /* fin = */ 0); + + CU_ASSERT(sconsumed == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + /* Make sure that Section Acknowledgement is not written. */ + CU_ASSERT(1 == nghttp3_buf_len(&conn->qdec.dbuf)); + CU_ASSERT(indatalen == + consumed_total + ud.deferred_consume_cb.consumed_total); + + nghttp3_conn_del(conn); + nghttp3_qpack_encoder_free(&qenc); + nghttp3_buf_free(&ebuf, mem); +} + +void test_nghttp3_conn_stream_data_overflow(void) { +#if SIZE_MAX > UINT32_MAX + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_conn *conn; + nghttp3_callbacks callbacks; + nghttp3_settings settings; + const nghttp3_nv nva[] = { + MAKE_NV(":path", "/"), + MAKE_NV(":authority", "example.com"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + }; + nghttp3_vec vec[256]; + nghttp3_ssize sveccnt; + int rv; + int64_t stream_id; + nghttp3_data_reader dr; + int fin; + + memset(&callbacks, 0, sizeof(callbacks)); + nghttp3_settings_default(&settings); + + /* Specify NGHTTP3_MAX_VARINT + 1 bytes data in + nghttp3_read_data_callback. */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + dr.read_data = stream_data_overflow_read_data; + + /* QPACK decoder stream */ + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(10 == stream_id); + CU_ASSERT(1 == sveccnt); + + nghttp3_conn_add_write_offset(conn, 10, vec[0].len); + + /* QPACK encoder stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(6 == stream_id); + CU_ASSERT(1 == sveccnt); + + nghttp3_conn_add_write_offset(conn, 6, vec[0].len); + + /* Write request stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(NGHTTP3_ERR_STREAM_DATA_OVERFLOW == sveccnt); + + nghttp3_conn_del(conn); + + /* nghttp3_stream_outq_add detects stream data overflow */ + nghttp3_conn_client_new(&conn, &callbacks, &settings, mem, NULL); + nghttp3_conn_bind_qpack_streams(conn, 6, 10); + + dr.read_data = stream_data_almost_overflow_read_data; + + /* QPACK decoder stream */ + rv = nghttp3_conn_submit_request(conn, 0, nva, nghttp3_arraylen(nva), &dr, + NULL); + + CU_ASSERT(0 == rv); + + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(10 == stream_id); + CU_ASSERT(1 == sveccnt); + + nghttp3_conn_add_write_offset(conn, 10, vec[0].len); + + /* QPACK encoder stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(6 == stream_id); + CU_ASSERT(1 == sveccnt); + + nghttp3_conn_add_write_offset(conn, 6, vec[0].len); + + /* Write request stream */ + sveccnt = nghttp3_conn_writev_stream(conn, &stream_id, &fin, vec, + nghttp3_arraylen(vec)); + + CU_ASSERT(NGHTTP3_ERR_STREAM_DATA_OVERFLOW == sveccnt); + + nghttp3_conn_del(conn); +#endif /* SIZE_MAX > UINT32_MAX */ +} diff --git a/tests/nghttp3_conn_test.h b/tests/nghttp3_conn_test.h new file mode 100644 index 0000000..f17327a --- /dev/null +++ b/tests/nghttp3_conn_test.h @@ -0,0 +1,58 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CONN_TEST_H +#define NGTCP2_CONN_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp3_conn_read_control(void); +void test_nghttp3_conn_write_control(void); +void test_nghttp3_conn_submit_request(void); +void test_nghttp3_conn_http_request(void); +void test_nghttp3_conn_http_resp_header(void); +void test_nghttp3_conn_http_req_header(void); +void test_nghttp3_conn_http_content_length(void); +void test_nghttp3_conn_http_content_length_mismatch(void); +void test_nghttp3_conn_http_non_final_response(void); +void test_nghttp3_conn_http_trailers(void); +void test_nghttp3_conn_http_ignore_content_length(void); +void test_nghttp3_conn_http_record_request_method(void); +void test_nghttp3_conn_http_error(void); +void test_nghttp3_conn_qpack_blocked_stream(void); +void test_nghttp3_conn_just_fin(void); +void test_nghttp3_conn_submit_response_read_blocked(void); +void test_nghttp3_conn_recv_uni(void); +void test_nghttp3_conn_recv_goaway(void); +void test_nghttp3_conn_shutdown_server(void); +void test_nghttp3_conn_shutdown_client(void); +void test_nghttp3_conn_priority_update(void); +void test_nghttp3_conn_request_priority(void); +void test_nghttp3_conn_set_stream_priority(void); +void test_nghttp3_conn_shutdown_stream_read(void); +void test_nghttp3_conn_stream_data_overflow(void); + +#endif /* NGTCP2_CONN_TEST_H */ diff --git a/tests/nghttp3_conv_test.c b/tests/nghttp3_conv_test.c new file mode 100644 index 0000000..6db014b --- /dev/null +++ b/tests/nghttp3_conv_test.c @@ -0,0 +1,51 @@ +/* + * nghttp3 + * + * Copyright (c) 2020 nghttp3 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 "nghttp3_conv_test.h" + +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "nghttp3_conv.h" +#include "nghttp3_test_helper.h" + +void test_nghttp3_pri_to_uint8(void) { + { + nghttp3_pri pri = {1, 0}; + CU_ASSERT(1 == nghttp3_pri_to_uint8(&pri)); + } + { + nghttp3_pri pri = {1, 1}; + CU_ASSERT((0x80 | 1) == nghttp3_pri_to_uint8(&pri)); + } + { + nghttp3_pri pri = {7, 1}; + CU_ASSERT((0x80 | 7) == nghttp3_pri_to_uint8(&pri)); + } + { + nghttp3_pri pri = {7, 0}; + CU_ASSERT(7 == nghttp3_pri_to_uint8(&pri)); + } +} diff --git a/tests/nghttp3_conv_test.h b/tests/nghttp3_conv_test.h new file mode 100644 index 0000000..c1274c0 --- /dev/null +++ b/tests/nghttp3_conv_test.h @@ -0,0 +1,34 @@ +/* + * nghttp3 + * + * Copyright (c) 2020 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CONV_TEST_H +#define NGTCP2_CONV_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp3_pri_to_uint8(void); + +#endif /* NGTCP2_CONV_TEST_H */ diff --git a/tests/nghttp3_http_test.c b/tests/nghttp3_http_test.c new file mode 100644 index 0000000..89aa2e8 --- /dev/null +++ b/tests/nghttp3_http_test.c @@ -0,0 +1,676 @@ +/* + * nghttp3 + * + * Copyright (c) 2020 nghttp3 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 "nghttp3_http_test.h" + +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "nghttp3_http.h" +#include "nghttp3_macro.h" +#include "nghttp3_test_helper.h" + +void test_nghttp3_http_parse_priority(void) { + int rv; + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = ""; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)-1 == pri.urgency); + CU_ASSERT(-1 == pri.inc); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=7,i"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)7 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0,i=?0"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)0 == pri.urgency); + CU_ASSERT(0 == pri.inc); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=3, i"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)3 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0, i, i=?0, u=6"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)6 == pri.urgency); + CU_ASSERT(0 == pri.inc); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0,"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=0, "; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u="; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?1"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)-1 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?2"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i=?"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "i="; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=-1"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = "u=8"; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } + + { + nghttp3_pri 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 = nghttp3_http_parse_priority(&pri, v, sizeof(v) - 1); + + CU_ASSERT(0 == rv); + CU_ASSERT((uint32_t)2 == pri.urgency); + CU_ASSERT(1 == pri.inc); + } + + { + nghttp3_pri pri = {(uint32_t)-1, -1}; + const uint8_t v[] = {'u', '='}; + + rv = nghttp3_http_parse_priority(&pri, v, sizeof(v)); + + CU_ASSERT(NGHTTP3_ERR_INVALID_ARGUMENT == rv); + } +} + +void test_nghttp3_sf_parse_item(void) { + { + nghttp3_sf_value val; + const uint8_t s[] = "?1"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "?1 "; + val.type = 0xff; + + CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "?1;foo=bar"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(1 == val.b); + } + + { + const uint8_t s[] = {'?', '1', ';', 'f', 'o', 'o', '='}; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s))); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "?0"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(0 == val.b); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "?0 "; + val.type = 0xff; + + CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BOOLEAN == val.type); + CU_ASSERT(0 == val.b); + } + + { + const uint8_t s[] = "?2"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "?"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "?1"; + + CU_ASSERT(2 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:"; + val.type = 0xff; + + CU_ASSERT(46 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(44 == val.s.len); + CU_ASSERT(0 == memcmp("cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==", + val.s.base, val.s.len)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==: "; + val.type = 0xff; + + CU_ASSERT(46 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(44 == val.s.len); + CU_ASSERT(0 == memcmp("cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==", + val.s.base, val.s.len)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "::"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(0 == val.s.len); + } + + { + const uint8_t s[] = ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg=="; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":@:"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = ":foo:"; + + CU_ASSERT(5 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = + ":abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=:"; + val.type = 0xff; + + CU_ASSERT(67 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_BYTESEQ == val.type); + CU_ASSERT(65 == val.s.len); + CU_ASSERT( + 0 == + memcmp( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=", + val.s.base, val.s.len)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "foo123/456"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_TOKEN == val.type); + CU_ASSERT(10 == val.s.len); + CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "foo123/456 "; + val.type = 0xff; + + CU_ASSERT(10 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_TOKEN == val.type); + CU_ASSERT(10 == val.s.len); + CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "*"; + val.type = 0xff; + + CU_ASSERT(1 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_TOKEN == val.type); + CU_ASSERT(1 == val.s.len); + CU_ASSERT(0 == memcmp(s, val.s.base, val.s.len)); + } + + { + const uint8_t s[] = "*"; + + CU_ASSERT(1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "\"hello world\""; + val.type = 0xff; + + CU_ASSERT(13 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(11 == val.s.len); + CU_ASSERT(0 == memcmp("hello world", val.s.base, val.s.len)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "\"hello world\" "; + val.type = 0xff; + + CU_ASSERT(13 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(11 == val.s.len); + CU_ASSERT(0 == memcmp("hello world", val.s.base, val.s.len)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "\"foo\\\"\\\\\""; + val.type = 0xff; + + CU_ASSERT(9 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(7 == val.s.len); + CU_ASSERT(0 == memcmp("foo\\\"\\\\", val.s.base, val.s.len)); + } + + { + const uint8_t s[] = "\"foo\\x\""; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"foo"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"\x7f\""; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"\x1f\""; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "\"foo\""; + + CU_ASSERT(5 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "4.5"; + val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(3 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(4.5 - val.d) < 1e-9); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "4.5 "; + val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(3 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(4.5 - val.d) < 1e-9); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "-4.5"; + val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(4 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(-4.5 - val.d) < 1e-9); + } + + { + const uint8_t s[] = "4.5"; + + CU_ASSERT(3 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "123456789012.123"; + val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(16 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_DECIMAL == val.type); + CU_ASSERT(fabs(123456789012.123 - val.d) < 1e-9); + } + + { + const uint8_t s[] = "1123456789012.123"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "123456789012.1234"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "1."; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "123456789012345"; + val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(15 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INTEGER == val.type); + CU_ASSERT(123456789012345 == val.i); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "1 "; + val.type = NGHTTP3_SF_VALUE_TYPE_DECIMAL; + + CU_ASSERT(1 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INTEGER == val.type); + CU_ASSERT(1 == val.i); + } + + { + const uint8_t s[] = "1"; + + CU_ASSERT(1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "1234567890123456"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "\"foo\";a; b=\"bar\";c=1.3;d=9;e=baz;f=:aaa:"; + val.type = 0xff; + + CU_ASSERT(41 == nghttp3_sf_parse_item(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_STRING == val.type); + CU_ASSERT(0 == memcmp("foo", val.s.base, val.s.len)); + } + + { + const uint8_t s[] = "\"foo\";a; b=\"bar"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "foo;"; + + CU_ASSERT(-1 == nghttp3_sf_parse_item(NULL, s, s + sizeof(s) - 1)); + } +} + +void test_nghttp3_sf_parse_inner_list(void) { + { + nghttp3_sf_value val; + const uint8_t s[] = "()"; + val.type = 0xff; + + CU_ASSERT(2 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "( )"; + val.type = 0xff; + + CU_ASSERT(7 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "(a)"; + val.type = 0xff; + + CU_ASSERT(3 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "(a b)"; + val.type = 0xff; + + CU_ASSERT(5 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "( a b )"; + val.type = 0xff; + + CU_ASSERT(10 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + nghttp3_sf_value val; + const uint8_t s[] = "( a;foo=bar)"; + val.type = 0xff; + + CU_ASSERT(12 == nghttp3_sf_parse_inner_list(&val, s, s + sizeof(s) - 1)); + CU_ASSERT(NGHTTP3_SF_VALUE_TYPE_INNER_LIST == val.type); + } + + { + const uint8_t s[] = "("; + + CU_ASSERT(-1 == nghttp3_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a"; + + CU_ASSERT(-1 == nghttp3_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a "; + + CU_ASSERT(-1 == nghttp3_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } + + { + const uint8_t s[] = "(a;b"; + + CU_ASSERT(-1 == nghttp3_sf_parse_inner_list(NULL, s, s + sizeof(s) - 1)); + } +} + +#define check_header_value(S) \ + nghttp3_check_header_value((const uint8_t *)S, sizeof(S) - 1) + +void test_nghttp3_check_header_value(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("!|}~")); + CU_ASSERT(!check_header_value(" !|}~")); + CU_ASSERT(!check_header_value("!|}~ ")); + CU_ASSERT(!check_header_value("\t!|}~")); + CU_ASSERT(!check_header_value("!|}~\t")); + 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")); +} diff --git a/tests/nghttp3_http_test.h b/tests/nghttp3_http_test.h new file mode 100644 index 0000000..47f902c --- /dev/null +++ b/tests/nghttp3_http_test.h @@ -0,0 +1,37 @@ +/* + * nghttp3 + * + * Copyright (c) 2020 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_HTTP_TEST_H +#define NGTCP2_HTTP_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp3_http_parse_priority(void); +void test_nghttp3_sf_parse_item(void); +void test_nghttp3_sf_parse_inner_list(void); +void test_nghttp3_check_header_value(void); + +#endif /* NGTCP2_CONN_TEST_H */ diff --git a/tests/nghttp3_qpack_test.c b/tests/nghttp3_qpack_test.c new file mode 100644 index 0000000..c8e1983 --- /dev/null +++ b/tests/nghttp3_qpack_test.c @@ -0,0 +1,779 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "nghttp3_qpack_test.h" + +#include <stdlib.h> + +#include <CUnit/CUnit.h> + +#include "nghttp3_qpack.h" +#include "nghttp3_macro.h" +#include "nghttp3_test_helper.h" + +static void check_decode_header(nghttp3_qpack_decoder *dec, nghttp3_buf *pbuf, + nghttp3_buf *rbuf, nghttp3_buf *ebuf, + int64_t stream_id, const nghttp3_nv *nva, + size_t nvlen, const nghttp3_mem *mem) { + nghttp3_ssize nread; + nghttp3_qpack_stream_context sctx; + nghttp3_qpack_nv qnv; + const nghttp3_nv *nv; + uint8_t flags; + size_t i = 0; + + nread = + nghttp3_qpack_decoder_read_encoder(dec, ebuf->pos, nghttp3_buf_len(ebuf)); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(ebuf) == nread); + + nghttp3_qpack_stream_context_init(&sctx, stream_id, mem); + + nread = nghttp3_qpack_decoder_read_request( + dec, &sctx, &qnv, &flags, pbuf->pos, nghttp3_buf_len(pbuf), 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(pbuf) == nread); + + for (; nghttp3_buf_len(rbuf);) { + nread = nghttp3_qpack_decoder_read_request( + dec, &sctx, &qnv, &flags, rbuf->pos, nghttp3_buf_len(rbuf), 1); + + CU_ASSERT(nread > 0); + + if (nread < 0) { + break; + } + + rbuf->pos += nread; + + if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) { + break; + } + if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) { + nv = &nva[i++]; + CU_ASSERT(nv->namelen == qnv.name->len); + CU_ASSERT(0 == memcmp(nv->name, qnv.name->base, nv->namelen)); + CU_ASSERT(nv->valuelen == qnv.value->len); + CU_ASSERT(0 == memcmp(nv->value, qnv.value->base, nv->valuelen)); + + nghttp3_rcbuf_decref(qnv.name); + nghttp3_rcbuf_decref(qnv.value); + } + } + + CU_ASSERT(i == nvlen); + + nghttp3_qpack_stream_context_free(&sctx); + nghttp3_buf_reset(pbuf); + nghttp3_buf_reset(rbuf); + nghttp3_buf_reset(ebuf); +} + +static void decode_header_block(nghttp3_qpack_decoder *dec, nghttp3_buf *pbuf, + nghttp3_buf *rbuf, int64_t stream_id, + const nghttp3_mem *mem) { + nghttp3_ssize nread; + nghttp3_qpack_stream_context sctx; + nghttp3_qpack_nv qnv; + uint8_t flags; + + nghttp3_qpack_stream_context_init(&sctx, stream_id, mem); + + nread = nghttp3_qpack_decoder_read_request( + dec, &sctx, &qnv, &flags, pbuf->pos, nghttp3_buf_len(pbuf), 0); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(pbuf) == nread); + + for (;;) { + nread = nghttp3_qpack_decoder_read_request( + dec, &sctx, &qnv, &flags, rbuf->pos, nghttp3_buf_len(rbuf), 1); + + CU_ASSERT(nread >= 0); + + if (nread < 0) { + break; + } + + if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) { + CU_ASSERT(nread == 0); + CU_ASSERT(!(flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT)); + CU_ASSERT(0 == nghttp3_buf_len(rbuf)); + CU_ASSERT(nghttp3_buf_len(&dec->dbuf) > 0); + + break; + } + + CU_ASSERT(nread > 0); + CU_ASSERT(flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT); + + nghttp3_rcbuf_decref(qnv.name); + nghttp3_rcbuf_decref(qnv.value); + + rbuf->pos += nread; + } + + nghttp3_qpack_stream_context_free(&sctx); +} + +void test_nghttp3_qpack_encoder_encode(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_qpack_encoder enc; + nghttp3_qpack_decoder dec; + nghttp3_nv nva[] = { + MAKE_NV(":path", "/rsrc.php/v3/yn/r/rIPZ9Qkrdd9.png"), + MAKE_NV(":authority", "static.xx.fbcdn.net"), + MAKE_NV(":scheme", "https"), + MAKE_NV(":method", "GET"), + MAKE_NV("accept-encoding", "gzip, deflate, br"), + MAKE_NV("accept-language", "en-US,en;q=0.9"), + MAKE_NV( + "user-agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36(KHTML, " + "like Gecko) Chrome/63.0.3239.70 Safari/537.36"), + MAKE_NV("accept", "image/webp,image/apng,image/*,*/*;q=0.8"), + MAKE_NV("referer", "https://static.xx.fbcdn.net/rsrc.php/v3/yT/l/0,cross/" + "dzXGESIlGQQ.css"), + }; + int rv; + nghttp3_buf pbuf, rbuf, ebuf; + nghttp3_qpack_stream *stream; + nghttp3_qpack_header_block_ref *ref; + + nghttp3_buf_init(&pbuf); + nghttp3_buf_init(&rbuf); + nghttp3_buf_init(&ebuf); + rv = nghttp3_qpack_encoder_init(&enc, 4096, mem); + + CU_ASSERT(0 == rv); + + nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 1); + + nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096); + + rv = nghttp3_qpack_decoder_init(&dec, 4096, 1, mem); + + CU_ASSERT(0 == rv); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva, + nghttp3_arraylen(nva)); + + CU_ASSERT(0 == rv); + + stream = nghttp3_qpack_encoder_find_stream(&enc, 0); + + CU_ASSERT(NULL != stream); + CU_ASSERT(nghttp3_qpack_encoder_stream_is_blocked(&enc, stream)); + CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + ref = + *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 0); + + CU_ASSERT(5 == ref->max_cnt); + CU_ASSERT(1 == ref->min_cnt); + CU_ASSERT(5 == nghttp3_qpack_stream_get_max_cnt(stream)); + CU_ASSERT(1 == nghttp3_qpack_encoder_get_min_cnt(&enc)); + CU_ASSERT(2 == nghttp3_buf_len(&pbuf)); + + check_decode_header(&dec, &pbuf, &rbuf, &ebuf, 0, nva, nghttp3_arraylen(nva), + mem); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 4, nva, + nghttp3_arraylen(nva)); + + CU_ASSERT(0 == rv); + + stream = nghttp3_qpack_encoder_find_stream(&enc, 4); + + CU_ASSERT(NULL == stream); + + check_decode_header(&dec, &pbuf, &rbuf, &ebuf, 4, nva, nghttp3_arraylen(nva), + mem); + + nghttp3_qpack_encoder_ack_header(&enc, 0); + + CU_ASSERT(5 == enc.krcnt); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 8, nva, + nghttp3_arraylen(nva)); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + check_decode_header(&dec, &pbuf, &rbuf, &ebuf, 8, nva, nghttp3_arraylen(nva), + mem); + + nghttp3_qpack_decoder_free(&dec); + nghttp3_qpack_encoder_free(&enc); + nghttp3_buf_free(&ebuf, mem); + nghttp3_buf_free(&rbuf, mem); + nghttp3_buf_free(&pbuf, mem); +} + +void test_nghttp3_qpack_encoder_still_blocked(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_qpack_encoder enc; + nghttp3_nv nva1[] = { + MAKE_NV(":status", "103"), + MAKE_NV("link", "foo"), + }; + nghttp3_nv nva2[] = { + MAKE_NV(":status", "200"), + MAKE_NV("content-type", "text/foo"), + }; + int rv; + nghttp3_buf pbuf, rbuf, ebuf; + nghttp3_qpack_stream *stream; + nghttp3_qpack_header_block_ref *ref; + + nghttp3_buf_init(&pbuf); + nghttp3_buf_init(&rbuf); + nghttp3_buf_init(&ebuf); + rv = nghttp3_qpack_encoder_init(&enc, 4096, mem); + + CU_ASSERT(0 == rv); + + nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 1); + + nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva1, + nghttp3_arraylen(nva1)); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva2, + nghttp3_arraylen(nva2)); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + stream = nghttp3_qpack_encoder_find_stream(&enc, 0); + + ref = + *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 0); + + CU_ASSERT(nghttp3_ringbuf_len(&stream->refs) > 1); + CU_ASSERT(ref->max_cnt != nghttp3_qpack_stream_get_max_cnt(stream)); + + ref = + *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 1); + + CU_ASSERT(ref->max_cnt == nghttp3_qpack_stream_get_max_cnt(stream)); + + nghttp3_qpack_encoder_ack_header(&enc, 0); + + CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + stream = nghttp3_qpack_encoder_find_stream(&enc, 0); + + ref = + *(nghttp3_qpack_header_block_ref **)nghttp3_ringbuf_get(&stream->refs, 0); + + CU_ASSERT(1 == nghttp3_ringbuf_len(&stream->refs)); + CU_ASSERT(ref->max_cnt == nghttp3_qpack_stream_get_max_cnt(stream)); + + nghttp3_qpack_encoder_ack_header(&enc, 0); + + CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + CU_ASSERT(NULL == nghttp3_qpack_encoder_find_stream(&enc, 0)); + + nghttp3_qpack_encoder_free(&enc); + nghttp3_buf_free(&ebuf, mem); + nghttp3_buf_free(&rbuf, mem); + nghttp3_buf_free(&pbuf, mem); +} + +void test_nghttp3_qpack_encoder_set_dtable_cap(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_qpack_encoder enc; + nghttp3_qpack_decoder dec; + nghttp3_buf pbuf, rbuf, ebuf; + const nghttp3_nv nva1[] = { + MAKE_NV(":path", "/"), + MAKE_NV("date", "bar1"), + }; + const nghttp3_nv nva2[] = { + MAKE_NV(":path", "/"), + MAKE_NV("vary", "bar2"), + }; + int rv; + nghttp3_ssize nread; + + nghttp3_buf_init(&pbuf); + nghttp3_buf_init(&rbuf); + nghttp3_buf_init(&ebuf); + + rv = nghttp3_qpack_encoder_init(&enc, 4096, mem); + + CU_ASSERT(0 == rv); + + nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 3); + + nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096); + + rv = nghttp3_qpack_decoder_init(&dec, 4096, 3, mem); + + CU_ASSERT(0 == rv); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva1, + nghttp3_arraylen(nva1)); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == enc.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD == + enc.ctx.dtable_size); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + CU_ASSERT(1 == dec.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD == + dec.ctx.dtable_size); + CU_ASSERT(4096 == dec.ctx.max_dtable_capacity); + + decode_header_block(&dec, &pbuf, &rbuf, 0, mem); + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 4, nva2, + nghttp3_arraylen(nva2)); + + CU_ASSERT(0 == rv); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD + + strlen("vary") + strlen("bar2") + + NGHTTP3_QPACK_ENTRY_OVERHEAD == + enc.ctx.dtable_size); + CU_ASSERT(2 == enc.ctx.next_absidx); + CU_ASSERT(2 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + CU_ASSERT(2 == dec.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD + + strlen("vary") + strlen("bar2") + + NGHTTP3_QPACK_ENTRY_OVERHEAD == + dec.ctx.dtable_size); + CU_ASSERT(4096 == dec.ctx.max_dtable_capacity); + + decode_header_block(&dec, &pbuf, &rbuf, 4, mem); + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 0); + + CU_ASSERT(0 == enc.ctx.max_dtable_capacity); + CU_ASSERT(2 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + /* Cannot index more headers because we set max_dtable_capacity to + 0. */ + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 8, nva2, + nghttp3_arraylen(nva2)); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == enc.ctx.max_dtable_capacity); + CU_ASSERT(2 == enc.ctx.next_absidx); + CU_ASSERT(2 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + CU_ASSERT(2 == dec.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD + + strlen("vary") + strlen("bar2") + + NGHTTP3_QPACK_ENTRY_OVERHEAD == + dec.ctx.dtable_size); + CU_ASSERT(4096 == dec.ctx.max_dtable_capacity); + + decode_header_block(&dec, &pbuf, &rbuf, 8, mem); + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + /* Acking stream 0 will evict first entry */ + nghttp3_qpack_encoder_ack_header(&enc, 0); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 12, nva2, + nghttp3_arraylen(nva2)); + + CU_ASSERT(0 == rv); + CU_ASSERT(strlen("vary") + strlen("bar2") + NGHTTP3_QPACK_ENTRY_OVERHEAD == + enc.ctx.dtable_size); + CU_ASSERT(0 == enc.ctx.max_dtable_capacity); + CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + /* decoder still has 2 entries because encoder does not emit Set + Dynamic Table Capacity. */ + CU_ASSERT(2 == dec.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD + + strlen("vary") + strlen("bar2") + + NGHTTP3_QPACK_ENTRY_OVERHEAD == + dec.ctx.dtable_size); + CU_ASSERT(4096 == dec.ctx.max_dtable_capacity); + + decode_header_block(&dec, &pbuf, &rbuf, 12, mem); + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + /* Acking stream 4 will evict another entry */ + nghttp3_qpack_encoder_ack_header(&enc, 4); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 16, nva2, + nghttp3_arraylen(nva2)); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == enc.ctx.dtable_size); + CU_ASSERT(0 == enc.ctx.max_dtable_capacity); + CU_ASSERT(0 == enc.last_max_dtable_update); + CU_ASSERT(SIZE_MAX == enc.min_dtable_update); + CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + CU_ASSERT(2 == dec.ctx.next_absidx); + CU_ASSERT(0 == dec.ctx.dtable_size); + CU_ASSERT(0 == dec.ctx.max_dtable_capacity); + + decode_header_block(&dec, &pbuf, &rbuf, 16, mem); + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + nghttp3_qpack_decoder_free(&dec); + nghttp3_qpack_encoder_free(&enc); + + /* Check that minimum size is emitted */ + nghttp3_qpack_encoder_init(&enc, 4096, mem); + nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 1); + nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096); + nghttp3_qpack_decoder_init(&dec, 4096, 1, mem); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 0, nva1, + nghttp3_arraylen(nva1)); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == enc.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD == + enc.ctx.dtable_size); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + CU_ASSERT(1 == dec.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD == + dec.ctx.dtable_size); + CU_ASSERT(4096 == dec.ctx.max_dtable_capacity); + + decode_header_block(&dec, &pbuf, &rbuf, 0, mem); + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 0); + nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 1024); + + CU_ASSERT(0 == enc.min_dtable_update); + CU_ASSERT(1024 == enc.last_max_dtable_update); + + nghttp3_qpack_encoder_ack_header(&enc, 0); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf, &rbuf, &ebuf, 4, nva1, + nghttp3_arraylen(nva1)); + + CU_ASSERT(0 == rv); + CU_ASSERT(2 == enc.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD == + enc.ctx.dtable_size); + CU_ASSERT(SIZE_MAX == enc.min_dtable_update); + CU_ASSERT(1024 == enc.last_max_dtable_update); + CU_ASSERT(1024 == enc.ctx.max_dtable_capacity); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT(nread == (nghttp3_ssize)nghttp3_buf_len(&ebuf)); + CU_ASSERT(2 == dec.ctx.next_absidx); + CU_ASSERT(strlen("date") + strlen("bar1") + NGHTTP3_QPACK_ENTRY_OVERHEAD == + dec.ctx.dtable_size); + CU_ASSERT(1024 == dec.ctx.max_dtable_capacity); + + decode_header_block(&dec, &pbuf, &rbuf, 4, mem); + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + nghttp3_qpack_decoder_free(&dec); + nghttp3_qpack_encoder_free(&enc); + nghttp3_buf_free(&ebuf, mem); + nghttp3_buf_free(&rbuf, mem); + nghttp3_buf_free(&pbuf, mem); +} + +void test_nghttp3_qpack_decoder_feedback(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_qpack_encoder enc; + nghttp3_qpack_decoder dec; + nghttp3_buf pbuf1, rbuf1, pbuf2, rbuf2, pbuf3, rbuf3, ebuf, dbuf; + const nghttp3_nv nva1[] = { + MAKE_NV(":path", "/"), + MAKE_NV("date", "bar1"), + }; + const nghttp3_nv nva2[] = { + MAKE_NV(":path", "/"), + MAKE_NV("vary", "bar2"), + }; + const nghttp3_nv nva3[] = { + MAKE_NV(":path", "/"), + MAKE_NV("link", "bar3"), + }; + int rv; + nghttp3_ssize nread; + + nghttp3_buf_init(&pbuf1); + nghttp3_buf_init(&rbuf1); + nghttp3_buf_init(&pbuf2); + nghttp3_buf_init(&rbuf2); + nghttp3_buf_init(&pbuf3); + nghttp3_buf_init(&rbuf3); + nghttp3_buf_init(&ebuf); + nghttp3_buf_init(&dbuf); + + nghttp3_buf_reserve(&dbuf, 4096, mem); + + rv = nghttp3_qpack_encoder_init(&enc, 4096, mem); + + CU_ASSERT(0 == rv); + + nghttp3_qpack_encoder_set_max_blocked_streams(&enc, 2); + + nghttp3_qpack_encoder_set_max_dtable_capacity(&enc, 4096); + + rv = nghttp3_qpack_decoder_init(&dec, 4096, 2, mem); + + CU_ASSERT(0 == rv); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf1, &rbuf1, &ebuf, 0, nva1, + nghttp3_arraylen(nva1)); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf2, &rbuf2, &ebuf, 4, nva2, + nghttp3_arraylen(nva2)); + + CU_ASSERT(0 == rv); + CU_ASSERT(2 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&ebuf) == nread); + + /* Process stream 4 first */ + decode_header_block(&dec, &pbuf2, &rbuf2, 4, mem); + + CU_ASSERT(2 == dec.written_icnt); + + nghttp3_qpack_decoder_write_decoder(&dec, &dbuf); + + nread = nghttp3_qpack_encoder_read_decoder(&enc, dbuf.pos, + nghttp3_buf_len(&dbuf)); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&dbuf) == nread); + /* This will unblock all streams because higher insert count is + acknowledged. */ + CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + CU_ASSERT(1 == nghttp3_map_size(&enc.streams)); + CU_ASSERT(1 == nghttp3_pq_size(&enc.min_cnts)); + CU_ASSERT(0 == nghttp3_ksl_len(&enc.blocked_streams)); + CU_ASSERT(2 == enc.krcnt); + + /* Process stream 0 */ + decode_header_block(&dec, &pbuf1, &rbuf1, 0, mem); + nghttp3_buf_reset(&dbuf); + nghttp3_qpack_decoder_write_decoder(&dec, &dbuf); + + nread = nghttp3_qpack_encoder_read_decoder(&enc, dbuf.pos, + nghttp3_buf_len(&dbuf)); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&dbuf) == nread); + CU_ASSERT(0 == nghttp3_map_size(&enc.streams)); + CU_ASSERT(0 == nghttp3_pq_size(&enc.min_cnts)); + CU_ASSERT(2 == enc.krcnt); + + /* Encode another header, and then read encoder stream only. Write + decoder stream. */ + nghttp3_buf_reset(&ebuf); + rv = nghttp3_qpack_encoder_encode(&enc, &pbuf3, &rbuf3, &ebuf, 8, nva3, + nghttp3_arraylen(nva3)); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + + nread = nghttp3_qpack_decoder_read_encoder(&dec, ebuf.pos, + nghttp3_buf_len(&ebuf)); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&ebuf) == nread); + + nghttp3_buf_reset(&dbuf); + nghttp3_qpack_decoder_write_decoder(&dec, &dbuf); + + CU_ASSERT(nghttp3_buf_len(&dbuf) > 0); + CU_ASSERT(3 == dec.written_icnt); + + nread = nghttp3_qpack_encoder_read_decoder(&enc, dbuf.pos, + nghttp3_buf_len(&dbuf)); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&dbuf) == nread); + CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + CU_ASSERT(1 == nghttp3_map_size(&enc.streams)); + CU_ASSERT(3 == enc.krcnt); + + /* Cancel stream 8 */ + rv = nghttp3_qpack_decoder_cancel_stream(&dec, 8); + + CU_ASSERT(0 == rv); + + nghttp3_buf_reset(&dbuf); + nghttp3_qpack_decoder_write_decoder(&dec, &dbuf); + + CU_ASSERT(nghttp3_buf_len(&dbuf) > 0); + + nread = nghttp3_qpack_encoder_read_decoder(&enc, dbuf.pos, + nghttp3_buf_len(&dbuf)); + + CU_ASSERT((nghttp3_ssize)nghttp3_buf_len(&dbuf) == nread); + CU_ASSERT(0 == nghttp3_qpack_encoder_get_num_blocked_streams(&enc)); + CU_ASSERT(0 == nghttp3_ksl_len(&enc.blocked_streams)); + CU_ASSERT(0 == nghttp3_pq_size(&enc.min_cnts)); + CU_ASSERT(0 == nghttp3_map_size(&enc.streams)); + + nghttp3_qpack_decoder_free(&dec); + nghttp3_qpack_encoder_free(&enc); + nghttp3_buf_free(&dbuf, mem); + nghttp3_buf_free(&ebuf, mem); + nghttp3_buf_free(&rbuf3, mem); + nghttp3_buf_free(&pbuf3, mem); + nghttp3_buf_free(&rbuf2, mem); + nghttp3_buf_free(&pbuf2, mem); + nghttp3_buf_free(&rbuf1, mem); + nghttp3_buf_free(&pbuf1, mem); +} + +void test_nghttp3_qpack_decoder_stream_overflow(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_qpack_decoder dec; + size_t i; + int rv; + + nghttp3_qpack_decoder_init(&dec, 4096, 0, mem); + + for (i = 0;; ++i) { + rv = nghttp3_qpack_decoder_cancel_stream(&dec, (int64_t)i); + if (rv == NGHTTP3_ERR_QPACK_FATAL) { + break; + } + } + + nghttp3_qpack_decoder_free(&dec); +} + +void test_nghttp3_qpack_huffman(void) { + size_t i, j; + uint8_t raw[100], ebuf[4096], dbuf[4096]; + uint8_t *end; + nghttp3_qpack_huffman_decode_context ctx; + nghttp3_ssize nwrite; + + srand(1000000007); + + for (i = 0; i < 100000; ++i) { + for (j = 0; j < sizeof(raw); ++j) { + raw[j] = (uint8_t)round(((double)rand() / RAND_MAX * 255)); + } + end = nghttp3_qpack_huffman_encode(ebuf, raw, sizeof(raw)); + + nghttp3_qpack_huffman_decode_context_init(&ctx); + nwrite = + nghttp3_qpack_huffman_decode(&ctx, dbuf, ebuf, (size_t)(end - ebuf), 1); + if (nwrite <= 0) { + CU_ASSERT(nwrite > 0); + continue; + } + CU_ASSERT((sizeof(raw) == (size_t)nwrite)); + CU_ASSERT(0 == memcmp(raw, dbuf, sizeof(raw))); + } +} + +void test_nghttp3_qpack_huffman_decode_failure_state(void) { + nghttp3_qpack_huffman_decode_context ctx; + const uint8_t data[] = {0xff, 0xff, 0xff, 0xff}; + uint8_t buf[4096]; + nghttp3_ssize nwrite; + + nghttp3_qpack_huffman_decode_context_init(&ctx); + nwrite = nghttp3_qpack_huffman_decode(&ctx, buf, data, sizeof(data) - 1, 0); + + CU_ASSERT(0 == nwrite); + CU_ASSERT(!nghttp3_qpack_huffman_decode_failure_state(&ctx)); + + nwrite = nghttp3_qpack_huffman_decode(&ctx, buf, data, 1, 0); + + CU_ASSERT(0 == nwrite); + CU_ASSERT(nghttp3_qpack_huffman_decode_failure_state(&ctx)); +} + +void test_nghttp3_qpack_decoder_reconstruct_ricnt(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_qpack_decoder dec; + uint64_t ricnt; + int rv; + + rv = nghttp3_qpack_decoder_init(&dec, 100, 1, mem); + + CU_ASSERT(0 == rv); + + dec.ctx.next_absidx = 10; + + rv = nghttp3_qpack_decoder_reconstruct_ricnt(&dec, &ricnt, 3); + + CU_ASSERT(0 == rv); + CU_ASSERT(8 == ricnt); + + nghttp3_qpack_decoder_free(&dec); +} diff --git a/tests/nghttp3_qpack_test.h b/tests/nghttp3_qpack_test.h new file mode 100644 index 0000000..8f3240c --- /dev/null +++ b/tests/nghttp3_qpack_test.h @@ -0,0 +1,41 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_QPACK_TEST_H +#define NGTCP2_QPACK_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp3_qpack_encoder_encode(void); +void test_nghttp3_qpack_encoder_still_blocked(void); +void test_nghttp3_qpack_encoder_set_dtable_cap(void); +void test_nghttp3_qpack_decoder_feedback(void); +void test_nghttp3_qpack_decoder_stream_overflow(void); +void test_nghttp3_qpack_huffman(void); +void test_nghttp3_qpack_huffman_decode_failure_state(void); +void test_nghttp3_qpack_decoder_reconstruct_ricnt(void); + +#endif /* NGTCP2_QPCK_TEST_H */ diff --git a/tests/nghttp3_test_helper.c b/tests/nghttp3_test_helper.c new file mode 100644 index 0000000..93ebfec --- /dev/null +++ b/tests/nghttp3_test_helper.c @@ -0,0 +1,139 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "nghttp3_test_helper.h" + +#include <string.h> +#include <assert.h> + +#include "nghttp3_str.h" +#include "nghttp3_conv.h" + +void nghttp3_write_frame(nghttp3_buf *dest, nghttp3_frame *fr) { + switch (fr->hd.type) { + case NGHTTP3_FRAME_SETTINGS: + nghttp3_frame_write_settings_len(&fr->hd.length, &fr->settings); + dest->last = nghttp3_frame_write_settings(dest->last, &fr->settings); + break; + case NGHTTP3_FRAME_GOAWAY: + nghttp3_frame_write_goaway_len(&fr->hd.length, &fr->goaway); + dest->last = nghttp3_frame_write_goaway(dest->last, &fr->goaway); + break; + case NGHTTP3_FRAME_PRIORITY_UPDATE: + case NGHTTP3_FRAME_PRIORITY_UPDATE_PUSH_ID: + nghttp3_frame_write_priority_update_len(&fr->hd.length, + &fr->priority_update); + dest->last = + nghttp3_frame_write_priority_update(dest->last, &fr->priority_update); + break; + default: + assert(0); + } +} + +void nghttp3_write_frame_qpack(nghttp3_buf *dest, nghttp3_qpack_encoder *qenc, + int64_t stream_id, nghttp3_frame *fr) { + int rv; + const nghttp3_nv *nva; + size_t nvlen; + nghttp3_buf pbuf, rbuf, ebuf; + const nghttp3_mem *mem = nghttp3_mem_default(); + (void)rv; + + switch (fr->hd.type) { + case NGHTTP3_FRAME_HEADERS: + nva = fr->headers.nva; + nvlen = fr->headers.nvlen; + break; + default: + assert(0); + abort(); + } + + nghttp3_buf_init(&pbuf); + nghttp3_buf_init(&rbuf); + nghttp3_buf_init(&ebuf); + + rv = nghttp3_qpack_encoder_encode(qenc, &pbuf, &rbuf, &ebuf, stream_id, nva, + nvlen); + assert(0 == rv); + assert(0 == nghttp3_buf_len(&ebuf)); + + fr->hd.length = (int64_t)(nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf)); + + dest->last = nghttp3_frame_write_hd(dest->last, &fr->hd); + dest->last = nghttp3_cpymem(dest->last, pbuf.pos, nghttp3_buf_len(&pbuf)); + dest->last = nghttp3_cpymem(dest->last, rbuf.pos, nghttp3_buf_len(&rbuf)); + + nghttp3_buf_free(&rbuf, mem); + nghttp3_buf_free(&pbuf, mem); +} + +void nghttp3_write_frame_qpack_dyn(nghttp3_buf *dest, nghttp3_buf *ebuf, + nghttp3_qpack_encoder *qenc, + int64_t stream_id, nghttp3_frame *fr) { + int rv; + const nghttp3_nv *nva; + size_t nvlen; + nghttp3_buf pbuf, rbuf; + const nghttp3_mem *mem = nghttp3_mem_default(); + (void)rv; + + switch (fr->hd.type) { + case NGHTTP3_FRAME_HEADERS: + nva = fr->headers.nva; + nvlen = fr->headers.nvlen; + break; + default: + assert(0); + abort(); + } + + nghttp3_buf_init(&pbuf); + nghttp3_buf_init(&rbuf); + + rv = nghttp3_qpack_encoder_encode(qenc, &pbuf, &rbuf, ebuf, stream_id, nva, + nvlen); + assert(0 == rv); + + fr->hd.length = (int64_t)(nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf)); + + dest->last = nghttp3_frame_write_hd(dest->last, &fr->hd); + dest->last = nghttp3_cpymem(dest->last, pbuf.pos, nghttp3_buf_len(&pbuf)); + dest->last = nghttp3_cpymem(dest->last, rbuf.pos, nghttp3_buf_len(&rbuf)); + + nghttp3_buf_free(&rbuf, mem); + nghttp3_buf_free(&pbuf, mem); +} + +void nghttp3_write_frame_data(nghttp3_buf *dest, size_t len) { + nghttp3_frame_data fr; + + fr.hd.type = NGHTTP3_FRAME_DATA; + fr.hd.length = (int64_t)len; + + dest->last = nghttp3_frame_write_hd(dest->last, &fr.hd); + memset(dest->last, 0, len); + dest->last += len; +} diff --git a/tests/nghttp3_test_helper.h b/tests/nghttp3_test_helper.h new file mode 100644 index 0000000..6f6516b --- /dev/null +++ b/tests/nghttp3_test_helper.h @@ -0,0 +1,72 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 NGHTTP3_TEST_HELPER +#define NGHTTP3_TEST_HELPER + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include "nghttp3_buf.h" +#include "nghttp3_frame.h" +#include "nghttp3_qpack.h" + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t *)(NAME), (uint8_t *)(VALUE), sizeof((NAME)) - 1, \ + sizeof((VALUE)) - 1, NGHTTP3_NV_FLAG_NONE \ + } + +/* + * nghttp3_write_frame writes |fr| to |dest|. This function + * calculates the payload length and assigns it to fr->hd.length; + */ +void nghttp3_write_frame(nghttp3_buf *dest, nghttp3_frame *fr); + +/* + * nghttp3_write_frame_qpack writes |fr| to |dest|. |fr| is supposed + * to be a frame which uses QPACK encoder |qenc|. |qenc| must be + * configured so that it does not use dynamic table. This function + * calculates the payload length and assigns it to fr->hd.length; + */ +void nghttp3_write_frame_qpack(nghttp3_buf *dest, nghttp3_qpack_encoder *qenc, + int64_t stream_id, nghttp3_frame *fr); + +/* + * nghttp3_write_frame_qpack_dyn is similar to + * nghttp3_write_frame_qpack but it can use dynamic table. The it + * will write encoder stream to |ebuf|. + */ +void nghttp3_write_frame_qpack_dyn(nghttp3_buf *dest, nghttp3_buf *ebuf, + nghttp3_qpack_encoder *qenc, + int64_t stream_id, nghttp3_frame *fr); + +/* + * nghttp3_write_frame_data writes DATA frame which has |len| bytes of + * payload. + */ +void nghttp3_write_frame_data(nghttp3_buf *dest, size_t len); + +#endif /* NGHTTP3_TEST_HELPER */ diff --git a/tests/nghttp3_tnode_test.c b/tests/nghttp3_tnode_test.c new file mode 100644 index 0000000..831a7d3 --- /dev/null +++ b/tests/nghttp3_tnode_test.c @@ -0,0 +1,164 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 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 "nghttp3_tnode_test.h" + +#include <CUnit/CUnit.h> + +#include "nghttp3_tnode.h" +#include "nghttp3_macro.h" +#include "nghttp3_test_helper.h" + +static int cycle_less(const nghttp3_pq_entry *lhsx, + const nghttp3_pq_entry *rhsx) { + const nghttp3_tnode *lhs = nghttp3_struct_of(lhsx, nghttp3_tnode, pe); + const nghttp3_tnode *rhs = nghttp3_struct_of(rhsx, nghttp3_tnode, pe); + + if (lhs->cycle == rhs->cycle) { + return lhs->id < rhs->id; + } + + return rhs->cycle - lhs->cycle <= NGHTTP3_TNODE_MAX_CYCLE_GAP; +} + +void test_nghttp3_tnode_schedule(void) { + const nghttp3_mem *mem = nghttp3_mem_default(); + nghttp3_tnode node, node2; + nghttp3_pq pq; + int rv; + nghttp3_pq_entry *ent; + nghttp3_tnode *p; + + /* Schedule node with incremental enabled */ + nghttp3_tnode_init(&node, 0, (1 << 7) | NGHTTP3_DEFAULT_URGENCY); + + nghttp3_pq_init(&pq, cycle_less, mem); + + rv = nghttp3_tnode_schedule(&node, &pq, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == node.cycle); + + /* Schedule another node */ + nghttp3_tnode_init(&node2, 1, (1 << 7) | NGHTTP3_DEFAULT_URGENCY); + + rv = nghttp3_tnode_schedule(&node2, &pq, 0); + + CU_ASSERT(0 == rv); + + /* Rescheduling node with nwrite > 0 */ + + rv = nghttp3_tnode_schedule(&node, &pq, 1000); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == node.cycle); + + /* Rescheduling node with nwrite == 0 */ + + rv = nghttp3_tnode_schedule(&node, &pq, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == node.cycle); + + nghttp3_pq_free(&pq); + + /* Schedule node without incremental */ + nghttp3_tnode_init(&node, 0, NGHTTP3_DEFAULT_URGENCY); + + nghttp3_pq_init(&pq, cycle_less, mem); + + rv = nghttp3_tnode_schedule(&node, &pq, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == node.cycle); + + /* Schedule another node */ + nghttp3_tnode_init(&node2, 1, NGHTTP3_DEFAULT_URGENCY); + + rv = nghttp3_tnode_schedule(&node2, &pq, 0); + + CU_ASSERT(0 == rv); + + /* Rescheduling node with nwrite > 0 */ + + rv = nghttp3_tnode_schedule(&node, &pq, 1000); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == node.cycle); + + /* Rescheduling node with nwrit == 0 */ + + rv = nghttp3_tnode_schedule(&node, &pq, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == node.cycle); + + nghttp3_pq_free(&pq); + + /* Stream with lower stream ID takes precedence */ + nghttp3_pq_init(&pq, cycle_less, mem); + + nghttp3_tnode_init(&node2, 1, NGHTTP3_DEFAULT_URGENCY); + + rv = nghttp3_tnode_schedule(&node2, &pq, 0); + + CU_ASSERT(0 == rv); + + nghttp3_tnode_init(&node, 0, NGHTTP3_DEFAULT_URGENCY); + + rv = nghttp3_tnode_schedule(&node, &pq, 0); + + CU_ASSERT(0 == rv); + + ent = nghttp3_pq_top(&pq); + + p = nghttp3_struct_of(ent, nghttp3_tnode, pe); + + CU_ASSERT(0 == p->id); + + nghttp3_pq_free(&pq); + + /* Check the same reversing push order */ + nghttp3_pq_init(&pq, cycle_less, mem); + + nghttp3_tnode_init(&node, 0, NGHTTP3_DEFAULT_URGENCY); + + rv = nghttp3_tnode_schedule(&node, &pq, 0); + + CU_ASSERT(0 == rv); + + nghttp3_tnode_init(&node2, 1, NGHTTP3_DEFAULT_URGENCY); + + rv = nghttp3_tnode_schedule(&node2, &pq, 0); + + CU_ASSERT(0 == rv); + + ent = nghttp3_pq_top(&pq); + + p = nghttp3_struct_of(ent, nghttp3_tnode, pe); + + CU_ASSERT(0 == p->id); + + nghttp3_pq_free(&pq); +} diff --git a/tests/nghttp3_tnode_test.h b/tests/nghttp3_tnode_test.h new file mode 100644 index 0000000..7e4db02 --- /dev/null +++ b/tests/nghttp3_tnode_test.h @@ -0,0 +1,34 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_TNODE_TEST_H +#define NGTCP2_TNODE_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_nghttp3_tnode_schedule(void); + +#endif /* NGTCP2_TNODE_TEST_H */ |